-
Notifications
You must be signed in to change notification settings - Fork 4.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Migrate Chart visualization to React Part 1: Renderer #4130
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import React, { useState, useEffect, useMemo } from 'react'; | ||
import { RendererPropTypes } from '@/visualizations'; | ||
|
||
import { clientConfig } from '@/services/auth'; | ||
import resizeObserver from '@/services/resizeObserver'; | ||
|
||
import getChartData from '../getChartData'; | ||
import { Plotly, prepareCustomChartData, createCustomChartRenderer } from '../plotly'; | ||
|
||
export default function CustomPlotlyChart({ options, data }) { | ||
if (!clientConfig.allowCustomJSVisualizations) { | ||
return null; | ||
} | ||
|
||
const [container, setContainer] = useState(null); | ||
|
||
const renderCustomChart = useMemo( | ||
() => createCustomChartRenderer(options.customCode, options.enableConsoleLogs), | ||
[options.customCode, options.enableConsoleLogs], | ||
); | ||
|
||
const plotlyData = useMemo( | ||
() => prepareCustomChartData(getChartData(data.rows, options)), | ||
[options, data], | ||
); | ||
|
||
useEffect(() => { | ||
if (container) { | ||
const unwatch = resizeObserver(container, () => { | ||
// Clear existing data with blank data for succeeding codeCall adds data to existing plot. | ||
Plotly.purge(container); | ||
renderCustomChart(plotlyData.x, plotlyData.ys, container, Plotly); | ||
}); | ||
return unwatch; | ||
} | ||
}, [container, plotlyData]); | ||
|
||
// Cleanup when component destroyed | ||
useEffect(() => { | ||
if (container) { | ||
return () => Plotly.purge(container); | ||
} | ||
}, [container]); | ||
|
||
return <div className="chart-visualization-container" ref={setContainer} />; | ||
} | ||
|
||
CustomPlotlyChart.propTypes = RendererPropTypes; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { isArray, isObject } from 'lodash'; | ||
import React, { useState, useEffect } from 'react'; | ||
import { RendererPropTypes } from '@/visualizations'; | ||
import resizeObserver from '@/services/resizeObserver'; | ||
|
||
import getChartData from '../getChartData'; | ||
import { Plotly, prepareData, prepareLayout, updateData, applyLayoutFixes } from '../plotly'; | ||
|
||
export default function PlotlyChart({ options, data }) { | ||
const [container, setContainer] = useState(null); | ||
|
||
useEffect(() => { | ||
if (container) { | ||
const plotlyOptions = { showLink: false, displaylogo: false }; | ||
|
||
const chartData = getChartData(data.rows, options); | ||
const plotlyData = prepareData(chartData, options); | ||
const plotlyLayout = prepareLayout(container, options, plotlyData); | ||
|
||
// It will auto-purge previous graph | ||
Plotly.newPlot(container, plotlyData, plotlyLayout, plotlyOptions).then(() => { | ||
applyLayoutFixes(container, plotlyLayout, (e, u) => Plotly.relayout(e, u)); | ||
}); | ||
|
||
container.on('plotly_restyle', (updates) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be moved to the below There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I understand your point. It uses some variables from the same scope, so cannot be moved to another There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Perhaps one of us is misunderstanding useEffect's 2nd argument usage. My concern here is that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
// This event is triggered if some plotly data/layout has changed. | ||
// We need to catch only changes of traces visibility to update stacking | ||
if (isArray(updates) && isObject(updates[0]) && updates[0].visible) { | ||
updateData(plotlyData, options); | ||
Plotly.relayout(container, plotlyLayout); | ||
} | ||
}); | ||
|
||
const unwatch = resizeObserver(container, () => { | ||
applyLayoutFixes(container, plotlyLayout, (e, u) => Plotly.relayout(e, u)); | ||
}); | ||
return unwatch; | ||
} | ||
}, [options, data, container]); | ||
|
||
// Cleanup when component destroyed | ||
useEffect(() => { | ||
if (container) { | ||
return () => Plotly.purge(container); | ||
} | ||
}, [container]); | ||
|
||
return <div className="chart-visualization-container" ref={setContainer} />; | ||
} | ||
|
||
PlotlyChart.propTypes = RendererPropTypes; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import React from 'react'; | ||
import { RendererPropTypes } from '@/visualizations'; | ||
|
||
import PlotlyChart from './PlotlyChart'; | ||
import CustomPlotlyChart from './CustomPlotlyChart'; | ||
|
||
import './renderer.less'; | ||
|
||
export default function Renderer({ options, ...props }) { | ||
if (options.globalSeriesType === 'custom') { | ||
return <CustomPlotlyChart options={options} {...props} />; | ||
} | ||
return <PlotlyChart options={options} {...props} />; | ||
} | ||
|
||
Renderer.propTypes = RendererPropTypes; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
.plotly-chart-container { | ||
.chart-visualization-container { | ||
height: 400px; | ||
overflow: hidden; | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { each } from 'lodash'; | ||
import { normalizeValue } from './utils'; | ||
|
||
export function prepareCustomChartData(series) { | ||
const x = []; | ||
const ys = {}; | ||
|
||
each(series, ({ name, data }) => { | ||
ys[name] = []; | ||
each(data, (point) => { | ||
x.push(normalizeValue(point.x)); | ||
ys[name].push(normalizeValue(point.y)); | ||
}); | ||
}); | ||
|
||
return { x, ys }; | ||
} | ||
|
||
export function createCustomChartRenderer(code, logErrorsToConsole = false) { | ||
// Create a function from custom code; catch syntax errors | ||
let render = () => {}; | ||
try { | ||
render = new Function('x, ys, element, Plotly', code); // eslint-disable-line no-new-func | ||
} catch (err) { | ||
if (logErrorsToConsole) { | ||
console.log(`Error while executing custom graph: ${err}`); // eslint-disable-line no-console | ||
} | ||
} | ||
|
||
// Return function that will invoke custom code; catch runtime errors | ||
return (x, ys, element, Plotly) => { | ||
try { | ||
render(x, ys, element, Plotly); | ||
} catch (err) { | ||
if (logErrorsToConsole) { | ||
console.log(`Error while executing custom graph: ${err}`); // eslint-disable-line no-console | ||
} | ||
} | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@arikfr That's actually all we need from Plotly here + few
Plotly.relayout
calls below. That's why I think we don't need extra dependency ofreact-plotly.js
- it will not allow us to get rid ofplotly.js
anyway because React component is just a wrapper around it.