@amcharts/amcharts5-mcp 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/content/SKILL.md CHANGED
@@ -201,6 +201,27 @@ series.set("tooltip", tooltip);
201
201
 
202
202
  `getStrokeFromSprite` (default `false`) copies the sprite's stroke color when `true`.
203
203
 
204
+ ### Shared vs per-sprite tooltip
205
+
206
+ Setting **only** `tooltipText` on a sprite (no `tooltip:` instance anywhere on it) is enough to get a hover tooltip: the sprite lazily resolves the **Root's shared default `Tooltip`** (`sprite.getTooltip()` returns it). You only need your own `am5.Tooltip.new(root, {...})` when that element's tooltip must **look different** from the shared one, or on XY charts, where series/axis tooltips are **cursor-driven** rather than hover-driven and need their own instance.
207
+
208
+ So for non-XY charts do not create a Tooltip per series — set `tooltipText` on the right template and style the look once on the shared tooltip:
209
+
210
+ ```js
211
+ // Where tooltipText goes on non-XY charts
212
+ pieSeries.slices.template.set("tooltipText", "{category}: {value}"); // pie / funnel / venn slices
213
+ hierarchySeries.nodes.template.set("tooltipText", "{category}: {sum}"); // hierarchy nodes
214
+ flowSeries.nodes.nodes.template.set("tooltipText", "{name}"); // flow nodes
215
+ flowSeries.links.template.set("tooltipText", "{sourceId} → {targetId}: {value}"); // flow links
216
+ wordCloud.labels.template.set("tooltipText", "{category}: {value}"); // word cloud words
217
+ polygonSeries.mapPolygons.template.set("tooltipText", "{name}"); // map polygons (map points: the bullet sprite)
218
+
219
+ // Style the shared tooltip once — it lives on root.container (applies everywhere no dedicated Tooltip was set)
220
+ var shared = root.container.get("tooltip");
221
+ shared.set("getFillFromSprite", false);
222
+ shared.get("background").setAll({ fill: am5.color(0x000000), fillOpacity: 0.8 });
223
+ ```
224
+
204
225
  ## Chart title
205
226
 
206
227
  Do NOT add titles as HTML elements — they are outside the canvas and won't appear in exports. Add an `am5.Label` to the container BEFORE the chart, and set `verticalLayout`:
@@ -300,12 +321,12 @@ series.set("heatRules", [{
300
321
  | `maxValue` | number | Override auto-calculated max (skips `calculateAggregates`) |
301
322
  | `customFunction` | function | `(sprite, min, max, value)` — full control over the rule |
302
323
 
303
- **Using heat rules on bullets** — bullets need an explicit `am5.Template`:
324
+ **Using heat rules on bullets** — bullets need an explicit `am5.Template`, and the series must track the field the rule reads:
304
325
 
305
326
  ```js
306
327
  var circleTemplate = am5.Template.new({});
307
328
 
308
- series.bullets.push(function() {
329
+ series.bullets.push(function(root, series, dataItem) {
309
330
  return am5.Bullet.new(root, {
310
331
  sprite: am5.Circle.new(root, {
311
332
  fill: series.get("fill"),
@@ -323,6 +344,13 @@ series.set("heatRules", [{
323
344
  }]);
324
345
  ```
325
346
 
347
+ Rules that make bullet heat rules work (each failure is **silent** — every bullet just comes out the same size):
348
+ - `target` must be the **`Template`** the bullet sprites are built from (3rd argument of `am5.Circle.new(...)`), not the sprite and not `series.bullets`.
349
+ - `dataField` is the data item **property key** (`"valueY"`, `"valueX"`, `"value"`, …), **not** the raw column name. To size XY bullets by a column that is not already an axis value, register it as the base value field: `series.set("valueField", "population")` and use `dataField: "value"`.
350
+ - `calculateAggregates: true` is required (unless `minValue`/`maxValue` are given) — without it `valueLow`/`valueHigh` are never computed and every bullet gets the midpoint size.
351
+ - Setting `valueField` (or any `*Field`) **after** the series has processed data does not re-read the data — re-set it: `series.data.setAll(series.data.values.slice())`.
352
+ - `ChartSerializer` calls the bullet factory with a sample data item to introspect the sprite. That throwaway sprite joins the template but has no `dataItem`, and (as of 5.20.5) the heat-rule loop then throws on `target.dataItem` — on a `LineSeries` the stroke silently disappears. If you serialize charts that have bullet heat rules, have the factory call `sprite._setDataItem(dataItem)` itself, or serialize before adding the rule.
353
+
326
354
  **HeatLegend:**
327
355
 
328
356
  ```js
@@ -404,6 +432,23 @@ const cursor = chart.set("cursor", am5xy.XYCursor.new(root, {}));
404
432
 
405
433
  **Reading animated settings is unreliable mid-animation.** Right after `series.appear()` / `chart.appear()`, animated settings like `opacity` are still transitioning — `get("opacity")` can return `0` (the start value). Read such settings after the animation completes, or don't persist values read during appear (a common way to accidentally bake `opacity:0` into generated code).
406
434
 
435
+ ## Settings that already equal the default — omit them
436
+
437
+ Generated code routinely spells out settings that equal the amCharts default (verified against the default themes of 5.20.5). They add noise and mislead readers into thinking they matter. Do not emit these unless changing them:
438
+
439
+ | Class | Setting(s) that are already the default |
440
+ |-------|------------------------------------------|
441
+ | All hierarchy series | `childDataField: "children"`, `downDepth: 1`, `initialDepth: 5` |
442
+ | `Sunburst`, `Partition`, `Treemap`, `VoronoiTreemap` | `singleBranchOnly: true` (`Partition`/`Treemap` also `upDepth: 0`; `VoronoiTreemap` also `shapeType: "polygon"`) |
443
+ | `Tree`, `ForceDirected` | `singleBranchOnly: false`, `upDepth: Infinity`, `topDepth: 0` (`Tree` also `orientation: "vertical"`) |
444
+ | `PieSeries`, `FunnelSeries` | `alignLabels: true` (`FunnelSeries` also `orientation: "vertical"`, `startLocation: 0`, `endLocation: 1`, `bottomRatio: 0`) |
445
+ | `XYSeries` (all XY series) | `maskBullets: true` — note the default is **true**, so `maskBullets: false` is a real change |
446
+ | `LineSeries` (incl. smoothed/step) | `connect: true`; `StepLineSeries` `noRisers: false` |
447
+ | `XYChart` | `maxTooltipDistance` — leave **unset** (unset shows tooltips for all items in the category; `0` is *not* the same) |
448
+ | `HeatLegend` | `stepCount: 1` |
449
+
450
+ Runtime-mutated settings (`visible`, `x`/`y`, `opacity`, `scale`, cursor line `visible`, physics/random layouts, computed `AxisRendererCurve.points`) cannot be checked by "remove and re-`get()`" — amCharts rewrites them after render. User-set values live in `entity._userProperties` (what `ChartSerializer` reads), separate from the full `_settings` bag that includes theme and internal values.
451
+
407
452
  ## Dynamic data
408
453
 
409
454
  ```js
@@ -555,6 +600,8 @@ colors.set("colors", [
555
600
  ]);
556
601
  ```
557
602
 
603
+ **A `ColorSet` must always hold at least one color.** `colorSet.set("colors", [])` crashes the next `next()`/`getIndex()` call (`Cannot read properties of undefined (reading 'toHSL')` inside `generateColors`) as soon as a series iterates the palette — as of 5.20.5 the empty list is not guarded. To "reset" a palette, swap in a fresh `am5.ColorSet.new(root, {})` instead of emptying the list. The theme's `baseColor` also resolves lazily, so a **synchronous** re-color right after swapping a `ColorSet` can read a transient value (sprites momentarily black); re-set the series data and let amCharts' own data pass recolor the sprites.
604
+
558
605
  ## Data processor
559
606
 
560
607
  ```js
@@ -569,6 +616,17 @@ series.data.processor = am5.DataProcessor.new(root, {
569
616
  // Configure processor BEFORE setting data
570
617
  ```
571
618
 
619
+ ## Serializing to JSON (ChartSerializer / JsonParser)
620
+
621
+ `am5plugins_json.ChartSerializer` turns a live chart into a JSON config and `am5plugins_json.JsonParser` rebuilds it (`await parser.parse(config, { parent: root.container })`). Rules that keep round-trips working:
622
+
623
+ - **Serialize the chart's top-level container child, never a bare series.** `serializer.serializeAll(root.container.children.getIndex(0))`. Serializing an unwrapped series whose settings point back at itself (`selectedDataItem` holds a `DataItem` whose `component` is the series) produces a cycle: `_pruneEmptyObjects` overflows the stack (`removeEmptyObjects: true`, the default) or `JSON.stringify` throws "Converting circular structure". With the container as root the series becomes a `#series-0` reference and the cycle disappears.
624
+ - **Adapters do not round-trip.** `includeAdapters: true` writes each adapter as `{ key, callback: "function (…) {…}" }`; the parser does not turn that string back into a function (and the closure's `chart`/`series`/`root` would not exist anyway). Before 5.20.4 the string was registered as a callback and crashed the chart (`i[s] is not a function` in `Entity.fold`); since 5.20.4 such adapters are skipped. Either way the effect is lost — replace palette-coloring adapters with declarative equivalents: `colorByDataItem: true` on column series (5.20.4), a per-item `fill` data field + `templateField`, or `heatRules`.
625
+ - **Only user-set settings are serialized (5.20.3)** — theme and library defaults are left out, so a serialized config is much smaller than before and a value you never set will not appear in it. Add `includeRoot: true` (5.20.2) to also write a top-level `root` section (Root settings/properties, `interfaceColors`, formatters).
626
+ - **Re-parsing into an existing chart:** `parser.parse(config, { updateTargets: "soft" })` (5.20.3) applies settings onto an existing object of the same `type` instead of replacing it. The default `"strict"` replaces.
627
+ - **Not captured by `ChartSerializer` (as of 5.20.5)** — re-apply these in code after `parse()`: a `ZoomableContainer`'s `contents.children` and its `ZoomTools` (the serialized container comes back empty — track "chart is zoomable" out-of-band, or use `am5.SerialChartContainer` which builds the zoomable wrapper itself); Venn `hoverGraphics` and Venn slice-template `states` (a custom Venn hover cannot be expressed in JSON); custom elements without `themeTags: ["serialize"]`.
628
+ - **Bullets** are captured by calling the factory with a sample data item — write factories as `function(root, series, dataItem)` (pitfall #36) and see the heat-rule caveat under "Heat rules".
629
+
572
630
  ## Accessibility
573
631
 
574
632
  ```js
@@ -721,6 +779,10 @@ onUnmounted(() => { root.dispose(); });
721
779
  31. **Globe rotation uses negative coordinates** — To center the globe (`geoOrthographic`) on a geographic point, set `rotationX` to **-longitude** and `rotationY` to **-latitude**. E.g., to center on Paris (48.86°N, 2.35°E): `chart.animate({ key: "rotationX", to: -2.35 }); chart.animate({ key: "rotationY", to: -48.86 });`. Using positive values rotates the globe the wrong way.
722
780
  32. **`positionOnLine` with multi-segment lines limits per-segment control** — `MapPointSeries` data items can animate along a line via `positionOnLine` (0→1). If the line has 3+ points (multi-segment), position 0.5 is the midpoint of the *entire* path, making per-segment effects (scaling at each segment midpoint, pausing between segments, etc.) difficult. For advanced per-segment animations, use **single-segment lines** (2 points each) and animate the bullet across them sequentially. E.g., instead of one line [A,B,C,D], create [A,B], [B,C], [C,D].
723
781
  33. **Do NOT exclude Antarctica (`exclude: ["AQ"]`) by default** — Many amCharts demos exclude Antarctica because they use Mercator projection where it appears disproportionately large. This is a demo-specific choice, not a best practice. Unless the user explicitly asks to exclude Antarctica, or references a demo that does so, keep Antarctica in the map. With non-Mercator projections (`geoNaturalEarth1`, `geoEqualEarth`, `geoOrthographic`, `geoEquirectangular`), Antarctica renders at a reasonable size.
782
+ 34. **Custom GeoJSON for `MapPolygonSeries` needs CLOCKWISE exterior rings** — amCharts projects on a sphere (d3-geo), where ring winding decides which side is "inside". The RFC 7946 default is counter-clockwise, which d3-geo reads as *the whole globe minus the polygon*, so every polygon floods the entire map and the real shape shows only as a sliver. Programmatically generated GeoJSON (grids, cells, buffers) is usually CCW. amCharts does **not** auto-rewind — reverse each ring yourself: `feature.geometry.coordinates = feature.geometry.coordinates.map(ring => ring.slice().reverse())` (for `MultiPolygon`, one level deeper). See `references/map.md`.
783
+ 35. **`axis.dispose()` does NOT remove the axis from `chart.xAxes`/`chart.yAxes`** — unlike `series.dispose()`, which self-removes from `chart.series`. A disposed axis stays in the list, keeps showing up in anything built from `chart.yAxes`, and makes `ChartSerializer` throw `Template is disposed`. Remove axes with `chart.yAxes.removeValue(axis)` (or `removeIndex(i)`) — the list auto-disposes the removed axis, no separate `dispose()` needed.
784
+ 36. **Declare bullet callbacks as `function(root, series, dataItem)`** — amCharts passes all three; closing over an outer `root` works at runtime but breaks JSON round-trips (`ChartSerializer` captures the function source, and the outer `root` dangles on re-bind). Every `series.bullets.push(...)` in these references uses the parameter form — copy it.
785
+ 37. **A `ColorSet` must contain at least one color** — `colors.set("colors", [])` crashes the next `next()`/`getIndex()`. Swap in a fresh `am5.ColorSet.new(root, {})` instead. See "ColorSet" above.
724
786
 
725
787
  ## Easing functions
726
788
 
@@ -758,7 +820,7 @@ Skip this step entirely if you cannot execute code (e.g., chat-only context with
758
820
 
759
821
  ## Recent API changes (newer than the bundled class reference)
760
822
 
761
- The bundled per-class API reference was snapshotted on **2026-03-15**, so it predates the changes below. Latest release covered here: **5.20.1** (2026-08-03). Prefer these names/settings; for anything newer, verify against the live docs (see next section).
823
+ The bundled per-class API reference was snapshotted on **2026-03-15**, so it predates the changes below. Latest release covered here: **5.20.5** (2026-09-03). Prefer these names/settings; for anything newer, verify against the live docs (see next section).
762
824
 
763
825
  **Renamed settings (old name still works but is deprecated — use the new one):**
764
826
 
@@ -787,6 +849,8 @@ am5hierarchy.VoronoiTreemap.new(root, { shapeType: "rectangle" }); // was: type
787
849
  - `XYChart`: `strokeWidths` (array of pixel widths) and `strokeDasharrays` (array of dash arrays) — cycled across line series as they are added, exactly like `colors`. Lets series be told apart without relying on color, e.g. with the `Patterns` theme.
788
850
  - `XYCursor`: `clickTolerance` (default `0`) — how many pixels outside the plot area a press may start and still begin a zoom/selection. The selection itself still starts at the plot edge.
789
851
  - `XYSeries`: a value field (`valueYField`, `openValueYField`, …) can now be changed after creation — re-set the series data afterwards for it to take effect. A series can also be reassigned to a different `xAxis`/`yAxis` after creation.
852
+ - Column series (`ColumnSeries`, `CandlestickSeries`, `OHLCSeries`, …) (5.20.4): `colorByDataItem: true` (default `false`) gives each column its own color from the series' new `colors` (`ColorSet`) setting — the series makes its own `ColorSet` if none is given. The color lands on the data item's `fill` field. Prefer this over the old "adapter on `columns.template` `fill`" recipe: it is declarative and survives JSON serialization.
853
+ - `Scrollbar` (5.20.2): `opposite: true` (default `false`) puts a chart's `scrollbarX` **below** the plot and `scrollbarY` to the **left**, instead of above / right. Only affects scrollbars set via `chart.set("scrollbarX"/"scrollbarY", …)`. This is a **different** setting from the long-standing `opposite` on `AxisRendererX`/`AxisRendererY`.
790
854
 
791
855
  *WordCloud (5.20.1)*
792
856
  - `svgPath` arranges words into a shape (experimental), with `maskByShape` to clip them to the outline and `shapeTolerance` to control spill (negative = padding inside). See `references/wordcloud.md`.
@@ -799,8 +863,23 @@ am5hierarchy.VoronoiTreemap.new(root, { shapeType: "rectangle" }); // was: type
799
863
  - `Root`: `ariaLabel` is now applied to the `<div>` holding the chart's focusable elements (5.20.0).
800
864
  - `Label`: `oversizedBehavior: "truncate"` now ignores `maxHeight` (there is no way to truncate text vertically).
801
865
  - A `Container`'s `Rectangle`/`RoundedRectangle` background now defaults `crisp` to `true` unless set explicitly (5.20.0).
866
+ - `Root` (5.20.2): `fontFamily`, `fontSize` (number or string) and `fontWeight` (`"normal" | "bold" | "bolder" | "lighter" | "100"…`"900"`) settings — defaults for **all** text elements unless overridden on an element. Set them in `am5.Root.new("chartdiv", { fontFamily: "Inter" })` or `root.setAll({...})` instead of a custom theme rule on `Label`.
867
+ - `am5.SerialChartContainer` (5.20.2) — a directly usable `SerialChart` whose `seriesContainer` sits inside a `ZoomableContainer` (exposed as `chart.zoomableContainer`), so series such as `ForceDirected` can be zoomed and panned. Exported from the **`am5` root module**, not a chart package. Optional `zoomTools` setting takes a `ZoomTools` instance whose `target` is pointed at the series container automatically. Bullets added to its series are drawn (5.20.4) and flow-chart bullets are placed correctly inside it (5.20.5). See `references/hierarchy.md`.
868
+
869
+ *JSON config / serialization (5.20.2 – 5.20.4)*
870
+ - `ChartSerializer` now emits **only user-set settings** (5.20.3) — theme and library defaults are left out. Anything diffing or post-processing serializer output must expect much smaller configs.
871
+ - A JSON config may carry a top-level **`root`** section (5.20.2) that applies settings and properties to the `Root` object before the chart is parsed; `ChartSerializer` writes it when `includeRoot: true` (default `false`). A `Root`'s `interfaceColors`, `utc`, `fps`, formatters and `tabindex` round-trip through it (5.20.3).
872
+ - `JsonParser.parse(config, { updateTargets: "soft" })` (5.20.3) — an option on the **second argument of `parse()`**, not a parser setting. `"soft"` applies settings onto an existing object of the same `type` instead of replacing it; default `"strict"` replaces. `parse()` is async and returns a Promise.
873
+ - Gantt charts are now serializable/parsable (5.20.3). Cross-references are written as references (`"@series.get('fill')"`) instead of copies; large values (a map's `geoJSON`, a `Root`'s locale) are written as the name of the pack they came from and rebuilt on parse.
874
+ - An adapter whose callback did not survive the JSON round-trip is now **skipped** on parse instead of breaking the chart (5.20.4) — adapters still do not round-trip, see "Serializing to JSON" above.
875
+
876
+ *Bullets on non-XY charts (5.20.2)*
877
+ - `ArcDiagram`: bullets on nodes are now placed on the node circle, `locationX`/`locationY` being fractions of it (before 5.20.2 they were created but never positioned). Flow charts (`Sankey`/`Chord`): node bullets are children of the node, drawn above its shape and below its label, positioned relative to the node. `FunnelSeries`/`PyramidSeries`: bullet `locationX`/`locationY` follow the slice's sloping edges instead of its bounding box.
878
+ - A bullet `Graphics` with no paint of its own takes the color of **its own slice or node** on pie, funnel, flow and hierarchy charts — so it is the same color as what it sits on and **invisible** unless offset, stroked, or given a contrasting `fill` deliberately. Setting `fill` on the bullet sprite turns the inheritance off.
802
879
 
803
880
  *Maps*
881
+ - `MapChart` (5.20.2): `doubleClickZoom` (default **`true`** — a behavior change: a double click/tap zooms in, shift + double click zooms out; set `false` to opt out). On maps panned by rotating (`panX: "rotateX"` / `panY: "rotateY"`) the clicked point rotates to the center, otherwise it stays under the pointer. `boxZoom: "none" | "drag" | "shift" | "ctrl" | "alt"` (default `"none"`) is **not a boolean** — it names the key held while dragging a rectangle to zoom into; `"drag"` needs no key, so pair it with `panX`/`panY: "none"`. The rectangle is the readonly `chart.boxZoomSelection` element (a `Rectangle`). There are no keyboard-navigation settings on `MapChart`.
882
+ - `MapLineSeries` (5.20.3): `pointIds` data field (`{ pointIds: ["JFK", "LAX"] }`) names the `MapPointSeries` points a line connects, looked up in the series given by the new `pointSeries` setting; `pointIdsField` (default `"pointIds"`) renames the field. `pointsToConnect` wins if both are set. See `references/map.md`.
804
883
  - `MapChart`: `projectionName` (5.19.0) — string alternative to `projection`, e.g. `"geoOrthographic"`, mainly for JSON config. Register extra ones with `am5map.registerProjection()`. Bundled projections are no longer eagerly imported, so unused ones tree-shake away.
805
884
  - `MapSankeySeries` (5.17.0) — Sankey overlaid on a map; auto-resolves `sourceId`/`targetId` once `polygonSeries` geoJSON loads (5.17.1), so no `datavalidated` wrapper needed. See `references/map.md`.
806
885
  - `MapPointSeries` defaults changed to `longitudeField: "longitude"`, `latitudeField: "latitude"` (5.16.1).
@@ -816,6 +895,7 @@ am5hierarchy.VoronoiTreemap.new(root, { shapeType: "rectangle" }); // was: type
816
895
 
817
896
  *All entities*
818
897
  - `onDebounced(key, cb, delay)` / `offDebounced(key, cb?)` and `onPrivateDebounced` / `offDebouncedPrivate` (5.17.3) — fire once after rapid changes settle.
898
+ - `once(key, cb)` and `onceDebounced(key, cb, delay)` (5.20.4) — like `on()`/`onDebounced()` for a **settings** key, but the callback fires only the first time that setting changes, then removes itself. Both return an `IDisposer`. (Distinct from `events.once("eventName", …)`, which has existed for events all along.)
819
899
  - `Label`: `fontFamily: "inherit"` (5.17.3) uses the chart container's computed font.
820
900
 
821
901
  ## Verify unfamiliar API before using it
package/content/flow.md CHANGED
@@ -254,6 +254,10 @@ series.links.template.events.on("click", function(ev) {
254
254
  });
255
255
  ```
256
256
 
257
+ ## Bullets on nodes (5.20.2)
258
+
259
+ Bullets pushed to `series.nodes.bullets` are children of the node since 5.20.2: drawn above the node shape and below its label, positioned relative to the node (`locationX`/`locationY` as fractions of it). On `ArcDiagram` they sit on the node circle — before 5.20.2 they were created but never placed. A bullet `Graphics` with no `fill`/`stroke` of its own takes the **node's** color, i.e. the same color as what it sits on — give it a contrasting `fill`, a `stroke`, or an offset or it is invisible. Node/link tooltips need no `am5.Tooltip` instance: set `tooltipText` on `series.nodes.nodes.template` / `series.links.template` and the root's shared tooltip is used.
260
+
257
261
  ## Animated bullets along links
258
262
 
259
263
  Animated labels/circles that flow along Sankey or Chord links use `series.bullets.push()` — **NOT** `series.links.template.bullets.push()`. The bullet position (0 = source, 1 = target) is animated in a loop, and an adapter fades opacity based on position.
@@ -319,6 +319,27 @@ series.events.on("datavalidated", function() {
319
319
  });
320
320
  ```
321
321
 
322
+ ## Zoomable / pannable hierarchy — `SerialChartContainer` (5.20.2)
323
+
324
+ `am5.SerialChartContainer` (exported from the **`am5` root module**, not `am5hierarchy`) is a directly usable `SerialChart` whose `seriesContainer` sits inside a `ZoomableContainer`, so a large `ForceDirected`, `Tree` or `Pack` can be wheel-zoomed and dragged. Prefer it over hand-wrapping the chart in `am5.ZoomableContainer` — it also survives `ChartSerializer` round-trips, which a manual `ZoomableContainer` does not (its `contents` are not serialized).
325
+
326
+ ```js
327
+ const chart = root.container.children.push(am5.SerialChartContainer.new(root, {
328
+ zoomTools: am5.ZoomTools.new(root, {}) // optional +/−/home buttons, target is wired automatically
329
+ }));
330
+
331
+ const series = chart.series.push(am5hierarchy.ForceDirected.new(root, {
332
+ valueField: "value",
333
+ categoryField: "name",
334
+ childDataField: "children"
335
+ }));
336
+ series.data.setAll(data);
337
+
338
+ chart.zoomableContainer.setAll({ maxZoomLevel: 8 }); // the ZoomableContainer holding the series
339
+ ```
340
+
341
+ Bullets on series inside a `SerialChartContainer` are drawn as of 5.20.4, and flow-chart (`Sankey`/`Chord`/`ArcDiagram`) bullets are placed correctly inside one as of 5.20.5.
342
+
322
343
  ## Disposal
323
344
 
324
345
  ```js
package/content/map.md CHANGED
@@ -123,6 +123,28 @@ polygonSeries.data.setAll([
123
123
  ]);
124
124
  ```
125
125
 
126
+ **Custom GeoJSON: exterior rings must be CLOCKWISE.** amCharts projects on a sphere (d3-geo), where a ring's winding order decides which side is "inside". The GeoJSON spec (RFC 7946) winds exterior rings counter-clockwise, and d3-geo reads a CCW ring as *the whole globe minus the polygon* — so every polygon fills the entire viewport and the real shape shows only as a sliver (looks like a zoom/fit bug, is not). GIS exports are usually already clockwise; programmatically generated GeoJSON (grids, cells, buffers) usually is not. amCharts does **not** auto-rewind, so reverse the rings yourself:
127
+
128
+ ```js
129
+ // Fix winding before handing custom GeoJSON to MapPolygonSeries
130
+ geojson.features.forEach(function(feature) {
131
+ var g = feature.geometry;
132
+ if (g.type === "Polygon") {
133
+ g.coordinates = g.coordinates.map(function(ring) { return ring.slice().reverse(); });
134
+ } else if (g.type === "MultiPolygon") {
135
+ g.coordinates = g.coordinates.map(function(poly) {
136
+ return poly.map(function(ring) { return ring.slice().reverse(); });
137
+ });
138
+ }
139
+ });
140
+ polygonSeries.set("geoJSON", geojson);
141
+
142
+ // Fit the map to a custom region (no country geodata needed): left/right = longitude, top/bottom = latitude
143
+ chart.zoomToGeoBounds({ left: -8.6, right: 1.8, top: 60.9, bottom: 49.9 }, 500);
144
+ ```
145
+
146
+ `zoomToGeoBounds(bounds, duration?)` is more reliable than `homeGeoPoint` + `homeZoomLevel` + `goHome()` when the only series is custom polygons.
147
+
126
148
  ### Choropleth (heat map) via heat rules
127
149
 
128
150
  ```js
@@ -145,6 +167,17 @@ const heatLegend = chart.children.push(am5.HeatLegend.new(root, {
145
167
  }));
146
168
  ```
147
169
 
170
+ **Styling heat-legend segments.** With `stepCount: N` the legend draws N discrete `RoundedRectangle` markers inside `heatLegend.markerContainer`, all styled through `heatLegend.markers.template` (it accepts the usual Graphics settings — `stroke`, `strokeWidth`, `strokeOpacity`, `fillOpacity`, `cornerRadius*`, `tooltipText` for a per-block hover tooltip). Segment **thickness** is the marker's cross-axis size: a **vertical** legend uses the marker `width` (default `15`) with `height: 100%`; a **horizontal** legend uses `height` (default `15`) with `width: 100%`. Style via the template — `heatLegend.markers.values` can read empty even while `markerContainer.children` holds the rectangles.
171
+
172
+ ```js
173
+ heatLegend.markers.template.setAll({
174
+ width: 30, // thicker bar on a vertical legend (use height on a horizontal one)
175
+ stroke: am5.color(0xffffff),
176
+ strokeWidth: 1,
177
+ tooltipText: "{value}"
178
+ });
179
+ ```
180
+
148
181
  ### MapPointSeries (markers, bubbles, labels)
149
182
 
150
183
  ```js
@@ -156,7 +189,7 @@ const pointSeries = chart.series.push(
156
189
  );
157
190
 
158
191
  // Define bullet appearance
159
- pointSeries.bullets.push(function() {
192
+ pointSeries.bullets.push(function(root, series, dataItem) {
160
193
  return am5.Bullet.new(root, {
161
194
  sprite: am5.Circle.new(root, {
162
195
  radius: 5,
@@ -214,8 +247,24 @@ lineSeries.pushDataItem({
214
247
  coordinates: [[-74.01, 40.71], [-0.45, 51.47]] // [lng, lat] pairs
215
248
  }
216
249
  });
250
+
251
+ // OR (5.20.3) name the points by id — data-driven, JSON-friendly, no data-item references needed
252
+ const pointSeries2 = chart.series.push(am5map.MapPointSeries.new(root, {})); // ids come from the "id" data field
253
+ pointSeries2.data.setAll([
254
+ { id: "JFK", latitude: 40.64, longitude: -73.78 },
255
+ { id: "LAX", latitude: 33.94, longitude: -118.41 }
256
+ ]);
257
+ const lineSeries2 = chart.series.push(am5map.MapLineSeries.new(root, {
258
+ pointSeries: pointSeries2 // where pointIds are looked up
259
+ // pointIdsField: "pointIds" // default field name
260
+ }));
261
+ lineSeries2.data.setAll([
262
+ { pointIds: ["JFK", "LAX"] } // the line waits until every id has a point, then draws
263
+ ]);
217
264
  ```
218
265
 
266
+ `pointsToConnect` wins if a data item has both. Fixed points (`fixed: true`) cannot be used with either.
267
+
219
268
  ### MapSankeySeries (geographic flow bands)
220
269
 
221
270
  Draws variable-width curved bands between geographic points on a map. Band thickness represents a numeric value — like a Sankey diagram projected onto a map. Extends `MapPolygonSeries` and generates actual GeoJSON polygon geometries, so bands follow the map projection during pan, zoom, and rotation.
@@ -365,7 +414,7 @@ var sankeySeries = chart.series.push(
365
414
  #### Animated bullets along bands
366
415
 
367
416
  ```js
368
- sankeySeries.bullets.push(function() {
417
+ sankeySeries.bullets.push(function(root, series, dataItem) {
369
418
  return am5.Bullet.new(root, {
370
419
  locationX: 0,
371
420
  autoRotate: true,
@@ -420,7 +469,7 @@ sankeySeries.data.setAll([
420
469
  ```js
421
470
  // Create a point that follows a line
422
471
  var planeSeries = chart.series.push(am5map.MapPointSeries.new(root, {}));
423
- planeSeries.bullets.push(function() {
472
+ planeSeries.bullets.push(function(root, series, dataItem) {
424
473
  return am5.Bullet.new(root, {
425
474
  sprite: am5.Graphics.new(root, {
426
475
  svgPath: "m2,106h28l24,30h72l-44,-133h35l80,132h98c21,0 21,34 0,34l-98,0 -80,134h-35l43,-133h-71l-24,30h-28l15,-47",
@@ -471,6 +520,19 @@ chart.chartContainer.children.push(am5.Button.new(root, {
471
520
  })).events.on("click", function() {
472
521
  chart.goHome();
473
522
  });
523
+
524
+ // Fit to a lat/long box (left/right = longitude, top/bottom = latitude)
525
+ chart.zoomToGeoBounds({ left: -10, right: 40, top: 70, bottom: 35 }, 800);
526
+ ```
527
+
528
+ **Pointer zoom behaviors (5.20.2):** a double click/tap zooms in and shift + double click zooms out **by default** (`doubleClickZoom: true`; set `false` to turn it off). `boxZoom` lets the user drag a rectangle to zoom into — its value is the key that must be held: `"shift"`, `"ctrl"`, `"alt"`, or `"drag"` for no key (then set `panX`/`panY: "none"`, since a plain drag otherwise pans); default `"none"`. The rectangle being drawn is `chart.boxZoomSelection` (a `Rectangle`, style its `fill`/`stroke`).
529
+
530
+ ```js
531
+ am5map.MapChart.new(root, {
532
+ boxZoom: "shift", // hold shift and drag to zoom into the box
533
+ doubleClickZoom: false // opt out of the default double-click zoom
534
+ });
535
+ chart.boxZoomSelection.setAll({ fill: am5.color(0x1e88e5), fillOpacity: 0.15 });
474
536
  ```
475
537
 
476
538
  ## Globe (orthographic projection)
@@ -552,6 +614,8 @@ am5map.MapChart.new(root, {
552
614
  rotationZ: 0, // tilt
553
615
  wheelY: "zoom", // "zoom", "none"
554
616
  wheelSensitivity: 1, // zoom speed multiplier
617
+ doubleClickZoom: true, // (5.20.2) double click zooms in, shift+double click out — default true
618
+ boxZoom: "none", // (5.20.2) "none" | "drag" | "shift" | "ctrl" | "alt" — key held to drag a zoom box
555
619
  })
556
620
  ```
557
621
 
@@ -783,7 +847,7 @@ root.dispose();
783
847
 
784
848
  // Point series for airports
785
849
  var pointSeries = chart.series.push(am5map.MapPointSeries.new(root, {}));
786
- pointSeries.bullets.push(function() {
850
+ pointSeries.bullets.push(function(root, series, dataItem) {
787
851
  return am5.Bullet.new(root, {
788
852
  sprite: am5.Circle.new(root, {
789
853
  radius: 4,
@@ -895,7 +959,7 @@ Demonstrates: orthographic projection, single-segment lines for per-segment anim
895
959
 
896
960
  // City points with pulse + label
897
961
  var pointSeries = chart.series.push(am5map.MapPointSeries.new(root, {}));
898
- pointSeries.bullets.push(function () {
962
+ pointSeries.bullets.push(function(root, series, dataItem) {
899
963
  var container = am5.Container.new(root, {});
900
964
  container.children.push(am5.Circle.new(root, {
901
965
  radius: 3.5, fill: cityColor, fillOpacity: 0.9,
@@ -932,7 +996,7 @@ Demonstrates: orthographic projection, single-segment lines for per-segment anim
932
996
 
933
997
  // Plane series — one per segment, all hidden initially
934
998
  var planeSeries = chart.series.push(am5map.MapPointSeries.new(root, {}));
935
- planeSeries.bullets.push(function () {
999
+ planeSeries.bullets.push(function(root, series, dataItem) {
936
1000
  return am5.Bullet.new(root, {
937
1001
  sprite: am5.Graphics.new(root, {
938
1002
  svgPath: "m2,106h28l24,30h72l-44,-133h35l80,132h98c21,0 21,34 0,34l-98,0 -80,134h-35l43,-133h-71l-24,30h-28l15,-47",
@@ -1201,7 +1265,7 @@ Demonstrates: `MapSankeySeries` with `sourceId`/`targetId` data (country codes),
1201
1265
  });
1202
1266
 
1203
1267
  // Animated coffee bean bullets
1204
- sankeySeries.bullets.push(function() {
1268
+ sankeySeries.bullets.push(function(root, series, dataItem) {
1205
1269
  return am5.Bullet.new(root, {
1206
1270
  locationX: 0,
1207
1271
  autoRotate: true,
@@ -1492,7 +1556,7 @@ Demonstrates: `MapSankeySeries` with explicit `sourceLongitude`/`sourceLatitude`
1492
1556
  });
1493
1557
 
1494
1558
  // Animated oil drop bullets
1495
- sankeySeries.bullets.push(function() {
1559
+ sankeySeries.bullets.push(function(root, series, dataItem) {
1496
1560
  return am5.Bullet.new(root, {
1497
1561
  locationX: 0,
1498
1562
  autoRotate: true,
package/content/pie.md CHANGED
@@ -260,6 +260,22 @@ am5percent.PictorialStackedSeries.new(root, {
260
260
  })
261
261
  ```
262
262
 
263
+ ## Bullets on slices
264
+
265
+ ```js
266
+ series.bullets.push(function(root, series, dataItem) {
267
+ return am5.Bullet.new(root, {
268
+ locationX: 0.5, locationY: 0.5, // fractions of the slice (funnel/pyramid: along the sloping edges since 5.20.2)
269
+ sprite: am5.Circle.new(root, {
270
+ radius: 6,
271
+ fill: am5.color(0xffffff) // set a paint — an unpainted bullet takes its own slice's color and is invisible
272
+ })
273
+ });
274
+ });
275
+ ```
276
+
277
+ Percent series have **no series-level `fill`**: a bullet `Graphics` with no `fill`/`stroke` is auto-colored per data item from its own slice (5.20.0+), so it blends in unless stroked, offset, or given a contrasting fill. Slice tooltips need no `am5.Tooltip` instance — `series.slices.template.set("tooltipText", "{category}: {value}")` uses the root's shared tooltip.
278
+
263
279
  ## Legend (for all percent charts)
264
280
 
265
281
  ```js
package/content/radar.md CHANGED
@@ -286,7 +286,7 @@ columnSeries.columns.template.events.on("click", function(ev) {
286
286
  });
287
287
 
288
288
  // Pointer over/out on radar line data points (via bullets)
289
- series.bullets.push(function() {
289
+ series.bullets.push(function(root, series, dataItem) {
290
290
  var circle = am5.Circle.new(root, {
291
291
  radius: 5,
292
292
  fill: series.get("fill"),
@@ -214,7 +214,7 @@ series.set("snapTooltip", true);
214
214
  Bullets on timeline charts work the same as XY charts:
215
215
 
216
216
  ```js
217
- series.bullets.push(function() {
217
+ series.bullets.push(function(root, series, dataItem) {
218
218
  return am5.Bullet.new(root, {
219
219
  sprite: am5.Circle.new(root, {
220
220
  radius: 5,
@@ -258,7 +258,7 @@ series.bullets.push(function(root, series, dataItem) {
258
258
 
259
259
  ```js
260
260
  // Click on data points (via bullets)
261
- series.bullets.push(function() {
261
+ series.bullets.push(function(root, series, dataItem) {
262
262
  var circle = am5.Circle.new(root, {
263
263
  radius: 5,
264
264
  fill: series.get("fill"),
@@ -346,7 +346,7 @@ chart.get("cursor").events.on("cursormoved", function(ev) {
346
346
  series.fills.template.setAll({ visible: true, fillOpacity: 0.3 });
347
347
 
348
348
  // Add bullets
349
- series.bullets.push(function() {
349
+ series.bullets.push(function(root, series, dataItem) {
350
350
  return am5.Bullet.new(root, {
351
351
  sprite: am5.Circle.new(root, {
352
352
  radius: 4,
package/content/venn.md CHANGED
@@ -147,6 +147,10 @@ series.labels.template.adapters.add("text", function(text, target) {
147
147
  });
148
148
  ```
149
149
 
150
+ ## Serialization gotcha: custom hover is not captured
151
+
152
+ `series.hoverGraphics` (the dashed set outline used in the amCharts venn demo) and `slices.template.states.create("hover", …)` are **not** written by `am5plugins_json.ChartSerializer` as of 5.20.5 — a venn rebuilt from JSON loses any custom hover look (`slices.template.tooltipText` does round-trip). Re-apply `hoverGraphics.setAll({...})` in code after `JsonParser.parse()`.
153
+
150
154
  ## Click events
151
155
 
152
156
  ```js
@@ -79,6 +79,8 @@ series.appear(1000);
79
79
  | `sequencedInterpolation` | boolean | `true` | Stagger the word reveal instead of showing all at once |
80
80
  | `sequencedDelay` | number | `15` | Milliseconds between words when `sequencedInterpolation` is on |
81
81
 
82
+ **Parser settings and `text`:** `minValue`, `maxCount`, `minWordLength` and `excludeWords` are consumed when `text` is parsed. Since **5.20.0** changing any of them on a live series re-parses `text` automatically. On older versions they only took effect on the next `text` assignment — set them **before** `text`, or re-assign `text` after changing them.
83
+
82
84
  ### excludeWords example
83
85
 
84
86
  ```js
package/content/xy.md CHANGED
@@ -95,6 +95,7 @@ chart.set("cursor", am5xy.XYCursor.new(root, {
95
95
  ### Scrollbar
96
96
  ```js
97
97
  chart.set("scrollbarX", am5.Scrollbar.new(root, { orientation: "horizontal" }));
98
+ // opposite: true (5.20.2) puts scrollbarX below the plot / scrollbarY on the left
98
99
  ```
99
100
 
100
101
  ### Legend
@@ -191,6 +192,10 @@ const xAxis = chart.xAxes.push(am5xy.DateAxis.new(root, {
191
192
  - `markUnitChange: true` — highlights when time unit changes on axis labels (e.g., new month)
192
193
  - `tooltipDateFormat` — format string for cursor tooltip
193
194
 
195
+ **Live updates — two settings that do not refresh on `set()`:**
196
+ - **Date format maps** (`dateFormats`, `periodChangeDateFormats`, `minorDateFormats`): after `axis.set("dateFormats", {...})` already-rendered labels keep their old text. `axis.markDirtyValues()` is not enough — call **`axis.markDirtySize()`** to make the axis re-run label formatting.
197
+ - **`groupIntervals`** (with `groupData: true`): amCharts only (re)computes grouping when a series' data is set, so changing the allowed intervals on an already-grouped axis does nothing until you re-set the data on every series bound to the axis: `series.data.setAll(series.data.values.slice())`.
198
+
194
199
  **Data format:**
195
200
  ```js
196
201
  series.data.setAll([
@@ -324,7 +329,12 @@ series.columns.template.setAll({
324
329
  width: am5.percent(80), // column width
325
330
  });
326
331
 
327
- // Individual column colors from data:
332
+ // Individual column colors (5.20.4) — declarative, serializes to JSON:
333
+ series.set("colorByDataItem", true); // each column takes the next color from series.get("colors")
334
+ // series.set("colors", am5.ColorSet.new(root, { colors: [...] })); // optional own palette (the series makes one if absent)
335
+ // Per-item color from data instead: templateField + a `fill` in each data item (see pie.md "Data-driven slice colors")
336
+
337
+ // Pre-5.20.4 fallback — an adapter (does NOT survive JSON serialization):
328
338
  series.columns.template.adapters.add("fill", (fill, target) => {
329
339
  return chart.get("colors").getIndex(series.columns.indexOf(target));
330
340
  });
@@ -403,6 +413,7 @@ Enables zooming/panning via scrollbar. Can optionally include a preview chart.
403
413
  // Simple scrollbar:
404
414
  chart.set("scrollbarX", am5.Scrollbar.new(root, {
405
415
  orientation: "horizontal"
416
+ // opposite: true // (5.20.2) place below the plot instead of above (scrollbarY: left instead of right)
406
417
  }));
407
418
 
408
419
  // Scrollbar with preview chart:
@@ -610,8 +621,13 @@ var series2 = chart.series.push(am5xy.LineSeries.new(root, {
610
621
  xAxis: xAxis, yAxis: yAxis2,
611
622
  valueYField: "units", valueXField: "date"
612
623
  }));
624
+
625
+ // Removing an axis — go through the list, NOT axis.dispose():
626
+ chart.yAxes.removeValue(yAxis2); // the list auto-disposes the removed axis
613
627
  ```
614
628
 
629
+ `axis.dispose()` marks the axis disposed but **leaves it in `chart.yAxes`/`chart.xAxes`** (unlike `series.dispose()`, which removes itself from `chart.series`). The stale entry keeps rendering in anything built from the list and makes `ChartSerializer` throw `Template is disposed`. Always use `removeValue(axis)` / `removeIndex(i)`.
630
+
615
631
  ## Axis titles
616
632
 
617
633
  ```js
@@ -166,6 +166,20 @@ xAxis.onPrivate("selectionMax", function(value, target) {
166
166
  console.log("End date changed:", end);
167
167
  });
168
168
 
169
+ ### One-shot handlers (5.20.4)
170
+
171
+ `once(key, callback)` works like `on()` but fires only the first time that setting changes (or is set), then removes itself. `onceDebounced(key, callback, delay)` is the debounced version. Both return an `IDisposer`.
172
+
173
+ series.once("visible", function(visible, target) {
174
+ console.log("First visibility change", visible);
175
+ });
176
+
177
+ xAxis.onceDebounced("start", function(start) {
178
+ console.log("Zoom settled at", start);
179
+ }, 300);
180
+
181
+ (Do not confuse with `events.once("eventName", …)`, which has existed for regular events all along.)
182
+
169
183
  ### Removing
170
184
 
171
185
  Turning off value change events are similar to regular events: we can just use `off()` or `offPrivate()` methods.
@@ -92,6 +92,16 @@ Include adapters in the output.
92
92
 
93
93
  Export functions as references or stringify them.
94
94
 
95
+ `includeRoot`
96
+
97
+ `boolean`
98
+
99
+ `false`
100
+
101
+ Add a top-level `root` section with the `Root` object's settings and properties (`interfaceColors`, formatters, `utc`, `fps`, `tabindex`) to the output. `JsonParser` applies a `root` section before parsing the chart. *(5.20.2)*
102
+
103
+ **What gets serialized (5.20.3+):** only **user-set** settings — theme and library defaults are left out, so the output is much smaller than in earlier versions and a value you never set will not appear. Chart-set values and data a chart builds for itself (a flow's nodes, a word cloud's words) are not written. Objects pointing at one another are written as references (e.g. `"@series.get('fill')"`) instead of copies, and big values (a map's `geoJSON`, a `Root`'s locale) are written as the name of the pack they came from and rebuilt on parse.
104
+
95
105
  The below instructs serializer to include both states and adapters in the output:
96
106
 
97
107
  const serializer = am5plugins\_json.ChartSerializer.new(root, {
@@ -137,13 +147,17 @@ pieSeries.children.push(am5.Label.new(root, {
137
147
  - Venn
138
148
  - WordCloud
139
149
  - MapChart
150
+ - Gantt *(5.20.3)*
140
151
 
141
152
  ## Limitations
142
153
 
143
154
  ### Limited functionality
144
155
 
145
- - Bullets (series or axis): No support for bullets that reference outside objects or context-based bullets (where bullet code depends on context to determine its appearance)
156
+ - Bullets (series or axis): No support for bullets that reference outside objects or context-based bullets (where bullet code depends on context to determine its appearance). Declare bullet factories as `function(root, series, dataItem)` — a factory that closes over an outer `root` dangles on re-bind.
146
157
  - Bullets or adapters that rely on global variables. Parser runs in a different scope, thus global variables might not be available.
158
+ - **Adapters do not round-trip.** With `includeAdapters: true` each adapter is written as `{ key, callback: "function (…) {…}" }` (a string) — `JsonParser` does not turn it back into a function. Before 5.20.4 the string was registered as a callback and crashed the chart (`i[s] is not a function` in `Entity.fold`); since 5.20.4 such adapters are skipped on parse. Prefer declarative equivalents (`colorByDataItem` on column series, per-item `fill` + `templateField`, `heatRules`).
159
+ - **Serialize the chart's top-level container child, not a bare series.** A series whose settings point back at itself (e.g. `selectedDataItem`, a `DataItem` whose `component` is the series) forms a cycle; serialized on its own it overflows `removeEmptyObjects` pruning or makes `JSON.stringify` throw "Converting circular structure". As a child of the serialized container it becomes a `#series-0` reference and the cycle disappears.
160
+ - A bullet heat rule's throwaway sprite (created while the serializer introspects the bullet factory) has no `dataItem`; as of 5.20.5 the heat-rule loop then throws and, on a `LineSeries`, the stroke disappears. Have the factory call `sprite._setDataItem(dataItem)` or serialize before adding the rule.
147
161
 
148
162
  ### Unsupported features
149
163
 
@@ -153,8 +167,9 @@ pieSeries.children.push(am5.Label.new(root, {
153
167
  - Templates: `setup` function
154
168
  - Custom draw functions
155
169
  - Events
170
+ - `ZoomableContainer`: its `contents.children` and any `ZoomTools` are not serialized — a serialized `ZoomableContainer` parses back empty. Use `SerialChartContainer` (5.20.2), which builds its own zoomable wrapper, or re-wrap in code after parsing.
171
+ - Venn: `hoverGraphics` and slice-template `states` are not serialized — re-apply a custom hover in code after parsing.
156
172
 
157
173
  ### Unsupported chart types
158
174
 
159
- - Gantt
160
175
  - StockChart
@@ -659,6 +659,8 @@ Since adapters are functions, they can't technically be part of a serialized JSO
659
659
  }\]
660
660
  }
661
661
 
662
+ **Adapters and JSON round-trips:** a function reference works only when the config is a JavaScript object in the same scope. Once the config has been through `JSON.stringify` (or `ChartSerializer` with `functionsAs: "string"`), the callback is a string — `JsonParser` does not evaluate it, and since 5.20.4 such an adapter is skipped (before that it was registered as-is and crashed the chart with `i[s] is not a function`). Effects that must survive a round-trip need a declarative equivalent: `colorByDataItem` on column series (5.20.4), a per-item `fill` field with `templateField`, or `heatRules`.
663
+
662
664
  ## Parsing
663
665
 
664
666
  To parse serialized configs, we'll need:
@@ -679,6 +681,18 @@ parser.parse({
679
681
  // ...
680
682
  });
681
683
 
684
+ ### Parse options (`IParseSettings`)
685
+
686
+ The second argument of `parse()` is an options object — these are **not** settings of the parser itself:
687
+
688
+ - `parent` — a `Container` to place the parsed chart into.
689
+ - `updateTargets` *(5.20.3)* — `"strict"` (default): when the source object has a `type`, a new object of that type is created and replaces the existing one. `"soft"`: if source and target are the same type, only settings are applied onto the existing object (as if `type` had not been specified); objects are replaced only when the types differ. Use `"soft"` to update a live chart from a modified config without rebuilding it.
690
+
691
+ var parser = am5plugins\_json.JsonParser.new(root);
692
+ parser.parse(config, { parent: root.container, updateTargets: "soft" });
693
+
694
+ A config may also carry a top-level `root` section *(5.20.2)* whose `settings`/`properties` are applied to the `Root` object before the chart is parsed (`ChartSerializer` writes one when `includeRoot: true`).
695
+
682
696
  ### Post-parse handler
683
697
 
684
698
  The `parse()` method is asynchronous and returns a `Promise`.
@@ -337,3 +337,4 @@ Extended by: IColumnSeriesDataItem, IRadarColumnSeriesDataItem, ICurveColumnSeri
337
337
  - **graphics** (`Graphics`) — An actual Graphics element (Column/Slice/Candlestick/OHLC).
338
338
  - **legendDataItem** (`DataItem`) — If data items from this series are used to feed a Legend, this will hold a reference to the equivalent Legend data item. Click here for more info
339
339
  - **rangeGraphics** (`Array`) — In case axis ranges are added to the series, it creates a separate element (Graphics) for each axis range. This array holds them all.
340
+ - **fill** (`Color`) — Color of the column, when the series colors each data item separately (colorByDataItem). @since 5.20.4
@@ -86,3 +86,5 @@ Extended by: IColumnSeriesSettings, IRadarColumnSeriesSettings, ICurveColumnSeri
86
86
  - **adjustBulletPosition** (`undefined | false | true`) — Default true Whether positions of bullets should be calculated based on portion of column currently visual (true) or the whole length/height of the column (false).
87
87
  - **clustered** (`undefined | false | true`) — Default true Indicates if series must divvy up available space with other column series (true; default) or take up the whole available space (false). Click here for more info
88
88
  - **useLastColorForLegendMarker** (`undefined | false | true`) — If set to true will use color of the last visible column for legend marker. Otherwise, series fill/stroke will be used. @since 5.1.13
89
+ - **colorByDataItem** (`undefined | false | true`) — Default false If set to true, each column gets its own color from `colors`, instead of the whole series sharing one color. @since 5.20.4
90
+ - **colors** (`ColorSet`) — A ColorSet to take colors from when colorByDataItem is set. The series makes its own if none is given, rather than taking the chart's: the chart's palette is stepped once per series, and one palette per series also means editing one does not change the others. @since 5.20.4
@@ -175,3 +175,4 @@ Extends: ISerializerSettings
175
175
 
176
176
  - **includeProjection** (`undefined | false | true`) — Default false Include projection settings in the output. If enabled, the projection function will be included in the output, according to the functionsAs setting.
177
177
  - **removeEmptyObjects** (`undefined | false | true`) — Default true Remove empty objects from the output.
178
+ - **includeRoot** (`undefined | false | true`) — Default false If set to true, adds a top-level `root` section with the Root object's settings and properties (interfaceColors, formatters, utc, fps, tabindex) to the serialized output. JsonParser applies a `root` section before parsing the chart. @since 5.20.2
@@ -75,3 +75,5 @@ Extends: ISerialChartSettings
75
75
  - **zoomControl** (`ZoomControl`) — A ZoomControl instance. Click here for more info
76
76
  - **zoomLevel** (`undefined | number`) — Current zoom level.
77
77
 
78
+ - **boxZoom** (`"none" | "drag" | "shift" | "ctrl" | "alt"`) — Default "none" Allows zooming the map to an area drawn with a pointer. The value is the key that must be held while dragging; "drag" means no key is needed — since a plain drag pans the map, use "drag" with panX/panY set to "none". The drawn area is represented by the boxZoomSelection element. NOT a boolean. @since 5.20.2
79
+ - **doubleClickZoom** (`undefined | false | true`) — Default true Setting true will make map zoom in on a double click (or double tap), and zoom out on a shift + double click. On maps panned by rotating (panX: "rotateX" and/or panY: "rotateY") the clicked location is rotated to the center of the map; otherwise it stays under the pointer. @since 5.20.2
@@ -20,3 +20,4 @@ Extended by: IGraticuleSeriesDataItem
20
20
  - **lineType** (`"curved" | "straight"`) — Default "curved" A line type. "curved" (default) - connects points using shortest distance, which will result in curved lines based on map projection. "straight" - connects points using visually straight lines, and will not cross the -180/180 longitude. @since 5.2.32
21
21
  - **mapLine** (`MapLine`) — Related MapLine object.
22
22
  - **pointsToConnect** (`Array`) — An array of data items from MapPointSeries to use as line end-points. Note, fixed points can not be used here.
23
+ - **pointIds** (`Array<string>`) — Ids of the points (data items of the series set in pointSeries, matched by their id) a line connects, e.g. `{ pointIds: ["JFK", "LAX"] }`. The line waits for every id to match a point before it is drawn, so it is never drawn short. Fixed points cannot be used, same as with pointsToConnect. Ignored if pointsToConnect is set on the same data item. @since 5.20.3
@@ -88,3 +88,5 @@ Extended by: IGraticuleSeriesSettings
88
88
 
89
89
  - **clipBack** (`undefined | false | true`) — If set to true will hide line segments that are in the invisible range of the map. For example on the side of the globe facing away from the viewer when used with Orthographic projection. NOTE: not all projections have invisible side.
90
90
  - **lineType** (`"curved" | "straight"`) — Default "curved" A line type. "curved" (default) - connects points using shortest distance, which will result in curved lines based on map projection. "straight" - connects points using visually straight lines, and will not cross the -180/180 longitude. @since 5.2.24
91
+ - **pointIdsField** (`undefined | string`) — Default "pointIds" A field in data that holds ids of the points a line connects. @since 5.20.3
92
+ - **pointSeries** (`MapPointSeries`) — A MapPointSeries the pointIds of a data item are looked up in. @since 5.20.3
@@ -2,11 +2,17 @@
2
2
  title: "IParseSettings"
3
3
  type: "interface"
4
4
  source: "https://www.amcharts.com/docs/v5/reference/iparsesettings/"
5
- scraped: "2026-03-15"
5
+ scraped: "2026-09-14"
6
6
  ---
7
7
 
8
- Inheritance
8
+ Options object passed as the **second argument** of `JsonParser.parse(config, options)`. These are not settings of the parser object itself.
9
9
 
10
- ## Properties
10
+ ```javascript
11
+ const parser = am5plugins_json.JsonParser.new(root);
12
+ const chart = await parser.parse(config, { parent: root.container, updateTargets: "soft" });
13
+ ```
11
14
 
15
+ ## Properties
12
16
 
17
+ - **parent** (`Container`) — Parent container to place the parsed chart into.
18
+ - **updateTargets** (`"strict" | "soft"`) — Default "strict" If set to "strict" and source object has `type` set, a new object of that type will be created and used in place of existing one. In case of "soft", parser will check if source and target objects are of the same type and overwrite only if types differ. Otherwise only settings will be applied, just like if the `type` was not specified at all. @since 5.20.3
@@ -43,3 +43,7 @@ If set to some string, it will be used as inner <div> ARIA-LABEL.
43
43
  - **role** (`undefined | string`) — Allows setting a "role" for the inner <div>. @since 5.3.17 Click here for more info
44
44
  - **tooltipContainerBounds** (`undefined | { bottom: number,  left: number,  right: number,  top: number }`) — Allows defining margins around chart area for tooltips to go outside the chart itself. @since 5.2.24
45
45
 
46
+ - **fontFamily** (`undefined | string`) — Default font family for all text elements, unless overridden on an element. @since 5.20.2
47
+ - **fontSize** (`undefined | number | string`) — Default font size for all text elements, unless overridden on an element. @since 5.20.2
48
+ - **fontWeight** (`"normal" | "bold" | "bolder" | "lighter" | "100" | "200" | "300" | "400" | "500" | "600" | "700" | "800" | "900"`) — Default font weight for all text elements, unless overridden on an element. @since 5.20.2
49
+ - **sanitizeHTML** (`undefined | false | true`) — Default true If set to true, dynamically-set HTML (html/labelHTML, HTML tooltips, modal content) is sanitized. Set to false to opt out. @since 5.19.0
@@ -52,3 +52,4 @@ Extended by: ISliderSettings, IXYChartScrollbarSettings
52
52
  - **end** (`undefined | number`) — Relative end of the selected range, with 0 meaning beginning, and 1 the end.
53
53
  - **orientation** (`"horizontal" | "vertical"`) — Orientation of the scrollbar.
54
54
  - **start** (`undefined | number`) — Relative start of the selected range, with 0 meaning beginning, and 1 the end.
55
+ - **opposite** (`undefined | false | true`) — Default false If set to true, a chart's scrollbarX is placed below the plot area and its scrollbarY to the left, instead of above and to the right. Only has effect on scrollbars set via a chart's scrollbarX/scrollbarY settings; a scrollbar the user added to a container themselves is left where it is. Unrelated to the `opposite` setting of AxisRendererX/AxisRendererY. @since 5.20.2
@@ -0,0 +1,16 @@
1
+ ---
2
+ title: "ISerialChartContainerSettings"
3
+ type: "interface"
4
+ source: "https://www.amcharts.com/docs/v5/reference/iserialchartcontainersettings/"
5
+ scraped: "2026-09-14"
6
+ ---
7
+
8
+ ## Inheritance
9
+
10
+ Extends: ISerialChartSettings
11
+
12
+ > **Note:** This class also inherits all settings, properties, methods, and events from ISerialChartSettings (and its ancestors). Use `get_doc` or `get_core_reference` with the parent class name to see inherited members.
13
+
14
+ ## Properties
15
+
16
+ - **zoomTools** (`ZoomTools`) — A ZoomTools instance to add to the chart. When set, its `target` is automatically pointed at the chart's `seriesContainer`. @since 5.20.2
@@ -66,3 +66,7 @@ Extends: SerialChart
66
66
  - **wheelY** (`"none" | "zoom" | "rotateX" | "rotateY"`) — Default "zoom" Defines what happens when mouse wheel is turned. Click here for more info
67
67
  - **zoomControl** (`ZoomControl`) — A ZoomControl instance. Click here for more info
68
68
  - **zoomLevel** (`undefined | number`) — Current zoom level.
69
+
70
+ ## Properties
71
+
72
+ - **boxZoomSelection** (`Rectangle`) — A Rectangle element that shows the area being drawn when boxZoom is enabled. Read-only; style its fill/stroke. @since 5.20.2
@@ -17,7 +17,7 @@ import * as am5 from "@amcharts/amcharts5"
17
17
  ## Inheritance
18
18
 
19
19
  Extends: Chart
20
- Extended by: PercentChart, XYChart, MapChart
20
+ Extended by: PercentChart, XYChart, MapChart, SerialChartContainer
21
21
 
22
22
  > **Note:** This class also inherits all settings, properties, methods, and events from Chart (and its ancestors). Use `get_doc` or `get_core_reference` with the parent class name to see inherited members.
23
23
 
@@ -0,0 +1,34 @@
1
+ ---
2
+ title: "SerialChartContainer"
3
+ type: "class"
4
+ source: "https://www.amcharts.com/docs/v5/reference/serialchartcontainer/"
5
+ scraped: "2026-09-14"
6
+ ---
7
+
8
+ A `SerialChart` whose `seriesContainer` is a `ZoomableContainer`, so that the chart contents can be zoomed and panned.
9
+
10
+ Unlike the abstract `SerialChart`, this chart attaches its `seriesContainer` to the display tree on its own, so it can be instantiated and used directly — e.g. to make a `ForceDirected`, `Tree` or `Pack` series zoomable/pannable. Bullets sit alongside the series inside the zoomable container, so they zoom and pan with it.
11
+
12
+ @since 5.20.2
13
+
14
+ ## Import
15
+
16
+ ```javascript
17
+ // Import SerialChartContainer — exported from the root module, not a chart package
18
+ import * as am5 from "@amcharts/amcharts5"
19
+ const chart = root.container.children.push(am5.SerialChartContainer.new(root, {}));
20
+ ```
21
+
22
+ ## Inheritance
23
+
24
+ Extends: SerialChart
25
+
26
+ > **Note:** This class also inherits all settings, properties, methods, and events from SerialChart (and its ancestors). Use `get_doc` or `get_core_reference` with the parent class name to see inherited members.
27
+
28
+ ## Settings
29
+
30
+ - **zoomTools** (`ZoomTools`) — A ZoomTools instance to add to the chart. When set, its `target` is automatically pointed at the chart's `seriesContainer`.
31
+
32
+ ## Properties
33
+
34
+ - **zoomableContainer** (`ZoomableContainer`) — Default ZoomableContainer.new() A ZoomableContainer which holds chart's `seriesContainer` so it can be zoomed and panned. Theme tag `serialchartcontainer` (default theme: `wheelable: false`, `pinchZoom: false`, `maskContent: true`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amcharts/amcharts5-mcp",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "mcpName": "io.github.amcharts/amcharts5",
5
5
  "description": "On-demand access to amCharts 5 docs, code examples, and API reference",
6
6
  "type": "module",
package/src/tools.js CHANGED
@@ -12,6 +12,26 @@ import { CHART_TYPE_MAP, extractImportBlock, searchDocs } from "./content.js";
12
12
  export function registerTools(server, content) {
13
13
  const { docs, extendedDocs, examples } = content;
14
14
 
15
+ // Resolve any identifier a search result prints — "skill/SKILL.md",
16
+ // "skill/xy", "SKILL", "xy.md", "extended/charts/xy-chart/cursor",
17
+ // "charts/xy-chart/cursor", "reference/xycursor" — to a doc. Search results
18
+ // label their source exactly this way, so an agent can paste the label
19
+ // straight into get_section / get_doc.
20
+ function resolveDoc(id) {
21
+ let key = String(id || "").trim().replace(/^\/+|\/+$/g, "").replace(/\.md$/i, "");
22
+ if (!key) return null;
23
+ let skillKey = key.startsWith("skill/") ? key.slice("skill/".length) : key;
24
+ if (docs.has(skillKey)) return { doc: docs.get(skillKey), label: `skill/${skillKey}` };
25
+ const extKey = key.startsWith("extended/") ? key.slice("extended/".length) : key;
26
+ if (extendedDocs.has(extKey)) return { doc: extendedDocs.get(extKey), label: `extended/${extKey}` };
27
+ // Case-insensitive skill fallback ("skill" → "SKILL", "Xy" → "xy").
28
+ const lower = skillKey.toLowerCase();
29
+ for (const [name, doc] of docs) {
30
+ if (name.toLowerCase() === lower) return { doc, label: `skill/${name}` };
31
+ }
32
+ return null;
33
+ }
34
+
15
35
  // --- Tool: list_chart_types ---
16
36
  server.tool(
17
37
  "list_chart_types",
@@ -86,12 +106,39 @@ export function registerTools(server, content) {
86
106
  return { content: [{ type: "text", text: `No results found for "${query}".${hint}` }] };
87
107
  }
88
108
 
109
+ // Per-result excerpt budget. When only one or two sections match, the
110
+ // whole point is to read them — grow the budget instead of cutting the
111
+ // answer off.
112
+ const budget = results.length <= 2 ? 6000 : 2000;
113
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
114
+
89
115
  let text = `# Search results for "${query}" (scope: ${scope})\n\n`;
90
116
  for (const r of results) {
117
+ const id = `${r.source}/${r.file}`;
91
118
  text += `## ${r.docTitle} → ${r.heading}\n`;
92
- text += `*(source: ${r.source}/${r.file}.md, relevance: ${r.score})*\n\n`;
93
- // Truncate very long sections
94
- const body = r.body.length > 2000 ? r.body.slice(0, 2000) + "\n\n...(truncated)" : r.body;
119
+ text += `*(source: ${id}.md, relevance: ${r.score})*\n\n`;
120
+ let body = r.body;
121
+ if (body.length > budget) {
122
+ // The excerpt is a fixed-size head, so the line that matched the
123
+ // query may lie past the cut. Quote the matching lines beyond the
124
+ // head so the answer is never lost, and print the exact call that
125
+ // returns the full section.
126
+ const head = body.slice(0, budget);
127
+ const beyond = [];
128
+ let offset = 0;
129
+ for (const line of body.split("\n")) {
130
+ const start = offset;
131
+ offset += line.length + 1;
132
+ if (start < budget) continue;
133
+ const l = line.toLowerCase();
134
+ if (terms.some(t => l.includes(t))) beyond.push(line.trim().slice(0, 400));
135
+ if (beyond.length >= 8) break;
136
+ }
137
+ body = head + `\n\n...(truncated — full section: get_section(file: "${id}", heading: "${r.heading}"))`;
138
+ if (beyond.length) {
139
+ body += `\n\nMatching lines beyond the excerpt:\n` + beyond.map(l => `> ${l}`).join("\n");
140
+ }
141
+ }
95
142
  text += body + "\n\n---\n\n";
96
143
  }
97
144
  return { content: [{ type: "text", text }] };
@@ -101,28 +148,29 @@ export function registerTools(server, content) {
101
148
  // --- Tool: get_section ---
102
149
  server.tool(
103
150
  "get_section",
104
- "Get a specific section from an amCharts 5 reference file by heading name. Use search_docs first to find the right section.",
151
+ "Get the FULL text of one section of a doc by heading — use it to expand a truncated search_docs / search_all result. `file` accepts the source label exactly as search results print it ('skill/SKILL.md', 'skill/xy', 'extended/charts/xy-chart/cursor', 'reference/xycursor') as well as a bare skill file name ('SKILL', 'xy', 'map').",
105
152
  {
106
- file: z.string().describe("Reference file name without .md, e.g. 'xy', 'pie', 'SKILL', 'map'"),
107
- heading: z.string().describe("Section heading to retrieve, e.g. 'Core setup pattern', 'Axis types', 'Common pitfalls'"),
153
+ file: z.string().describe("Doc identifier: a skill file ('SKILL', 'xy', 'pie', 'map') or a source label from search results ('skill/SKILL.md', 'extended/concepts/events', 'reference/xycursor'). '.md' and 'skill/' / 'extended/' prefixes are optional."),
154
+ heading: z.string().describe("Section heading to retrieve (case-insensitive substring), e.g. 'Core setup pattern', 'Axis types', 'Common pitfalls', 'Recent API changes'"),
108
155
  },
109
156
  async ({ file, heading }) => {
110
- const doc = docs.get(file);
111
- if (!doc) {
112
- return { content: [{ type: "text", text: `File "${file}" not found.` }] };
157
+ const resolved = resolveDoc(file);
158
+ if (!resolved) {
159
+ const skillFiles = [...docs.keys()].map(k => `skill/${k}`).join(", ");
160
+ return { content: [{ type: "text", text: `File "${file}" not found.\n\nSkill files: ${skillFiles}\n\nExtended docs use their path, e.g. 'charts/xy-chart/cursor', 'concepts/events', 'reference/xycursor' (with or without an 'extended/' prefix). Use search_docs / search_all to find one.` }] };
113
161
  }
162
+ const { doc, label } = resolved;
114
163
 
115
- const section = doc.sections.find(
116
- s => s.heading.toLowerCase().includes(heading.toLowerCase())
117
- );
164
+ const needle = heading.toLowerCase();
165
+ const section = doc.sections.find(s => s.heading.toLowerCase().includes(needle));
118
166
  if (!section) {
119
167
  const available = doc.sections.map(s => s.heading).filter(Boolean).join("\n- ");
120
168
  return {
121
- content: [{ type: "text", text: `Section "${heading}" not found in ${file}.md.\n\nAvailable sections:\n- ${available}` }],
169
+ content: [{ type: "text", text: `Section "${heading}" not found in ${label}.md.\n\nAvailable sections:\n- ${available}` }],
122
170
  };
123
171
  }
124
172
  return {
125
- content: [{ type: "text", text: `# ${section.heading}\n\n${section.body}` }],
173
+ content: [{ type: "text", text: `# ${section.heading}\n*(source: ${label}.md)*\n\n${section.body}` }],
126
174
  };
127
175
  }
128
176
  );
@@ -244,22 +292,26 @@ export function registerTools(server, content) {
244
292
  // --- Tool: get_doc ---
245
293
  server.tool(
246
294
  "get_doc",
247
- "Get a full documentation page from the extended amCharts 5 docs. Use search_all first to find the right path.",
295
+ "Get a full documentation page — an extended doc by path, or a skill file by 'skill/<name>'. Accepts the source label exactly as search_docs / search_all print it. Use search_all first to find the right path.",
248
296
  {
249
- path: z.string().describe("Doc path, e.g. 'charts/xy-chart/cursor', 'concepts/events', 'getting-started/integrations/react', 'reference/xycursor' (per-class API). For a class's full settings + defaults prefer get_api_reference."),
297
+ path: z.string().describe("Doc path, e.g. 'charts/xy-chart/cursor', 'concepts/events', 'getting-started/integrations/react', 'reference/xycursor' (per-class API), or a skill file such as 'skill/SKILL.md' / 'skill/xy'. For a class's full settings + defaults prefer get_api_reference."),
250
298
  },
251
299
  async ({ path: docPath }) => {
252
- const key = docPath.replace(/^\/|\/$/g, "").replace(/\.md$/, "");
253
- const doc = extendedDocs.get(key);
254
- if (!doc) {
300
+ const key = String(docPath || "").replace(/^\/|\/$/g, "").replace(/\.md$/, "");
301
+ // Extended docs take precedence for bare paths; 'skill/…' always means the skill layer.
302
+ const resolved = key.startsWith("skill/")
303
+ ? resolveDoc(key)
304
+ : (extendedDocs.has(key) ? { doc: extendedDocs.get(key), label: `extended/${key}` } : resolveDoc(key));
305
+ if (!resolved) {
255
306
  // List available top-level paths
256
307
  const paths = [...extendedDocs.keys()];
257
308
  const topLevel = [...new Set(paths.map(p => p.split("/")[0]))].sort();
258
309
  return {
259
- content: [{ type: "text", text: `Doc "${key}" not found.\n\nAvailable top-level sections: ${topLevel.join(", ")}\n\nUse search_all to find the right path.` }],
310
+ content: [{ type: "text", text: `Doc "${key}" not found.\n\nAvailable top-level sections: skill, ${topLevel.join(", ")}\n\nSkill files: ${[...docs.keys()].map(k => `skill/${k}`).join(", ")}\n\nUse search_all to find the right path.` }],
260
311
  };
261
312
  }
262
- return { content: [{ type: "text", text: `# ${doc.title}\n\n${doc.content}` }] };
313
+ const { doc, label } = resolved;
314
+ return { content: [{ type: "text", text: `# ${doc.title}\n*(source: ${label}.md)*\n\n${doc.content}` }] };
263
315
  }
264
316
  );
265
317