devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
1,432 lines (1,392 loc) • 55.5 kB
JavaScript
/**
* DevExtreme (cjs/viz/vector_map/map_layer.js)
* Version: 24.2.6
* Build date: Mon Mar 17 2025
*
* Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
exports.MapLayerCollection = MapLayerCollection;
exports.getMaxBound = getMaxBound;
var _common = require("../../core/utils/common");
var _extend2 = require("../../core/utils/extend");
var _iterator = require("../../core/utils/iterator");
var _data = require("../../common/data");
var _type = require("../../core/utils/type");
var _deferred = require("../../core/utils/deferred");
var _utils = require("../core/utils");
const _noop = _common.noop;
const _extend = _extend2.extend;
const _each = _iterator.each;
const _concat = Array.prototype.concat;
const TYPE_AREA = "area";
const TYPE_LINE = "line";
const TYPE_MARKER = "marker";
const STATE_DEFAULT = 0;
const STATE_HOVERED = 1;
const STATE_SELECTED = 2;
const STATE_TO_INDEX = [0, 1, 2, 2];
const TOLERANCE = 1;
const SELECTIONS = {
none: null,
single: -1,
multiple: NaN
};
const _isArray = Array.isArray;
const _Number = Number;
const _String = String;
const _abs = Math.abs;
const _round = Math.round;
const _min = Math.min;
const _max = Math.max;
const _sqrt = Math.sqrt;
function getMaxBound(arr) {
return arr.reduce(((a, c) => c ? [_min(a[0], c[0]), _min(a[1], c[1]), _max(a[2], c[2]), _max(a[3], c[3])] : a), arr[0])
}
function getSelection(selectionMode) {
let selection = (0, _utils.normalizeEnum)(selectionMode);
selection = selection in SELECTIONS ? SELECTIONS[selection] : SELECTIONS.single;
if (null !== selection) {
selection = {
state: {},
single: selection
}
}
return selection
}
function getName(opt, index) {
return (opt[index] || {}).name
}
function EmptySource() {}
EmptySource.prototype.count = function() {
return 0
};
function ArraySource(raw) {
this.raw = raw
}
ArraySource.prototype = {
constructor: ArraySource,
count: function() {
return this.raw.length
},
item: function(index) {
return this.raw[index]
},
geometry: function(item) {
return {
coordinates: item.coordinates
}
},
attributes: function(item) {
return item.attributes
},
getBBox: function(index) {
return 0 === arguments.length ? void 0 : this.raw[index].bbox
}
};
function GeoJsonSource(raw) {
this.raw = raw
}
GeoJsonSource.prototype = {
constructor: GeoJsonSource,
count: function() {
return this.raw.features.length
},
item: function(index) {
return this.raw.features[index]
},
geometry: function(item) {
return item.geometry
},
attributes: function(item) {
return item.properties
},
getBBox: function(index) {
return 0 === arguments.length ? this.raw.bbox : this.raw.features[index].bbox
}
};
function isGeoJsonObject(obj) {
return _isArray(obj.features)
}
function unwrapFromDataSource(source) {
let sourceType;
if (source) {
if (isGeoJsonObject(source)) {
sourceType = GeoJsonSource
} else if (1 === source.length && source[0] && isGeoJsonObject(source[0])) {
sourceType = GeoJsonSource;
source = source[0]
} else if (_isArray(source)) {
sourceType = ArraySource
}
}
sourceType = sourceType || EmptySource;
return new sourceType(source)
}
function wrapToDataSource(option) {
return option ? isGeoJsonObject(option) ? [option] : option : []
}
function customizeHandles(proxies, callback, widget) {
callback.call(widget, proxies)
}
function setAreaLabelVisibility(label) {
label.text.attr({
visibility: label.size[0] / label.spaceSize[0] < 1 && label.size[1] / label.spaceSize[1] < 1 ? null : "hidden"
})
}
function setLineLabelVisibility(label) {
label.text.attr({
visibility: label.size[0] / label.spaceSize[0] < 1 || label.size[1] / label.spaceSize[1] < 1 ? null : "hidden"
})
}
function getDataValue(proxy, dataField) {
return proxy.attribute(dataField)
}
const TYPE_TO_TYPE_MAP = {
Point: "marker",
MultiPoint: "line",
LineString: "line",
MultiLineString: "line",
Polygon: "area",
MultiPolygon: "area"
};
function pick(a, b) {
return void 0 !== a ? a : b
}
function guessTypeByData(sample) {
let type = TYPE_TO_TYPE_MAP[sample.type];
const coordinates = sample.coordinates;
if (!type) {
if ("number" === typeof coordinates[0]) {
type = "marker"
} else if ("number" === typeof coordinates[0][0]) {
type = "line"
} else {
type = "area"
}
}
return type
}
const emptyStrategy = {
setup: _noop,
reset: _noop,
arrange: _noop,
updateGrouping: _noop,
getDefaultColor: _noop
};
const strategiesByType = {};
const strategiesByGeometry = {};
const strategiesByElementType = {};
let groupByColor;
let groupBySize;
let selectStrategy = function(options, data) {
let type = (0, _utils.normalizeEnum)(options.type);
let elementType = (0, _utils.normalizeEnum)(options.elementType);
let sample;
const strategy = _extend({}, emptyStrategy);
if (data.count() > 0) {
sample = data.geometry(data.item(0));
type = strategiesByType[type] ? type : guessTypeByData(sample);
_extend(strategy, strategiesByType[type]);
strategy.fullType = strategy.type = type;
if (strategiesByGeometry[type]) {
_extend(strategy, strategiesByGeometry[type](sample))
}
if (strategiesByElementType[type]) {
elementType = strategiesByElementType[type][elementType] ? elementType : strategiesByElementType[type]._default;
_extend(strategy, strategiesByElementType[type][elementType]);
strategy.elementType = elementType;
strategy.fullType += ":" + elementType
}
}
return strategy
};
function applyElementState(figure, styles, state, field) {
figure[field].attr(styles[field][state])
}
strategiesByType.area = {
projectLabel: projectAreaLabel,
transform: transformPointList,
transformLabel: transformAreaLabel,
draw: function(context, figure, data) {
figure.root = context.renderer.path([], "area").data(context.dataKey, data)
},
refresh: _noop,
getLabelOffset: function(label) {
setAreaLabelVisibility(label);
return [0, 0]
},
getStyles: function(settings) {
const color = settings.color || null;
const borderColor = settings.borderColor || null;
const borderWidth = pick(settings.borderWidth, null);
const opacity = pick(settings.opacity, null);
return {
root: [{
class: "dxm-area",
stroke: borderColor,
"stroke-width": borderWidth,
fill: color,
opacity: opacity
}, {
class: "dxm-area dxm-area-hovered",
stroke: settings.hoveredBorderColor || borderColor,
"stroke-width": pick(settings.hoveredBorderWidth, borderWidth),
fill: settings.hoveredColor || color,
opacity: pick(settings.hoveredOpacity, opacity)
}, {
class: "dxm-area dxm-area-selected",
stroke: settings.selectedBorderColor || borderColor,
"stroke-width": pick(settings.selectedBorderWidth, borderWidth),
fill: settings.selectedColor || color,
opacity: pick(settings.selectedOpacity, opacity)
}]
}
},
setState: function(figure, styles, state) {
applyElementState(figure, styles, state, "root")
},
hasLabelsGroup: true,
updateGrouping: function(context) {
groupByColor(context)
},
getDefaultColor: _noop
};
strategiesByType.line = {
projectLabel: projectLineLabel,
transform: transformPointList,
transformLabel: transformLineLabel,
draw: function(context, figure, data) {
figure.root = context.renderer.path([], "line").data(context.dataKey, data)
},
refresh: _noop,
getLabelOffset: function(label) {
setLineLabelVisibility(label);
return [0, 0]
},
getStyles: function(settings) {
const color = settings.color || settings.borderColor || null;
const width = pick(settings.borderWidth, null);
const opacity = pick(settings.opacity, null);
return {
root: [{
class: "dxm-line",
stroke: color,
"stroke-width": width,
opacity: opacity
}, {
class: "dxm-line dxm-line-hovered",
stroke: settings.hoveredColor || settings.hoveredBorderColor || color,
"stroke-width": pick(settings.hoveredBorderWidth, width),
opacity: pick(settings.hoveredOpacity, opacity)
}, {
class: "dxm-line dxm-line-selected",
stroke: settings.selectedColor || settings.selectedBorderColor || color,
"stroke-width": pick(settings.selectedBorderWidth, width),
opacity: pick(settings.selectedOpacity, opacity)
}]
}
},
setState: function(figure, styles, state) {
applyElementState(figure, styles, state, "root")
},
hasLabelsGroup: true,
updateGrouping: function(context) {
groupByColor(context)
},
getDefaultColor: _noop
};
strategiesByType.marker = {
project: projectPoint,
transform: transformPoint,
draw: function(context, figure, data) {
figure.root = context.renderer.g();
this._draw(context, figure, data)
},
refresh: _noop,
hasLabelsGroup: false,
getLabelOffset: function(label, settings) {
return [_round((label.size[0] + _max(settings.size || 0, 0)) / 2) + 2, 0]
},
getStyles: function(settings) {
const styles = {
root: [{
class: "dxm-marker"
}, {
class: "dxm-marker dxm-marker-hovered"
}, {
class: "dxm-marker dxm-marker-selected"
}]
};
this._getStyles(styles, settings);
return styles
},
setState: function(figure, styles, state) {
applyElementState(figure, styles, state, "root");
this._setState(figure, styles, state)
},
updateGrouping: function(context) {
groupByColor(context);
groupBySize(context)
},
getDefaultColor: function(ctx, palette) {
return ctx.params.themeManager.getAccentColor(palette)
}
};
strategiesByGeometry.area = function(sample) {
return {
project: (projection, coordinates) => coordinates[0] && coordinates[0][0] && coordinates[0][0][0] && "number" === typeof coordinates[0][0][0][0] ? projectMultiPolygon(projection, coordinates) : projectPolygon(projection, coordinates)
}
};
strategiesByGeometry.line = function(sample) {
const coordinates = sample.coordinates;
return {
project: coordinates[0] && coordinates[0][0] && "number" === typeof coordinates[0][0][0] ? projectPolygon : projectLineString
}
};
strategiesByElementType.marker = {
_default: "dot",
dot: {
setup: function(context) {
context.filter = context.renderer.shadowFilter("-40%", "-40%", "180%", "200%", 0, 1, 1, "#000000", .2)
},
reset: function(context) {
context.filter.dispose();
context.filter = null
},
_draw: function(ctx, figure, data) {
figure.back = ctx.renderer.circle().sharp().data(ctx.dataKey, data).append(figure.root);
figure.dot = ctx.renderer.circle().sharp().data(ctx.dataKey, data).append(figure.root)
},
refresh: function(ctx, figure, data, proxy, settings) {
figure.dot.attr({
filter: settings.shadow ? ctx.filter.id : null
})
},
_getStyles: function(styles, style) {
const size = style.size > 0 ? _Number(style.size) : 0;
const hoveredSize = size;
const selectedSize = size + (style.selectedStep > 0 ? _Number(style.selectedStep) : 0);
const hoveredBackSize = hoveredSize + (style.backStep > 0 ? _Number(style.backStep) : 0);
const selectedBackSize = selectedSize + (style.backStep > 0 ? _Number(style.backStep) : 0);
const color = style.color || null;
const borderColor = style.borderColor || null;
const borderWidth = pick(style.borderWidth, null);
const opacity = pick(style.opacity, null);
const backColor = style.backColor || null;
const backOpacity = pick(style.backOpacity, null);
styles.dot = [{
r: size / 2,
stroke: borderColor,
"stroke-width": borderWidth,
fill: color,
opacity: opacity
}, {
r: hoveredSize / 2,
stroke: style.hoveredBorderColor || borderColor,
"stroke-width": pick(style.hoveredBorderWidth, borderWidth),
fill: style.hoveredColor || color,
opacity: pick(style.hoveredOpacity, opacity)
}, {
r: selectedSize / 2,
stroke: style.selectedBorderColor || borderColor,
"stroke-width": pick(style.selectedBorderWidth, borderWidth),
fill: style.selectedColor || color,
opacity: pick(style.selectedOpacity, opacity)
}];
styles.back = [{
r: size / 2,
stroke: "none",
"stroke-width": 0,
fill: backColor,
opacity: backOpacity
}, {
r: hoveredBackSize / 2,
stroke: "none",
"stroke-width": 0,
fill: backColor,
opacity: backOpacity
}, {
r: selectedBackSize / 2,
stroke: "none",
"stroke-width": 0,
fill: backColor,
opacity: backOpacity
}]
},
_setState: function(figure, styles, state) {
applyElementState(figure, styles, state, "dot");
applyElementState(figure, styles, state, "back")
}
},
bubble: {
_draw: function(ctx, figure, data) {
figure.bubble = ctx.renderer.circle().sharp().data(ctx.dataKey, data).append(figure.root)
},
refresh: function(ctx, figure, data, proxy, settings) {
figure.bubble.attr({
r: settings.size / 2
})
},
_getStyles: function(styles, style) {
const color = style.color || null;
const borderColor = style.borderColor || null;
const borderWidth = pick(style.borderWidth, null);
const opacity = pick(style.opacity, null);
styles.bubble = [{
stroke: borderColor,
"stroke-width": borderWidth,
fill: color,
opacity: opacity
}, {
stroke: style.hoveredBorderColor || borderColor,
"stroke-width": pick(style.hoveredBorderWidth, borderWidth),
fill: style.hoveredColor || style.color,
opacity: pick(style.hoveredOpacity, opacity)
}, {
stroke: style.selectedBorderColor || borderColor,
"stroke-width": pick(style.selectedBorderWidth, borderWidth),
fill: style.selectedColor || style.color,
opacity: pick(style.selectedOpacity, opacity)
}]
},
_setState: function(figure, styles, state) {
applyElementState(figure, styles, state, "bubble")
},
arrange: function(context, handles) {
const values = [];
let i;
const ii = values.length = handles.length;
const settings = context.settings;
const dataField = settings.dataField;
const minSize = settings.minSize > 0 ? _Number(settings.minSize) : 0;
const maxSize = settings.maxSize > minSize ? _Number(settings.maxSize) : minSize;
if (settings.sizeGroups) {
return
}
for (i = 0; i < ii; ++i) {
values[i] = _max(getDataValue(handles[i].proxy, dataField) || 0, 0)
}
const minValue = _min.apply(null, values);
const maxValue = _max.apply(null, values);
const deltaValue = maxValue - minValue || 1;
const deltaSize = maxSize - minSize;
for (i = 0; i < ii; ++i) {
handles[i]._settings.size = minSize + deltaSize * (values[i] - minValue) / deltaValue
}
},
updateGrouping: function(context) {
const dataField = context.settings.dataField;
strategiesByType.marker.updateGrouping(context);
groupBySize(context, (function(proxy) {
return getDataValue(proxy, dataField)
}))
}
},
pie: {
_draw: function(ctx, figure, data) {
figure.pie = ctx.renderer.g().append(figure.root);
figure.border = ctx.renderer.circle().sharp().data(ctx.dataKey, data).append(figure.root)
},
refresh: function(ctx, figure, data, proxy, settings) {
const values = getDataValue(proxy, ctx.settings.dataField) || [];
const colors = settings._colors;
let sum = 0;
const pie = figure.pie;
const renderer = ctx.renderer;
const dataKey = ctx.dataKey;
const r = (settings.size > 0 ? _Number(settings.size) : 0) / 2;
let start = 90;
let end = start;
let zeroSum = false;
sum = values.reduce((function(total, item) {
return total + (item || 0)
}), 0);
if (0 === sum) {
zeroSum = true;
sum = 360 / values.length
}
values.forEach((function(item, i) {
start = end;
end += zeroSum ? sum : (item || 0) / sum * 360;
renderer.arc(0, 0, 0, r, start, end).attr({
"stroke-linejoin": "round",
fill: colors[i]
}).data(dataKey, data).append(pie)
}));
figure.border.attr({
r: r
})
},
_getStyles: function(styles, style) {
const opacity = pick(style.opacity, null);
const borderColor = style.borderColor || null;
const borderWidth = pick(style.borderWidth, null);
styles.pie = [{
opacity: opacity
}, {
opacity: pick(style.hoveredOpacity, opacity)
}, {
opacity: pick(style.selectedOpacity, opacity)
}];
styles.border = [{
stroke: borderColor,
"stroke-width": borderWidth
}, {
stroke: style.hoveredBorderColor || borderColor,
"stroke-width": pick(style.hoveredBorderWidth, borderWidth)
}, {
stroke: style.selectedBorderColor || borderColor,
"stroke-width": pick(style.selectedBorderWidth, borderWidth)
}]
},
_setState: function(figure, styles, state) {
applyElementState(figure, styles, state, "pie");
applyElementState(figure, styles, state, "border")
},
arrange: function(context, handles) {
let i;
const ii = handles.length;
const dataField = context.settings.dataField;
let values;
let count = 0;
let palette;
for (i = 0; i < ii; ++i) {
values = getDataValue(handles[i].proxy, dataField);
if (values && values.length > count) {
count = values.length
}
}
if (count > 0) {
palette = context.params.themeManager.createPalette(context.settings.palette, {
useHighlight: true,
extensionMode: "alternate"
});
values = palette.generateColors(count);
context.settings._colors = values;
context.grouping.color = {
callback: _noop,
field: "",
partition: [],
values: []
};
context.params.dataExchanger.set(context.name, "color", {
partition: [],
values: values
})
}
}
},
image: {
_draw: function(ctx, figure, data) {
figure.image = ctx.renderer.image(null, null, null, null, null, "center").attr({
"pointer-events": "visible"
}).data(ctx.dataKey, data).append(figure.root)
},
refresh: function(ctx, figure, data, proxy) {
figure.image.attr({
href: getDataValue(proxy, ctx.settings.dataField)
})
},
_getStyles: function(styles, style) {
const size = style.size > 0 ? _Number(style.size) : 0;
const hoveredSize = size + (style.hoveredStep > 0 ? _Number(style.hoveredStep) : 0);
const selectedSize = size + (style.selectedStep > 0 ? _Number(style.selectedStep) : 0);
const opacity = pick(style.opacity, null);
styles.image = [{
x: -size / 2,
y: -size / 2,
width: size,
height: size,
opacity: opacity
}, {
x: -hoveredSize / 2,
y: -hoveredSize / 2,
width: hoveredSize,
height: hoveredSize,
opacity: pick(style.hoveredOpacity, opacity)
}, {
x: -selectedSize / 2,
y: -selectedSize / 2,
width: selectedSize,
height: selectedSize,
opacity: pick(style.selectedOpacity, opacity)
}]
},
_setState: function(figure, styles, state) {
applyElementState(figure, styles, state, "image")
}
}
};
function projectPoint(projection, coordinates) {
return projection.project(coordinates)
}
function projectPointList(projection, coordinates) {
const output = [];
let i;
const ii = output.length = coordinates.length;
for (i = 0; i < ii; ++i) {
output[i] = projection.project(coordinates[i])
}
return output
}
function projectLineString(projection, coordinates) {
return [projectPointList(projection, coordinates)]
}
function projectPolygon(projection, coordinates) {
const output = [];
let i;
const ii = output.length = coordinates.length;
for (i = 0; i < ii; ++i) {
output[i] = projectPointList(projection, coordinates[i])
}
return output
}
function projectMultiPolygon(projection, coordinates) {
const output = [];
let i;
const ii = output.length = coordinates.length;
for (i = 0; i < ii; ++i) {
output[i] = projectPolygon(projection, coordinates[i])
}
return _concat.apply([], output)
}
function transformPoint(content, projection, coordinates) {
const data = projection.transform(coordinates);
content.root.attr({
translateX: data[0],
translateY: data[1]
})
}
function transformList(projection, coordinates) {
const output = [];
let i;
const ii = coordinates.length;
let item;
let k = 0;
output.length = 2 * ii;
for (i = 0; i < ii; ++i) {
item = projection.transform(coordinates[i]);
output[k++] = item[0];
output[k++] = item[1]
}
return output
}
function transformPointList(content, projection, coordinates) {
const output = [];
let i;
const ii = output.length = coordinates.length;
for (i = 0; i < ii; ++i) {
output[i] = transformList(projection, coordinates[i])
}
content.root.attr({
points: output
})
}
function transformAreaLabel(label, projection, coordinates) {
const data = projection.transform(coordinates[0]);
label.spaceSize = projection.getSquareSize(coordinates[1]);
label.text.attr({
translateX: data[0],
translateY: data[1]
});
setAreaLabelVisibility(label)
}
function transformLineLabel(label, projection, coordinates) {
const data = projection.transform(coordinates[0]);
label.spaceSize = projection.getSquareSize(coordinates[1]);
label.text.attr({
translateX: data[0],
translateY: data[1]
});
setLineLabelVisibility(label)
}
function getItemSettings(context, proxy, settings) {
const result = combineSettings(context.settings, settings);
applyGrouping(context.grouping, proxy, result);
if (void 0 === settings.color && settings.paletteIndex >= 0) {
result.color = result._colors[settings.paletteIndex]
}
return result
}
function applyGrouping(grouping, proxy, settings) {
_each(grouping, (function(name, data) {
const index = findGroupingIndex(data.callback(proxy, data.field), data.partition);
if (index >= 0) {
settings[name] = data.values[index]
}
}))
}
function findGroupingIndex(value, partition) {
let start = 0;
let end = partition.length - 1;
let index = -1;
let middle;
if (partition[start] <= value && value <= partition[end]) {
if (value === partition[end]) {
index = end - 1
} else {
while (end - start > 1) {
middle = start + end >> 1;
if (value < partition[middle]) {
end = middle
} else {
start = middle
}
}
index = start
}
}
return index
}
function raiseChanged(context, handle, state, name) {
context.params.eventTrigger(name, {
target: handle.proxy,
state: state
})
}
function combineSettings(common, partial) {
const obj = _extend({}, common, partial);
obj.label = _extend({}, common.label, obj.label);
obj.label.font = _extend({}, common.label.font, obj.label.font);
return obj
}
function processCommonSettings(context, options) {
const themeManager = context.params.themeManager;
const strategy = context.str;
const settings = combineSettings(_extend({
label: {},
color: strategy.getDefaultColor(context, options.palette)
}, themeManager.theme("layer:" + strategy.fullType)), options);
let colors;
let i;
let palette;
if (settings.paletteSize > 0) {
palette = themeManager.createDiscretePalette(settings.palette, settings.paletteSize);
for (i = 0, colors = []; i < settings.paletteSize; ++i) {
colors.push(palette.getColor(i))
}
settings._colors = colors
}
return settings
}
function valueCallback(proxy, dataField) {
return proxy.attribute(dataField)
}
let performGrouping = function(context, partition, settingField, dataField, valuesCallback) {
let values;
if (dataField && partition && partition.length > 1) {
values = valuesCallback(partition.length - 1);
context.grouping[settingField] = {
callback: (0, _type.isFunction)(dataField) ? dataField : valueCallback,
field: dataField,
partition: partition,
values: values
};
context.params.dataExchanger.set(context.name, settingField, {
partition: partition,
values: values,
defaultColor: context.settings.color
})
}
};
function dropGrouping(context) {
const name = context.name;
const dataExchanger = context.params.dataExchanger;
_each(context.grouping, (function(field) {
dataExchanger.set(name, field, null)
}));
context.grouping = {}
}
groupByColor = function(context) {
performGrouping(context, context.settings.colorGroups, "color", context.settings.colorGroupingField, (function(count) {
const _palette = context.params.themeManager.createDiscretePalette(context.settings.palette, count);
let i;
const list = [];
for (i = 0; i < count; ++i) {
list.push(_palette.getColor(i))
}
return list
}))
};
groupBySize = function(context, valueCallback) {
const settings = context.settings;
performGrouping(context, settings.sizeGroups, "size", valueCallback || settings.sizeGroupingField, (function(count) {
const minSize = settings.minSize > 0 ? _Number(settings.minSize) : 0;
const maxSize = settings.maxSize >= minSize ? _Number(settings.maxSize) : 0;
let i = 0;
const sizes = [];
if (count > 1) {
for (i = 0; i < count; ++i) {
sizes.push((minSize * (count - i - 1) + maxSize * i) / (count - 1))
}
} else if (1 === count) {
sizes.push((minSize + maxSize) / 2)
}
return sizes
}))
};
function setFlag(flags, flag, state) {
if (state) {
flags |= flag
} else {
flags &= ~flag
}
return flags
}
function hasFlag(flags, flag) {
return !!(flags & flag)
}
function createLayerProxy(layer, name, index) {
const proxy = {
index: index,
name: name,
getElements: function() {
return layer.getProxies()
},
clearSelection: function(_noEvent) {
layer.clearSelection(_noEvent);
return proxy
},
getDataSource: function() {
return layer.getDataSource()
},
getBounds: () => layer.getBounds()
};
return proxy
}
let MapLayerElement;
let MapLayer = function(params, container, name, index) {
this._params = params;
this._onProjection();
this.proxy = createLayerProxy(this, name, index);
this._context = {
name: name,
layer: this.proxy,
renderer: params.renderer,
projection: params.projection,
params: params,
dataKey: params.dataKey,
str: emptyStrategy,
hover: false,
selection: null,
grouping: {},
root: params.renderer.g().attr({
class: "dxm-layer"
}).linkOn(container, name).linkAppend()
};
this._container = container;
this._options = {};
this._handles = [];
this._data = new EmptySource;
this._dataSourceLoaded = null
};
MapLayer.prototype = _extend({
constructor: MapLayer,
getDataReadyCallback() {
return this._dataSourceLoaded
},
_onProjection: function() {
const that = this;
that._removeHandlers = that._params.projection.on({
engine: function() {
that._project()
},
screen: function() {
that._transform()
},
center: function() {
that._transformCore()
},
zoom: function() {
that._transform()
}
})
},
getData() {
return this._data
},
_dataSourceLoadErrorHandler: function() {
this._dataSourceChangedHandler()
},
_dataSourceChangedHandler: function() {
this._data = unwrapFromDataSource(this._dataSource && this._dataSource.items());
this._update(true)
},
_dataSourceOptions: function() {
return {
paginate: false
}
},
_getSpecificDataSourceOption: function() {
return this._specificDataSourceOption
},
_normalizeDataSource: function(dataSource) {
const store = dataSource.store();
if ("raw" === store._loadMode) {
store._loadMode = void 0
}
return dataSource
},
_offProjection: function() {
this._removeHandlers();
this._removeHandlers = null
},
dispose: function() {
this._disposeDataSource();
this._destroyHandles();
dropGrouping(this._context);
this._context.root.linkRemove().linkOff();
this._context.labelRoot && this._context.labelRoot.linkRemove().linkOff();
this._context.str.reset(this._context);
this._offProjection();
this._params = this._container = this._context = this.proxy = null;
return this
},
setOptions: function(options) {
const that = this;
options = that._options = options || {};
that._dataSourceLoaded = new _deferred.Deferred;
if ("dataSource" in options && options.dataSource !== that._options_dataSource) {
that._options_dataSource = options.dataSource;
that._params.notifyDirty();
that._specificDataSourceOption = wrapToDataSource(options.dataSource);
that._refreshDataSource()
} else if (that._data.count() > 0) {
that._params.notifyDirty();
that._update(void 0 !== options.type && options.type !== that._context.str.type || void 0 !== options.elementType && options.elementType !== that._context.str.elementType)
}
that._transformCore()
},
_update: function(isContextChanged) {
const that = this;
const context = that._context;
if (isContextChanged) {
context.str.reset(context);
context.root.clear();
context.labelRoot && context.labelRoot.clear();
that._params.tracker.reset();
that._destroyHandles();
context.str = selectStrategy(that._options, that._data);
context.str.setup(context);
that.proxy.type = context.str.type;
that.proxy.elementType = context.str.elementType
}
context.settings = processCommonSettings(context, that._options);
context.hasSeparateLabel = !!(context.settings.label.enabled && context.str.hasLabelsGroup);
context.hover = !!(0, _utils.parseScalar)(context.settings.hoverEnabled, true);
if (context.selection) {
_each(context.selection.state, (function(_, handle) {
handle && handle.resetSelected()
}))
}
context.selection = getSelection(context.settings.selectionMode);
if (context.hasSeparateLabel) {
if (!context.labelRoot) {
context.labelRoot = context.renderer.g().attr({
class: "dxm-layer-labels"
}).linkOn(that._container, {
name: context.name + "-labels",
after: context.name
}).linkAppend();
that._transformCore()
}
} else if (context.labelRoot) {
context.labelRoot.linkRemove().linkOff();
context.labelRoot = null
}
if (isContextChanged) {
that._createHandles()
}
dropGrouping(context);
context.str.arrange(context, that._handles);
context.str.updateGrouping(context);
that._updateHandles();
that._params.notifyReady();
if (that._dataSourceLoaded) {
that._dataSourceLoaded.resolve();
that._dataSourceLoaded = null
} else {
that._params.dataReady()
}
},
getBounds() {
return getMaxBound(this._handles.map((_ref => {
let {
proxy: proxy
} = _ref;
return proxy.coordinates().map((coords => {
if (!_isArray(coords)) {
return
}
const coordsToBoundsSearch = _isArray(coords[0][0]) ? coords.reduce(((ac, val) => ac.concat(val)), []) : coords;
const initValue = coordsToBoundsSearch[0];
return coordsToBoundsSearch.reduce(((min, c) => [_min(min[0], c[0]), _min(min[1], c[1]), _max(min[2], c[0]), _max(min[3], c[1])]), [initValue[0], initValue[1], initValue[0], initValue[1]])
}))
})).map(getMaxBound))
},
_destroyHandles() {
this._handles.forEach((h => h.dispose()));
if (this._context.selection) {
this._context.selection.state = {}
}
this._handles = []
},
_createHandles: function() {
const handles = this._handles = [];
const data = this._data;
let i;
const ii = handles.length = data.count();
const context = this._context;
const geometry = data.geometry;
const attributes = data.attributes;
let handle;
let dataItem;
for (i = 0; i < ii; ++i) {
dataItem = data.item(i);
handles[i] = new MapLayerElement(context, i, geometry(dataItem), attributes(dataItem))
}(0, _type.isFunction)(this._options.customize) && customizeHandles(this.getProxies(), this._options.customize, this._params.widget);
for (i = 0; i < ii; ++i) {
handle = handles[i];
handle.project();
handle.draw();
handle.transform()
}
if (context.selection) {
_each(context.selection.state, (function(_, handle) {
handle && handle.restoreSelected()
}))
}
},
_updateHandles: function() {
const handles = this._handles;
let i;
const ii = handles.length;
for (i = 0; i < ii; ++i) {
handles[i].refresh()
}
if (this._context.settings.label.enabled) {
for (i = 0; i < ii; ++i) {
handles[i].measureLabel()
}
for (i = 0; i < ii; ++i) {
handles[i].adjustLabel()
}
}
},
_transformCore: function() {
const transform = this._params.projection.getTransform();
this._context.root.attr(transform);
this._context.labelRoot && this._context.labelRoot.attr(transform)
},
_project: function() {
const handles = this._handles;
let i;
const ii = handles.length;
for (i = 0; i < ii; ++i) {
handles[i].project()
}
},
_transform: function() {
const handles = this._handles;
let i;
const ii = handles.length;
this._transformCore();
for (i = 0; i < ii; ++i) {
handles[i].transform()
}
},
getProxies() {
return this._handles.map((p => p.proxy))
},
getProxy: function(index) {
return this._handles[index].proxy
},
raiseClick: function(i, dxEvent) {
this._params.eventTrigger("click", {
target: this._handles[i].proxy,
event: dxEvent
})
},
hoverItem: function(i, state) {
this._handles[i].setHovered(state)
},
selectItem: function(i, state, _noEvent) {
this._handles[i].setSelected(state, _noEvent)
},
clearSelection: function() {
const selection = this._context.selection;
if (selection) {
_each(selection.state, (function(_, handle) {
handle && handle.setSelected(false)
}));
selection.state = {}
}
}
}, _data.DataHelperMixin);
function createProxy(handle, coords, attrs) {
const proxy = {
coordinates: function() {
return coords
},
attribute: function(name, value) {
if (arguments.length > 1) {
attrs[name] = value;
return proxy
} else {
return arguments.length > 0 ? attrs[name] : attrs
}
},
selected: function(state, _noEvent) {
if (arguments.length > 0) {
handle.setSelected(state, _noEvent);
return proxy
} else {
return handle.isSelected()
}
},
applySettings: function(settings) {
handle.update(settings);
return proxy
}
};
return proxy
}
MapLayerElement = function(context, index, geometry, attributes) {
const proxy = this.proxy = createProxy(this, geometry.coordinates, _extend({}, attributes));
this._ctx = context;
this._index = index;
this._fig = this._label = null;
this._state = 0;
this._coordinates = geometry.coordinates;
this._settings = {
label: {}
};
proxy.index = index;
proxy.layer = context.layer;
this._data = {
name: context.name,
index: index
}
};
MapLayerElement.prototype = {
constructor: MapLayerElement,
dispose: function() {
this._ctx = this.proxy = this._settings = this._fig = this._label = this.data = null;
return this
},
project: function() {
const context = this._ctx;
this._projection = context.str.project(context.projection, this._coordinates);
if (context.hasSeparateLabel && this._label) {
this._projectLabel()
}
},
_projectLabel: function() {
this._labelProjection = this._ctx.str.projectLabel(this._projection)
},
draw: function() {
const context = this._ctx;
context.str.draw(context, this._fig = {}, this._data);
this._fig.root.append(context.root)
},
transform: function() {
const that = this;
const context = that._ctx;
context.str.transform(that._fig, context.projection, that._projection);
if (context.hasSeparateLabel && that._label) {
that._transformLabel()
}
},
_transformLabel: function() {
this._ctx.str.transformLabel(this._label, this._ctx.projection, this._labelProjection)
},
refresh: function() {
const strategy = this._ctx.str;
const settings = getItemSettings(this._ctx, this.proxy, this._settings);
this._styles = strategy.getStyles(settings);
strategy.refresh(this._ctx, this._fig, this._data, this.proxy, settings);
this._refreshLabel(settings);
this._setState()
},
_refreshLabel: function(settings) {
const that = this;
const context = that._ctx;
const labelSettings = settings.label;
let label = that._label;
if (context.settings.label.enabled) {
if (!label) {
label = that._label = {
root: context.labelRoot || that._fig.root,
text: context.renderer.text().attr({
class: "dxm-label"
}),
size: [0, 0]
};
if (context.hasSeparateLabel) {
that._projectLabel();
that._transformLabel()
}
}
label.value = _String(that.proxy.text || that.proxy.attribute(labelSettings.dataField) || "");
if (label.value) {
label.text.attr({
text: label.value,
x: 0,
y: 0
}).css((0, _utils.patchFontOptions)(labelSettings.font)).attr({
align: "center",
stroke: labelSettings.stroke,
"stroke-width": labelSettings["stroke-width"],
"stroke-opacity": labelSettings["stroke-opacity"]
}).data(context.dataKey, that._data).append(label.root);
label.settings = settings
}
} else if (label) {
label.text.remove();
that._label = null
}
},
measureLabel: function() {
const label = this._label;
let bBox;
if (label.value) {
bBox = label.text.getBBox();
label.size = [bBox.width, bBox.height, -bBox.y - bBox.height / 2]
}
},
adjustLabel: function() {
const label = this._label;
let offset;
if (label.value) {
offset = this._ctx.str.getLabelOffset(label, label.settings);
label.settings = null;
label.text.attr({
x: offset[0],
y: offset[1] + label.size[2]
})
}
},
update: function(settings) {
const that = this;
that._settings = combineSettings(that._settings, settings);
if (that._fig) {
that.refresh();
if (that._label && that._label.value) {
that.measureLabel();
that.adjustLabel()
}
}
},
_setState: function() {
this._ctx.str.setState(this._fig, this._styles, STATE_TO_INDEX[this._state])
},
_setForeground: function() {
const root = this._fig.root;
this._state ? root.toForeground() : root.toBackground()
},
setHovered: function(state) {
const that = this;
const currentState = hasFlag(that._state, 1);
const newState = !!state;
if (that._ctx.hover && currentState !== newState) {
that._state = setFlag(that._state, 1, newState);
that._setState();
that._setForeground();
raiseChanged(that._ctx, that, newState, "hoverChanged")
}
return that
},
setSelected: function(state, _noEvent) {
const that = this;
const currentState = hasFlag(that._state, 2);
const newState = !!state;
const selection = that._ctx.selection;
let tmp;
if (selection && currentState !== newState) {
that._state = setFlag(that._state, 2, newState);
tmp = selection.state[selection.single];
selection.state[selection.single] = null;
if (tmp) {
tmp.setSelected(false)
}
selection.state[selection.single || that._index] = state ? that : null;
if (that._fig) {
that._setState();
that._setForeground();
if (!_noEvent) {
raiseChanged(that._ctx, that, newState, "selectionChanged")
}
}
}
},
isSelected: function() {
return hasFlag(this._state, 2)
},
resetSelected: function() {
this._state = setFlag(this._state, 2, false)
},
restoreSelected: function() {
this._fig.root.toForeground()
}
};
function calculatePolygonCentroid(coordinates) {
let i;
const length = coordinates.length;
let v1;
let v2 = coordinates[length - 1];
let cross;
let cx = 0;
let cy = 0;
let area = 0;
let minX = 1 / 0;
let maxX = -1 / 0;
let minY = 1 / 0;
let maxY = -1 / 0;
for (i = 0; i < length; ++i) {
v1 = v2;
v2 = coordinates[i];
cross = v1[0] * v2[1] - v2[0] * v1[1];
area += cross;
cx += (v1[0] + v2[0]) * cross;
cy += (v1[1] + v2[1]) * cross;
minX = _min(minX, v2[0]);
maxX = _max(maxX, v2[0]);
minY = _min(minY, v2[1]);
maxY = _max(maxY, v2[1])
}
return {
area: _abs(area) / 2,
center: [2 * cx / 3 / area - (minX + maxX) / 2, 2 * cy / 3 / area - (minY + maxY) / 2]
}
}
function calculateLineStringData(coordinates) {
let i;
const ii = coordinates.length;
let v1;
let v2 = coordinates[0] || [];
let totalLength = 0;
const items = [0];
let min0 = v2[0];
let max0 = v2[0];
let min1 = v2[1];
let max1 = v2[1];
for (i = 1; i < ii; ++i) {
v1 = v2;
v2 = coordinates[i];
totalLength += _sqrt((v1[0] - v2[0]) * (v1[0] - v2[0]) + (v1[1] - v2[1]) * (v1[1] - v2[1]));
items[i] = totalLength;
min0 = _min(min0, v2[0]);
max0 = _max(max0, v2[0]);
min1 = _min(min1, v2[1]);
max1 = _max(max1, v2[1])
}
i = findGroupingIndex(totalLength / 2, items);
v1 = coordinates[i];
v2 = coordinates[i + 1];
const t = (totalLength / 2 - items[i]) / (items[i + 1] - items[i]);
return ii ? [
[v1[0] * (1 - t) + v2[0] * t, v1[1] * (1 - t) + v2[1] * t],
[max0 - min0, max1 - min1], totalLength
]