I am using AmChart 5's XY Chart to show some data. When a button is clicked users can see this chart in a MUI Dialog to do some manipulations on it. However, when I want to show my chart in a MUI Dialog with a unique ID, it gives "Could not find HTML element with id" error. When I place the same chart outside the Dialog in the same component, everything works perfectly.
The part where I define the chart:
useLayoutEffect(() => {
let root = am5.Root.new(id);
root.utc = true;
// Set themes
// https://www.amcharts.com/docs/v5/concepts/themes/
root.setThemes([am5themes_Animated.new(root)]);
// Create chart
// https://www.amcharts.com/docs/v5/charts/xy-chart/
let chart = root.container.children.push(
am5xy.XYChart.new(root, {
panX: false,
panY: false,
pinchZoomX: false,
pinchZoomY: false,
})
);
// Add cursor
// https://www.amcharts.com/docs/v5/charts/xy-chart/cursor/
let cursor = chart.set("cursor", am5xy.XYCursor.new(root, {}));
cursor.lineY.set("visible", false);
cursor.lineX.set("visible", false);
// Create axes
// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
let xAxis = chart.xAxes.push(
am5xy.DateAxis.new(root, {
baseInterval: {
timeUnit: "month",
count: 3,
},
gridIntervals: [{ timeUnit: "month", count: 3 }],
renderer: am5xy.AxisRendererX.new(root, {}),
tooltip: am5.Tooltip.new(root, {}),
dateFormats: {
month: "MM/yy",
},
tooltipDateFormats: {
month: "MM/yy",
},
})
);
let yAxis = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {}),
})
);
let series = chart.series.push(
am5xy.LineSeries.new(root, {
name: "Series",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value",
valueXField: "date",
tooltip: am5.Tooltip.new(root, {
labelText: "{valueY}",
}),
fill: am5.color("#fdce97"),
stroke: am5.color("#fdce97"),
width: 4,
})
);
let otherSeries = chart.series.push(
am5xy.LineSeries.new(root, {
name: "Other Series",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value",
valueXField: "date",
tooltip: am5.Tooltip.new(root, {
labelText: "{valueY}",
}),
fill: am5.color("#19214d"),
stroke: am5.color("#19214d"),
})
);
seriesRef.current = otherSeries; // chart assigned to the reference to use later
series.bullets.push(function () {
let bulletCircle = am5.Circle.new(root, {
radius: 2,
stroke: am5.color("#fdce97"),
strokeWidth: 1.5,
});
return am5.Bullet.new(root, {
sprite: bulletCircle,
});
});
otherSeries.bullets.push(function () {
let bulletCircle = am5.Circle.new(root, {
radius: 2,
stroke: am5.color("#19214d"),
strokeWidth: 1.5,
});
return am5.Bullet.new(root, {
sprite: bulletCircle,
});
});
// Invisible bullet which will be dragged (to avoid some conflicting between
// drag position and bullet position which results flicker)
otherSeries.bullets.push(() => {
let bulletCircle = am5.Circle.new(root, {
radius: 6,
fillOpacity: 0,
fill: otherSeries.get("fill"),
draggable: true,
cursorOverStyle: "pointer",
});
bulletCircle.events.on("dragged", function (e) {
handleDrag(e);
});
bulletCircle.events.on("dragstop", function (e) {
//handleChange(e, otherSeries);
});
return am5.Bullet.new(root, {
sprite: bulletCircle,
});
});
// Drag handler
const handleDrag = (e: any) => {
let point = chart.plotContainer.toLocal(e.point);
let date = xAxis.positionToValue(xAxis.coordinateToPosition(point.x));
let value = round(
yAxis.positionToValue(yAxis.coordinateToPosition(point.y)),
1
);
let dataItem = e.target.dataItem;
dataItem.set("valueX", date);
dataItem.set("valueXWorking", date);
dataItem.set("valueY", value);
dataItem.set("valueYWorking", value);
};
// Set data
const draggableData: any = [];
draggableValues.forEach((value: any, index: number) => {
draggableData.push({
value: value,
date: dates[index],
});
});
const originalData: any = [];
originalValues.forEach((value: any, index: number) => {
originalData.push({
value: value,
date: dates[index],
});
});
//setOriginalData(originalData);
series.data.processor = am5.DataProcessor.new(root, {
numericFields: ["value"],
dateFields: ["date"],
dateFormat: "dd/MM/yyyy",
});
otherSeries.data.processor = am5.DataProcessor.new(root, {
numericFields: ["value"],
dateFields: ["date"],
dateFormat: "dd/MM/yyyy",
});
otherSeries.data.setAll(draggableData);
series.data.setAll(originalData);
return () => {
root.dispose();
};
}, []);
The part where I call the chart:
return (
<Dialog open={true} onClose={handleCloseDialog} maxWidth={dialogMaxWidth}>
<Box
sx={{
display: "flex",
flexDirection: "column",
backgroundColor: "grey.200",
minWidth: 500,
}}
>
<Box
sx={{
display: "inline-flex",
alignItems: "center",
px: 2.5,
py: 2.5,
}}
>
<Box
sx={{
display: "inline-flex",
flexDirection: "column",
backgroundColor: "white",
width: "100%",
boxShadow: 1,
borderRadius: "6px",
p: 3,
}}
>
<Box id={id} sx={{ width: "326px", height: "300px" }}></Box>
</Box>
</Box>
</Box>
</Dialog>
);
If I were to call this function like this where the chart is outside the chart, it works perfectly:
return (
<>
<Box id={id} sx={{ width: "326px", height: "300px" }}></Box>
<Dialog open={true} onClose={handleCloseDialog} maxWidth={dialogMaxWidth}>
<Box
sx={{
display: "flex",
flexDirection: "column",
backgroundColor: "grey.200",
minWidth: 500,
}}
>
<Box
sx={{
display: "inline-flex",
alignItems: "center",
px: 2.5,
py: 2.5,
}}
>
<Box
sx={{
display: "inline-flex",
flexDirection: "column",
backgroundColor: "white",
width: "100%",
boxShadow: 1,
borderRadius: "6px",
p: 3,
}}
>
</Box>
</Box>
</Box>
</Dialog>
</>
);
To fix this, you need to make sure that you create the amChart 5
instance only after the Dialog
is open and the element is mounted. You can use a ref
to access the element instead of an id
, and use the onEntered
prop of the Dialog
component to trigger a callback function that creates the chart.
For example:
const chartRef = useRef(null);
const chartInstanceRef = useRef(null);
useEffect(() => {
return () => {
if (chartInstanceRef.current) {
chartInstanceRef.current.dispose();
}
};
}, []);
const handleEntered = () => {
const element = chartRef.current;
const root = am5.Root.new(element);
/* define chart */
chartInstanceRef.current = root;
};
return (
<Dialog
open={open}
onClose={onClose}
onEntered={handleEntered}
maxWidth={dialogMaxWidth}
>
<div ref={chartRef} style={{ width: "100%", height: "500px" }}></div>
</Dialog>
);