From cfeb49e4fa6de673a4cd00dd2da68b0b56cad88c Mon Sep 17 00:00:00 2001 From: Yordan Stoyanov Date: Thu, 22 Oct 2026 11:25:01 +0200 Subject: [PATCH 01/10] docs(shared-charts): add OpenSpec change for chart refresh interval Ticket: WC-3362 --- .../add-chart-refresh-interval/.openspec.yaml | 2 + .../add-chart-refresh-interval/design.md | 84 +++++++++++++++++++ .../add-chart-refresh-interval/proposal.md | 29 +++++++ .../specs/chart-refresh-interval/spec.md | 74 ++++++++++++++++ .../add-chart-refresh-interval/tasks.md | 31 +++++++ 5 files changed, 220 insertions(+) create mode 100644 packages/shared/charts/openspec/changes/add-chart-refresh-interval/.openspec.yaml create mode 100644 packages/shared/charts/openspec/changes/add-chart-refresh-interval/design.md create mode 100644 packages/shared/charts/openspec/changes/add-chart-refresh-interval/proposal.md create mode 100644 packages/shared/charts/openspec/changes/add-chart-refresh-interval/specs/chart-refresh-interval/spec.md create mode 100644 packages/shared/charts/openspec/changes/add-chart-refresh-interval/tasks.md diff --git a/packages/shared/charts/openspec/changes/add-chart-refresh-interval/.openspec.yaml b/packages/shared/charts/openspec/changes/add-chart-refresh-interval/.openspec.yaml new file mode 100644 index 0000000000..dc36fb1bf8 --- /dev/null +++ b/packages/shared/charts/openspec/changes/add-chart-refresh-interval/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-21 diff --git a/packages/shared/charts/openspec/changes/add-chart-refresh-interval/design.md b/packages/shared/charts/openspec/changes/add-chart-refresh-interval/design.md new file mode 100644 index 0000000000..658420f984 --- /dev/null +++ b/packages/shared/charts/openspec/changes/add-chart-refresh-interval/design.md @@ -0,0 +1,84 @@ +## Context + +Datagrid 2 and Gallery support "Refresh time (in seconds)" through `DatasourceService` in `@mendix/widget-plugin-grid` (`src/core/Datasource.service.ts`). It runs a MobX `autorun` that reads `datasource.items`, restarts a `setTimeout` on every items change, and when the timeout fires it calls `datasource.reload()` unless the data source is `loading`. That service only handles a single `ListValue` and belongs to the grid DI container setup. + +The chart widgets are plain React function components and use no DI container: + +- Area, Bar, Bubble, Column, Line, Time series: a list of series objects, each with `dataSet: "static" | "dynamic"` and `staticDataSource` / `dynamicDataSource`, mapped through `usePlotChartDataSeries` from `@mendix/shared-charts`. +- Pie/Doughnut and Heat map: one `seriesDataSource` consumed by widget-local hooks (`src/hooks/data.ts`). + +All of them already keep their last data while a data source reloads. Plot series read `items`, which stay populated while status is `loading`. The Pie and Heat map hooks only update state when `status === Available`. That means a background reload does not blank the chart. + +## Goals / Non-Goals + +**Goals:** + +- One shared, unit-tested implementation of the refresh timer in `@mendix/shared-charts`. +- Behaviour that matches Datagrid 2 / Gallery: seconds in the XML, timer restarts on data change, no reload while loading. +- Small per-widget changes: one XML property, one hook call. + +**Non-Goals:** + +- Custom chart, which uses attribute-based data rather than `ListValue`. +- A "silent refresh" loading indicator: charts show no loading UI today. +- Reusing or refactoring `DatasourceService`. +- Fixing Gallery's refresh wiring. `galleryConfig` hard-codes `refreshIntervalMs: 0` and `GY.refreshInterval` is never bound. That is out of scope here and is tracked separately. + +## Decisions + +### 1. A React hook `useRefreshInterval(datasources, intervalSeconds)` in shared/charts + +```ts +export function useRefreshInterval(datasources: Array, intervalSeconds: number): void; +``` + +Location: `src/hooks/useRefreshInterval.ts`, exported from `src/main.ts`. + +- _Why a hook rather than `DatasourceService`:_ charts have no `SetupComponentHost` / gate / container, and building one just for a timer would be over-engineering. `DatasourceService` also takes a single data source, while plot charts have N of them. +- _Why seconds in, not ms:_ every widget passes `props.refreshInterval` straight through. The `* 1000` conversion happens in one place, inside the hook. + +### 2. Restart the timer when any data source changes + +Mendix hands the widget a new `ListValue` object on every data source update, including an items change. The hook therefore compares the data source references, and `intervalSeconds`, with the previous render, and only on a difference clears the pending timeout and schedules a new one. This is the React equivalent of the `autorun` in `DatasourceService`, which reruns whenever the gate's `datasource` prop changes. + +- The comparison is a shallow, element-wise one inside a dependency-free `useEffect`, using refs. This avoids spreading a variable-length array into a deps list. A parent re-render with the same data source objects does not restart the timer. +- _Implementation note:_ the comparison is on data source identity rather than `items` identity. Data source identity is a superset (it also changes on `loading` → `available`), so the timer is never left stale when a reload returns an equal `items` reference. +- _Alternative considered:_ `setInterval` on a fixed schedule. Rejected because it would reload straight after a user-triggered or other refresh and would differ from Datagrid behaviour. + +### 3. When the timer fires, reload each non-loading data source + +On fire, iterate over the latest data sources from the ref and call `ds.reload()` for each defined `ds` whose `status !== "loading"`. Each reload yields new `items`, which restarts the timer (Decision 2). + +- _Alternative:_ skip all reloads if any source is loading. Rejected because one slow source would stall the refresh of the others. + +### 4. Widget wiring + +- Series charts: `useRefreshInterval(props..map(s => s.dataSet === "static" ? s.staticDataSource : s.dynamicDataSource), props.refreshInterval)`. Memoise the mapped array (or let the hook tolerate a new array each render, since its deps are derived from `items` and not from array identity). +- Pie/Doughnut, Heat map: `useRefreshInterval([props.seriesDataSource], props.refreshInterval)`. +- `LineChart` is wrapped in `memo` with a custom `flatEqual` comparator. A new `refreshInterval` prop is a primitive and compares through `defaultEqual`, so no comparator change is needed. + +### 5. XML property + +```xml + + Refresh time (in seconds) + + +``` + +Place it at the end of the "General > Data source" group, after the series / `seriesDataSource` properties. Caption and key are the same as in Datagrid/Gallery. The tracked `typings/*Props.d.ts` files are regenerated by the widget build, not edited by hand. + +## Risks / Trade-offs + +- [Several sources reload at slightly different times, so the timer restarts several times per cycle] → Harmless. The next reload happens `interval` seconds after the last arrival. This is documented in the spec ("restart on data change"). +- [A very small interval (e.g. 1s) with heavy aggregations puts load on the server] → Same trade-off as Datagrid. It is the developer's choice, and the default stays off. +- [Plotly re-render on every reload even when data is unchanged] → Existing `usePlotChartDataSeries` effect behaviour. Acceptable, and the same as a manual refresh. +- [Editor preview / design mode] → Preview components do not receive live `ListValue`s, so the hook is called only in the runtime components. + +## Migration Plan + +Additive only. Existing apps get `refreshInterval = 0` (disabled) when they upgrade the widget, so no migration is needed. Rollback means reverting the widget versions. + +## Open Questions + +- Should each chart's editorConfig hide `refreshInterval` in some configurations? Current assumption: no, it is always visible, as in Datagrid. diff --git a/packages/shared/charts/openspec/changes/add-chart-refresh-interval/proposal.md b/packages/shared/charts/openspec/changes/add-chart-refresh-interval/proposal.md new file mode 100644 index 0000000000..be45c12a3d --- /dev/null +++ b/packages/shared/charts/openspec/changes/add-chart-refresh-interval/proposal.md @@ -0,0 +1,29 @@ +## Why + +Charts in Mendix used to offer a refresh interval, and the docs still describe it. The pluggable chart widgets have no such option today (WC-3362). If a chart reads data that changes without a client-side commit, such as an aggregation over a view entity, it shows stale data until the user refreshes the page by hand. Datagrid 2 and Gallery already have a "Refresh time (in seconds)" property, so charts should match them. + +## What Changes + +- Add a `refreshInterval` integer property ("Refresh time (in seconds)", default `0` = disabled) to the list-based chart widgets: Area, Bar, Bubble, Column, Heat map, Line, Pie/Doughnut, Time series. Custom chart is out of scope because it reads an attribute, not a list data source. +- When the interval is above 0, the chart reloads every list data source it uses after that many seconds. The timer restarts whenever the data changes. This matches the Datagrid 2 / Gallery behaviour. +- A reload in progress never starts a second one. The chart keeps showing the current data while it reloads, so it does not flash to empty. +- Add one shared hook to `@mendix/shared-charts` so all eight widgets use the same refresh logic. +- Each affected widget gets an "Added" entry under `[Unreleased]` in its CHANGELOG. + +## Capabilities + +### New Capabilities + +- `chart-refresh-interval`: periodic background reload of the list data sources behind a chart widget, set by a "Refresh time (in seconds)" property. + +### Modified Capabilities + + + +## Impact + +- `packages/shared/charts`: new hook `useRefreshInterval` in `src/hooks/`, exported from `src/main.ts`, with unit tests. +- Widget XML and the regenerated `typings/*Props.d.ts` in: `area-chart-web`, `bar-chart-web`, `bubble-chart-web`, `column-chart-web`, `heatmap-chart-web`, `line-chart-web`, `pie-doughnut-chart-web`, `time-series-chart-web`. +- Each of those widget components calls the hook with its data sources (per series: `staticDataSource` / `dynamicDataSource`; for pie and heat map: `seriesDataSource`). +- Test mocks/fixtures that build container props need the new `refreshInterval: 0` prop. +- No new dependencies. Not breaking: the default `0` keeps today's behaviour. diff --git a/packages/shared/charts/openspec/changes/add-chart-refresh-interval/specs/chart-refresh-interval/spec.md b/packages/shared/charts/openspec/changes/add-chart-refresh-interval/specs/chart-refresh-interval/spec.md new file mode 100644 index 0000000000..6169b6af99 --- /dev/null +++ b/packages/shared/charts/openspec/changes/add-chart-refresh-interval/specs/chart-refresh-interval/spec.md @@ -0,0 +1,74 @@ +## ADDED Requirements + +### Requirement: Refresh time property on list-based chart widgets + +The Area, Bar, Bubble, Column, Heat map, Line, Pie/Doughnut and Time series chart widgets SHALL each expose an integer property `refreshInterval`, captioned "Refresh time (in seconds)", in the "General > Data source" property group, with default value `0`. The Custom chart widget SHALL NOT expose this property. + +#### Scenario: Property available in Studio Pro + +- **WHEN** a developer selects one of the eight list-based chart widgets in Studio Pro +- **THEN** a "Refresh time (in seconds)" integer property appears in the Data source group with the value `0` + +#### Scenario: Custom chart unchanged + +- **WHEN** a developer selects a Custom chart widget +- **THEN** no "Refresh time (in seconds)" property is shown + +### Requirement: Disabled by default + +When `refreshInterval` is `0` or negative, the chart SHALL NOT schedule any automatic reload of its data sources. + +#### Scenario: Default value keeps current behaviour + +- **WHEN** a chart is rendered with `refreshInterval` set to `0` +- **THEN** no data source `reload()` is triggered by the widget, however long it stays mounted + +### Requirement: Periodic reload of all chart data sources + +When `refreshInterval` is greater than `0`, the chart SHALL call `reload()` on every list data source it renders after `refreshInterval` seconds without a data change. For series-based charts this means each series' active data source (`staticDataSource` for single series, `dynamicDataSource` for multiple series). For Pie/Doughnut and Heat map it means `seriesDataSource`. Undefined data sources SHALL be ignored. + +#### Scenario: Single data source reloads after interval + +- **WHEN** a Pie chart is rendered with `refreshInterval` = 5 and its data does not change +- **THEN** `seriesDataSource.reload()` is called once after 5 seconds + +#### Scenario: All series reload + +- **WHEN** a Line chart with two series, one static and one dynamic, is rendered with `refreshInterval` = 10 +- **THEN** after 10 seconds `reload()` is called on the first series' `staticDataSource` and on the second series' `dynamicDataSource` + +### Requirement: Timer restarts on data change + +The refresh timer SHALL restart whenever the items of any of the chart's data sources change, so that a reload happens `refreshInterval` seconds after the most recent data update and not on a fixed wall-clock schedule. + +#### Scenario: Data update postpones the next reload + +- **WHEN** a chart has `refreshInterval` = 10 and its data source delivers new items 6 seconds after the last reload +- **THEN** the next automatic reload happens 10 seconds after that update, not 4 seconds later + +### Requirement: No overlapping reloads + +When the timer fires, the chart SHALL NOT call `reload()` on a data source whose status is `loading`. + +#### Scenario: Data source still loading + +- **WHEN** the refresh timer fires while a data source has status `loading` +- **THEN** `reload()` is not called on that data source + +### Requirement: Existing data stays visible during reload + +During an automatic reload, the chart SHALL keep rendering its last loaded data until the new data arrives. It SHALL NOT clear or blank the plot. + +#### Scenario: No flash of empty chart + +- **WHEN** an automatic reload is in progress +- **THEN** the chart continues to show the previously loaded data points + +### Requirement: Timer cleanup + +The chart SHALL cancel any pending refresh timer when it unmounts or when `refreshInterval` changes. + +#### Scenario: Unmount cancels timer + +- **WHEN** a chart with `refreshInterval` > 0 is unmounted before the timer fires +- **THEN** no `reload()` call happens afterwards diff --git a/packages/shared/charts/openspec/changes/add-chart-refresh-interval/tasks.md b/packages/shared/charts/openspec/changes/add-chart-refresh-interval/tasks.md new file mode 100644 index 0000000000..973cb58139 --- /dev/null +++ b/packages/shared/charts/openspec/changes/add-chart-refresh-interval/tasks.md @@ -0,0 +1,31 @@ +## 1. Shared hook (`packages/shared/charts`) + +- [x] 1.1 Add `src/hooks/useRefreshInterval.ts` exporting `useRefreshInterval(datasources: Array, intervalSeconds: number): void`. It does nothing when `intervalSeconds <= 0`, restarts its `setTimeout` whenever any data source object (and therefore its items) changes, calls `reload()` on fire for each defined data source whose status is not `loading`, and clears the timer on unmount or interval change. +- [x] 1.2 Export the hook from `src/main.ts` +- [x] 1.3 Add `src/hooks/__tests__/useRefreshInterval.spec.ts` (jest fake timers + `@testing-library/react` `renderHook`, `ListValueBuilder` from `@mendix/widget-plugin-test-utils`) covering: disabled at 0, single reload after interval, all data sources reloaded, timer restart on items change, loading source skipped, undefined sources ignored, unmount cancels timer +- [x] 1.4 `pnpm run test` and `pnpm run lint` pass in `packages/shared/charts` + +## 2. Series-based charts (Area, Bar, Bubble, Column, Line, Time series) + +- [x] 2.1 Add the `refreshInterval` integer property (caption "Refresh time (in seconds)", default `0`) to the end of the "General > Data source" group in `AreaChart.xml`, `BarChart.xml`, `BubbleChart.xml`, `ColumnChart.xml`, `LineChart.xml`, `TimeSeries.xml` +- [x] 2.2 Regenerate `typings/*Props.d.ts` for each by building the widget, not by hand-editing +- [x] 2.3 Call `useRefreshInterval` in each component, mapping the series list (`series` for Area/Bar/Column, `lines` for Bubble/Line/Time series) to each series' active data source (`dataSet === "static" ? staticDataSource : dynamicDataSource`) +- [x] 2.4 Update test fixtures / mock props with `refreshInterval: 0` where the type requires it, and update any affected snapshots +- [x] 2.5 Add one spec to `LineChart.spec.tsx` asserting that `reload()` is called on each series' data source after the interval + +## 3. Single-datasource charts (Pie/Doughnut, Heat map) + +- [x] 3.1 Add the `refreshInterval` property to the "General > Data source" group in `PieChart.xml` and `HeatMap.xml` +- [x] 3.2 Regenerate `typings/*Props.d.ts` +- [x] 3.3 Call `useRefreshInterval([props.seriesDataSource], props.refreshInterval)` in `PieChart.tsx` and `HeatMap.tsx` +- [x] 3.4 Update test fixtures / mock props and snapshots + +## 4. Changelogs + +- [x] 4.1 Add an `### Added` entry under `## [Unreleased]` in the CHANGELOG.md of all eight affected widgets: "We added a "Refresh time (in seconds)" property that periodically reloads the chart data." +- [x] 4.2 Do not bump versions (that happens at release time). Do not touch `charts-web/CHANGELOG.md` (aggregated at release). + +## 5. Verification + +- [x] 5.1 `pnpm run test` and `pnpm run lint` pass in each of the eight widget packages +- [ ] 5.2 Manual QA in Studio Pro: a Line chart on a view entity with `refreshInterval = 5` updates after the underlying data changes; at `0` it does not update; the chart does not flash empty during reload From 183f4e1e4019af26402c427ab24ad291a3f3ef91 Mon Sep 17 00:00:00 2001 From: Yordan Stoyanov Date: Thu, 22 Oct 2026 11:25:03 +0200 Subject: [PATCH 02/10] feat(shared-charts): add useRefreshInterval hook Reloads the given data sources after the interval, restarting the timer on every data source update and skipping sources that are still loading. Mirrors the Datagrid 2 / Gallery refresh behaviour. Adds seriesDataSources helper. Ticket: WC-3362 --- .../__tests__/useRefreshInterval.spec.ts | 133 ++++++++++++++++++ .../charts/src/hooks/useRefreshInterval.ts | 62 ++++++++ packages/shared/charts/src/main.ts | 1 + 3 files changed, 196 insertions(+) create mode 100644 packages/shared/charts/src/hooks/__tests__/useRefreshInterval.spec.ts create mode 100644 packages/shared/charts/src/hooks/useRefreshInterval.ts diff --git a/packages/shared/charts/src/hooks/__tests__/useRefreshInterval.spec.ts b/packages/shared/charts/src/hooks/__tests__/useRefreshInterval.spec.ts new file mode 100644 index 0000000000..a61991eea9 --- /dev/null +++ b/packages/shared/charts/src/hooks/__tests__/useRefreshInterval.spec.ts @@ -0,0 +1,133 @@ +import { renderHook } from "@testing-library/react"; +import { ListValue } from "mendix"; +import { ListValueBuilder } from "@mendix/widget-plugin-test-utils"; +import { seriesDataSources, useRefreshInterval } from "../useRefreshInterval"; + +describe("seriesDataSources", () => { + it("picks the active data source of each series", () => { + const staticDs = new ListValueBuilder().build(); + const dynamicDs = new ListValueBuilder().build(); + + expect( + seriesDataSources([ + { dataSet: "static", staticDataSource: staticDs, dynamicDataSource: dynamicDs }, + { dataSet: "dynamic", staticDataSource: staticDs, dynamicDataSource: dynamicDs }, + { dataSet: "dynamic" } + ]) + ).toEqual([staticDs, dynamicDs, undefined]); + }); +}); + +type HookProps = { datasources: Array; interval: number }; + +function setup(initialProps: HookProps): ReturnType> { + return renderHook(({ datasources, interval }: HookProps) => useRefreshInterval(datasources, interval), { + initialProps + }); +} + +describe("useRefreshInterval", () => { + beforeEach(() => jest.useFakeTimers()); + afterEach(() => jest.useRealTimers()); + + it("does not reload when interval is 0", () => { + const ds = new ListValueBuilder().build(); + setup({ datasources: [ds], interval: 0 }); + + jest.advanceTimersByTime(60_000); + + expect(ds.reload).not.toHaveBeenCalled(); + }); + + it("reloads data source once after the interval", () => { + const ds = new ListValueBuilder().build(); + setup({ datasources: [ds], interval: 5 }); + + jest.advanceTimersByTime(4999); + expect(ds.reload).not.toHaveBeenCalled(); + jest.advanceTimersByTime(1); + expect(ds.reload).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(60_000); + expect(ds.reload).toHaveBeenCalledTimes(1); + }); + + it("reloads all data sources and ignores undefined ones", () => { + const a = new ListValueBuilder().build(); + const b = new ListValueBuilder().build(); + setup({ datasources: [a, undefined, b], interval: 10 }); + + jest.advanceTimersByTime(10_000); + + expect(a.reload).toHaveBeenCalledTimes(1); + expect(b.reload).toHaveBeenCalledTimes(1); + }); + + it("restarts the timer when a data source updates", () => { + const ds = new ListValueBuilder().build(); + const { rerender } = setup({ datasources: [ds], interval: 10 }); + + jest.advanceTimersByTime(6000); + const updated = new ListValueBuilder().withSize(3).build(); + rerender({ datasources: [updated], interval: 10 }); + + jest.advanceTimersByTime(4000); + expect(ds.reload).not.toHaveBeenCalled(); + expect(updated.reload).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(6000); + expect(updated.reload).toHaveBeenCalledTimes(1); + expect(ds.reload).not.toHaveBeenCalled(); + }); + + it("does not restart the timer on re-render with the same data sources", () => { + const ds = new ListValueBuilder().build(); + const { rerender } = setup({ datasources: [ds], interval: 10 }); + + jest.advanceTimersByTime(6000); + rerender({ datasources: [ds], interval: 10 }); + jest.advanceTimersByTime(4000); + + expect(ds.reload).toHaveBeenCalledTimes(1); + }); + + it("skips data sources that are loading", () => { + const loading = new ListValueBuilder().isLoading().build(); + const ready = new ListValueBuilder().build(); + setup({ datasources: [loading, ready], interval: 5 }); + + jest.advanceTimersByTime(5000); + + expect(loading.reload).not.toHaveBeenCalled(); + expect(ready.reload).toHaveBeenCalledTimes(1); + }); + + it("reschedules when the interval changes", () => { + const ds = new ListValueBuilder().build(); + const { rerender } = setup({ datasources: [ds], interval: 10 }); + + rerender({ datasources: [ds], interval: 0 }); + jest.advanceTimersByTime(60_000); + + expect(ds.reload).not.toHaveBeenCalled(); + }); + + it("keeps refreshing after a StrictMode remount", () => { + const ds = new ListValueBuilder().build(); + renderHook(() => useRefreshInterval([ds], 5), { reactStrictMode: true }); + + jest.advanceTimersByTime(5000); + + expect(ds.reload).toHaveBeenCalledTimes(1); + }); + + it("cancels the pending timer on unmount", () => { + const ds = new ListValueBuilder().build(); + const { unmount } = setup({ datasources: [ds], interval: 5 }); + + unmount(); + jest.advanceTimersByTime(60_000); + + expect(ds.reload).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/shared/charts/src/hooks/useRefreshInterval.ts b/packages/shared/charts/src/hooks/useRefreshInterval.ts new file mode 100644 index 0000000000..e077f525ee --- /dev/null +++ b/packages/shared/charts/src/hooks/useRefreshInterval.ts @@ -0,0 +1,62 @@ +import { ListValue } from "mendix"; +import { useEffect, useRef } from "react"; +import { PlotDataSeries } from "./usePlotChartDataSeries"; + +/** + * Periodically reloads the given data sources, mirroring the "Refresh time" + * behaviour of Datagrid 2 and Gallery: the timer restarts on every data source + * update, so a reload happens `intervalSeconds` after the most recent change. + * Data sources that are still loading are skipped. + */ +export function useRefreshInterval(datasources: Array, intervalSeconds: number): void { + const latestRef = useRef(datasources); + const prevDatasourcesRef = useRef | null>(null); + const prevIntervalRef = useRef(intervalSeconds); + const timerIdRef = useRef | undefined>(undefined); + + // Runs after every render: reschedule only when a data source or the interval changed. + useEffect(() => { + latestRef.current = datasources; + const changed = + prevDatasourcesRef.current === null || + prevIntervalRef.current !== intervalSeconds || + !shallowEqual(prevDatasourcesRef.current, datasources); + if (!changed) { + return; + } + prevDatasourcesRef.current = datasources; + prevIntervalRef.current = intervalSeconds; + + clearTimeout(timerIdRef.current); + timerIdRef.current = undefined; + if (intervalSeconds > 0) { + timerIdRef.current = setTimeout(() => { + for (const ds of latestRef.current) { + if (ds && ds.status !== "loading") { + ds.reload(); + } + } + }, intervalSeconds * 1000); + } + }); + + useEffect( + () => () => { + clearTimeout(timerIdRef.current); + // Forget previous inputs so a remount (e.g. StrictMode) schedules the timer again. + prevDatasourcesRef.current = null; + }, + [] + ); +} + +/** Returns the active data source of each series ("static" or "dynamic" data set). */ +export function seriesDataSources( + series: Array> +): Array { + return series.map(serie => (serie.dataSet === "static" ? serie.staticDataSource : serie.dynamicDataSource)); +} + +function shallowEqual(a: T[], b: T[]): boolean { + return a.length === b.length && a.every((value, index) => value === b[index]); +} diff --git a/packages/shared/charts/src/main.ts b/packages/shared/charts/src/main.ts index 80896f1b08..453d78cd46 100644 --- a/packages/shared/charts/src/main.ts +++ b/packages/shared/charts/src/main.ts @@ -4,6 +4,7 @@ export type * from "./helpers/EditorStore"; export type * from "./helpers/playground-context"; export type * from "./helpers/useEditorStore"; export * from "./hooks/usePlotChartDataSeries"; +export * from "./hooks/useRefreshInterval"; export * from "./utils/compareAttrValuesAsc"; export * from "./utils/configs"; export * from "./utils/chartStyles"; From ecf0b4a9561f0f4bddd2bf6f8613d950a0eb1cd4 Mon Sep 17 00:00:00 2001 From: Yordan Stoyanov Date: Thu, 22 Oct 2026 11:25:05 +0200 Subject: [PATCH 03/10] feat(area-chart-web): add refresh time property Add "Refresh time (in seconds)" to the Data source group (default 0 = off). When set, the chart periodically reloads its data source(s). Ticket: WC-3362 --- packages/pluggableWidgets/area-chart-web/CHANGELOG.md | 4 ++++ packages/pluggableWidgets/area-chart-web/src/AreaChart.tsx | 6 +++++- packages/pluggableWidgets/area-chart-web/src/AreaChart.xml | 4 ++++ .../area-chart-web/src/__tests__/AreaChart.spec.tsx | 1 + .../area-chart-web/typings/AreaChartProps.d.ts | 2 ++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/pluggableWidgets/area-chart-web/CHANGELOG.md b/packages/pluggableWidgets/area-chart-web/CHANGELOG.md index 51cc6f0a39..c45322fa1b 100644 --- a/packages/pluggableWidgets/area-chart-web/CHANGELOG.md +++ b/packages/pluggableWidgets/area-chart-web/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- We added a "Refresh time (in seconds)" property that periodically reloads the chart data. + ## [6.2.1] - 2025-07-15 ### Changed diff --git a/packages/pluggableWidgets/area-chart-web/src/AreaChart.tsx b/packages/pluggableWidgets/area-chart-web/src/AreaChart.tsx index 40a2060cdd..8828e4517d 100644 --- a/packages/pluggableWidgets/area-chart-web/src/AreaChart.tsx +++ b/packages/pluggableWidgets/area-chart-web/src/AreaChart.tsx @@ -2,8 +2,10 @@ import { ChartWidget, ChartWidgetProps, containerPropsEqual, + seriesDataSources, SeriesMapper, - usePlotChartDataSeries + usePlotChartDataSeries, + useRefreshInterval } from "@mendix/shared-charts/main"; import "@mendix/shared-charts/ui/Chart.scss"; import classNames from "classnames"; @@ -32,6 +34,8 @@ const areaChartSeriesOptions: ChartWidgetProps["seriesOptions"] = {}; // disable eslint rule to have nice component name in component tree at devtools export const AreaChart = memo(function AreaChart(props: AreaChartContainerProps): ReactElement { + useRefreshInterval(seriesDataSources(props.series), props.refreshInterval); + const mapSeries = useCallback>((line, dataPoints, { getExpressionValue }) => { const lineColorExpression = line.dataSet === "static" ? line.staticLineColor : line.dynamicLineColor; const markerColorExpression = line.dataSet === "static" ? line.staticMarkerColor : line.dynamicMarkerColor; diff --git a/packages/pluggableWidgets/area-chart-web/src/AreaChart.xml b/packages/pluggableWidgets/area-chart-web/src/AreaChart.xml index 3b9c7eda08..c6aef14ce8 100644 --- a/packages/pluggableWidgets/area-chart-web/src/AreaChart.xml +++ b/packages/pluggableWidgets/area-chart-web/src/AreaChart.xml @@ -194,6 +194,10 @@ + + Refresh time (in seconds) + + diff --git a/packages/pluggableWidgets/area-chart-web/src/__tests__/AreaChart.spec.tsx b/packages/pluggableWidgets/area-chart-web/src/__tests__/AreaChart.spec.tsx index bdd10352b9..1c1d4865f6 100644 --- a/packages/pluggableWidgets/area-chart-web/src/__tests__/AreaChart.spec.tsx +++ b/packages/pluggableWidgets/area-chart-web/src/__tests__/AreaChart.spec.tsx @@ -18,6 +18,7 @@ describe("The AreaChart widget", () => { return render( string; series: SeriesPreviewType[]; + refreshInterval: number | null; enableAdvancedOptions: boolean; showPlaygroundSlot: boolean; playground: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; From 2bdc01bcfcf1dfeedaf23ee8733134b1c97199d6 Mon Sep 17 00:00:00 2001 From: Yordan Stoyanov Date: Thu, 22 Oct 2026 11:25:07 +0200 Subject: [PATCH 04/10] feat(bar-chart-web): add refresh time property Add "Refresh time (in seconds)" to the Data source group (default 0 = off). When set, the chart periodically reloads its data source(s). Ticket: WC-3362 --- packages/pluggableWidgets/bar-chart-web/CHANGELOG.md | 4 ++++ .../pluggableWidgets/bar-chart-web/src/BarChart.tsx | 11 ++++++++++- .../pluggableWidgets/bar-chart-web/src/BarChart.xml | 4 ++++ .../bar-chart-web/src/__tests__/BarChart.spec.tsx | 1 + .../bar-chart-web/typings/BarChartProps.d.ts | 2 ++ 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/pluggableWidgets/bar-chart-web/CHANGELOG.md b/packages/pluggableWidgets/bar-chart-web/CHANGELOG.md index 482886e036..7fc505c3f9 100644 --- a/packages/pluggableWidgets/bar-chart-web/CHANGELOG.md +++ b/packages/pluggableWidgets/bar-chart-web/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- We added a "Refresh time (in seconds)" property that periodically reloads the chart data. + ## [6.2.1] - 2025-07-15 ### Changed diff --git a/packages/pluggableWidgets/bar-chart-web/src/BarChart.tsx b/packages/pluggableWidgets/bar-chart-web/src/BarChart.tsx index 200e67c0e4..e329aeea21 100644 --- a/packages/pluggableWidgets/bar-chart-web/src/BarChart.tsx +++ b/packages/pluggableWidgets/bar-chart-web/src/BarChart.tsx @@ -1,4 +1,11 @@ -import { ChartWidget, ChartWidgetProps, containerPropsEqual, usePlotChartDataSeries } from "@mendix/shared-charts/main"; +import { + ChartWidget, + ChartWidgetProps, + containerPropsEqual, + seriesDataSources, + usePlotChartDataSeries, + useRefreshInterval +} from "@mendix/shared-charts/main"; import "@mendix/shared-charts/ui/Chart.scss"; import classNames from "classnames"; import { memo, ReactElement, useCallback, useMemo } from "react"; @@ -32,6 +39,8 @@ const barChartSeriesOptions: ChartWidgetProps["seriesOptions"] = { // disable eslint rule to have nice component name in component tree at devtools export const BarChart = memo(function BarChart(props: BarChartContainerProps): ReactElement | null { + useRefreshInterval(seriesDataSources(props.series), props.refreshInterval); + const layoutOptions = useMemo( () => ({ ...barChartLayoutOptions, diff --git a/packages/pluggableWidgets/bar-chart-web/src/BarChart.xml b/packages/pluggableWidgets/bar-chart-web/src/BarChart.xml index 428c827fa6..d7054c24f3 100644 --- a/packages/pluggableWidgets/bar-chart-web/src/BarChart.xml +++ b/packages/pluggableWidgets/bar-chart-web/src/BarChart.xml @@ -154,6 +154,10 @@ + + Refresh time (in seconds) + + diff --git a/packages/pluggableWidgets/bar-chart-web/src/__tests__/BarChart.spec.tsx b/packages/pluggableWidgets/bar-chart-web/src/__tests__/BarChart.spec.tsx index ae49dd88bb..86f60135fd 100644 --- a/packages/pluggableWidgets/bar-chart-web/src/__tests__/BarChart.spec.tsx +++ b/packages/pluggableWidgets/bar-chart-web/src/__tests__/BarChart.spec.tsx @@ -20,6 +20,7 @@ describe("The BarChart widget", () => { return render( string; series: SeriesPreviewType[]; + refreshInterval: number | null; enableAdvancedOptions: boolean; showPlaygroundSlot: boolean; playground: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; From 534b74ec2f7703bf1a2f1dc0c1ccccebbfef598e Mon Sep 17 00:00:00 2001 From: Yordan Stoyanov Date: Thu, 22 Oct 2026 11:25:09 +0200 Subject: [PATCH 05/10] feat(bubble-chart-web): add refresh time property Add "Refresh time (in seconds)" to the Data source group (default 0 = off). When set, the chart periodically reloads its data source(s). Ticket: WC-3362 --- .../pluggableWidgets/bubble-chart-web/CHANGELOG.md | 4 ++++ .../bubble-chart-web/src/BubbleChart.tsx | 11 ++++++++++- .../bubble-chart-web/src/BubbleChart.xml | 4 ++++ .../src/__tests__/BubbleChart.spec.tsx | 1 + .../bubble-chart-web/typings/BubbleChartProps.d.ts | 2 ++ 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/pluggableWidgets/bubble-chart-web/CHANGELOG.md b/packages/pluggableWidgets/bubble-chart-web/CHANGELOG.md index a9cdfe0c0a..4ce50367b9 100644 --- a/packages/pluggableWidgets/bubble-chart-web/CHANGELOG.md +++ b/packages/pluggableWidgets/bubble-chart-web/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- We added a "Refresh time (in seconds)" property that periodically reloads the chart data. + ## [6.2.1] - 2025-07-15 ### Changed diff --git a/packages/pluggableWidgets/bubble-chart-web/src/BubbleChart.tsx b/packages/pluggableWidgets/bubble-chart-web/src/BubbleChart.tsx index 6d1bdaf671..4c15a0c5e4 100644 --- a/packages/pluggableWidgets/bubble-chart-web/src/BubbleChart.tsx +++ b/packages/pluggableWidgets/bubble-chart-web/src/BubbleChart.tsx @@ -1,4 +1,11 @@ -import { ChartWidget, ChartWidgetProps, traceEqual, usePlotChartDataSeries } from "@mendix/shared-charts/main"; +import { + ChartWidget, + ChartWidgetProps, + seriesDataSources, + traceEqual, + usePlotChartDataSeries, + useRefreshInterval +} from "@mendix/shared-charts/main"; import "@mendix/shared-charts/ui/Chart.scss"; import Big from "big.js"; import classNames from "classnames"; @@ -30,6 +37,8 @@ export const BubbleChart = memo( // disable eslint rule to have nice component name in component tree at devtools function BubbleChart(props: BubbleChartContainerProps): ReactElement { + useRefreshInterval(seriesDataSources(props.lines), props.refreshInterval); + const getSizes = (line: LinesType): number[] => { const sizes: number[] = []; let dataset: LinesType["staticDataSource"] | LinesType["dynamicDataSource"] | undefined; diff --git a/packages/pluggableWidgets/bubble-chart-web/src/BubbleChart.xml b/packages/pluggableWidgets/bubble-chart-web/src/BubbleChart.xml index 37320031b0..d13236ac12 100644 --- a/packages/pluggableWidgets/bubble-chart-web/src/BubbleChart.xml +++ b/packages/pluggableWidgets/bubble-chart-web/src/BubbleChart.xml @@ -183,6 +183,10 @@ + + Refresh time (in seconds) + + diff --git a/packages/pluggableWidgets/bubble-chart-web/src/__tests__/BubbleChart.spec.tsx b/packages/pluggableWidgets/bubble-chart-web/src/__tests__/BubbleChart.spec.tsx index f43caae19a..03b93bf816 100644 --- a/packages/pluggableWidgets/bubble-chart-web/src/__tests__/BubbleChart.spec.tsx +++ b/packages/pluggableWidgets/bubble-chart-web/src/__tests__/BubbleChart.spec.tsx @@ -24,6 +24,7 @@ describe("The Bubble widget", () => { return render( string; lines: LinesPreviewType[]; + refreshInterval: number | null; enableAdvancedOptions: boolean; showPlaygroundSlot: boolean; playground: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; From 5d232aa1d773a7c523b24c70c6b46df54aace91a Mon Sep 17 00:00:00 2001 From: Yordan Stoyanov Date: Thu, 22 Oct 2026 11:25:11 +0200 Subject: [PATCH 06/10] feat(column-chart-web): add refresh time property Add "Refresh time (in seconds)" to the Data source group (default 0 = off). When set, the chart periodically reloads its data source(s). Ticket: WC-3362 --- .../pluggableWidgets/column-chart-web/CHANGELOG.md | 4 ++++ .../column-chart-web/src/ColumnChart.tsx | 11 ++++++++++- .../column-chart-web/src/ColumnChart.xml | 4 ++++ .../src/__tests__/ColumnChart.spec.tsx | 1 + .../column-chart-web/typings/ColumnChartProps.d.ts | 2 ++ 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/pluggableWidgets/column-chart-web/CHANGELOG.md b/packages/pluggableWidgets/column-chart-web/CHANGELOG.md index 87a8314a72..fb72fb4f43 100644 --- a/packages/pluggableWidgets/column-chart-web/CHANGELOG.md +++ b/packages/pluggableWidgets/column-chart-web/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- We added a "Refresh time (in seconds)" property that periodically reloads the chart data. + ## [6.2.1] - 2025-07-15 ### Changed diff --git a/packages/pluggableWidgets/column-chart-web/src/ColumnChart.tsx b/packages/pluggableWidgets/column-chart-web/src/ColumnChart.tsx index ab88ad57c2..d03c21d32c 100644 --- a/packages/pluggableWidgets/column-chart-web/src/ColumnChart.tsx +++ b/packages/pluggableWidgets/column-chart-web/src/ColumnChart.tsx @@ -1,4 +1,11 @@ -import { ChartWidget, ChartWidgetProps, containerPropsEqual, usePlotChartDataSeries } from "@mendix/shared-charts/main"; +import { + ChartWidget, + ChartWidgetProps, + containerPropsEqual, + seriesDataSources, + usePlotChartDataSeries, + useRefreshInterval +} from "@mendix/shared-charts/main"; import "@mendix/shared-charts/ui/Chart.scss"; import classNames from "classnames"; import { memo, ReactElement, useCallback, useMemo } from "react"; @@ -32,6 +39,8 @@ const columnChartSeriesOptions: ChartWidgetProps["seriesOptions"] = { // disable eslint rule to have nice component name in component tree at devtools export const ColumnChart = memo(function ColumnChart(props: ColumnChartContainerProps): ReactElement | null { + useRefreshInterval(seriesDataSources(props.series), props.refreshInterval); + const layoutOptions = useMemo( () => ({ ...columnChartLayoutOptions, diff --git a/packages/pluggableWidgets/column-chart-web/src/ColumnChart.xml b/packages/pluggableWidgets/column-chart-web/src/ColumnChart.xml index d734636b82..df4aed903c 100644 --- a/packages/pluggableWidgets/column-chart-web/src/ColumnChart.xml +++ b/packages/pluggableWidgets/column-chart-web/src/ColumnChart.xml @@ -154,6 +154,10 @@ + + Refresh time (in seconds) + + diff --git a/packages/pluggableWidgets/column-chart-web/src/__tests__/ColumnChart.spec.tsx b/packages/pluggableWidgets/column-chart-web/src/__tests__/ColumnChart.spec.tsx index 272aa405df..0ec5d2ee60 100644 --- a/packages/pluggableWidgets/column-chart-web/src/__tests__/ColumnChart.spec.tsx +++ b/packages/pluggableWidgets/column-chart-web/src/__tests__/ColumnChart.spec.tsx @@ -26,6 +26,7 @@ describe("The ColumnChart widget", () => { return render( string; series: SeriesPreviewType[]; + refreshInterval: number | null; advancedOptions: boolean; showPlaygroundSlot: boolean; playground: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; From f5ced7270e1a06bfa76c51ca10fa74352ec90471 Mon Sep 17 00:00:00 2001 From: Yordan Stoyanov Date: Thu, 22 Oct 2026 11:25:13 +0200 Subject: [PATCH 07/10] feat(heatmap-chart-web): add refresh time property Add "Refresh time (in seconds)" to the Data source group (default 0 = off). When set, the chart periodically reloads its data source(s). Ticket: WC-3362 --- packages/pluggableWidgets/heatmap-chart-web/CHANGELOG.md | 4 ++++ packages/pluggableWidgets/heatmap-chart-web/src/HeatMap.tsx | 4 +++- packages/pluggableWidgets/heatmap-chart-web/src/HeatMap.xml | 4 ++++ .../heatmap-chart-web/src/__tests__/HeatMap.spec.tsx | 1 + .../heatmap-chart-web/typings/HeatMapProps.d.ts | 2 ++ 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/pluggableWidgets/heatmap-chart-web/CHANGELOG.md b/packages/pluggableWidgets/heatmap-chart-web/CHANGELOG.md index 870a698e66..5fa0a56688 100644 --- a/packages/pluggableWidgets/heatmap-chart-web/CHANGELOG.md +++ b/packages/pluggableWidgets/heatmap-chart-web/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- We added a "Refresh time (in seconds)" property that periodically reloads the chart data. + ## [6.2.1] - 2025-07-15 ### Fixed diff --git a/packages/pluggableWidgets/heatmap-chart-web/src/HeatMap.tsx b/packages/pluggableWidgets/heatmap-chart-web/src/HeatMap.tsx index da39955c06..96a13a3cd9 100644 --- a/packages/pluggableWidgets/heatmap-chart-web/src/HeatMap.tsx +++ b/packages/pluggableWidgets/heatmap-chart-web/src/HeatMap.tsx @@ -1,4 +1,4 @@ -import { ChartWidget, ChartWidgetProps } from "@mendix/shared-charts/main"; +import { ChartWidget, ChartWidgetProps, useRefreshInterval } from "@mendix/shared-charts/main"; import "@mendix/shared-charts/ui/Chart.scss"; import classNames from "classnames"; import { ReactElement, useMemo } from "react"; @@ -54,6 +54,8 @@ const heatmapChartSeriesOptions: ChartWidgetProps["seriesOptions"] = { }; export function HeatMap(props: HeatMapContainerProps): ReactElement | null { + useRefreshInterval([props.seriesDataSource], props.refreshInterval); + const heatmapChartData = useHeatMapDataSeries({ customSeriesOptions: props.customSeriesOptions, horizontalAxisAttribute: props.horizontalAxisAttribute, diff --git a/packages/pluggableWidgets/heatmap-chart-web/src/HeatMap.xml b/packages/pluggableWidgets/heatmap-chart-web/src/HeatMap.xml index b62773b750..314584adb8 100644 --- a/packages/pluggableWidgets/heatmap-chart-web/src/HeatMap.xml +++ b/packages/pluggableWidgets/heatmap-chart-web/src/HeatMap.xml @@ -28,6 +28,10 @@ + + Refresh time (in seconds) + + diff --git a/packages/pluggableWidgets/heatmap-chart-web/src/__tests__/HeatMap.spec.tsx b/packages/pluggableWidgets/heatmap-chart-web/src/__tests__/HeatMap.spec.tsx index d893a8b370..55504b8d8b 100644 --- a/packages/pluggableWidgets/heatmap-chart-web/src/__tests__/HeatMap.spec.tsx +++ b/packages/pluggableWidgets/heatmap-chart-web/src/__tests__/HeatMap.spec.tsx @@ -19,6 +19,7 @@ describe("The HeatMap widget", () => { return render( ; seriesItemSelection?: SelectionSingleValue; + refreshInterval: number; horizontalAxisAttribute?: ListAttributeValue; horizontalSortAttribute?: ListAttributeValue; horizontalSortOrder: HorizontalSortOrderEnum; @@ -85,6 +86,7 @@ export interface HeatMapPreviewProps { seriesDataSource: {} | { caption: string } | { type: string } | null; seriesValueAttribute: string; seriesItemSelection: "None" | "Single"; + refreshInterval: number | null; horizontalAxisAttribute: string; horizontalSortAttribute: string; horizontalSortOrder: HorizontalSortOrderEnum; From bc98efa63a4f87e6e9bde3ef6cb3b3e91db1d89d Mon Sep 17 00:00:00 2001 From: Yordan Stoyanov Date: Thu, 22 Oct 2026 11:25:15 +0200 Subject: [PATCH 08/10] feat(line-chart-web): add refresh time property Add "Refresh time (in seconds)" to the Data source group (default 0 = off). When set, the chart periodically reloads its data source(s). Ticket: WC-3362 --- .../line-chart-web/CHANGELOG.md | 4 +++ .../line-chart-web/src/LineChart.tsx | 11 ++++++- .../line-chart-web/src/LineChart.xml | 4 +++ .../src/__tests__/LineChart.spec.tsx | 32 ++++++++++++++++++- .../typings/LineChartProps.d.ts | 2 ++ 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/packages/pluggableWidgets/line-chart-web/CHANGELOG.md b/packages/pluggableWidgets/line-chart-web/CHANGELOG.md index fc4fdb00a0..a823c0960c 100644 --- a/packages/pluggableWidgets/line-chart-web/CHANGELOG.md +++ b/packages/pluggableWidgets/line-chart-web/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- We added a "Refresh time (in seconds)" property that periodically reloads the chart data. + ## [6.2.1] - 2025-07-15 ### Changed diff --git a/packages/pluggableWidgets/line-chart-web/src/LineChart.tsx b/packages/pluggableWidgets/line-chart-web/src/LineChart.tsx index 196d2c1b75..26711f5109 100644 --- a/packages/pluggableWidgets/line-chart-web/src/LineChart.tsx +++ b/packages/pluggableWidgets/line-chart-web/src/LineChart.tsx @@ -1,4 +1,11 @@ -import { ChartWidget, ChartWidgetProps, traceEqual, usePlotChartDataSeries } from "@mendix/shared-charts/main"; +import { + ChartWidget, + ChartWidgetProps, + seriesDataSources, + traceEqual, + usePlotChartDataSeries, + useRefreshInterval +} from "@mendix/shared-charts/main"; import "@mendix/shared-charts/ui/Chart.scss"; import { defaultEqual, flatEqual } from "@mendix/widget-plugin-platform/utils/flatEqual"; import classNames from "classnames"; @@ -28,6 +35,8 @@ export const LineChart = memo( // disable eslint rule to have nice component name in component tree at devtools function LineChart(props: LineChartContainerProps): ReactElement | null { + useRefreshInterval(seriesDataSources(props.lines), props.refreshInterval); + const chartLines = usePlotChartDataSeries( props.lines, useCallback((line, dataPoints, { getExpressionValue }) => { diff --git a/packages/pluggableWidgets/line-chart-web/src/LineChart.xml b/packages/pluggableWidgets/line-chart-web/src/LineChart.xml index eb756dc021..d06870bceb 100644 --- a/packages/pluggableWidgets/line-chart-web/src/LineChart.xml +++ b/packages/pluggableWidgets/line-chart-web/src/LineChart.xml @@ -184,6 +184,10 @@ + + Refresh time (in seconds) + + diff --git a/packages/pluggableWidgets/line-chart-web/src/__tests__/LineChart.spec.tsx b/packages/pluggableWidgets/line-chart-web/src/__tests__/LineChart.spec.tsx index b896a11f8b..3895d58b99 100644 --- a/packages/pluggableWidgets/line-chart-web/src/__tests__/LineChart.spec.tsx +++ b/packages/pluggableWidgets/line-chart-web/src/__tests__/LineChart.spec.tsx @@ -16,11 +16,16 @@ import { LineChart } from "../LineChart"; describe("The LineChart widget", () => { function renderLineChart(configs: Array>): RenderResult { + return renderLineChartWithLines(configs.map(setupBasicSeries)); + } + + function renderLineChartWithLines(lines: LinesType[], refreshInterval = 0): RenderResult { return render( { ); } + describe("refresh interval", () => { + beforeEach(() => jest.useFakeTimers()); + afterEach(() => jest.useRealTimers()); + + it("reloads the data source of every series after the interval", () => { + const first = setupBasicSeries({}); + const second = setupBasicSeries({}); + renderLineChartWithLines([first, second], 5); + + jest.advanceTimersByTime(4999); + expect(first.staticDataSource!.reload).not.toHaveBeenCalled(); + jest.advanceTimersByTime(1); + expect(first.staticDataSource!.reload).toHaveBeenCalledTimes(1); + expect(second.staticDataSource!.reload).toHaveBeenCalledTimes(1); + }); + + it("does not reload when the interval is 0", () => { + const line = setupBasicSeries({}); + renderLineChartWithLines([line], 0); + + jest.advanceTimersByTime(60_000); + expect(line.staticDataSource!.reload).not.toHaveBeenCalled(); + }); + }); + it("visualizes data as a line chart", () => { renderLineChart([{}]); diff --git a/packages/pluggableWidgets/line-chart-web/typings/LineChartProps.d.ts b/packages/pluggableWidgets/line-chart-web/typings/LineChartProps.d.ts index d4ee0d930f..a01dbd6dd1 100644 --- a/packages/pluggableWidgets/line-chart-web/typings/LineChartProps.d.ts +++ b/packages/pluggableWidgets/line-chart-web/typings/LineChartProps.d.ts @@ -78,6 +78,7 @@ export interface LineChartContainerProps { style?: CSSProperties; tabIndex?: number; lines: LinesType[]; + refreshInterval: number; enableAdvancedOptions: boolean; showPlaygroundSlot: boolean; playground?: ReactNode; @@ -106,6 +107,7 @@ export interface LineChartPreviewProps { renderMode: "design" | "xray" | "structure"; translate: (text: string) => string; lines: LinesPreviewType[]; + refreshInterval: number | null; enableAdvancedOptions: boolean; showPlaygroundSlot: boolean; playground: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; From a39d9663f3f9d1d3032ff6d618cbf2355a24f5ee Mon Sep 17 00:00:00 2001 From: Yordan Stoyanov Date: Thu, 22 Oct 2026 11:25:16 +0200 Subject: [PATCH 09/10] feat(pie-doughnut-chart-web): add refresh time property Add "Refresh time (in seconds)" to the Data source group (default 0 = off). When set, the chart periodically reloads its data source(s). Ticket: WC-3362 --- packages/pluggableWidgets/pie-doughnut-chart-web/CHANGELOG.md | 4 ++++ .../pluggableWidgets/pie-doughnut-chart-web/src/PieChart.tsx | 4 +++- .../pluggableWidgets/pie-doughnut-chart-web/src/PieChart.xml | 4 ++++ .../pie-doughnut-chart-web/src/__tests__/PieChart.spec.tsx | 1 + .../pie-doughnut-chart-web/typings/PieChartProps.d.ts | 2 ++ 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/pluggableWidgets/pie-doughnut-chart-web/CHANGELOG.md b/packages/pluggableWidgets/pie-doughnut-chart-web/CHANGELOG.md index 014c035684..58e0177daa 100644 --- a/packages/pluggableWidgets/pie-doughnut-chart-web/CHANGELOG.md +++ b/packages/pluggableWidgets/pie-doughnut-chart-web/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- We added a "Refresh time (in seconds)" property that periodically reloads the chart data. + ## [6.2.1] - 2025-07-15 ### Changed diff --git a/packages/pluggableWidgets/pie-doughnut-chart-web/src/PieChart.tsx b/packages/pluggableWidgets/pie-doughnut-chart-web/src/PieChart.tsx index 126d060163..6d61004f02 100644 --- a/packages/pluggableWidgets/pie-doughnut-chart-web/src/PieChart.tsx +++ b/packages/pluggableWidgets/pie-doughnut-chart-web/src/PieChart.tsx @@ -1,4 +1,4 @@ -import { ChartWidget, ChartWidgetProps } from "@mendix/shared-charts/main"; +import { ChartWidget, ChartWidgetProps, useRefreshInterval } from "@mendix/shared-charts/main"; import "@mendix/shared-charts/ui/Chart.scss"; import classNames from "classnames"; import { ReactElement } from "react"; @@ -29,6 +29,8 @@ const pieChartSeriesOptions: ChartWidgetProps["seriesOptions"] = { }; export function PieChart(props: PieChartContainerProps): ReactElement | null { + useRefreshInterval([props.seriesDataSource], props.refreshInterval); + const pieChartData = usePieChartDataSeries({ holeRadius: props.holeRadius, customSeriesOptions: props.customSeriesOptions, diff --git a/packages/pluggableWidgets/pie-doughnut-chart-web/src/PieChart.xml b/packages/pluggableWidgets/pie-doughnut-chart-web/src/PieChart.xml index 40df63ba51..5605e1e656 100644 --- a/packages/pluggableWidgets/pie-doughnut-chart-web/src/PieChart.xml +++ b/packages/pluggableWidgets/pie-doughnut-chart-web/src/PieChart.xml @@ -59,6 +59,10 @@ + + Refresh time (in seconds) + + diff --git a/packages/pluggableWidgets/pie-doughnut-chart-web/src/__tests__/PieChart.spec.tsx b/packages/pluggableWidgets/pie-doughnut-chart-web/src/__tests__/PieChart.spec.tsx index 2d3f0a9f36..0d70aecdd1 100644 --- a/packages/pluggableWidgets/pie-doughnut-chart-web/src/__tests__/PieChart.spec.tsx +++ b/packages/pluggableWidgets/pie-doughnut-chart-web/src/__tests__/PieChart.spec.tsx @@ -25,6 +25,7 @@ describe("The PieChart widget", () => { return render( ; seriesItemSelection?: SelectionSingleValue; + refreshInterval: number; enableAdvancedOptions: boolean; showPlaygroundSlot: boolean; playground?: ReactNode; @@ -60,6 +61,7 @@ export interface PieChartPreviewProps { seriesSortOrder: SeriesSortOrderEnum; seriesColorAttribute: string; seriesItemSelection: "None" | "Single"; + refreshInterval: number | null; enableAdvancedOptions: boolean; showPlaygroundSlot: boolean; playground: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; From 3b1e150e303812587dd249189a98fabbfb3817b1 Mon Sep 17 00:00:00 2001 From: Yordan Stoyanov Date: Thu, 22 Oct 2026 11:25:18 +0200 Subject: [PATCH 10/10] feat(time-series-chart-web): add refresh time property Add "Refresh time (in seconds)" to the Data source group (default 0 = off). When set, the chart periodically reloads its data source(s). Ticket: WC-3362 --- .../time-series-chart-web/CHANGELOG.md | 4 ++++ .../time-series-chart-web/src/TimeSeries.tsx | 11 ++++++++++- .../time-series-chart-web/src/TimeSeries.xml | 4 ++++ .../src/__test__/TimeSeries.spec.tsx | 1 + .../typings/TimeSeriesProps.d.ts | 2 ++ 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/pluggableWidgets/time-series-chart-web/CHANGELOG.md b/packages/pluggableWidgets/time-series-chart-web/CHANGELOG.md index 24cbd06650..10ad211b5f 100644 --- a/packages/pluggableWidgets/time-series-chart-web/CHANGELOG.md +++ b/packages/pluggableWidgets/time-series-chart-web/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- We added a "Refresh time (in seconds)" property that periodically reloads the chart data. + ## [6.2.1] - 2025-07-15 ### Changed diff --git a/packages/pluggableWidgets/time-series-chart-web/src/TimeSeries.tsx b/packages/pluggableWidgets/time-series-chart-web/src/TimeSeries.tsx index 8cfc124091..7577d0b938 100644 --- a/packages/pluggableWidgets/time-series-chart-web/src/TimeSeries.tsx +++ b/packages/pluggableWidgets/time-series-chart-web/src/TimeSeries.tsx @@ -1,4 +1,11 @@ -import { ChartWidget, ChartWidgetProps, traceEqual, usePlotChartDataSeries } from "@mendix/shared-charts/main"; +import { + ChartWidget, + ChartWidgetProps, + seriesDataSources, + traceEqual, + usePlotChartDataSeries, + useRefreshInterval +} from "@mendix/shared-charts/main"; import "@mendix/shared-charts/ui/Chart.scss"; import { defaultEqual, flatEqual } from "@mendix/widget-plugin-platform/utils/flatEqual"; import classNames from "classnames"; @@ -54,6 +61,8 @@ export const TimeSeries = memo( // disable eslint rule to have nice component name in component tree at devtools function TimeSeries(props: TimeSeriesContainerProps): ReactElement | null { + useRefreshInterval(seriesDataSources(props.lines), props.refreshInterval); + const chartLines = usePlotChartDataSeries( props.lines, useCallback(line => { diff --git a/packages/pluggableWidgets/time-series-chart-web/src/TimeSeries.xml b/packages/pluggableWidgets/time-series-chart-web/src/TimeSeries.xml index d7cb12de69..edc09086dc 100644 --- a/packages/pluggableWidgets/time-series-chart-web/src/TimeSeries.xml +++ b/packages/pluggableWidgets/time-series-chart-web/src/TimeSeries.xml @@ -169,6 +169,10 @@ + + Refresh time (in seconds) + + diff --git a/packages/pluggableWidgets/time-series-chart-web/src/__test__/TimeSeries.spec.tsx b/packages/pluggableWidgets/time-series-chart-web/src/__test__/TimeSeries.spec.tsx index 3be62ac2ae..d0697b723d 100644 --- a/packages/pluggableWidgets/time-series-chart-web/src/__test__/TimeSeries.spec.tsx +++ b/packages/pluggableWidgets/time-series-chart-web/src/__test__/TimeSeries.spec.tsx @@ -22,6 +22,7 @@ describe("The TimeSeries widget", () => { return render( string; lines: LinesPreviewType[]; + refreshInterval: number | null; enableAdvancedOptions: boolean; showPlaygroundSlot: boolean; playground: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> };