123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540 |
- /*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
- var _config = require("../config");
- var __DEV__ = _config.__DEV__;
- var echarts = require("../echarts");
- var zrUtil = require("zrender/lib/core/util");
- var modelUtil = require("../util/model");
- var graphicUtil = require("../util/graphic");
- var layoutUtil = require("../util/layout");
- var _number = require("../util/number");
- var parsePercent = _number.parsePercent;
- /*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
- var _nonShapeGraphicElements = {
- // Reserved but not supported in graphic component.
- path: null,
- compoundPath: null,
- // Supported in graphic component.
- group: graphicUtil.Group,
- image: graphicUtil.Image,
- text: graphicUtil.Text
- }; // -------------
- // Preprocessor
- // -------------
- echarts.registerPreprocessor(function (option) {
- var graphicOption = option.graphic; // Convert
- // {graphic: [{left: 10, type: 'circle'}, ...]}
- // or
- // {graphic: {left: 10, type: 'circle'}}
- // to
- // {graphic: [{elements: [{left: 10, type: 'circle'}, ...]}]}
- if (zrUtil.isArray(graphicOption)) {
- if (!graphicOption[0] || !graphicOption[0].elements) {
- option.graphic = [{
- elements: graphicOption
- }];
- } else {
- // Only one graphic instance can be instantiated. (We dont
- // want that too many views are created in echarts._viewMap)
- option.graphic = [option.graphic[0]];
- }
- } else if (graphicOption && !graphicOption.elements) {
- option.graphic = [{
- elements: [graphicOption]
- }];
- }
- }); // ------
- // Model
- // ------
- var GraphicModel = echarts.extendComponentModel({
- type: 'graphic',
- defaultOption: {
- // Extra properties for each elements:
- //
- // left/right/top/bottom: (like 12, '22%', 'center', default undefined)
- // If left/rigth is set, shape.x/shape.cx/position will not be used.
- // If top/bottom is set, shape.y/shape.cy/position will not be used.
- // This mechanism is useful when you want to position a group/element
- // against the right side or the center of this container.
- //
- // width/height: (can only be pixel value, default 0)
- // Only be used to specify contianer(group) size, if needed. And
- // can not be percentage value (like '33%'). See the reason in the
- // layout algorithm below.
- //
- // bounding: (enum: 'all' (default) | 'raw')
- // Specify how to calculate boundingRect when locating.
- // 'all': Get uioned and transformed boundingRect
- // from both itself and its descendants.
- // This mode simplies confining a group of elements in the bounding
- // of their ancester container (e.g., using 'right: 0').
- // 'raw': Only use the boundingRect of itself and before transformed.
- // This mode is similar to css behavior, which is useful when you
- // want an element to be able to overflow its container. (Consider
- // a rotated circle needs to be located in a corner.)
- // info: custom info. enables user to mount some info on elements and use them
- // in event handlers. Update them only when user specified, otherwise, remain.
- // Note: elements is always behind its ancestors in this elements array.
- elements: [],
- parentId: null
- },
- /**
- * Save el options for the sake of the performance (only update modified graphics).
- * The order is the same as those in option. (ancesters -> descendants)
- *
- * @private
- * @type {Array.<Object>}
- */
- _elOptionsToUpdate: null,
- /**
- * @override
- */
- mergeOption: function (option) {
- // Prevent default merge to elements
- var elements = this.option.elements;
- this.option.elements = null;
- GraphicModel.superApply(this, 'mergeOption', arguments);
- this.option.elements = elements;
- },
- /**
- * @override
- */
- optionUpdated: function (newOption, isInit) {
- var thisOption = this.option;
- var newList = (isInit ? thisOption : newOption).elements;
- var existList = thisOption.elements = isInit ? [] : thisOption.elements;
- var flattenedList = [];
- this._flatten(newList, flattenedList);
- var mappingResult = modelUtil.mappingToExists(existList, flattenedList);
- modelUtil.makeIdAndName(mappingResult); // Clear elOptionsToUpdate
- var elOptionsToUpdate = this._elOptionsToUpdate = [];
- zrUtil.each(mappingResult, function (resultItem, index) {
- var newElOption = resultItem.option;
- if (!newElOption) {
- return;
- }
- elOptionsToUpdate.push(newElOption);
- setKeyInfoToNewElOption(resultItem, newElOption);
- mergeNewElOptionToExist(existList, index, newElOption);
- setLayoutInfoToExist(existList[index], newElOption);
- }, this); // Clean
- for (var i = existList.length - 1; i >= 0; i--) {
- if (existList[i] == null) {
- existList.splice(i, 1);
- } else {
- // $action should be volatile, otherwise option gotten from
- // `getOption` will contain unexpected $action.
- delete existList[i].$action;
- }
- }
- },
- /**
- * Convert
- * [{
- * type: 'group',
- * id: 'xx',
- * children: [{type: 'circle'}, {type: 'polygon'}]
- * }]
- * to
- * [
- * {type: 'group', id: 'xx'},
- * {type: 'circle', parentId: 'xx'},
- * {type: 'polygon', parentId: 'xx'}
- * ]
- *
- * @private
- * @param {Array.<Object>} optionList option list
- * @param {Array.<Object>} result result of flatten
- * @param {Object} parentOption parent option
- */
- _flatten: function (optionList, result, parentOption) {
- zrUtil.each(optionList, function (option) {
- if (!option) {
- return;
- }
- if (parentOption) {
- option.parentOption = parentOption;
- }
- result.push(option);
- var children = option.children;
- if (option.type === 'group' && children) {
- this._flatten(children, result, option);
- } // Deleting for JSON output, and for not affecting group creation.
- delete option.children;
- }, this);
- },
- // FIXME
- // Pass to view using payload? setOption has a payload?
- useElOptionsToUpdate: function () {
- var els = this._elOptionsToUpdate; // Clear to avoid render duplicately when zooming.
- this._elOptionsToUpdate = null;
- return els;
- }
- }); // -----
- // View
- // -----
- echarts.extendComponentView({
- type: 'graphic',
- /**
- * @override
- */
- init: function (ecModel, api) {
- /**
- * @private
- * @type {module:zrender/core/util.HashMap}
- */
- this._elMap = zrUtil.createHashMap();
- /**
- * @private
- * @type {module:echarts/graphic/GraphicModel}
- */
- this._lastGraphicModel;
- },
- /**
- * @override
- */
- render: function (graphicModel, ecModel, api) {
- // Having leveraged between use cases and algorithm complexity, a very
- // simple layout mechanism is used:
- // The size(width/height) can be determined by itself or its parent (not
- // implemented yet), but can not by its children. (Top-down travel)
- // The location(x/y) can be determined by the bounding rect of itself
- // (can including its descendants or not) and the size of its parent.
- // (Bottom-up travel)
- // When `chart.clear()` or `chart.setOption({...}, true)` with the same id,
- // view will be reused.
- if (graphicModel !== this._lastGraphicModel) {
- this._clear();
- }
- this._lastGraphicModel = graphicModel;
- this._updateElements(graphicModel);
- this._relocate(graphicModel, api);
- },
- /**
- * Update graphic elements.
- *
- * @private
- * @param {Object} graphicModel graphic model
- */
- _updateElements: function (graphicModel) {
- var elOptionsToUpdate = graphicModel.useElOptionsToUpdate();
- if (!elOptionsToUpdate) {
- return;
- }
- var elMap = this._elMap;
- var rootGroup = this.group; // Top-down tranverse to assign graphic settings to each elements.
- zrUtil.each(elOptionsToUpdate, function (elOption) {
- var $action = elOption.$action;
- var id = elOption.id;
- var existEl = elMap.get(id);
- var parentId = elOption.parentId;
- var targetElParent = parentId != null ? elMap.get(parentId) : rootGroup;
- var elOptionStyle = elOption.style;
- if (elOption.type === 'text' && elOptionStyle) {
- // In top/bottom mode, textVerticalAlign should not be used, which cause
- // inaccurately locating.
- if (elOption.hv && elOption.hv[1]) {
- elOptionStyle.textVerticalAlign = elOptionStyle.textBaseline = null;
- } // Compatible with previous setting: both support fill and textFill,
- // stroke and textStroke.
- !elOptionStyle.hasOwnProperty('textFill') && elOptionStyle.fill && (elOptionStyle.textFill = elOptionStyle.fill);
- !elOptionStyle.hasOwnProperty('textStroke') && elOptionStyle.stroke && (elOptionStyle.textStroke = elOptionStyle.stroke);
- } // Remove unnecessary props to avoid potential problems.
- var elOptionCleaned = getCleanedElOption(elOption); // For simple, do not support parent change, otherwise reorder is needed.
- if (!$action || $action === 'merge') {
- existEl ? existEl.attr(elOptionCleaned) : createEl(id, targetElParent, elOptionCleaned, elMap);
- } else if ($action === 'replace') {
- removeEl(existEl, elMap);
- createEl(id, targetElParent, elOptionCleaned, elMap);
- } else if ($action === 'remove') {
- removeEl(existEl, elMap);
- }
- var el = elMap.get(id);
- if (el) {
- el.__ecGraphicWidthOption = elOption.width;
- el.__ecGraphicHeightOption = elOption.height;
- setEventData(el, graphicModel, elOption);
- }
- });
- },
- /**
- * Locate graphic elements.
- *
- * @private
- * @param {Object} graphicModel graphic model
- * @param {module:echarts/ExtensionAPI} api extension API
- */
- _relocate: function (graphicModel, api) {
- var elOptions = graphicModel.option.elements;
- var rootGroup = this.group;
- var elMap = this._elMap;
- var apiWidth = api.getWidth();
- var apiHeight = api.getHeight(); // Top-down to calculate percentage width/height of group
- for (var i = 0; i < elOptions.length; i++) {
- var elOption = elOptions[i];
- var el = elMap.get(elOption.id);
- if (!el || !el.isGroup) {
- continue;
- }
- var parentEl = el.parent;
- var isParentRoot = parentEl === rootGroup; // Like 'position:absolut' in css, default 0.
- el.__ecGraphicWidth = parsePercent(el.__ecGraphicWidthOption, isParentRoot ? apiWidth : parentEl.__ecGraphicWidth) || 0;
- el.__ecGraphicHeight = parsePercent(el.__ecGraphicHeightOption, isParentRoot ? apiHeight : parentEl.__ecGraphicHeight) || 0;
- } // Bottom-up tranvese all elements (consider ec resize) to locate elements.
- for (var i = elOptions.length - 1; i >= 0; i--) {
- var elOption = elOptions[i];
- var el = elMap.get(elOption.id);
- if (!el) {
- continue;
- }
- var parentEl = el.parent;
- var containerInfo = parentEl === rootGroup ? {
- width: apiWidth,
- height: apiHeight
- } : {
- width: parentEl.__ecGraphicWidth,
- height: parentEl.__ecGraphicHeight
- }; // PENDING
- // Currently, when `bounding: 'all'`, the union bounding rect of the group
- // does not include the rect of [0, 0, group.width, group.height], which
- // is probably weird for users. Should we make a break change for it?
- layoutUtil.positionElement(el, elOption, containerInfo, null, {
- hv: elOption.hv,
- boundingMode: elOption.bounding
- });
- }
- },
- /**
- * Clear all elements.
- *
- * @private
- */
- _clear: function () {
- var elMap = this._elMap;
- elMap.each(function (el) {
- removeEl(el, elMap);
- });
- this._elMap = zrUtil.createHashMap();
- },
- /**
- * @override
- */
- dispose: function () {
- this._clear();
- }
- });
- function createEl(id, targetElParent, elOption, elMap) {
- var graphicType = elOption.type;
- var Clz = _nonShapeGraphicElements.hasOwnProperty(graphicType) // Those graphic elements are not shapes. They should not be
- // overwritten by users, so do them first.
- ? _nonShapeGraphicElements[graphicType] : graphicUtil.getShapeClass(graphicType);
- var el = new Clz(elOption);
- targetElParent.add(el);
- elMap.set(id, el);
- el.__ecGraphicId = id;
- }
- function removeEl(existEl, elMap) {
- var existElParent = existEl && existEl.parent;
- if (existElParent) {
- existEl.type === 'group' && existEl.traverse(function (el) {
- removeEl(el, elMap);
- });
- elMap.removeKey(existEl.__ecGraphicId);
- existElParent.remove(existEl);
- }
- } // Remove unnecessary props to avoid potential problems.
- function getCleanedElOption(elOption) {
- elOption = zrUtil.extend({}, elOption);
- zrUtil.each(['id', 'parentId', '$action', 'hv', 'bounding'].concat(layoutUtil.LOCATION_PARAMS), function (name) {
- delete elOption[name];
- });
- return elOption;
- }
- function isSetLoc(obj, props) {
- var isSet;
- zrUtil.each(props, function (prop) {
- obj[prop] != null && obj[prop] !== 'auto' && (isSet = true);
- });
- return isSet;
- }
- function setKeyInfoToNewElOption(resultItem, newElOption) {
- var existElOption = resultItem.exist; // Set id and type after id assigned.
- newElOption.id = resultItem.keyInfo.id;
- !newElOption.type && existElOption && (newElOption.type = existElOption.type); // Set parent id if not specified
- if (newElOption.parentId == null) {
- var newElParentOption = newElOption.parentOption;
- if (newElParentOption) {
- newElOption.parentId = newElParentOption.id;
- } else if (existElOption) {
- newElOption.parentId = existElOption.parentId;
- }
- } // Clear
- newElOption.parentOption = null;
- }
- function mergeNewElOptionToExist(existList, index, newElOption) {
- // Update existing options, for `getOption` feature.
- var newElOptCopy = zrUtil.extend({}, newElOption);
- var existElOption = existList[index];
- var $action = newElOption.$action || 'merge';
- if ($action === 'merge') {
- if (existElOption) {
- // We can ensure that newElOptCopy and existElOption are not
- // the same object, so `merge` will not change newElOptCopy.
- zrUtil.merge(existElOption, newElOptCopy, true); // Rigid body, use ignoreSize.
- layoutUtil.mergeLayoutParam(existElOption, newElOptCopy, {
- ignoreSize: true
- }); // Will be used in render.
- layoutUtil.copyLayoutParams(newElOption, existElOption);
- } else {
- existList[index] = newElOptCopy;
- }
- } else if ($action === 'replace') {
- existList[index] = newElOptCopy;
- } else if ($action === 'remove') {
- // null will be cleaned later.
- existElOption && (existList[index] = null);
- }
- }
- function setLayoutInfoToExist(existItem, newElOption) {
- if (!existItem) {
- return;
- }
- existItem.hv = newElOption.hv = [// Rigid body, dont care `width`.
- isSetLoc(newElOption, ['left', 'right']), // Rigid body, dont care `height`.
- isSetLoc(newElOption, ['top', 'bottom'])]; // Give default group size. Otherwise layout error may occur.
- if (existItem.type === 'group') {
- existItem.width == null && (existItem.width = newElOption.width = 0);
- existItem.height == null && (existItem.height = newElOption.height = 0);
- }
- }
- function setEventData(el, graphicModel, elOption) {
- var eventData = el.eventData; // Simple optimize for large amount of elements that no need event.
- if (!el.silent && !el.ignore && !eventData) {
- eventData = el.eventData = {
- componentType: 'graphic',
- componentIndex: graphicModel.componentIndex,
- name: el.name
- };
- } // `elOption.info` enables user to mount some info on
- // elements and use them in event handlers.
- if (eventData) {
- eventData.info = el.info;
- }
- }
|