@@ -13,7 +13,7 @@ import {
1313 Legend ,
1414} from 'chart.js' ;
1515import zoomPlugin from 'chartjs-plugin-zoom' ;
16- import { ImageDown , Copy , FileDown } from 'lucide-react' ;
16+ import { ImageDown , Copy , FileDown , Minimize2 } from 'lucide-react' ;
1717import { getMinSteps } from "../utils/getMinSteps.js" ;
1818import { useTranslation } from 'react-i18next' ;
1919
@@ -118,6 +118,67 @@ export default function ChartContainer({
118118 link . click ( ) ;
119119 } , [ ] ) ;
120120
121+ const exportChartSmallImage = useCallback ( ( id ) => {
122+ const chart = chartRefs . current . get ( id ) ;
123+ if ( ! chart ) return ;
124+
125+ const maxSize = 50 * 1024 ; // 50KB
126+ const canvas = chart . canvas ;
127+
128+ // Create a temporary canvas for resizing
129+ const tempCanvas = document . createElement ( 'canvas' ) ;
130+ const ctx = tempCanvas . getContext ( '2d' ) ;
131+
132+ // Start with original size and reduce if needed
133+ let scale = 1 ;
134+ let quality = 0.8 ;
135+ let dataUrl ;
136+ let attempts = 0 ;
137+ const maxAttempts = 20 ;
138+
139+ // Try to get image under 50KB by adjusting quality and scale
140+ while ( attempts < maxAttempts ) {
141+ const width = Math . floor ( canvas . width * scale ) ;
142+ const height = Math . floor ( canvas . height * scale ) ;
143+
144+ tempCanvas . width = width ;
145+ tempCanvas . height = height ;
146+
147+ // Draw with white background (for JPEG)
148+ ctx . fillStyle = '#ffffff' ;
149+ ctx . fillRect ( 0 , 0 , width , height ) ;
150+ ctx . drawImage ( canvas , 0 , 0 , width , height ) ;
151+
152+ dataUrl = tempCanvas . toDataURL ( 'image/jpeg' , quality ) ;
153+
154+ // Calculate approximate file size (base64 is ~33% larger than binary)
155+ const base64Length = dataUrl . length - 'data:image/jpeg;base64,' . length ;
156+ const fileSize = Math . ceil ( base64Length * 0.75 ) ;
157+
158+ if ( fileSize <= maxSize ) {
159+ break ;
160+ }
161+
162+ // Reduce quality first, then scale
163+ if ( quality > 0.3 ) {
164+ quality -= 0.1 ;
165+ } else if ( scale > 0.3 ) {
166+ scale -= 0.1 ;
167+ quality = 0.7 ; // Reset quality for new scale
168+ } else {
169+ // Can't reduce further, use what we have
170+ break ;
171+ }
172+
173+ attempts ++ ;
174+ }
175+
176+ const link = document . createElement ( 'a' ) ;
177+ link . href = dataUrl ;
178+ link . download = `${ id } -small.jpg` ;
179+ link . click ( ) ;
180+ } , [ ] ) ;
181+
121182 const copyChartImage = useCallback ( async ( id ) => {
122183 const chart = chartRefs . current . get ( id ) ;
123184 if ( ! chart || ! navigator ?. clipboard ) return ;
@@ -764,6 +825,15 @@ export default function ChartContainer({
764825 >
765826 < ImageDown size = { 16 } />
766827 </ button >
828+ < button
829+ type = "button"
830+ className = "p-1 rounded-md text-gray-600 hover:text-green-600 hover:bg-gray-100"
831+ onClick = { ( ) => exportChartSmallImage ( `metric-comp-${ idx } ` ) }
832+ aria-label = { t ( 'exportSmallImage' ) }
833+ title = { t ( 'exportSmallImage' ) }
834+ >
835+ < Minimize2 size = { 16 } />
836+ </ button >
767837 < button
768838 type = "button"
769839 className = "p-1 rounded-md text-gray-600 hover:text-blue-600 hover:bg-gray-100"
@@ -814,6 +884,15 @@ export default function ChartContainer({
814884 >
815885 < ImageDown size = { 16 } />
816886 </ button >
887+ < button
888+ type = "button"
889+ className = "p-1 rounded-md text-gray-600 hover:text-green-600 hover:bg-gray-100"
890+ onClick = { ( ) => exportChartSmallImage ( `metric-${ idx } ` ) }
891+ aria-label = { t ( 'exportSmallImage' ) }
892+ title = { t ( 'exportSmallImage' ) }
893+ >
894+ < Minimize2 size = { 16 } />
895+ </ button >
817896 < button
818897 type = "button"
819898 className = "p-1 rounded-md text-gray-600 hover:text-blue-600 hover:bg-gray-100"
0 commit comments