@amcharts/amcharts5-mcp 1.1.4 → 1.1.5
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 +32 -2
- package/content/flow.md +2 -0
- package/content/map.md +571 -8
- package/content/timeline.md +17 -1
- package/content/xy.md +8 -2
- package/extended/charts/xy-chart/cursor.md +3 -7
- package/package.json +1 -1
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 `mapCircles.template`:
|
|
325
|
+
|
|
326
|
+
```js
|
|
327
|
+
sankeySeries.mapCircles.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
|
|
245
|
-
|
|
246
|
-
|
|
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.mapCircles.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
|
+
```
|
package/content/timeline.md
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|