diff --git a/uhepp-js/src/components/UheppHist.jsx b/uhepp-js/src/components/UheppHist.jsx
index 644b9c1297ce4a0c09f5521ef9c9acf16d2e559b..9fa19ca13ece41837ce4eccb89b9137b8d4c5966 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;