Skip to content

Commit 630e1d0

Browse files
authored
Merge pull request #159 from lyjeileen/menu
fix: customize vegalite menu
2 parents 95cd1b3 + 48100dc commit 630e1d0

File tree

3 files changed

+70
-22
lines changed

3 files changed

+70
-22
lines changed

src/components/visualization/vegaLiteViz/vegaLiteViz.cy.tsx

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,46 @@ import { supportedViewports } from '../../../../cypress/support/variables'
44
import VegaLiteViz from './vegaLiteViz'
55

66
describe('VegaLiteViz', () => {
7+
beforeEach(() => {
8+
cy.mount(
9+
<VegaLiteViz
10+
spec={{
11+
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
12+
width: 'container',
13+
data: {
14+
values: [
15+
{ a: 'A', b: 28 },
16+
{ a: 'B', b: 55 },
17+
{ a: 'C', b: 43 },
18+
],
19+
},
20+
mark: 'bar',
21+
encoding: {
22+
x: { field: 'a', type: 'nominal', axis: { labelAngle: 0 } },
23+
y: { field: 'b', type: 'quantitative' },
24+
},
25+
}}
26+
/>
27+
)
28+
})
29+
730
supportedViewports.forEach((viewport) => {
831
it(`should display the graphic on ${viewport} screen`, () => {
932
cy.viewport(viewport)
10-
cy.mount(
11-
<VegaLiteViz
12-
spec={{
13-
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
14-
width: 'container',
15-
data: {
16-
values: [
17-
{ a: 'A', b: 28 },
18-
{ a: 'B', b: 55 },
19-
{ a: 'C', b: 43 },
20-
],
21-
},
22-
mark: 'bar',
23-
encoding: {
24-
x: { field: 'a', type: 'nominal', axis: { labelAngle: 0 } },
25-
y: { field: 'b', type: 'quantitative' },
26-
},
27-
}}
28-
/>
29-
)
3033

3134
cy.get('[data-cy="vega-lite"]').should('exist')
3235
})
3336

37+
it(`should show the menu options properly on ${viewport} screen`, () => {
38+
cy.viewport(viewport)
39+
40+
cy.get('[data-cy="vega-lite"]').should('exist')
41+
cy.get('.rustic-vega-lite').should('exist')
42+
cy.get('[aria-label="menu"]').click()
43+
cy.contains('Save as SVG').should('exist')
44+
cy.contains('Save as PNG').should('exist')
45+
})
46+
3447
const invalidSpec = {
3548
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
3649
data: {

src/components/visualization/vegaLiteViz/vegaLiteViz.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const decorators = [
4040
<div
4141
style={{
4242
width: 'clamp(250px, 70vw, 1000px)',
43-
height: 'clamp(150px, 30vh, 400px)',
43+
height: 'clamp(150px, 40vh, 400px)',
4444
}}
4545
>
4646
<Story />

src/components/visualization/vegaLiteViz/vegaLiteViz.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,25 @@ import './vegaLiteViz.css'
22

33
import type { Theme } from '@mui/material/styles'
44
import Typography from '@mui/material/Typography'
5+
import Box from '@mui/system/Box'
56
import Stack from '@mui/system/Stack'
67
import useTheme from '@mui/system/useTheme'
78
import React, { useEffect, useRef, useState } from 'react'
89
import { renderToStaticMarkup } from 'react-dom/server'
910
import { default as VegaEmbed } from 'vega-embed'
1011

12+
import PopoverMenu, { type PopoverMenuItem } from '../../menu/popoverMenu'
1113
import 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. */
1416
function 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

Comments
 (0)