import React, { useState, useEffect, useMemo, memo, useCallback } from 'react'; // eslint-disable-next-line import/no-unresolved import Plotly from 'plotly.js-dist-min'; import createPlotlyComponent from 'react-plotly.js/factory'; import { isJson } from '@/_helpers/utils'; const Plot = createPlotlyComponent(Plotly); import { isEqual } from 'lodash'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; var tinycolor = require('tinycolor2'); export const Chart = function Chart({ width, height, darkMode, properties, styles, fireEvent, setExposedVariable, setExposedVariables, dataCy, }) { const [loadingState, setLoadingState] = useState(false); const getColor = (color) => { if (tinycolor(color).getBrightness() > 128) return '#000'; return '#fff'; }; const { padding, visibility, disabledState, boxShadow, backgroundColor, borderRadius } = styles; const { title, markerColor, showGridLines, type, data, jsonDescription, plotFromJson, showAxes, barmode } = properties; useEffect(() => { const loadingStateProperty = properties.loadingState; if (loadingStateProperty != undefined) { setLoadingState(loadingStateProperty); } }, [properties.loadingState]); const computedStyles = { width: width - 4, height, display: visibility ? '' : 'none', background: darkMode ? '#1f2936' : 'white', boxShadow, borderRadius, }; const dataString = data ?? []; const chartType = type; const jsonData = typeof jsonDescription === 'object' ? JSON.stringify(jsonDescription) : jsonDescription; const isDescriptionJson = plotFromJson ? isJson(jsonData) : false; const jsonChartData = isDescriptionJson ? JSON.parse(jsonData).data : []; const chartLayout = isDescriptionJson ? JSON.parse(jsonData).layout ?? {} : {}; const updatedBgColor = ['#fff', '#ffffff'].includes(backgroundColor) ? darkMode ? '#1f2936' : '#fff' : backgroundColor; const fontColor = getColor(updatedBgColor); const chartTitle = plotFromJson ? chartLayout?.title ?? title : title; useEffect(() => { const { xaxis, yaxis } = chartLayout; let xAxisTitle, yAxisTitle; if (xaxis) { xAxisTitle = xaxis?.title?.text; } if (yaxis) { yAxisTitle = yaxis?.title?.text; } const exposedVariables = { chartTitle: chartTitle, xAxisTitle: xAxisTitle, yAxisTitle: yAxisTitle, }; setExposedVariables(exposedVariables); }, [JSON.stringify(chartLayout, chartTitle)]); const layout = { width: width - 4, height, plot_bgcolor: updatedBgColor, paper_bgcolor: updatedBgColor, title: { text: chartTitle, font: { color: fontColor, }, }, legend: { text: chartTitle, font: { color: fontColor, }, }, xaxis: { showgrid: showGridLines, showline: true, color: fontColor, automargin: true, visible: showAxes, ...chartLayout.xaxis, }, yaxis: { showgrid: showGridLines, showline: true, color: fontColor, automargin: true, visible: showAxes, ...chartLayout.yaxis, }, margin: { l: padding, r: padding, b: padding, t: padding, }, ...(chartLayout.annotations && { annotations: chartLayout.annotations }), barmode: barmode, hoverlabel: { namelength: -1 }, }; const computeChartData = (data, dataString) => { let rawData = data; if (typeof rawData === 'string') { try { rawData = JSON.parse(dataString); } catch (err) { rawData = []; } } if (!Array.isArray(rawData)) { rawData = []; } let newData = []; if (chartType === 'pie') { newData = [ { type: chartType, values: rawData.map((item) => item['y']), labels: rawData.map((item) => item['x']), }, ]; } else { newData = [ { type: chartType || 'line', x: rawData.map((item) => item['x']), y: rawData.map((item) => item['y']), marker: { color: markerColor }, }, ]; } return newData; }; const memoizedChartData = useMemo( () => computeChartData(data, dataString), // eslint-disable-next-line react-hooks/exhaustive-deps [data, dataString, chartType, markerColor] ); const handleClick = useCallback((data) => { if (data.length > 0) { const { x: xAxisLabel, y: yAxisLabel, label: dataLabel, value: dataValue, percent: dataPercent, fullData: { name } = {}, } = data[0]; setExposedVariable('clickedDataPoint', { xAxisLabel, yAxisLabel, dataLabel, dataValue, dataPercent, dataSeriesName: name, }); fireEvent('onClick'); } }, []); const handleDoubleClick = useCallback(() => { fireEvent('onDoubleClick'); }, []); useEffect(() => { setExposedVariable('clearClickedPoint', () => { setExposedVariable('clickedDataPoint', {}); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return (
); }; // onClick event was not working when the component is re-rendered for every click. Hance, memoization is used const PlotComponent = memo( ({ data, layout, config, onClick, onDoubleClick }) => { return (