UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

1,432 lines (1,392 loc) • 55.5 kB
/** * 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 ]