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
@@ -0,0 +1,58 @@
package com.example.e2e;
import com.microsoft.playwright.Locator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* End-to-end coverage for the incremental update path of {@code apex-chart.ts}:
* a data update must patch the running ApexCharts instance instead of tearing
* the chart down and building it again.
*/
class DashboardChartPlaywrightTest extends PlaywrightTestBase {
private static final String PATCH = """
{"series":[{"name":"Revenue","data":[1,2,3,4,5,6]}],
"categories":["Jan","Feb","Mar","Apr","May","Jun"]}""";
@BeforeEach
void openDashboard() {
navigate("");
}
@Test
void dataUpdate_keepsTheRenderedSvgInPlace() {
Locator chart = page.locator("apex-chart").first();
assertThat(chart.locator("svg").first()).isVisible();
// Tag the live SVG node: a full rebuild would replace it and drop the tag.
chart.evaluate("el => el.querySelector('svg').dataset.marker = 'first-render'");
chart.evaluate("(el, patch) => el.updateData(patch)", PATCH);
assertEquals(1, chart.locator("svg[data-marker='first-render']").count(),
"the chart must be patched in place, not recreated");
assertEquals("1,2,3,4,5,6",
chart.evaluate("el => JSON.parse(el.lastOptionsJson).series[0].data.join(',')"),
"the patched data must have reached the chart");
}
@Test
void dataUpdate_keepsTheThemeOverlay() {
Locator chart = page.locator("apex-chart").first();
assertThat(chart.locator("svg").first()).isVisible();
chart.evaluate("(el, patch) => el.updateData(patch)", PATCH);
// grid.borderColor is only ever set by applyThemeOverlay(), from the
// resolved --dialect-border token — so a resolved rgb() value here means
// the overlay survived the incremental update.
Object borderColor = chart.evaluate("el => JSON.parse(el.lastOptionsJson).grid.borderColor");
assertTrue(String.valueOf(borderColor).startsWith("rgb"),
"expected a resolved theme color, got: " + borderColor);
}
}