mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-24 09:28:31 +00:00
* chore: moved fetching gds and lds from low priority calls
* Refactor: Optimize state updates by removing component dependencies on current state to avoid trade-offs
* clean up
* run on page queries after on app load queries are done executing
page load queries should be triggered even if the one or all the app load queries fails
* performance improvements:
- reduced calling resolver functions upto 50% times for components' rendering
- moved to new resolver function for resolving component properties: which 100 times more efficient as utitlises hash table to resolve references and only uses recursive function for resolving JS code by inferring the query
- reduced re-rendering of Box component upto 50-60%
- app load time improved by 30% : as resolver function is more efficient and 50% reduction on rendering of Box.jsx
* chore: removed currentState dependency from SubContainer and QueryManager
* chore: removed useCurrentState from old kanban component
* chore: filtered `false` from componentToRender array to avoid unwanted re-renders when there is an update in a component
* fix: minor improvement on buildAppDef function
* [performance] removes current state dependency from resolvers (#9934)
* Removes current state from function calls of resolver functions
* clean up
* remoes lodash clone deep to more efficient clone deep or copying utitlity method
* ts files to js
* replaced setTimeout to debounce method of lodash for more efficient delayed invocation and yielding mechanism to reschedule large number of tasks
* fixes file path for deepclone method
* fixes import file path for utils helpers
* fixes import file path for utils helpers
* clean up
* fixes: listView's custom resolvers are not getting updated
* chore: moved lastUpdatedRef state from Editor to the subscription of relsoverStore
* fixes: re-rendering of box component for trigger back resolvers
* fix: removing the delay as the runJS updates are not reaching to the components. While start running the query we are updated the componentIds that needs to be rendered. After running the query the same IDs are updated and this is stopping the re-render of the component
* Reverting commit 583460c
* fix: Table search event was not firing while clearing the search input
* fixes: mapping entites on clone page
* commit: Fixed issue on running query on app load with confirmation
* fix: we're mutating the data and which avoids the re-render to update the canvas bg colour. Added a deepClone to avoid it
* Bugfix - perf mapping (#10101)
* fixes: entity for compnent or query duplications with events
* maxDisplayLength for hinter's preview
* clean up
* fix: when multiple components are deleted, the codehinter has the deleted component as suggestion
* fixes: on typing fast the cursor postion changes its postion to first char of the line in multiline JS
* trial
* fix: moved currentValue state of codemirror to ref
* fix: the passed value to the codehinter was not updated properly
---------
Co-authored-by: Kavin Venkatachalam <kavin.saratha@gmail.com>
* fixes: evaluatin js code in codehinters with reserveed keyword
* fixes: reserved keywords string should be resolved and updated in the compnent
* re-rendering of componenrs in viewer
* fixes: resolving of refs
* added a timeout for filepicker
* removes 100s
* filepicker crashes (component) crash on page deleting
* fixes: app crash on adding file events
* fixes: hinter suggestions selection by keyboard
* [chore]: Show proper error message instead of app could not save (#10126)
* chore: show error message from BE instead of app could not save
* Added a fallback value
---------
Co-authored-by: arpitnath <arpitnath42@gmail.com>
248 lines
6.3 KiB
JavaScript
248 lines
6.3 KiB
JavaScript
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 (
|
|
<div class="widget-chart" data-disabled={disabledState} style={computedStyles} data-cy={dataCy}>
|
|
{loadingState === true ? (
|
|
<div style={{ width }} className="p-2 loader-main-container">
|
|
<center>
|
|
<div className="spinner-border mt-5" role="status"></div>
|
|
</center>
|
|
</div>
|
|
) : (
|
|
<PlotComponent
|
|
data={plotFromJson ? jsonChartData : memoizedChartData}
|
|
layout={layout}
|
|
config={{
|
|
displayModeBar: false,
|
|
}}
|
|
onClick={handleClick}
|
|
onDoubleClick={handleDoubleClick}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// 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 (
|
|
<Plot
|
|
data={data}
|
|
layout={deepClone(layout)} // Cloning the layout since the object is getting mutated inside the package
|
|
config={config}
|
|
onClick={(e) => {
|
|
onClick(e.points);
|
|
}}
|
|
onDoubleClick={() => {
|
|
onDoubleClick();
|
|
}}
|
|
/>
|
|
);
|
|
},
|
|
(prevProps, nextProps) => isEqual(prevProps, nextProps)
|
|
);
|