@amcharts/amcharts5-mcp 1.1.4 → 1.1.6

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # amCharts 5 MCP Server
2
2
 
3
- An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that gives AI assistants on-demand access to the complete amCharts 5 knowledge base: **141 documentation pages**, **283 code examples**, and **1,098 class API references**.
3
+ An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that gives AI assistants on-demand access to the complete amCharts 5 knowledge base: **140+ documentation pages**, **280+ code examples**, and **1,000+ class API references**.
4
4
 
5
5
  Works with **Claude Code**, **Claude Desktop**, **Cursor**, **Windsurf**, **VS Code + GitHub Copilot**, and any other MCP-compatible AI tool.
6
6
 
@@ -132,9 +132,9 @@ Once connected, your AI assistant can:
132
132
 
133
133
  The server includes **1,500+ documents**:
134
134
 
135
- - **141 documentation pages** — getting started, chart guides, concepts (themes, events, adapters, data binding, animations, accessibility, etc.), and framework integration guides (React, Angular, Vue, Next.js, Svelte, and more)
136
- - **283 code examples** — working demos across 16 categories including column/bar, line/area, pie/donut, maps, hierarchy, flow, radar/polar, stock, gauges, Gantt, timeline, and more
137
- - **1,098 class API references** — every class in the amCharts 5 library with its settings, properties, methods, events, and inheritance chain
135
+ - **140+ documentation pages** — getting started, chart guides, concepts (themes, events, adapters, data binding, animations, accessibility, etc.), and framework integration guides (React, Angular, Vue, Next.js, Svelte, and more)
136
+ - **280+ code examples** — working demos across 16 categories including column/bar, line/area, pie/donut, maps, hierarchy, flow, radar/polar, stock, gauges, Gantt, timeline, and more
137
+ - **1,000+ class API references** — every class in the amCharts 5 library with its settings, properties, methods, events, and inheritance chain
138
138
  - **Curated skill reference** — quick-start templates and chart-specific guides from the [amCharts 5 AI Skill](https://github.com/amcharts/amcharts5-skill)
139
139
 
140
140
  ## License
package/content/SKILL.md CHANGED
@@ -52,7 +52,7 @@ Docs: https://www.amcharts.com/docs/v5/
52
52
  | Core | `@amcharts/amcharts5` | `index.js` | Root, Theme, Legend, Tooltip, Label, Container |
53
53
  | XY charts | `@amcharts/amcharts5/xy` | `xy.js` | XYChart, axes, series |
54
54
  | Pie / Sliced | `@amcharts/amcharts5/percent` | `percent.js` | PieChart, SlicedChart, PieSeries, FunnelSeries, PyramidSeries, PictorialStackedSeries |
55
- | Map | `@amcharts/amcharts5/map` | `map.js` | MapChart, MapPolygonSeries, MapPointSeries, MapLineSeries |
55
+ | Map | `@amcharts/amcharts5/map` | `map.js` | MapChart, MapPolygonSeries, MapPointSeries, MapLineSeries, MapSankeySeries |
56
56
  | Hierarchy | `@amcharts/amcharts5/hierarchy` | `hierarchy.js` | Treemap, VoronoiTreemap, ForceDirected, Sunburst, Pack, Partition, Tree |
57
57
  | Flow | `@amcharts/amcharts5/flow` | `flow.js` | Sankey, Chord, ChordDirected, ChordNonRibbon, ArcDiagram |
58
58
  | Radar | `@amcharts/amcharts5/radar` | `radar.js` | RadarChart, AxisRendererCircular, AxisRendererRadial |
@@ -73,7 +73,7 @@ Based on the chart type the user is building, read the relevant reference file f
73
73
  |-----------|------|
74
74
  | Line, area, bar, column, candlestick, OHLC, scatter, stacked charts | `references/xy.md` |
75
75
  | Pie, donut, semi-circle, funnel, pyramid, pictorial stacked charts | `references/pie.md` |
76
- | World map, country map, choropleth, bubble map, point map | `references/map.md` |
76
+ | World map, country map, choropleth, bubble map, point map, map sankey | `references/map.md` |
77
77
  | Treemap, sunburst, force-directed, pack, partition, tree, org chart | `references/hierarchy.md` |
78
78
  | Sankey, chord, arc diagram, alluvial, flow visualization | `references/flow.md` |
79
79
  | Radar, spider, polar chart, gauge, speedometer, meter | `references/radar.md` |
@@ -638,6 +638,36 @@ onUnmounted(() => { root.dispose(); });
638
638
  25. **`MapChart` has NO `"colors"` setting** — `chart.get("colors")` returns `undefined` on `MapChart`. Only XY, Radar, and Percent charts auto-create a ColorSet. For maps, create your own: `am5.ColorSet.new(root, {})`.
639
639
  26. **`VoronoiTreemap` has NO `.rectangles` property** — Unlike `Treemap` (which has `series.rectangles.template`), `VoronoiTreemap` renders organic polygon cells. Style via `series.nodes.template` and its children, not `.rectangles`.
640
640
  27. **Labels with data placeholders need `populateText: true`** — When a Label uses data field placeholders like `text: "{name}"`, you MUST also set `populateText: true`. Without it, the placeholder is not resolved and the label appears blank. This applies everywhere Labels display dynamic data — bullet labels, map point labels, etc. Example: `am5.Label.new(root, { text: "{name}", populateText: true, ... })`.
641
+ 28. **Easing: `am5.ease.in()` does NOT exist** — amCharts 5 provides base easing functions (`am5.ease.cubic`, `am5.ease.bounce`, `am5.ease.elastic`, `am5.ease.linear`, `am5.ease.quad`, `am5.ease.sine`, `am5.ease.circle`, `am5.ease.exp`, `am5.ease.pow`) and three modifiers: `am5.ease.out()`, `am5.ease.inOut()`, `am5.ease.yoyo()`. Usage: `am5.ease.cubic` (ease-in by default), `am5.ease.out(am5.ease.cubic)` (ease-out), `am5.ease.inOut(am5.ease.cubic)` (ease in+out). There is NO `am5.ease.in()` — using it throws a runtime error. The base functions already ease-in by default.
642
+ 29. **Use `forceHidden` instead of `visible: false` when you need to guarantee something stays hidden** — amCharts internally manages `visible` on many elements (cursor lines, labels, ticks, tooltips, grid, etc.), toggling them on/off in response to user interaction or data changes. If you set `visible: false`, the library may set it back to `true`. `forceHidden: true` overrides all system visibility changes and keeps the element hidden regardless. Examples: `cursor.lineX.set("forceHidden", true)`, `yRenderer.labels.template.set("forceHidden", true)`, `xRenderer.grid.template.set("forceHidden", true)`. For cursor lines specifically, always access `cursor.lineX`/`cursor.lineY` after creation — do NOT pass them as constructor options.
643
+ 30. **`snapTooltip: true` on series for cursor tooltip snapping** — When using a cursor and you want tooltips to snap to data points, set `snapTooltip: true` on the series in addition to (or instead of) `snapToSeries` on the cursor. This is especially useful for timeline/curve charts.
644
+ 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.
645
+ 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].
646
+ 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.
647
+
648
+ ## Easing functions
649
+
650
+ amCharts 5 easing functions live under `am5.ease`:
651
+
652
+ ```js
653
+ // Base functions (ease-in by default):
654
+ am5.ease.linear am5.ease.quad am5.ease.cubic
655
+ am5.ease.sine am5.ease.circle am5.ease.exp
656
+ am5.ease.pow am5.ease.bounce am5.ease.elastic
657
+
658
+ // Modifiers — wrap a base function:
659
+ am5.ease.out(am5.ease.cubic) // ease-out
660
+ am5.ease.inOut(am5.ease.cubic) // ease in + out
661
+ am5.ease.yoyo(am5.ease.cubic) // animate to end, then back to start
662
+
663
+ // Usage in .animate():
664
+ sprite.animate({
665
+ key: "y", to: 100, duration: 400,
666
+ easing: am5.ease.out(am5.ease.cubic)
667
+ });
668
+ ```
669
+
670
+ **There is NO `am5.ease.in()`** — the base functions already ease-in by default.
641
671
 
642
672
  ## Validate generated code (if you can execute commands)
643
673
 
package/content/flow.md CHANGED
@@ -42,6 +42,8 @@ Node IDs ("A", "B", etc.) are automatically extracted — you don't define nodes
42
42
  | `am5flow.ChordNonRibbon` | Chord with line links | Arc nodes + line links |
43
43
  | `am5flow.ArcDiagram` | Arc connections between linear nodes | Linear nodes + arc links |
44
44
 
45
+ **Geographic Sankey (MapSankeySeries):** For Sankey-style flow bands drawn on a geographic map, use `am5map.MapSankeySeries` from the **map** package (not flow). See `references/map.md` → MapSankeySeries section.
46
+
45
47
  ## Sankey diagram
46
48
 
47
49
  ```js
package/content/map.md CHANGED
@@ -63,8 +63,8 @@ const chart = root.container.children.push(
63
63
  // Polygon series (countries/regions)
64
64
  const polygonSeries = chart.series.push(
65
65
  am5map.MapPolygonSeries.new(root, {
66
- geoJSON: am5geodata_worldLow,
67
- exclude: ["AQ"] // exclude Antarctica
66
+ geoJSON: am5geodata_worldLow
67
+ // exclude: ["AQ"] // exclude Antarctica (only if needed)
68
68
  // include: ["US", "CA", "MX"] // show only these
69
69
  })
70
70
  );
@@ -83,6 +83,11 @@ am5map.geoEqualEarth() // equal-area
83
83
  am5map.geoEquirectangular() // simple lat/long grid
84
84
  ```
85
85
 
86
+ **IMPORTANT — Antarctica (`exclude: ["AQ"]`):**
87
+ - Do NOT exclude Antarctica by default. Many amCharts demos exclude it because they use Mercator projection where Antarctica appears disproportionately large, but this is a demo-specific choice, not a best practice.
88
+ - Only exclude Antarctica when the user explicitly asks for it, or when referencing a specific demo that excludes it.
89
+ - With non-Mercator projections (`geoNaturalEarth1`, `geoEqualEarth`, `geoOrthographic`, `geoEquirectangular`) Antarctica renders at a reasonable size and should be included.
90
+
86
91
  ## Series types
87
92
 
88
93
  ### MapPolygonSeries (countries, regions)
@@ -91,7 +96,7 @@ am5map.geoEquirectangular() // simple lat/long grid
91
96
  const polygonSeries = chart.series.push(
92
97
  am5map.MapPolygonSeries.new(root, {
93
98
  geoJSON: am5geodata_worldLow,
94
- exclude: ["AQ"],
99
+ // exclude: ["AQ"], // only if user requests Antarctica exclusion
95
100
  valueField: "value", // for choropleth coloring
96
101
  calculateAggregates: true // needed for heat rules
97
102
  })
@@ -209,6 +214,225 @@ lineSeries.pushDataItem({
209
214
  });
210
215
  ```
211
216
 
217
+ ### MapSankeySeries (geographic flow bands)
218
+
219
+ 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.
220
+
221
+ **Requires** a `MapPolygonSeries` to resolve country IDs to geographic centroids.
222
+
223
+ ```js
224
+ // Create polygon series for countries (required for ID resolution)
225
+ var polygonSeries = chart.series.push(
226
+ am5map.MapPolygonSeries.new(root, {
227
+ geoJSON: am5geodata_worldLow
228
+ })
229
+ );
230
+
231
+ // Create Sankey flow series
232
+ var sankeySeries = chart.series.push(
233
+ am5map.MapSankeySeries.new(root, {
234
+ polygonSeries: polygonSeries,
235
+ maxWidth: 5 // max band width in geographic degrees
236
+ })
237
+ );
238
+ ```
239
+
240
+ #### Data format
241
+
242
+ Each item needs `sourceId`, `targetId`, and `value`. IDs use ISO 3166-1 alpha-2 codes matching the geodata.
243
+
244
+ ```js
245
+ sankeySeries.data.setAll([
246
+ { sourceId: "BR", targetId: "DE", value: 350 },
247
+ { sourceId: "BR", targetId: "US", value: 450 },
248
+ { sourceId: "DE", targetId: "FR", value: 150 },
249
+ { sourceId: "DE", targetId: "PL", value: 100 }
250
+ ]);
251
+ ```
252
+
253
+ **Explicit coordinates** instead of polygon IDs:
254
+ ```js
255
+ sankeySeries.data.setAll([
256
+ {
257
+ sourceLongitude: 2.35, sourceLatitude: 48.86, // Paris
258
+ targetLongitude: -74.01, targetLatitude: 40.71, // New York
259
+ value: 100
260
+ }
261
+ ]);
262
+ ```
263
+
264
+ **Waypoints** — route flows through intermediate points:
265
+ ```js
266
+ sankeySeries.data.setAll([
267
+ {
268
+ sourceId: "GB", targetId: "JP", value: 40,
269
+ waypoints: [
270
+ { longitude: 30, latitude: 65 },
271
+ { longitude: 90, latitude: 55 }
272
+ ]
273
+ }
274
+ ]);
275
+ ```
276
+
277
+ #### Key settings
278
+
279
+ | Setting | Type | Default | Description |
280
+ |---------|------|---------|-------------|
281
+ | `polygonSeries` | MapPolygonSeries | — | Reference series for resolving country IDs |
282
+ | `maxWidth` | number | `5` | Maximum band width in geographic degrees |
283
+ | `controlPointDistance` | number | `0.5` | Bezier control point distance (0–0.5) |
284
+ | `controlPointDistanceSource` | number | — | Control point distance at source end |
285
+ | `controlPointDistanceTarget` | number | — | Control point distance at target end |
286
+ | `orientation` | string | `"horizontal"` | `"horizontal"` or `"vertical"` — controls departure/arrival direction |
287
+ | `resolution` | number | `50` | Sample points per bezier segment |
288
+ | `autoSort` | boolean | `true` | Sort bands by target latitude to prevent overlap |
289
+ | `nodeType` | string | `"circle"` | `"circle"` or `"bar"` — endpoint node shape |
290
+ | `nodeWidth` | number | `1` | Bar width in degrees (bar nodeType only) |
291
+ | `nodePadding` | number | `0.3` | Extra padding on node shapes in degrees |
292
+ | `antimeridian` | string | `"short"` | `"short"` for shortest path, `"long"` to avoid ±180° crossing |
293
+ | `sourceIdField` | string | `"sourceId"` | Data field for source polygon ID |
294
+ | `targetIdField` | string | `"targetId"` | Data field for target polygon ID |
295
+ | `sourceLongitudeField` | string | `"sourceLongitude"` | Data field for source longitude |
296
+ | `sourceLatitudeField` | string | `"sourceLatitude"` | Data field for source latitude |
297
+ | `targetLongitudeField` | string | `"targetLongitude"` | Data field for target longitude |
298
+ | `targetLatitudeField` | string | `"targetLatitude"` | Data field for target latitude |
299
+ | `waypointsField` | string | `"waypoints"` | Data field for waypoints array |
300
+
301
+ #### Band appearance
302
+
303
+ Configure flow bands via `mapPolygons.template`:
304
+
305
+ ```js
306
+ sankeySeries.mapPolygons.template.setAll({
307
+ fill: am5.color(0xff6b35),
308
+ fillOpacity: 0.6,
309
+ strokeOpacity: 0,
310
+ tooltipText: "{sourceId} → {targetId}: {value}"
311
+ });
312
+ ```
313
+
314
+ Per-link control point overrides in data:
315
+ ```js
316
+ sankeySeries.data.setAll([
317
+ { sourceId: "CN", targetId: "US", value: 300, controlPointDistance: 0.2 },
318
+ { sourceId: "CN", targetId: "DE", value: 200, controlPointDistanceSource: 0.4 }
319
+ ]);
320
+ ```
321
+
322
+ #### Node configuration
323
+
324
+ Each unique endpoint gets a node shape. Configure via `nodes.mapPolygons.template`:
325
+
326
+ ```js
327
+ sankeySeries.nodes.mapPolygons.template.setAll({
328
+ fill: am5.color(0x8b5e3c),
329
+ fillOpacity: 0.9,
330
+ stroke: am5.color(0xffffff),
331
+ strokeWidth: 1.5
332
+ });
333
+ ```
334
+
335
+ Bar-style nodes (like traditional Sankey):
336
+ ```js
337
+ var sankeySeries = chart.series.push(
338
+ am5map.MapSankeySeries.new(root, {
339
+ polygonSeries: polygonSeries,
340
+ nodeType: "bar",
341
+ nodeWidth: 1.5 // bar width in degrees
342
+ })
343
+ );
344
+ ```
345
+
346
+ #### Animated bullets along bands
347
+
348
+ ```js
349
+ sankeySeries.bullets.push(function() {
350
+ return am5.Bullet.new(root, {
351
+ locationX: 0,
352
+ autoRotate: true,
353
+ sprite: am5.Circle.new(root, {
354
+ radius: 3,
355
+ fill: am5.color(0xffffff)
356
+ })
357
+ });
358
+ });
359
+
360
+ // Animate bullets along flow paths
361
+ sankeySeries.events.on("datavalidated", function() {
362
+ am5.array.each(sankeySeries.dataItems, function(dataItem) {
363
+ var bullets = dataItem.bullets;
364
+ if (bullets) {
365
+ am5.array.each(bullets, function(bullet) {
366
+ bullet.animate({
367
+ key: "locationX",
368
+ from: 0,
369
+ to: 1,
370
+ duration: 3000,
371
+ easing: am5.ease.linear,
372
+ loops: Infinity
373
+ });
374
+ });
375
+ }
376
+ });
377
+ });
378
+ ```
379
+
380
+ Bullets automatically hide on the back side of the globe (orthographic projection).
381
+
382
+ #### Multi-level flows
383
+
384
+ The same country can be both target and source — incoming and outgoing bands share unified stacking at intermediate nodes:
385
+
386
+ ```js
387
+ sankeySeries.data.setAll([
388
+ // Level 1: Producers → Hubs
389
+ { sourceId: "BR", targetId: "DE", value: 350 },
390
+ { sourceId: "VN", targetId: "DE", value: 200 },
391
+ // Level 2: Hubs → Consumers
392
+ { sourceId: "DE", targetId: "FR", value: 150 },
393
+ { sourceId: "DE", targetId: "PL", value: 100 }
394
+ ]);
395
+ ```
396
+
397
+ ### Animating bullets along lines (positionOnLine)
398
+
399
+ `MapPointSeries` data items have a `positionOnLine` setting (0–1) that positions the point along a line. Combined with `lineDataItem` and `autoRotate`, this enables flight-path-style animations.
400
+
401
+ ```js
402
+ // Create a point that follows a line
403
+ var planeSeries = chart.series.push(am5map.MapPointSeries.new(root, {}));
404
+ planeSeries.bullets.push(function() {
405
+ return am5.Bullet.new(root, {
406
+ sprite: am5.Graphics.new(root, {
407
+ svgPath: "m2,106h28l24,30h72l-44,-133h35l80,132h98c21,0 21,34 0,34l-98,0 -80,134h-35l43,-133h-71l-24,30h-28l15,-47",
408
+ scale: 0.04, centerY: am5.p50, centerX: am5.p50, fill: am5.color(0xff6600)
409
+ })
410
+ });
411
+ });
412
+
413
+ var plane = planeSeries.pushDataItem({
414
+ lineDataItem: lineDataItem, // a MapLineSeries data item
415
+ positionOnLine: 0,
416
+ autoRotate: true
417
+ });
418
+
419
+ // Animate along the line
420
+ plane.animate({ key: "positionOnLine", to: 1, duration: 3000,
421
+ easing: am5.ease.inOut(am5.ease.cubic) });
422
+
423
+ // Track position for globe rotation or scale changes
424
+ plane.on("positionOnLine", function(pos) {
425
+ // Center globe on plane's current position
426
+ chart.set("rotationX", -plane.get("longitude"));
427
+ chart.set("rotationY", -plane.get("latitude"));
428
+ });
429
+ ```
430
+
431
+ **IMPORTANT — Multi-segment lines and `positionOnLine`:**
432
+ - `positionOnLine` treats the entire line as one unit (0 = start, 1 = end). If a line has multiple segments (i.e. `pointsToConnect` has 3+ points), position 0.5 is the midpoint of the *entire* path.
433
+ - For advanced per-segment animations (e.g., scaling the bullet differently at the midpoint of each segment, or pausing between segments), use **single-segment lines** (2 points each) instead of one multi-segment line. This gives you full control: animate `positionOnLine` from 0→1 on each segment line sequentially, with independent timing, easing, and callbacks per segment.
434
+ - Example: instead of one line with points [A, B, C, D], create three lines: [A,B], [B,C], [C,D] and animate the bullet across them one at a time.
435
+
212
436
  ## Zoom controls
213
437
 
214
438
  ```js
@@ -241,11 +465,15 @@ var chart = root.container.children.push(am5map.MapChart.new(root, {
241
465
  rotationY: -30 // initial latitude center
242
466
  }));
243
467
 
244
- // Rotate to a specific country
245
- chart.animate({ key: "rotationX", to: -2.35, duration: 1500, easing: am5.ease.out(am5.ease.cubic) });
246
- chart.animate({ key: "rotationY", to: -48.86, duration: 1500, easing: am5.ease.out(am5.ease.cubic) });
468
+ // Rotate to a specific point (center it on the globe)
469
+ // IMPORTANT: use NEGATIVE longitude and NEGATIVE latitude
470
+ var paris = { latitude: 48.8566, longitude: 2.3522 };
471
+ chart.animate({ key: "rotationX", to: -paris.longitude, duration: 1500, easing: am5.ease.out(am5.ease.cubic) });
472
+ chart.animate({ key: "rotationY", to: -paris.latitude, duration: 1500, easing: am5.ease.out(am5.ease.cubic) });
247
473
  ```
248
474
 
475
+ **IMPORTANT — Globe rotation direction:** To center the globe on a geographic point, set `rotationX` to **negative longitude** and `rotationY` to **negative latitude**. This is because rotation moves the globe under the camera: rotating X by -2.35 brings longitude 2.35 to the center. A common mistake is using positive coordinates, which rotates the globe in the wrong direction.
476
+
249
477
  ## GraticuleSeries (grid lines / ocean background)
250
478
 
251
479
  ```js
@@ -374,7 +602,6 @@ root.dispose();
374
602
 
375
603
  var polygonSeries = chart.series.push(am5map.MapPolygonSeries.new(root, {
376
604
  geoJSON: am5geodata_worldLow,
377
- exclude: ["AQ"],
378
605
  valueField: "value",
379
606
  calculateAggregates: true
380
607
  }));
@@ -449,7 +676,7 @@ root.dispose();
449
676
  projection: am5map.geoMercator()
450
677
  }));
451
678
 
452
- // Background polygons
679
+ // Background polygons — excluding Antarctica here because Mercator makes it disproportionately large
453
680
  var polygonSeries = chart.series.push(am5map.MapPolygonSeries.new(root, {
454
681
  geoJSON: am5geodata_worldLow,
455
682
  exclude: ["AQ"]
@@ -572,3 +799,339 @@ root.dispose();
572
799
  </body>
573
800
  </html>
574
801
  ```
802
+
803
+ ## Example 4: Globe with animated flight path (positionOnLine + globe rotation)
804
+
805
+ Demonstrates: orthographic projection, single-segment lines for per-segment animation control, `positionOnLine` animation, globe auto-rotation to follow the plane, bullet scale animation, and city expand/shrink effects.
806
+
807
+ ```html
808
+ <!DOCTYPE html>
809
+ <html>
810
+ <head>
811
+ <meta charset="UTF-8">
812
+ <title>Globe Flight Path</title>
813
+ <script src="https://cdn.amcharts.com/lib/5/index.js"></script>
814
+ <script src="https://cdn.amcharts.com/lib/5/map.js"></script>
815
+ <script src="https://cdn.amcharts.com/lib/5/geodata/continentsLow.js"></script>
816
+ <script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
817
+ <style>#chartdiv { width: 100%; height: 600px; background: #0a0a1a; }</style>
818
+ </head>
819
+ <body>
820
+ <div id="chartdiv"></div>
821
+ <script>
822
+ var root = am5.Root.new("chartdiv");
823
+ root.setThemes([am5themes_Animated.new(root)]);
824
+ var orange = am5.color(0xff6b35);
825
+ var cityColor = am5.color(0xcc4a1a);
826
+
827
+ var cities = [
828
+ { name: "London", latitude: 51.5074, longitude: -0.1278 },
829
+ { name: "Dubai", latitude: 25.2048, longitude: 55.2708 },
830
+ { name: "Singapore", latitude: 1.3521, longitude: 103.8198 },
831
+ { name: "Sydney", latitude: -33.8688, longitude: 151.2093 },
832
+ { name: "Tokyo", latitude: 35.6762, longitude: 139.6503 },
833
+ { name: "Honolulu", latitude: 21.3069, longitude: -157.8583 },
834
+ { name: "Los Angeles", latitude: 34.0522, longitude: -118.2437 },
835
+ { name: "São Paulo", latitude: -23.5505, longitude: -46.6333 },
836
+ { name: "New York", latitude: 40.7128, longitude: -74.006 },
837
+ { name: "London", latitude: 51.5074, longitude: -0.1278 }
838
+ ];
839
+
840
+ // Globe centered on first city (note: NEGATIVE lat/lon for rotation)
841
+ var chart = root.container.children.push(am5map.MapChart.new(root, {
842
+ panX: "rotateX",
843
+ panY: "rotateY",
844
+ projection: am5map.geoOrthographic(),
845
+ rotationX: -cities[0].longitude,
846
+ rotationY: -cities[0].latitude
847
+ }));
848
+
849
+ // Ocean background
850
+ var bgSeries = chart.series.push(am5map.MapPolygonSeries.new(root, {}));
851
+ bgSeries.mapPolygons.template.setAll({
852
+ fill: am5.color(0x1a1a2e), fillOpacity: 1, strokeOpacity: 0
853
+ });
854
+ bgSeries.data.push({ geometry: am5map.getGeoRectangle(90, 180, -90, -180) });
855
+
856
+ // Graticule
857
+ var graticuleSeries = chart.series.push(am5map.GraticuleSeries.new(root, {}));
858
+ graticuleSeries.mapLines.template.setAll({
859
+ stroke: am5.color(0x4a4a65), strokeOpacity: 0.5, strokeWidth: 0.8
860
+ });
861
+
862
+ // Continents
863
+ var polygonSeries = chart.series.push(am5map.MapPolygonSeries.new(root, {
864
+ geoJSON: am5geodata_continentsLow
865
+ }));
866
+ polygonSeries.mapPolygons.template.setAll({
867
+ fill: am5.color(0x2a2a45), stroke: am5.color(0x3a3a55),
868
+ strokeWidth: 0.5, strokeOpacity: 0.6
869
+ });
870
+
871
+ // Dashed route lines — one per segment for animation control
872
+ var lineSeries = chart.series.push(am5map.MapLineSeries.new(root, {}));
873
+ lineSeries.mapLines.template.setAll({
874
+ stroke: orange, strokeOpacity: 0.35, strokeWidth: 1.5, strokeDasharray: [3, 3]
875
+ });
876
+
877
+ // City points with pulse + label
878
+ var pointSeries = chart.series.push(am5map.MapPointSeries.new(root, {}));
879
+ pointSeries.bullets.push(function () {
880
+ var container = am5.Container.new(root, {});
881
+ container.children.push(am5.Circle.new(root, {
882
+ radius: 3.5, fill: cityColor, fillOpacity: 0.9,
883
+ stroke: orange, strokeWidth: 4, strokeOpacity: 0
884
+ }));
885
+ container.children.push(am5.Label.new(root, {
886
+ text: "{name}", fill: am5.color(0xffffff), fontSize: 11,
887
+ fontWeight: "600", centerX: am5.p50, centerY: am5.p100,
888
+ dy: -8, opacity: 0, populateText: true
889
+ }));
890
+ return am5.Bullet.new(root, { sprite: container });
891
+ });
892
+
893
+ var pointsToConnect = [];
894
+ for (var i = 0; i < cities.length; i++) {
895
+ if (i === cities.length - 1) {
896
+ pointsToConnect.push(pointsToConnect[0]); // loop back
897
+ } else {
898
+ pointsToConnect.push(pointSeries.pushDataItem({
899
+ latitude: cities[i].latitude, longitude: cities[i].longitude,
900
+ name: cities[i].name
901
+ }));
902
+ }
903
+ }
904
+
905
+ // Single-segment lines (one per city pair) for per-segment control
906
+ var segmentLines = [];
907
+ var numSegments = cities.length - 1;
908
+ for (var i = 0; i < numSegments; i++) {
909
+ segmentLines.push(lineSeries.pushDataItem({
910
+ pointsToConnect: [pointsToConnect[i], pointsToConnect[i + 1]]
911
+ }));
912
+ }
913
+
914
+ // Plane series — one per segment, all hidden initially
915
+ var planeSeries = chart.series.push(am5map.MapPointSeries.new(root, {}));
916
+ planeSeries.bullets.push(function () {
917
+ return am5.Bullet.new(root, {
918
+ sprite: am5.Graphics.new(root, {
919
+ svgPath: "m2,106h28l24,30h72l-44,-133h35l80,132h98c21,0 21,34 0,34l-98,0 -80,134h-35l43,-133h-71l-24,30h-28l15,-47",
920
+ scale: 0.04, centerY: am5.p50, centerX: am5.p50,
921
+ fill: orange, forceHidden: true
922
+ })
923
+ });
924
+ });
925
+
926
+ var planes = [];
927
+ for (var i = 0; i < numSegments; i++) {
928
+ planes.push(planeSeries.pushDataItem({
929
+ lineDataItem: segmentLines[i], positionOnLine: 0, autoRotate: true
930
+ }));
931
+ }
932
+
933
+ var baseScale = 0.04, peakScale = 0.065;
934
+
935
+ // Haversine distance for proportional timing
936
+ function haversine(lat1, lon1, lat2, lon2) {
937
+ var R = 6371, dLat = (lat2 - lat1) * Math.PI / 180,
938
+ dLon = (lon2 - lon1) * Math.PI / 180;
939
+ var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
940
+ Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
941
+ Math.sin(dLon / 2) * Math.sin(dLon / 2);
942
+ return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
943
+ }
944
+
945
+ var msPerKm = 0.4;
946
+ var segDurations = [];
947
+ for (var i = 0; i < numSegments; i++) {
948
+ var d = haversine(cities[i].latitude, cities[i].longitude,
949
+ cities[i + 1].latitude, cities[i + 1].longitude);
950
+ segDurations.push(Math.max(800, Math.round(d * msPerKm)));
951
+ }
952
+
953
+ var activeCityIdx = -1;
954
+
955
+ function expandCity(idx) {
956
+ var di = pointsToConnect[idx];
957
+ if (!di || !di.bullets || !di.bullets[0]) return;
958
+ var container = di.bullets[0].get("sprite");
959
+ var dot = container.children.getIndex(0);
960
+ var label = container.children.getIndex(1);
961
+ activeCityIdx = idx;
962
+ dot.animate({ key: "radius", to: 14, duration: 400, easing: am5.ease.out(am5.ease.cubic) });
963
+ dot.animate({ key: "fillOpacity", to: 0, duration: 400, easing: am5.ease.out(am5.ease.cubic) });
964
+ dot.animate({ key: "strokeOpacity", to: 1, duration: 400, easing: am5.ease.out(am5.ease.cubic) });
965
+ label.setAll({ opacity: 0, dy: 0 });
966
+ label.animate({ key: "opacity", to: 1, duration: 300, easing: am5.ease.out(am5.ease.cubic) });
967
+ label.animate({ key: "dy", to: -10, duration: 300, easing: am5.ease.out(am5.ease.cubic) });
968
+ }
969
+
970
+ function shrinkCity(idx) {
971
+ var di = pointsToConnect[idx];
972
+ if (!di || !di.bullets || !di.bullets[0]) return;
973
+ var container = di.bullets[0].get("sprite");
974
+ var dot = container.children.getIndex(0);
975
+ var label = container.children.getIndex(1);
976
+ dot.animate({ key: "radius", to: 3.5, duration: 500, easing: am5.ease.cubic });
977
+ dot.animate({ key: "fillOpacity", to: 0.9, duration: 500, easing: am5.ease.cubic });
978
+ dot.animate({ key: "strokeOpacity", to: 0, duration: 500, easing: am5.ease.cubic });
979
+ label.animate({ key: "opacity", to: 0, duration: 400, easing: am5.ease.cubic });
980
+ activeCityIdx = -1;
981
+ }
982
+
983
+ var lastRotation = null;
984
+ var activeFlightDisposer = null;
985
+
986
+ function startFlight(plane, sprite, idx) {
987
+ if (activeCityIdx >= 0) shrinkCity(activeCityIdx);
988
+ var nextIdx = (idx + 1) % numSegments;
989
+ var expanded = false;
990
+
991
+ if (activeFlightDisposer) { activeFlightDisposer.dispose(); activeFlightDisposer = null; }
992
+
993
+ // Track position: rotate globe + scale bullet + expand destination city early
994
+ activeFlightDisposer = plane.on("positionOnLine", function (pos) {
995
+ chart.set("rotationX", -plane.get("longitude"));
996
+ chart.set("rotationY", -plane.get("latitude"));
997
+ var s = baseScale + (peakScale - baseScale) * Math.sin(pos * Math.PI);
998
+ if (plane.bullets && plane.bullets[0]) {
999
+ plane.bullets[0].get("sprite").set("scale", s);
1000
+ }
1001
+ if (!expanded && pos >= 0.85) { expanded = true; expandCity(nextIdx); }
1002
+ });
1003
+
1004
+ var anim = plane.animate({
1005
+ key: "positionOnLine", to: 1, duration: segDurations[idx],
1006
+ easing: am5.ease.inOut(am5.ease.cubic)
1007
+ });
1008
+ anim.events.on("stopped", function () {
1009
+ lastRotation = sprite.get("rotation", 0);
1010
+ sprite.set("forceHidden", true);
1011
+ flySegment(nextIdx);
1012
+ });
1013
+ }
1014
+
1015
+ function flySegment(idx) {
1016
+ var plane = planes[idx];
1017
+ var sprite = plane.bullets[0].get("sprite");
1018
+ plane.set("positionOnLine", 0);
1019
+ sprite.set("forceHidden", false);
1020
+
1021
+ if (lastRotation === null) {
1022
+ sprite.set("opacity", 0);
1023
+ sprite.animate({ key: "opacity", to: 1, duration: 400, easing: am5.ease.out(am5.ease.cubic) });
1024
+ }
1025
+
1026
+ if (lastRotation !== null) {
1027
+ var targetRotation = sprite.get("rotation", 0);
1028
+ plane.set("autoRotate", false);
1029
+ sprite.set("rotation", lastRotation);
1030
+ var diff = targetRotation - lastRotation;
1031
+ while (diff > 180) diff -= 360;
1032
+ while (diff < -180) diff += 360;
1033
+ var rotAnim = sprite.animate({
1034
+ key: "rotation", to: lastRotation + diff, duration: 500,
1035
+ easing: am5.ease.out(am5.ease.cubic)
1036
+ });
1037
+ rotAnim.events.on("stopped", function () {
1038
+ plane.set("autoRotate", true);
1039
+ startFlight(plane, sprite, idx);
1040
+ });
1041
+ } else {
1042
+ startFlight(plane, sprite, idx);
1043
+ }
1044
+ }
1045
+
1046
+ chart.appear(1000, 100);
1047
+ expandCity(0);
1048
+ setTimeout(function () { flySegment(0); }, 1500);
1049
+ </script>
1050
+ </body>
1051
+ </html>
1052
+ ```
1053
+
1054
+ ## Example 5: Map Sankey — trade flows between countries
1055
+
1056
+ Demonstrates: `MapSankeySeries` with geographic flow bands, node circles, tooltips, and multi-level flows.
1057
+
1058
+ ```html
1059
+ <!DOCTYPE html>
1060
+ <html>
1061
+ <head>
1062
+ <meta charset="UTF-8">
1063
+ <title>Map Sankey — Trade Flows</title>
1064
+ <script src="https://cdn.amcharts.com/lib/5/index.js"></script>
1065
+ <script src="https://cdn.amcharts.com/lib/5/map.js"></script>
1066
+ <script src="https://cdn.amcharts.com/lib/5/geodata/worldLow.js"></script>
1067
+ <script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
1068
+ <style>#chartdiv { width: 100%; height: 600px; }</style>
1069
+ </head>
1070
+ <body>
1071
+ <div id="chartdiv"></div>
1072
+ <script>
1073
+ var root = am5.Root.new("chartdiv");
1074
+ root.setThemes([am5themes_Animated.new(root)]);
1075
+
1076
+ var chart = root.container.children.push(am5map.MapChart.new(root, {
1077
+ panX: "rotateX",
1078
+ panY: "translateY",
1079
+ projection: am5map.geoNaturalEarth1()
1080
+ }));
1081
+
1082
+ // Background countries
1083
+ var polygonSeries = chart.series.push(am5map.MapPolygonSeries.new(root, {
1084
+ geoJSON: am5geodata_worldLow
1085
+ }));
1086
+ polygonSeries.mapPolygons.template.setAll({
1087
+ fill: am5.color(0xdddddd),
1088
+ stroke: am5.color(0xffffff),
1089
+ strokeWidth: 0.5
1090
+ });
1091
+
1092
+ // Sankey flow series
1093
+ var sankeySeries = chart.series.push(am5map.MapSankeySeries.new(root, {
1094
+ polygonSeries: polygonSeries,
1095
+ maxWidth: 4,
1096
+ controlPointDistance: 0.4,
1097
+ autoSort: true
1098
+ }));
1099
+
1100
+ // Band appearance
1101
+ sankeySeries.mapPolygons.template.setAll({
1102
+ fill: am5.color(0xff6b35),
1103
+ fillOpacity: 0.5,
1104
+ strokeOpacity: 0,
1105
+ tooltipText: "{sourceId} → {targetId}: {value}B"
1106
+ });
1107
+
1108
+ // Node appearance
1109
+ sankeySeries.nodes.mapPolygons.template.setAll({
1110
+ fill: am5.color(0x8b5e3c),
1111
+ fillOpacity: 0.9,
1112
+ stroke: am5.color(0xffffff),
1113
+ strokeWidth: 1.5
1114
+ });
1115
+
1116
+ // Multi-level trade data
1117
+ sankeySeries.data.setAll([
1118
+ // Producers → Trade hubs
1119
+ { sourceId: "BR", targetId: "DE", value: 350 },
1120
+ { sourceId: "BR", targetId: "US", value: 450 },
1121
+ { sourceId: "CN", targetId: "DE", value: 500 },
1122
+ { sourceId: "CN", targetId: "US", value: 600 },
1123
+ { sourceId: "AU", targetId: "JP", value: 200 },
1124
+ { sourceId: "AU", targetId: "CN", value: 300 },
1125
+ // Hubs → Consumers
1126
+ { sourceId: "DE", targetId: "FR", value: 250 },
1127
+ { sourceId: "DE", targetId: "GB", value: 300 },
1128
+ { sourceId: "US", targetId: "CA", value: 400 },
1129
+ { sourceId: "JP", targetId: "KR", value: 100 }
1130
+ ]);
1131
+
1132
+ chart.set("zoomControl", am5map.ZoomControl.new(root, {}));
1133
+ chart.appear(1000, 100);
1134
+ </script>
1135
+ </body>
1136
+ </html>
1137
+ ```
@@ -183,10 +183,26 @@ const series = chart.series.push(
183
183
 
184
184
  ## Cursor
185
185
 
186
+ Timeline charts use `CurveCursor`, NOT `XYCursor`. It follows the curve path.
187
+
186
188
  ```js
187
- chart.set("cursor", am5timeline.CurveCursor.new(root, {}));
189
+ // Create cursor — must use CurveCursor for timeline charts
190
+ var cursor = chart.set("cursor", am5timeline.CurveCursor.new(root, {
191
+ xAxis: xAxis, // recommended — enables axis-based snapping
192
+ yAxis: yAxis,
193
+ behavior: "none" // "zoomX", "zoomY", "zoomXY", "selectX", etc.
194
+ }));
195
+
196
+ // Hide crosshair lines (forceHidden stays hidden; visible:false can be overridden by the library)
197
+ cursor.lineX.set("forceHidden", true);
198
+ cursor.lineY.set("forceHidden", true);
199
+
200
+ // Snap tooltips to data points — set on the SERIES, not the cursor
201
+ series.set("snapTooltip", true);
188
202
  ```
189
203
 
204
+ **Do NOT** pass `lineX`/`lineY` as constructor options. Always access `cursor.lineX` / `cursor.lineY` after creation.
205
+
190
206
  ## Bullets
191
207
 
192
208
  Bullets on timeline charts work the same as XY charts:
package/content/xy.md CHANGED
@@ -358,15 +358,21 @@ const cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
358
358
  xAxis: xAxis, // optional: snap to axis
359
359
  }));
360
360
 
361
- // Customize appearance:
361
+ // Customize appearance (always access cursor.lineX/lineY AFTER creation):
362
362
  cursor.lineX.setAll({ stroke: am5.color(0x000000), strokeDasharray: [2, 2] });
363
- cursor.lineY.setAll({ visible: false });
363
+
364
+ // Hide a cursor line — use forceHidden (NOT visible:false):
365
+ cursor.lineY.set("forceHidden", true);
364
366
 
365
367
  // Snap tooltips to series data points:
366
368
  series.set("tooltip", am5.Tooltip.new(root, { labelText: "{valueY}" }));
367
369
  chart.set("cursor", am5xy.XYCursor.new(root, { snapToSeries: [series] }));
368
370
  ```
369
371
 
372
+ **Do NOT** pass `lineX`/`lineY` as constructor options to `XYCursor.new()`. Always access `cursor.lineX` / `cursor.lineY` after creation.
373
+
374
+ For timeline charts, use `am5timeline.CurveCursor` instead — see `references/timeline.md`.
375
+
370
376
  ## Scrollbar
371
377
 
372
378
  Enables zooming/panning via scrollbar. Can optionally include a preview chart.
@@ -292,9 +292,7 @@ cursor.lineX.setAll({
292
292
  strokeDasharray: \[\]
293
293
  });
294
294
 
295
- cursor.lineY.setAll({
296
- visible: false
297
- });
295
+ cursor.lineY.set("forceHidden", true);
298
296
 
299
297
  let cursor = chart.get("cursor");
300
298
 
@@ -304,11 +302,9 @@ cursor.lineX.setAll({
304
302
  strokeDasharray: \[\]
305
303
  });
306
304
 
307
- cursor.lineY.setAll({
308
- visible: false
309
- });
305
+ cursor.lineY.set("forceHidden", true);
310
306
 
311
- The above will apply color, remove dashing, and make vertical line thicker, while completely hide horizontal line.
307
+ The above will apply color, remove dashing, and make vertical line thicker, while completely hiding the horizontal line. Use `forceHidden: true` rather than `visible: false` to hide cursor lines — the cursor internally sets `visible: true` when the pointer enters the plot area, so `visible: false` gets overridden. `forceHidden` prevents this system behavior.
312
308
 
313
309
  ### Selection
314
310
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amcharts/amcharts5-mcp",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
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",