UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

1,436 lines (1,394 loc) • 54.8 kB
/** * DevExtreme (viz/vector_map/map_layer.js) * Version: 18.1.3 * Build date: Tue May 15 2018 * * Copyright (c) 2012 - 2018 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ "use strict"; var noop = require("../../core/utils/common").noop, extend = require("../../core/utils/extend").extend, each = require("../../core/utils/iterator").each, _Number = Number, _String = String, _abs = Math.abs, _round = Math.round, _min = Math.min, _max = Math.max, _sqrt = Math.sqrt, DataHelperMixin = require("../../data_helper"), _isFunction = require("../../core/utils/type").isFunction, _isArray = Array.isArray, vizUtils = require("../core/utils"), _parseScalar = vizUtils.parseScalar, _patchFontOptions = vizUtils.patchFontOptions, _normalizeEnum = vizUtils.normalizeEnum, _noop = noop, _extend = extend, _each = each, _concat = Array.prototype.concat, TYPE_AREA = "area", TYPE_LINE = "line", TYPE_MARKER = "marker", STATE_DEFAULT = 0, STATE_HOVERED = 1, STATE_SELECTED = 2, STATE_TO_INDEX = [0, 1, 2, 2], TOLERANCE = 1, SELECTIONS = { none: null, single: -1, multiple: NaN }; function getSelection(selectionMode) { var selection = _normalizeEnum(selectionMode); selection = selection in SELECTIONS ? SELECTIONS[selection] : SELECTIONS.single; if (null !== selection) { selection = { state: {}, single: selection } } return selection } 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 } }; 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 } }; function isGeoJsonObject(obj) { return _isArray(obj.features) } function unwrapFromDataSource(source) { var 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 customizeHandles_deprecated(proxies, callback) { var i, proxy, settings, ii = proxies.length; for (i = 0; i < ii; ++i) { proxy = proxies[i]; settings = callback.call(proxy, proxy) || {}; proxy.applySettings(settings); if (settings.isSelected) { proxy.selected(true) } } } function patchProxies(handles, name, data) { var i, dataItem, type = { areas: "area", markers: "marker" }[name], ii = handles.length; for (i = 0; i < ii; ++i) { handles[i].proxy.type = type } if ("marker" === type) { for (i = 0; i < ii; ++i) { dataItem = data.item(i); _extend(handles[i].proxy, { text: dataItem.text, value: dataItem.value, values: dataItem.values, url: dataItem.url }) } } } function setAreaLabelVisibility(label) { label.text.attr({ visibility: label.size[0] / label.spaceSize[0] < TOLERANCE && label.size[1] / label.spaceSize[1] < TOLERANCE ? null : "hidden" }) } function setLineLabelVisibility(label) { label.text.attr({ visibility: label.size[0] / label.spaceSize[0] < TOLERANCE || label.size[1] / label.spaceSize[1] < TOLERANCE ? null : "hidden" }) } function getDataValue(proxy, dataField, deprecatedField) { return proxy.attribute(dataField) || proxy[deprecatedField] } var TYPE_TO_TYPE_MAP = { Point: TYPE_MARKER, MultiPoint: TYPE_LINE, LineString: TYPE_LINE, MultiLineString: TYPE_LINE, Polygon: TYPE_AREA, MultiPolygon: TYPE_AREA }; function pick(a, b) { return void 0 !== a ? a : b } function guessTypeByData(sample) { var type = TYPE_TO_TYPE_MAP[sample.type], coordinates = sample.coordinates; if (!type) { if ("number" === typeof coordinates[0]) { type = TYPE_MARKER } else { if ("number" === typeof coordinates[0][0]) { type = TYPE_LINE } else { type = TYPE_AREA } } } return type } var selectStrategy = function(options, data) { var sample, type = _normalizeEnum(options.type), elementType = _normalizeEnum(options.elementType), 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]) } var emptyStrategy = { setup: _noop, reset: _noop, arrange: _noop, updateGrouping: _noop }; var strategiesByType = {}; strategiesByType[TYPE_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) { var color = settings.color || null, borderColor = settings.borderColor || null, borderWidth = pick(settings.borderWidth, null), 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) } }; strategiesByType[TYPE_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) { var color = settings.color || settings.borderColor || null, width = pick(settings.borderWidth, null), 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) } }; strategiesByType[TYPE_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) { var 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) } }; var strategiesByGeometry = {}; strategiesByGeometry[TYPE_AREA] = function(sample) { var coordinates = sample.coordinates; return { project: coordinates[0] && coordinates[0][0] && coordinates[0][0][0] && "number" === typeof coordinates[0][0][0][0] ? projectMultiPolygon : projectPolygon } }; strategiesByGeometry[TYPE_LINE] = function(sample) { var coordinates = sample.coordinates; return { project: coordinates[0] && coordinates[0][0] && "number" === typeof coordinates[0][0][0] ? projectPolygon : projectLineString } }; var strategiesByElementType = {}; strategiesByElementType[TYPE_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) { var size = style.size > 0 ? _Number(style.size) : 0, hoveredSize = size, selectedSize = size + (style.selectedStep > 0 ? _Number(style.selectedStep) : 0), hoveredBackSize = hoveredSize + (style.backStep > 0 ? _Number(style.backStep) : 0), selectedBackSize = selectedSize + (style.backStep > 0 ? _Number(style.backStep) : 0), color = style.color || null, borderColor = style.borderColor || null, borderWidth = pick(style.borderWidth, null), opacity = pick(style.opacity, null), backColor = style.backColor || null, 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) { var color = style.color || null, borderColor = style.borderColor || null, borderWidth = pick(style.borderWidth, null), 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) { var i, minValue, maxValue, deltaValue, deltaSize, values = [], ii = values.length = handles.length, settings = context.settings, dataField = settings.dataField, minSize = settings.minSize > 0 ? _Number(settings.minSize) : 0, 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, "value") || 0, 0) } minValue = _min.apply(null, values); maxValue = _max.apply(null, values); deltaValue = maxValue - minValue || 1; deltaSize = maxSize - minSize; for (i = 0; i < ii; ++i) { handles[i]._settings.size = minSize + deltaSize * (values[i] - minValue) / deltaValue } }, updateGrouping: function(context) { var dataField = context.settings.dataField; strategiesByType[TYPE_MARKER].updateGrouping(context); groupBySize(context, function(proxy) { return getDataValue(proxy, dataField, "value") }) } }, 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) { var i, values = getDataValue(proxy, ctx.settings.dataField, "values") || [], ii = values.length || 0, colors = settings._colors, sum = 0, pie = figure.pie, renderer = ctx.renderer, dataKey = ctx.dataKey, r = (settings.size > 0 ? _Number(settings.size) : 0) / 2, start = 90, end = start; for (i = 0; i < ii; ++i) { sum += values[i] || 0 } for (i = 0; i < ii; ++i) { start = end; end += (values[i] || 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) { var opacity = pick(style.opacity, null), borderColor = style.borderColor || null, 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) { var i, values, palette, ii = handles.length, dataField = context.settings.dataField, count = 0; for (i = 0; i < ii; ++i) { values = getDataValue(handles[i].proxy, dataField, "values"); if (values && values.length > count) { count = values.length } } if (count > 0) { values = []; palette = context.params.themeManager.createPalette(context.settings.palette, { useHighlight: true, extensionMode: "alternate" }); for (i = 0; i < count; ++i) { values.push(palette.getNextColor()) } 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, "url") }) }, _getStyles: function(styles, style) { var size = style.size > 0 ? _Number(style.size) : 0, hoveredSize = size + (style.hoveredStep > 0 ? _Number(style.hoveredStep) : 0), selectedSize = size + (style.selectedStep > 0 ? _Number(style.selectedStep) : 0), 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) { var i, output = [], 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) { var i, output = [], ii = output.length = coordinates.length; for (i = 0; i < ii; ++i) { output[i] = projectPointList(projection, coordinates[i]) } return output } function projectMultiPolygon(projection, coordinates) { var i, output = [], 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) { var data = projection.transform(coordinates); content.root.attr({ translateX: data[0], translateY: data[1] }) } function transformList(projection, coordinates) { var i, item, output = [], ii = coordinates.length, 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) { var i, output = [], 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) { var 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) { var 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) { var result = combineSettings(context.settings, settings); proxy.text = proxy.text || settings.text; 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) { var index = findGroupingIndex(data.callback(proxy, data.field), data.partition); if (index >= 0) { settings[name] = data.values[index] } }) } function findGroupingIndex(value, partition) { var middle, start = 0, end = partition.length - 1, index = -1; 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) { var 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(type, options, themeManager) { var colors, i, palette, settings = combineSettings(themeManager.theme("layer:" + type) || { label: {} }, options); 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) } var performGrouping = function(context, partition, settingField, dataField, valuesCallback) { var values; if (dataField && partition && partition.length > 1) { values = valuesCallback(partition.length - 1); context.grouping[settingField] = { callback: _isFunction(dataField) ? dataField : valueCallback, field: dataField, partition: partition, values: values }; context.params.dataExchanger.set(context.name, settingField, { partition: partition, values: values }) } }; function dropGrouping(context) { var name = context.name, dataExchanger = context.params.dataExchanger; _each(context.grouping, function(field) { dataExchanger.set(name, field, null) }); context.grouping = {} } var groupByColor = function(context) { performGrouping(context, context.settings.colorGroups, "color", context.settings.colorGroupingField, function(count) { var i, _palette = context.params.themeManager.createDiscretePalette(context.settings.palette, count), list = []; for (i = 0; i < count; ++i) { list.push(_palette.getColor(i)) } return list }) }; var groupBySize = function(context, valueCallback) { var settings = context.settings; performGrouping(context, settings.sizeGroups, "size", valueCallback || settings.sizeGroupingField, function(count) { var minSize = settings.minSize > 0 ? _Number(settings.minSize) : 0, maxSize = settings.maxSize >= minSize ? _Number(settings.maxSize) : 0, i = 0, 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) { var proxy = { index: index, name: name, getElements: function() { return layer.getProxies() }, clearSelection: function(_noEvent) { layer.clearSelection(_noEvent); return proxy }, getDataSource: function() { return layer.getDataSource() } }; return proxy } var MapLayer = function(params, container, name, index) { var that = this; that._params = params; that._onProjection(); that.proxy = createLayerProxy(that, name, index); that._context = { name: name, layer: that.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() }; that._container = container; that._options = {}; that._handles = []; that._data = new EmptySource }; MapLayer.prototype = _extend({ constructor: MapLayer, _onProjection: function() { var that = this; that._removeHandlers = that._params.projection.on({ engine: function() { that._project() }, screen: function() { that._transform() }, center: function() { that._transformCore() }, zoom: function() { that._transform() } }) }, _dataSourceLoadErrorHandler: function() { this._dataSourceChangedHandler() }, _dataSourceChangedHandler: function() { var that = this; that._data = unwrapFromDataSource(that._dataSource && that._dataSource.items()); that._update(true) }, _dataSourceOptions: function() { return { paginate: false } }, _getSpecificDataSourceOption: function() { return this._specificDataSourceOption }, _offProjection: function() { this._removeHandlers(); this._removeHandlers = null }, dispose: function() { var that = this; that._disposeDataSource(); that._destroyHandles(); dropGrouping(that._context); that._context.root.linkRemove().linkOff(); that._context.labelRoot && that._context.labelRoot.linkRemove().linkOff(); that._context.str.reset(that._context); that._offProjection(); that._params = that._container = that._context = that.proxy = null; return that }, setOptions: function(options) { var name, that = this; options = that._options = options || {}; name = !("dataSource" in options) && "data" in options ? "data" : "dataSource"; if (name in options && options[name] !== that._options_dataSource) { that._options_dataSource = options[name]; that._params.notifyDirty(); that._specificDataSourceOption = wrapToDataSource(options[name]); 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) { var that = this, 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.str.fullType, that._options, that._params.themeManager); context.hasSeparateLabel = !!(context.settings.label.enabled && context.str.hasLabelsGroup); context.hover = !!_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() }, _destroyHandles: function() { var i, handles = this._handles, ii = handles.length; for (i = 0; i < ii; ++i) { handles[i].dispose() } if (this._context.selection) { this._context.selection.state = {} } this._handles = [] }, _createHandles: function() { var i, handle, dataItem, that = this, handles = that._handles = [], data = that._data, ii = handles.length = data.count(), context = that._context, geometry = data.geometry, attributes = data.attributes; for (i = 0; i < ii; ++i) { dataItem = data.item(i); handles[i] = new MapLayerElement(context, i, geometry(dataItem), attributes(dataItem)) } if (_isFunction(that._options.customize)) { (that._options._deprecated ? customizeHandles_deprecated : customizeHandles)(that.getProxies(), that._options.customize, that._params.widget) } if (that._options._deprecated) { patchProxies(handles, context.name, data) } 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() { var i, handles = this._handles, 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() { var transform = this._params.projection.getTransform(); this._context.root.attr(transform); this._context.labelRoot && this._context.labelRoot.attr(transform) }, _project: function() { var i, handles = this._handles, ii = handles.length; for (i = 0; i < ii; ++i) { handles[i].project() } }, _transform: function() { var i, handles = this._handles, ii = handles.length; this._transformCore(); for (i = 0; i < ii; ++i) { handles[i].transform() } }, getProxies: function() { var i, handles = this._handles, proxies = [], ii = proxies.length = handles.length; for (i = 0; i < ii; ++i) { proxies[i] = handles[i].proxy } return proxies }, 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() { var selection = this._context.selection; if (selection) { _each(selection.state, function(_, handle) { handle && handle.setSelected(false) }); selection.state = {} } } }, DataHelperMixin); function createProxy(handle, coords, attrs) { var 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 } var MapLayerElement = function(context, index, geometry, attributes) { var that = this, proxy = that.proxy = createProxy(that, geometry.coordinates, _extend({}, attributes)); that._ctx = context; that._index = index; that._fig = that._label = null; that._state = STATE_DEFAULT; that._coordinates = geometry.coordinates; that._settings = { label: {} }; proxy.index = index; proxy.layer = context.layer; that._data = { name: context.name, index: index } }; MapLayerElement.prototype = { constructor: MapLayerElement, dispose: function() { var that = this; that._ctx = that.proxy = that._settings = that._fig = that._label = that.data = null; return that }, project: function() { var 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() { var that = this, context = this._ctx; context.str.draw(context, that._fig = {}, that._data); that._fig.root.append(context.root) }, transform: function() { var that = this, 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() { var that = this, strategy = that._ctx.str, settings = getItemSettings(that._ctx, that.proxy, that._settings); that._styles = strategy.getStyles(settings); strategy.refresh(that._ctx, that._fig, that._data, that.proxy, settings); that._refreshLabel(settings); that._setState() }, _refreshLabel: function(settings) { var that = this, context = that._ctx, labelSettings = settings.label, 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(_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() { var bBox, label = this._label; if (label.value) { bBox = label.text.getBBox(); label.size = [bBox.width, bBox.height, -bBox.y - bBox.height / 2] } }, adjustLabel: function() { var offset, label = this._label; 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) { var 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() { var root = this._fig.root; this._state ? root.toForeground() : root.toBackground() }, setHovered: function(state) { var that = this, currentState = hasFlag(that._state, STATE_HOVERED), newState = !!state; if (that._ctx.hover && currentState !== newState) { that._state = setFlag(that._state, STATE_HOVERED, newState); that._setState(); that._setForeground(); raiseChanged(that._ctx, that, newState, "hoverChanged") } return that }, setSelected: function(state, _noEvent) { var tmp, that = this, currentState = hasFlag(that._state, STATE_SELECTED), newState = !!state, selection = that._ctx.selection; if (selection && currentState !== newState) { that._state = setFlag(that._state, STATE_SELECTED, 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, STATE_SELECTED) }, resetSelected: function() { this._state = setFlag(this._state, STATE_SELECTED, false) }, restoreSelected: function() { this._fig.root.toForeground() } }; function calculatePolygonCentroid(coordinates) { var i, v1, cross, length = coordinates.length, v2 = coordinates[length - 1], cx = 0, cy = 0, area = 0, minX = 1 / 0, maxX = -(1 / 0), minY = 1 / 0, 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) { var i, v1, t, ii = coordinates.length, v2 = coordinates[0] || [], totalLength = 0, items = [0], min0 = v2[0], max0 = v2[0], min1 = v2[1], 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]; 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 ] : [] } function projectAreaLabel(coordinates) { var i, centroid, resultCentroid, ii = coordinates.length, maxArea = 0; for (i = 0; i < ii; ++i) { centroid = calculatePolygonCentroid(coordinates[i]); if (centroid.area > maxArea) { maxArea = centroid.area; resultCentroid = centroid } } return resultCentroid ? [resultCentroid.center, [_sqrt(resultCentroid.area), _sqrt(resultCentroid.area)]] : [ [], [] ] } function projectLineLabel(coordinates) { var i, data, resultData, ii = coordinates.length, maxLength = 0; for (i = 0; i < ii; ++i) { data = calculateLineStringData(coordinates[i]); if (data[2]