@@ -2,21 +2,25 @@ import './vegaLiteViz.css'
22
33import type { Theme } from '@mui/material/styles'
44import Typography from '@mui/material/Typography'
5+ import Box from '@mui/system/Box'
56import Stack from '@mui/system/Stack'
67import useTheme from '@mui/system/useTheme'
78import React , { useEffect , useRef , useState } from 'react'
89import { renderToStaticMarkup } from 'react-dom/server'
910import { default as VegaEmbed } from 'vega-embed'
1011
12+ import PopoverMenu , { type PopoverMenuItem } from '../../menu/popoverMenu'
1113import type { VegaLiteData } from '../../types'
1214
1315/** The `VegaLiteViz` component is a versatile tool for visualizing data using the Vega-Lite grammar. With support for various graphic types, it empowers users to create engaging and informative data visualizations effortlessly. */
1416function VegaLiteViz ( props : VegaLiteData ) {
1517 const chartRef = useRef < HTMLDivElement > ( null )
1618 const [ hasError , setHasError ] = useState < boolean > ( false )
19+
1720 const rusticTheme : Theme = useTheme ( )
1821 const isDarkTheme = rusticTheme . palette . mode === 'dark'
1922 const defaultFont = rusticTheme . typography . fontFamily
23+
2024 const tooltipStyle = {
2125 backgroundColor : rusticTheme . palette . primary . main ,
2226 color : rusticTheme . palette . background . paper ,
@@ -48,21 +52,48 @@ function VegaLiteViz(props: VegaLiteData) {
4852 id : 'rustic-vega-lite-tooltip' ,
4953 }
5054
55+ const menuItems : PopoverMenuItem [ ] = [
56+ {
57+ label : 'Save as SVG' ,
58+ } ,
59+ {
60+ label : 'Save as PNG' ,
61+ } ,
62+ ]
63+
5164 function renderChart ( ) {
5265 if ( chartRef . current && props . spec ) {
5366 const options = {
5467 config : { font : defaultFont } ,
5568 ...props . options ,
5669 theme : isDarkTheme ? props . theme ?. dark : props . theme ?. light ,
5770 tooltip : tooltipOptions ,
71+ actions : false ,
5872 }
5973
6074 if ( ! props . options ?. config ?. font ) {
6175 options . config . font = defaultFont
6276 }
6377
6478 VegaEmbed ( chartRef . current , props . spec , options )
65- . then ( ( ) => {
79+ . then ( ( result ) => {
80+ const opts = result . embedOptions
81+ const fileName =
82+ result . embedOptions . downloadFileName || 'visualization'
83+ const formats : Array < 'svg' | 'png' > = [ 'svg' , 'png' ]
84+
85+ formats . map ( ( format , index ) => {
86+ const scaleFactor =
87+ typeof opts . scaleFactor === 'object'
88+ ? opts . scaleFactor [ format ]
89+ : opts . scaleFactor
90+
91+ result . view . toImageURL ( format , scaleFactor ) . then ( ( url ) => {
92+ menuItems [ index ] . href = url
93+ menuItems [ index ] . downloadFileName = `${ fileName } .${ format } `
94+ } )
95+ } )
96+
6697 setHasError ( false )
6798 } )
6899 . catch ( ( ) => {
@@ -90,6 +121,10 @@ function VegaLiteViz(props: VegaLiteData) {
90121 } else {
91122 return (
92123 < Stack direction = "column" className = "rustic-vega-lite-container" >
124+ < Box justifyContent = "end" display = "flex" >
125+ < PopoverMenu menuItems = { menuItems } ariaLabel = "menu" />
126+ </ Box >
127+
93128 { props . title && (
94129 < Typography variant = "subtitle2" > { props . title } </ Typography >
95130 ) }
0 commit comments