@amcharts/amcharts5
Version:
amCharts 5
278 lines • 10.4 kB
JavaScript
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