Skip to content

Commit

Permalink
feat(any-chart-web): move logic to hook
Browse files Browse the repository at this point in the history
  • Loading branch information
rahmanunver committed Jan 16, 2025
1 parent 1a01ab8 commit a04898c
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 142 deletions.
146 changes: 4 additions & 142 deletions packages/pluggableWidgets/any-chart-web/src/AnyChart.tsx
Original file line number Diff line number Diff line change
@@ -1,148 +1,10 @@
import { debounce } from "@mendix/widget-plugin-platform/utils/debounce";
import { ReactElement, createElement, useEffect, useMemo, useRef, useState } from "react";
import { ReactElement, createElement } from "react";
import { AnyChartContainerProps } from "../typings/AnyChartProps";
import { PlotlyChart } from "./components/PlotlyChart";
import { useAnyChart } from "./hooks/useAnyChart";
import "./ui/AnyChart.scss";
import { ChartDataProcessor } from "./utils/ChartDataProcessor";

export default function AnyChart(props: AnyChartContainerProps): ReactElement {
const chartRef = useRef<HTMLDivElement>(null);
const [chart, setChart] = useState<PlotlyChart | null>(null);
const [containerDimensions, setContainerDimensions] = useState<{ width?: number; height?: number }>({});
const dataProcessor = useRef(new ChartDataProcessor());
const { chartRef, containerStyle } = useAnyChart(props);

const [setContainerDimensionsDebounced, abortDimensionsDebounce] = useMemo(
() =>
debounce((width: number, height: number) => {
setContainerDimensions({ width, height });
}, 100),
[]
);

useEffect(() => {
const resizeObserver = new ResizeObserver(entries => {
const { width, height } = entries[0].contentRect;
setContainerDimensionsDebounced(width, height);
});

if (chartRef.current) {
resizeObserver.observe(chartRef.current);
}

return () => {
resizeObserver.disconnect();
abortDimensionsDebounce();
if (chart) {
chart.destroy();
}
};
}, [chart, setContainerDimensionsDebounced, abortDimensionsDebounce]);

const [updateChartDebounced, abortChartUpdate] = useMemo(
() =>
debounce(
(
chartInstance: PlotlyChart | null,
updateData: {
data: any;
layout: any;
config: any;
width: number;
height: number;
}
) => {
if (!chartInstance) {
const newChart = new PlotlyChart(chartRef.current!, updateData);
setChart(newChart);
} else {
chartInstance.update(updateData);
}
},
100
),
[]
);

useEffect(() => {
if (!chartRef.current || !containerDimensions.width) {
return;
}

const data = dataProcessor.current.parseData(props.dataStatic, props.dataAttribute?.value, props.sampleData);

const layout = dataProcessor.current.parseLayout(
props.layoutStatic,
props.layoutAttribute?.value,
props.sampleLayout
);

const { width, height } = {
width: containerDimensions.width,
height: dataProcessor.current.calculateDimensions(
props.widthUnit,
props.width,
props.heightUnit,
props.height,
containerDimensions.width,
containerDimensions.height
).height
};

const updateData = {
data,
layout: {
...layout,
width,
height,
autosize: true,
font: {
family: "Open Sans, sans-serif",
size: 12
}
},
config: {
...dataProcessor.current.parseConfig(props.configurationOptions),
displayModeBar: props.devMode === "developer",
responsive: true,
staticPlot: false
},
width,
height
};

updateChartDebounced(chart, updateData);

return () => {
abortChartUpdate();
};
}, [
props.dataStatic,
props.dataAttribute?.value,
props.sampleData,
props.layoutStatic,
props.layoutAttribute?.value,
props.sampleLayout,
props.configurationOptions,
props.widthUnit,
props.width,
props.heightUnit,
props.height,
props.devMode,
containerDimensions,
chart,
updateChartDebounced,
abortChartUpdate
]);

return (
<div
ref={chartRef}
className="widget-any-chart"
style={{
width: props.widthUnit === "percentage" ? `${props.width}%` : `${props.width}px`,
height: props.heightUnit === "percentageOfParent" ? `${props.height}%` : undefined
}}
tabIndex={props.tabIndex}
/>
);
return <div ref={chartRef} className="widget-any-chart" style={containerStyle} tabIndex={props.tabIndex} />;
}
151 changes: 151 additions & 0 deletions packages/pluggableWidgets/any-chart-web/src/hooks/useAnyChart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { debounce } from "@mendix/widget-plugin-platform/utils/debounce";
import { useEffect, useMemo, useRef, useState, type RefObject } from "react";
import { AnyChartContainerProps } from "../../typings/AnyChartProps";
import { PlotlyChart } from "../components/PlotlyChart";
import { ChartDataProcessor } from "../utils/ChartDataProcessor";

interface UseAnyChartReturn {
chartRef: RefObject<HTMLDivElement>;
containerStyle: {
width?: string;
height?: string;
};
}

export function useAnyChart(props: AnyChartContainerProps): UseAnyChartReturn {
const chartRef = useRef<HTMLDivElement>(null);
const [chart, setChart] = useState<PlotlyChart | null>(null);
const [containerDimensions, setContainerDimensions] = useState<{ width?: number; height?: number }>({});
const dataProcessor = useRef(new ChartDataProcessor());

const [setContainerDimensionsDebounced, abortDimensionsDebounce] = useMemo(
() =>
debounce((width: number, height: number) => {
setContainerDimensions({ width, height });
}, 100),
[]
);

const [updateChartDebounced, abortChartUpdate] = useMemo(
() =>
debounce(
(
chartInstance: PlotlyChart | null,
updateData: {
data: any;
layout: any;
config: any;
width: number;
height: number;
}
) => {
if (!chartInstance) {
const newChart = new PlotlyChart(chartRef.current!, updateData);
setChart(newChart);
} else {
chartInstance.update(updateData);
}
},
100
),
[]
);

useEffect(() => {
const resizeObserver = new ResizeObserver(entries => {
const { width, height } = entries[0].contentRect;
setContainerDimensionsDebounced(width, height);
});

if (chartRef.current) {
resizeObserver.observe(chartRef.current);
}

return () => {
resizeObserver.disconnect();
abortDimensionsDebounce();
if (chart) {
chart.destroy();
}
};
}, [chart, setContainerDimensionsDebounced, abortDimensionsDebounce]);

useEffect(() => {
if (!chartRef.current || !containerDimensions.width) {
return;
}

const data = dataProcessor.current.parseData(props.dataStatic, props.dataAttribute?.value, props.sampleData);

const layout = dataProcessor.current.parseLayout(
props.layoutStatic,
props.layoutAttribute?.value,
props.sampleLayout
);

const { width, height } = {
width: containerDimensions.width,
height: dataProcessor.current.calculateDimensions(
props.widthUnit,
props.width,
props.heightUnit,
props.height,
containerDimensions.width,
containerDimensions.height
).height
};

const updateData = {
data,
layout: {
...layout,
width,
height,
autosize: true,
font: {
family: "Open Sans, sans-serif",
size: 12
}
},
config: {
...dataProcessor.current.parseConfig(props.configurationOptions),
displayModeBar: props.devMode === "developer",
responsive: true,
staticPlot: false
},
width,
height
};

updateChartDebounced(chart, updateData);

return () => {
abortChartUpdate();
};
}, [
props.dataStatic,
props.dataAttribute?.value,
props.sampleData,
props.layoutStatic,
props.layoutAttribute?.value,
props.sampleLayout,
props.configurationOptions,
props.widthUnit,
props.width,
props.heightUnit,
props.height,
props.devMode,
containerDimensions,
chart,
updateChartDebounced,
abortChartUpdate
]);

return {
chartRef,
containerStyle: {
width: props.widthUnit === "percentage" ? `${props.width}%` : `${props.width}px`,
height: props.heightUnit === "percentageOfParent" ? `${props.height}%` : undefined
}
};
}

0 comments on commit a04898c

Please sign in to comment.