UNPKG

@amcharts/amcharts5

Version:
278 lines 10.4 kB
import { MapSeries } from "./MapSeries"; import { MapLine } from "./MapLine"; import { ListTemplate } from "../../core/util/List"; import { Template } from "../../core/util/Template"; import * as $array from "../../core/util/Array"; /** * Creates a map series for displaying lines on the map. * * @see {@link https://www.amcharts.com/docs/v5/charts/map-chart/map-line-series/} for more info * @important */ export class MapLineSeries extends MapSeries { constructor() { super(...arguments); /** * The data items whose `pointsToConnect` this series worked out from * `pointIds`, so that they can be worked out again - and one set by hand can * be left alone. */ this._derivedPoints = new WeakSet(); /** * A [[ListTemplate]] of all lines in series. * * `mapLines.template` can also be used to configure lines. * * @default new ListTemplate<MapLine> */ this.mapLines = this.addDisposer(new ListTemplate(Template.new({}), () => MapLine._new(this._root, {}, [this.mapLines.template]))); this._types = ["LineString", "MultiLineString"]; } _afterNew() { this.fields.push("lineType", "pointIds"); this._setRawDefault("lineTypeField", "lineType"); this._setRawDefault("pointIdsField", "pointIds"); // Data can be set before the point series has loaded, so the lookup is // retried whenever that series validates - and again if it is swapped this.on("pointSeries", () => { this._setupPointSeriesListener(); }); super._afterNew(); this._setupPointSeriesListener(); } _dispose() { super._dispose(); if (this._pointSeriesDP) { this._pointSeriesDP.dispose(); this._pointSeriesDP = undefined; } } /** * (Re)subscribes to the current `pointSeries` so that lines whose points were * not there yet get connected once they are. */ _setupPointSeriesListener() { if (this._pointSeriesDP) { this._pointSeriesDP.dispose(); this._pointSeriesDP = undefined; } const pointSeries = this.get("pointSeries"); if (pointSeries) { this._pointSeriesDP = pointSeries.events.on("datavalidated", () => { this._resolvePendingPointIds(); }); if (pointSeries.dataItems.length > 0) { this._resolvePendingPointIds(); } } } /** * Retries every line that is still waiting for its points. */ _resolvePendingPointIds() { $array.each(this.dataItems, (dataItem) => { this._resolvePointIds(dataItem); }); } /** * Turns a data item's `pointIds` into the points themselves. Silent when an * id matches nothing: the line waits rather than being drawn short. */ _resolvePointIds(dataItem) { const current = dataItem.get("pointsToConnect"); // Whoever set the points outright meant it. Only a link this series made // itself is its to remake - after the points are reloaded, say, or the // series they come from is swapped for another if (current && !this._derivedPoints.has(dataItem)) { return false; } const pointIds = dataItem.get("pointIds"); const pointSeries = this.get("pointSeries"); if (!pointIds || pointIds.length === 0 || !pointSeries) { return false; } const points = []; for (let i = 0; i < pointIds.length; i++) { const point = pointSeries.getDataItemById(pointIds[i]); // A point that is not there, or has nowhere on the map to be - one // pinned to the screen, say - leaves the line waiting. Better nothing // than a line drawn short of where it was asked to go. Same test the // line itself uses to place a point (see `markDirtyValues`) if (!point) { return false; } // A fixed point sits at screen coordinates and is given a placeholder // geometry, so connecting to it draws a line to the middle of the ocean. // `fixed` only reaches the data item when `fixedField` is set, so the row // itself is the other place to look const context = point.dataContext; if (point.get("fixed") || (context && context.fixed)) { return false; } const hasPosition = (point.get("longitude") != null && point.get("latitude") != null) || point.get("geometry") != null; if (!hasPosition) { return false; } points.push(point); } // Already connected to exactly these if (current && current.length === points.length && !$array.any(points, (point) => current.indexOf(point) === -1)) { return false; } this._derivedPoints.add(dataItem); dataItem.set("pointsToConnect", points); return true; } /** * @ignore */ makeMapLine(dataItem) { const mapLine = this.children.push(this.mapLines.make()); mapLine._setDataItem(dataItem); this.mapLines.push(mapLine); return mapLine; } /** * @ignore */ markDirtyProjection() { $array.each(this.dataItems, (dataItem) => { let mapLine = dataItem.get("mapLine"); if (mapLine) { mapLine.markDirtyProjection(); } }); } _prepareChildren() { super._prepareChildren(); if (this.isDirty("stroke")) { this.mapLines.template._setC("stroke", this.get("stroke")); } } processDataItem(dataItem) { super.processDataItem(dataItem); let mapLine = dataItem.get("mapLine"); if (!mapLine) { mapLine = this.makeMapLine(dataItem); } this._handlePointsToConnect(dataItem); dataItem.on("pointsToConnect", () => { this._handlePointsToConnect(dataItem); }); // After the listener above, so that resolving connects the line right away this._resolvePointIds(dataItem); dataItem.set("mapLine", mapLine); this._addGeometry(dataItem.get("geometry"), this); mapLine.setPrivate("series", this); } _handlePointsToConnect(dataItem) { const pointsToConnect = dataItem.get("pointsToConnect"); if (pointsToConnect) { $array.each(pointsToConnect, (point) => { point.on("geometry", () => { this.markDirtyValues(dataItem); }); point.on("longitude", () => { this.markDirtyValues(dataItem); }); point.on("latitude", () => { this.markDirtyValues(dataItem); }); }); this.markDirtyValues(dataItem); } } /** * Forces a repaint of the element which relies on data. * * @since 5.0.21 */ markDirtyValues(dataItem) { super.markDirtyValues(); if (dataItem) { const mapLine = dataItem.get("mapLine"); if (mapLine) { const pointsToConnect = dataItem.get("pointsToConnect"); if (pointsToConnect) { let coordinates = []; $array.each(pointsToConnect, (point) => { const longitude = point.get("longitude"); const latitude = point.get("latitude"); if (longitude != null && latitude != null) { coordinates.push([longitude, latitude]); } else { const geometry = point.get("geometry"); if (geometry) { const coords = geometry.coordinates; if (coords) { coordinates.push([coords[0], coords[1]]); } } } }); let geometry = { type: "LineString", coordinates: coordinates }; dataItem.setRaw("geometry", geometry); mapLine._setC("geometry", geometry); } else { mapLine._setC("geometry", dataItem.get("geometry")); } } } } /** * @ignore */ disposeDataItem(dataItem) { super.disposeDataItem(dataItem); const mapLine = dataItem.get("mapLine"); if (mapLine) { this.mapLines.removeValue(mapLine); mapLine.dispose(); } } /** * @ignore */ _excludeDataItem(dataItem) { super._excludeDataItem(dataItem); const mapLine = dataItem.get("mapLine"); if (mapLine) { mapLine.setPrivate("visible", false); } } /** * @ignore */ _unexcludeDataItem(dataItem) { super._unexcludeDataItem(dataItem); const mapLine = dataItem.get("mapLine"); if (mapLine) { mapLine.setPrivate("visible", true); } } /** * @ignore */ _notIncludeDataItem(dataItem) { super._notIncludeDataItem(dataItem); const mapLine = dataItem.get("mapLine"); if (mapLine) { mapLine.setPrivate("visible", false); } } /** * @ignore */ _unNotIncludeDataItem(dataItem) { super._unNotIncludeDataItem(dataItem); const mapLine = dataItem.get("mapLine"); if (mapLine) { mapLine.setPrivate("visible", true); } } } MapLineSeries.className = "MapLineSeries"; MapLineSeries.classNames = MapSeries.classNames.concat([MapLineSeries.className]); //# sourceMappingURL=MapLineSeries.js.map