From d51983a028dfc5cc406ac66d2556a43d57f2ddc2 Mon Sep 17 00:00:00 2001 From: Frank Sauerburger <frank@sauerburger.com> Date: Tue, 23 Mar 2021 15:31:00 +0100 Subject: [PATCH] Add in-browser conversion --- uhepp-js/src/components/UheppHist.jsx | 71 +++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/uhepp-js/src/components/UheppHist.jsx b/uhepp-js/src/components/UheppHist.jsx index 644b9c1..9fa19ca 100644 --- a/uhepp-js/src/components/UheppHist.jsx +++ b/uhepp-js/src/components/UheppHist.jsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { AxisLeft, AxisBottom } from '@vx/axis'; import { AxisRight, AxisTop } from '@vx/axis'; import { AreaStack } from '@vx/shape'; @@ -625,8 +625,47 @@ const UheppHist = ({width, height, uhepp}) => { const UheppHistPost = ({width, height, uhepp}) => { const post_uhepp = uhepp // backwards compatibility - const [highlightedBin, setHighlightedBin] = useState(null); - const [showTotal, setShowTotal] = useState(false); + const [highlightedBin, setHighlightedBin] = useState(null) + const [showTotal, setShowTotal] = useState(false) + const [pngDownload, setPngDownload] = useState(false) + const [pngInProgress, setPngInProgress] = useState(false) + + const canvasHeight = height * 2 + const canvasWidth = width * 2 + const canvasRef = useRef(null) + const svgRef = useRef(null) + + useEffect(() => { + setPngDownload(false) + setPngInProgress(false) + }, [uhepp]) + + const generatePng = () => { + setPngInProgress(true) + const canvas = canvasRef.current + const ctx = canvas.getContext('2d') + const svg = svgRef.current.cloneNode(true) + svg.setAttribute("height", `${canvasHeight}px`) + svg.setAttribute("width", `${canvasWidth}px`) + const data = (new XMLSerializer()).serializeToString(svg) + + const svgBlob = new Blob([data], {type: 'image/svg+xml;charset=utf-8'}) + const url = URL.createObjectURL(svgBlob) + + const img = new Image() + img.onload = () => { + ctx.clearRect(0, 0, canvas.width, canvas.height) + ctx.drawImage(img, 0, 0) + URL.revokeObjectURL(url) + + canvas.toBlob(blob => { + const pngUrl = URL.createObjectURL(blob) + setPngDownload(pngUrl) + setPngInProgress(false) + }, "image/png") + } + img.src = url + } const defaultColorsCycle = new ColorCycle([ "#1f77b4", @@ -697,11 +736,13 @@ const UheppHistPost = ({width, height, uhepp}) => { const equidistent = computeEquidistent(uhepp.bins.rebin || uhepp.bins.edges, uhepp.bins.density_width) - return ( + return (<> <div className="uhepp-container"> <svg viewBox={`0 0 ${width} ${height}`} + ref={svgRef} onMouseOut={() => setHighlightedBin(null)} > <style> + * {'{font-family: sans-serif}'} rect {'{shape-rendering: crispEdges }'} .uhepp-brand {'{font-style: italic; font-weight: bold}'} .uhepp-brand, .uhepp-label {'{font-size: 20px}'} @@ -948,6 +989,26 @@ const UheppHistPost = ({width, height, uhepp}) => { </Group> } </svg> </div> - ) + <div className="container text-right"> + <canvas className="d-none" ref={canvasRef} width={canvasWidth} height={canvasHeight}></canvas> + { !pngDownload && + <button className="btn btn-outline-primary" + disabled={pngInProgress} + onClick={() => generatePng()}> + { !pngInProgress && <span> + <i className="fas fa-download"></i> Convert to .png + </span> } + { pngInProgress && <> + <span className="spinner-border spinner-border-sm" role="status" aria-hidden="true"> + </span> Converting... + </> } + </button> } + { pngDownload && + <a href={pngDownload} download={uhepp.metadata.filename + ".png"} className="btn btn-primary"> + <i className="fas fa-download"></i> Download .png + </a> } + </div> + </>) + } export default UheppHist; -- GitLab