Skip to content

Commit 066329f

Browse files
feat: add exportImages: a function which exports graph layers as pngs
1 parent 1824e68 commit 066329f

File tree

12 files changed

+1045
-334
lines changed

12 files changed

+1045
-334
lines changed

giraffe/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@influxdata/giraffe",
3-
"version": "2.2.0",
3+
"version": "2.3.0",
44
"main": "dist/index.js",
55
"module": "src/index.js",
66
"license": "MIT",
@@ -105,5 +105,8 @@
105105
"peerDependencies": {
106106
"react": "^16.8.0",
107107
"react-dom": "^16.8.0"
108+
},
109+
"dependencies": {
110+
"merge-images": "^2.0.0"
108111
}
109112
}

giraffe/src/components/Axes.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
import * as React from 'react'
2-
import {useRef, useLayoutEffect, FunctionComponent, CSSProperties} from 'react'
1+
import React, {
2+
CSSProperties,
3+
FunctionComponent,
4+
RefObject,
5+
useLayoutEffect,
6+
} from 'react'
37

48
import {TICK_PADDING_RIGHT, TICK_PADDING_TOP} from '../constants'
59
import {clearCanvas} from '../utils/clearCanvas'
@@ -8,6 +12,7 @@ import {Margins, Scale, SizedConfig, Formatter, ColumnType} from '../types'
812
import {PlotEnv} from '../utils/PlotEnv'
913

1014
interface Props {
15+
canvasRef: RefObject<HTMLCanvasElement>
1116
env: PlotEnv
1217
style: CSSProperties
1318
}
@@ -186,9 +191,7 @@ export const drawAxes = ({
186191
}
187192
}
188193

189-
export const Axes: FunctionComponent<Props> = ({env, style}) => {
190-
const canvas = useRef<HTMLCanvasElement>(null)
191-
194+
export const Axes: FunctionComponent<Props> = ({canvasRef, env, style}) => {
192195
const {
193196
innerWidth,
194197
innerHeight,
@@ -207,7 +210,7 @@ export const Axes: FunctionComponent<Props> = ({env, style}) => {
207210

208211
useLayoutEffect(() => {
209212
drawAxes({
210-
canvas: canvas.current,
213+
canvas: canvasRef.current,
211214
innerWidth,
212215
innerHeight,
213216
margins,
@@ -223,7 +226,7 @@ export const Axes: FunctionComponent<Props> = ({env, style}) => {
223226
yColumnType,
224227
})
225228
}, [
226-
canvas.current,
229+
canvasRef.current,
227230
innerWidth,
228231
innerHeight,
229232
margins,
@@ -241,7 +244,7 @@ export const Axes: FunctionComponent<Props> = ({env, style}) => {
241244
return (
242245
<canvas
243246
className="giraffe-axes"
244-
ref={canvas}
247+
ref={canvasRef}
245248
style={style}
246249
data-testid="giraffe-axes"
247250
/>

giraffe/src/components/BandLayer.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import * as React from 'react'
2-
import {useMemo, useRef, FunctionComponent} from 'react'
1+
import React, {FunctionComponent, RefObject, useMemo} from 'react'
32

43
import {LayerProps, BandLayerSpec, BandLayerConfig} from '../types'
54
import {BandHoverLayer} from './BandHoverLayer'
@@ -18,13 +17,24 @@ import {
1817
export interface Props extends LayerProps {
1918
spec: BandLayerSpec
2019
config: BandLayerConfig
20+
canvasRef: RefObject<HTMLCanvasElement>
2121
}
2222

2323
const HIGHLIGHT_HOVERED_LINE = 0.4
2424
const NO_HIGHLIGHT = 1
2525

2626
export const BandLayer: FunctionComponent<Props> = props => {
27-
const {config, spec, width, height, xScale, yScale, hoverX, hoverY} = props
27+
const {
28+
canvasRef,
29+
config,
30+
height,
31+
hoverX,
32+
hoverY,
33+
spec,
34+
width,
35+
xScale,
36+
yScale,
37+
} = props
2838

2939
const {
3040
lowerColumnName = '',
@@ -51,8 +61,6 @@ export const BandLayer: FunctionComponent<Props> = props => {
5161
shadeOpacity: config.shadeOpacity,
5262
}
5363

54-
const canvasRef = useRef<HTMLCanvasElement>(null)
55-
5664
useCanvas(
5765
canvasRef,
5866
width,

giraffe/src/components/LineLayer.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import * as React from 'react'
2-
import {useMemo, useRef, FunctionComponent} from 'react'
1+
import React, {FunctionComponent, RefObject, useMemo} from 'react'
32

43
import {LayerProps, LineLayerSpec, LineLayerConfig} from '../types'
54
import {LineHoverLayer} from './LineHoverLayer'
@@ -12,10 +11,21 @@ import {FILL} from '../constants/columnKeys'
1211
export interface Props extends LayerProps {
1312
spec: LineLayerSpec
1413
config: LineLayerConfig
14+
canvasRef: RefObject<HTMLCanvasElement>
1515
}
1616

1717
export const LineLayer: FunctionComponent<Props> = props => {
18-
const {config, spec, width, height, xScale, yScale, hoverX, hoverY} = props
18+
const {
19+
config,
20+
spec,
21+
width,
22+
height,
23+
xScale,
24+
yScale,
25+
hoverX,
26+
hoverY,
27+
canvasRef,
28+
} = props
1929
const {position} = config
2030

2131
const simplifiedLineData = useMemo(
@@ -32,8 +42,6 @@ export const LineLayer: FunctionComponent<Props> = props => {
3242
shadeAboveY: height,
3343
}
3444

35-
const canvasRef = useRef<HTMLCanvasElement>(null)
36-
3745
useCanvas(
3846
canvasRef,
3947
width,

giraffe/src/components/Plot.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import * as React from 'react'
2-
import {FunctionComponent} from 'react'
1+
import React, {FunctionComponent, RefObject, useRef} from 'react'
32
import AutoSizer from 'react-virtualized-auto-sizer'
43

54
import {Config, SizedConfig, LayerTypes} from '../types'
@@ -10,13 +9,28 @@ import {get} from '../utils/get'
109

1110
interface Props {
1211
config: Config
12+
axesCanvasRef?: RefObject<HTMLCanvasElement>
13+
layerCanvasRef?: RefObject<HTMLCanvasElement>
1314
}
1415

15-
export const Plot: FunctionComponent<Props> = ({config, children}) => {
16+
export const Plot: FunctionComponent<Props> = ({
17+
config,
18+
children,
19+
axesCanvasRef = useRef<HTMLCanvasElement>(null),
20+
layerCanvasRef = useRef<HTMLCanvasElement>(null),
21+
}) => {
1622
const graphType = get(config, 'layers.0.type')
1723

1824
if (config.width && config.height) {
19-
return <SizedPlot config={config as SizedConfig}>{children}</SizedPlot>
25+
return (
26+
<SizedPlot
27+
config={config as SizedConfig}
28+
axesCanvasRef={axesCanvasRef}
29+
layerCanvasRef={layerCanvasRef}
30+
>
31+
{children}
32+
</SizedPlot>
33+
)
2034
}
2135

2236
return (
@@ -38,7 +52,13 @@ export const Plot: FunctionComponent<Props> = ({config, children}) => {
3852
)
3953
}
4054
return (
41-
<SizedPlot config={{...config, width, height}}>{children}</SizedPlot>
55+
<SizedPlot
56+
config={{...config, width, height}}
57+
axesCanvasRef={axesCanvasRef}
58+
layerCanvasRef={layerCanvasRef}
59+
>
60+
{children}
61+
</SizedPlot>
4262
)
4363
}}
4464
</AutoSizer>

giraffe/src/components/RectLayer.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import * as React from 'react'
2-
import {useRef, FunctionComponent} from 'react'
1+
import React, {FunctionComponent, RefObject} from 'react'
32

43
import {useCanvas} from '../utils/useCanvas'
54
import {drawRects} from '../utils/drawRects'
@@ -14,6 +13,7 @@ import {RectLayerConfig, RectLayerSpec, LayerProps, TooltipData} from '../types'
1413
export interface Props extends LayerProps {
1514
spec: RectLayerSpec
1615
config: RectLayerConfig
16+
canvasRef: RefObject<HTMLCanvasElement>
1717
}
1818

1919
export const RectLayer: FunctionComponent<Props> = ({
@@ -27,6 +27,7 @@ export const RectLayer: FunctionComponent<Props> = ({
2727
hoverX,
2828
hoverY,
2929
columnFormatter,
30+
canvasRef,
3031
}) => {
3132
const hoveredRowIndices = findHoveredRects(
3233
spec.table,
@@ -49,8 +50,6 @@ export const RectLayer: FunctionComponent<Props> = ({
4950
fillOpacity: config.fillOpacity,
5051
}
5152

52-
const canvasRef = useRef<HTMLCanvasElement>(null)
53-
5453
useCanvas(
5554
canvasRef,
5655
width,

giraffe/src/components/SizedPlot.test.tsx

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {fireEvent, render, screen} from '@testing-library/react'
44

55
import {PlotEnv} from '../utils/PlotEnv'
66

7+
import {LineLayerConfig, SizedConfig} from '../types'
8+
79
jest.mock('./Geo', () => <></>) // this component causes all sorts of loading problems
810

911
import {newTable} from '../utils/newTable'
@@ -23,19 +25,23 @@ const layers = [
2325
type: 'line',
2426
x: '_time',
2527
y: '_value',
26-
},
28+
fill: [],
29+
} as LineLayerConfig,
2730
]
2831

29-
const config = {
32+
const config: SizedConfig = {
3033
table,
3134
layers,
3235
showAxes: false,
33-
width: '350px',
34-
height: '350px',
36+
width: 350,
37+
height: 350,
3538
}
3639

3740
const resetSpy = jest.spyOn(PlotEnv.prototype, 'resetDomains')
3841

42+
const axesRef: React.RefObject<HTMLCanvasElement> = React.createRef()
43+
const layersRef: React.RefObject<HTMLCanvasElement> = React.createRef()
44+
3945
describe('the SizedPlot', () => {
4046
describe('handling user interaction', () => {
4147
afterEach(() => {
@@ -44,7 +50,13 @@ describe('the SizedPlot', () => {
4450

4551
describe('the default behavior', () => {
4652
it('handles double clicks', () => {
47-
render(<SizedPlot config={config} />)
53+
render(
54+
<SizedPlot
55+
config={config}
56+
axesCanvasRef={axesRef}
57+
layerCanvasRef={layersRef}
58+
/>
59+
)
4860
fireEvent.doubleClick(screen.getByTestId('giraffe-inner-plot'))
4961

5062
expect(resetSpy).toHaveBeenCalled()
@@ -59,7 +71,13 @@ describe('the SizedPlot', () => {
5971
interactionHandlers: {doubleClick: fakeDoubleClickInteractionHandler},
6072
}
6173

62-
render(<SizedPlot config={localConfig} />)
74+
render(
75+
<SizedPlot
76+
config={localConfig}
77+
axesCanvasRef={axesRef}
78+
layerCanvasRef={layersRef}
79+
/>
80+
)
6381
fireEvent.doubleClick(screen.getByTestId('giraffe-inner-plot'))
6482

6583
expect(resetSpy).not.toHaveBeenCalled()
@@ -88,7 +106,13 @@ describe('the SizedPlot', () => {
88106
interactionHandlers: {hover: fakeHoverCallback},
89107
}
90108

91-
render(<SizedPlot config={localConfig} />)
109+
render(
110+
<SizedPlot
111+
config={localConfig}
112+
axesCanvasRef={axesRef}
113+
layerCanvasRef={layersRef}
114+
/>
115+
)
92116

93117
fireEvent.mouseOver(screen.getByTestId('giraffe-inner-plot'))
94118

giraffe/src/components/SizedPlot.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import React, {useCallback, FunctionComponent, CSSProperties} from 'react'
1+
import React, {
2+
CSSProperties,
3+
FunctionComponent,
4+
RefObject,
5+
useCallback,
6+
} from 'react'
27

38
import {Axes} from './Axes'
49
import {
@@ -34,11 +39,15 @@ import {AnnotationLayer} from './AnnotationLayer'
3439

3540
interface Props {
3641
config: SizedConfig
42+
axesCanvasRef: RefObject<HTMLCanvasElement>
43+
layerCanvasRef: RefObject<HTMLCanvasElement>
3744
}
3845

3946
export const SizedPlot: FunctionComponent<Props> = ({
4047
config: userConfig,
4148
children,
49+
axesCanvasRef,
50+
layerCanvasRef,
4251
}) => {
4352
const env = usePlotEnv(userConfig)
4453
const forceUpdate = useForceUpdate()
@@ -120,7 +129,9 @@ export const SizedPlot: FunctionComponent<Props> = ({
120129
userSelect: 'none',
121130
}}
122131
>
123-
{showAxes && <Axes env={env} style={fullsizeStyle} />}
132+
{showAxes && (
133+
<Axes env={env} canvasRef={axesCanvasRef} style={fullsizeStyle} />
134+
)}
124135
<div
125136
className="giraffe-inner-plot"
126137
data-testid="giraffe-inner-plot"
@@ -211,6 +222,7 @@ export const SizedPlot: FunctionComponent<Props> = ({
211222
case SpecTypes.Line:
212223
return (
213224
<LineLayer
225+
canvasRef={layerCanvasRef}
214226
key={layerIndex}
215227
{...sharedProps}
216228
spec={spec}
@@ -221,6 +233,7 @@ export const SizedPlot: FunctionComponent<Props> = ({
221233
case SpecTypes.Band:
222234
return (
223235
<BandLayer
236+
canvasRef={layerCanvasRef}
224237
key={layerIndex}
225238
{...sharedProps}
226239
spec={spec}
@@ -241,6 +254,7 @@ export const SizedPlot: FunctionComponent<Props> = ({
241254
case SpecTypes.Rect:
242255
return (
243256
<RectLayer
257+
canvasRef={layerCanvasRef}
244258
key={layerIndex}
245259
{...sharedProps}
246260
spec={spec}

giraffe/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export {
1313
} from './utils/formatters'
1414
export {getDomainDataFromLines} from './utils/lineData'
1515

16+
export {exportImage} from './utils/exportImage'
17+
1618
// Transforms
1719
export {lineTransform} from './transforms/line'
1820

0 commit comments

Comments
 (0)