fix: update ApexCharts data in place instead of re-rendering (#23)
CI / build-and-test (pull_request) Successful in 2m21s

Every data change went through renderChart, which rebuilt the chart from a
full option set: animations restarted and zoom/selection state was lost.

Add an updateData path that patches the live chart via ApexCharts'
updateSeries (and updateOptions only when categories/labels actually
change), exposed as AxisChart.updateData / PieChart.updateData. Before the
first render there is nothing to patch, so those fall back to setData.
The theme overlay is re-applied to the merged options, so a rebuild after
a detach starts from the patched data.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0124BiJikbhbiEsNfJxdWM69
This commit is contained in:
Pit Friedrich
2026-07-28 19:29:09 +02:00
parent 1b846751dd
commit 640616c60a
7 changed files with 257 additions and 0 deletions
@@ -17,6 +17,7 @@ public abstract class ApexChart extends Component implements HasSize {
protected static final JsonMapper MAPPER = JsonMapper.builder().build();
private final String chartType;
private boolean optionsSent;
protected ApexChart(String chartType) {
this.chartType = chartType;
@@ -28,6 +29,23 @@ public abstract class ApexChart extends Component implements HasSize {
protected void sendOptions(Map<String, Object> options) {
getElement().callJsFunction("renderChart", MAPPER.writeValueAsString(options));
optionsSent = true;
}
/**
* Sends a data-only patch ({@code series} plus {@code categories} or
* {@code labels}) that the client applies to the running chart, instead of
* rebuilding it from a full option set. Only valid once
* {@link #sendOptions} has run — see {@link #hasSentOptions()}.
*/
protected void sendDataPatch(Map<String, Object> patch) {
getElement().callJsFunction("updateData", MAPPER.writeValueAsString(patch));
}
/** Whether a full option set has been sent, i.e. whether there is a chart
* on the client a data patch could be applied to. */
protected boolean hasSentOptions() {
return optionsSent;
}
@ClientCallable
@@ -20,4 +20,22 @@ public abstract class AxisChart extends ApexChart {
sendOptions(options);
}
/**
* Replaces the data of an already rendered chart without re-rendering it:
* the client patches series (and categories) into the live chart, so the
* update animates from the previous values instead of flashing. Falls back
* to {@link #setData} as long as nothing has been rendered yet.
*/
public void updateData(String seriesName, List<Double> values, List<String> categories) {
if (!hasSentOptions()) {
setData(seriesName, values, categories);
return;
}
sendDataPatch(Map.of(
"series", List.of(Map.of("name", seriesName, "data", values)),
"categories", categories
));
}
}
@@ -16,4 +16,18 @@ public class PieChart extends ApexChart {
sendOptions(options);
}
/**
* Replaces the data of an already rendered chart without re-rendering it —
* see {@link AxisChart#updateData}. Falls back to {@link #setData} as long
* as nothing has been rendered yet.
*/
public void updateData(List<Double> values, List<String> labels) {
if (!hasSentOptions()) {
setData(values, labels);
return;
}
sendDataPatch(Map.of("series", values, "labels", labels));
}
}