diff --git a/src/App.css b/src/App.css index 0b5eb21..5cfea18 100644 --- a/src/App.css +++ b/src/App.css @@ -1774,3 +1774,41 @@ table tbody tr:hover { font-size: 1.5rem; } } + +/* Chart Export Button */ +.exportable-chart { + position: relative; +} + +.chart-export-btn { + position: absolute; + top: 8px; + right: 8px; + z-index: 10; + width: 32px; + height: 32px; + border: none; + background: var(--bg-secondary); + border-radius: 6px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + opacity: 0; + transition: opacity 0.15s ease, background 0.15s ease; +} + +.exportable-chart:hover .chart-export-btn { + opacity: 1; +} + +.chart-export-btn:hover { + background: var(--bg-tertiary); + color: var(--text-primary); +} + +.chart-canvas-wrapper { + width: 100%; + height: 100%; +} diff --git a/src/components/ChartExport.js b/src/components/ChartExport.js new file mode 100644 index 0000000..29d12ad --- /dev/null +++ b/src/components/ChartExport.js @@ -0,0 +1,57 @@ +import React, { useRef } from 'react'; + +// Wrapper component that adds PNG export to any chart +export function ExportableChart({ children, filename = 'chart', className = '' }) { + const chartRef = useRef(null); + + const exportAsPNG = () => { + const chartContainer = chartRef.current; + if (!chartContainer) return; + + const canvas = chartContainer.querySelector('canvas'); + if (!canvas) return; + + // Create a new canvas with white background + const exportCanvas = document.createElement('canvas'); + const ctx = exportCanvas.getContext('2d'); + + // Set dimensions with padding + const padding = 20; + exportCanvas.width = canvas.width + (padding * 2); + exportCanvas.height = canvas.height + (padding * 2); + + // Fill white background + ctx.fillStyle = '#ffffff'; + ctx.fillRect(0, 0, exportCanvas.width, exportCanvas.height); + + // Draw the chart + ctx.drawImage(canvas, padding, padding); + + // Export + const link = document.createElement('a'); + link.download = `${filename}-${new Date().toISOString().split('T')[0]}.png`; + link.href = exportCanvas.toDataURL('image/png', 1.0); + link.click(); + }; + + return ( +
+ +
+ {children} +
+
+ ); +} + +export default ExportableChart; diff --git a/src/components/Comparison.js b/src/components/Comparison.js index d30a9d6..0018fba 100644 --- a/src/components/Comparison.js +++ b/src/components/Comparison.js @@ -2,6 +2,7 @@ import React, { useState, useMemo, useCallback, useRef } from 'react'; import { useSearchParams } from 'react-router-dom'; import { Line, Bar } from 'react-chartjs-2'; import { EmptyState, FilterControls } from './shared'; +import { ExportableChart } from './ChartExport'; import { chartColors, createBaseOptions } from '../config/chartConfig'; import { filterDataByDateRange, @@ -603,9 +604,9 @@ function Comparison({ data, showDataLabels }) { -
+ -
+
@@ -624,9 +625,9 @@ function Comparison({ data, showDataLabels }) {
-
+ -
+ diff --git a/src/components/Dashboard.js b/src/components/Dashboard.js index 5423ef5..83ba2ee 100644 --- a/src/components/Dashboard.js +++ b/src/components/Dashboard.js @@ -2,6 +2,7 @@ import React, { useState, useMemo } from 'react'; import { useSearchParams } from 'react-router-dom'; import { Line, Doughnut, Bar } from 'react-chartjs-2'; import { Carousel, EmptyState, FilterControls, StatCard } from './shared'; +import { ExportableChart } from './ChartExport'; import { chartColors, createBaseOptions } from '../config/chartConfig'; import { filterData, @@ -435,41 +436,41 @@ function Dashboard({ data, showDataLabels }) { -
+ -
+ {filters.museum === 'all' && (

Visitors by Museum

-
+ -
+
)} {filters.museum === 'all' && (

Revenue by Museum

-
+ -
+
)}

Quarterly Revenue (YoY)

-
+ -
+

District Performance

-
+ -
+