| /* |
| * 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. |
| */ |
| import * as zrUtil from 'zrender/src/core/util'; |
| import * as pathTool from 'zrender/src/tool/path'; |
| import * as colorTool from 'zrender/src/tool/color'; |
| import * as matrix from 'zrender/src/core/matrix'; |
| import * as vector from 'zrender/src/core/vector'; |
| import Path from 'zrender/src/graphic/Path'; |
| import Transformable from 'zrender/src/mixin/Transformable'; |
| import ZImage from 'zrender/src/graphic/Image'; |
| import Group from 'zrender/src/container/Group'; |
| import Text from 'zrender/src/graphic/Text'; |
| import Circle from 'zrender/src/graphic/shape/Circle'; |
| import Sector from 'zrender/src/graphic/shape/Sector'; |
| import Ring from 'zrender/src/graphic/shape/Ring'; |
| import Polygon from 'zrender/src/graphic/shape/Polygon'; |
| import Polyline from 'zrender/src/graphic/shape/Polyline'; |
| import Rect from 'zrender/src/graphic/shape/Rect'; |
| import Line from 'zrender/src/graphic/shape/Line'; |
| import BezierCurve from 'zrender/src/graphic/shape/BezierCurve'; |
| import Arc from 'zrender/src/graphic/shape/Arc'; |
| import CompoundPath from 'zrender/src/graphic/CompoundPath'; |
| import LinearGradient from 'zrender/src/graphic/LinearGradient'; |
| import RadialGradient from 'zrender/src/graphic/RadialGradient'; |
| import BoundingRect from 'zrender/src/core/BoundingRect'; |
| import IncrementalDisplayable from 'zrender/src/graphic/IncrementalDisplayable'; |
| var round = Math.round; |
| var mathMax = Math.max; |
| var mathMin = Math.min; |
| var EMPTY_OBJ = {}; |
| /** |
| * Extend shape with parameters |
| */ |
| |
| export function extendShape(opts) { |
| return Path.extend(opts); |
| } |
| /** |
| * Extend path |
| */ |
| |
| export function extendPath(pathData, opts) { |
| return pathTool.extendFromString(pathData, opts); |
| } |
| /** |
| * Create a path element from path data string |
| * @param {string} pathData |
| * @param {Object} opts |
| * @param {module:zrender/core/BoundingRect} rect |
| * @param {string} [layout=cover] 'center' or 'cover' |
| */ |
| |
| export function makePath(pathData, opts, rect, layout) { |
| var path = pathTool.createFromString(pathData, opts); |
| |
| if (rect) { |
| if (layout === 'center') { |
| rect = centerGraphic(rect, path.getBoundingRect()); |
| } |
| |
| resizePath(path, rect); |
| } |
| |
| return path; |
| } |
| /** |
| * Create a image element from image url |
| * @param {string} imageUrl image url |
| * @param {Object} opts options |
| * @param {module:zrender/core/BoundingRect} rect constrain rect |
| * @param {string} [layout=cover] 'center' or 'cover' |
| */ |
| |
| export function makeImage(imageUrl, rect, layout) { |
| var path = new ZImage({ |
| style: { |
| image: imageUrl, |
| x: rect.x, |
| y: rect.y, |
| width: rect.width, |
| height: rect.height |
| }, |
| onload: function (img) { |
| if (layout === 'center') { |
| var boundingRect = { |
| width: img.width, |
| height: img.height |
| }; |
| path.setStyle(centerGraphic(rect, boundingRect)); |
| } |
| } |
| }); |
| return path; |
| } |
| /** |
| * Get position of centered element in bounding box. |
| * |
| * @param {Object} rect element local bounding box |
| * @param {Object} boundingRect constraint bounding box |
| * @return {Object} element position containing x, y, width, and height |
| */ |
| |
| function centerGraphic(rect, boundingRect) { |
| // Set rect to center, keep width / height ratio. |
| var aspect = boundingRect.width / boundingRect.height; |
| var width = rect.height * aspect; |
| var height; |
| |
| if (width <= rect.width) { |
| height = rect.height; |
| } else { |
| width = rect.width; |
| height = width / aspect; |
| } |
| |
| var cx = rect.x + rect.width / 2; |
| var cy = rect.y + rect.height / 2; |
| return { |
| x: cx - width / 2, |
| y: cy - height / 2, |
| width: width, |
| height: height |
| }; |
| } |
| |
| export var mergePath = pathTool.mergePath; |
| /** |
| * Resize a path to fit the rect |
| * @param {module:zrender/graphic/Path} path |
| * @param {Object} rect |
| */ |
| |
| export function resizePath(path, rect) { |
| if (!path.applyTransform) { |
| return; |
| } |
| |
| var pathRect = path.getBoundingRect(); |
| var m = pathRect.calculateTransform(rect); |
| path.applyTransform(m); |
| } |
| /** |
| * Sub pixel optimize line for canvas |
| * |
| * @param {Object} param |
| * @param {Object} [param.shape] |
| * @param {number} [param.shape.x1] |
| * @param {number} [param.shape.y1] |
| * @param {number} [param.shape.x2] |
| * @param {number} [param.shape.y2] |
| * @param {Object} [param.style] |
| * @param {number} [param.style.lineWidth] |
| * @return {Object} Modified param |
| */ |
| |
| export function subPixelOptimizeLine(param) { |
| var shape = param.shape; |
| var lineWidth = param.style.lineWidth; |
| |
| if (round(shape.x1 * 2) === round(shape.x2 * 2)) { |
| shape.x1 = shape.x2 = subPixelOptimize(shape.x1, lineWidth, true); |
| } |
| |
| if (round(shape.y1 * 2) === round(shape.y2 * 2)) { |
| shape.y1 = shape.y2 = subPixelOptimize(shape.y1, lineWidth, true); |
| } |
| |
| return param; |
| } |
| /** |
| * Sub pixel optimize rect for canvas |
| * |
| * @param {Object} param |
| * @param {Object} [param.shape] |
| * @param {number} [param.shape.x] |
| * @param {number} [param.shape.y] |
| * @param {number} [param.shape.width] |
| * @param {number} [param.shape.height] |
| * @param {Object} [param.style] |
| * @param {number} [param.style.lineWidth] |
| * @return {Object} Modified param |
| */ |
| |
| export function subPixelOptimizeRect(param) { |
| var shape = param.shape; |
| var lineWidth = param.style.lineWidth; |
| var originX = shape.x; |
| var originY = shape.y; |
| var originWidth = shape.width; |
| var originHeight = shape.height; |
| shape.x = subPixelOptimize(shape.x, lineWidth, true); |
| shape.y = subPixelOptimize(shape.y, lineWidth, true); |
| shape.width = Math.max(subPixelOptimize(originX + originWidth, lineWidth, false) - shape.x, originWidth === 0 ? 0 : 1); |
| shape.height = Math.max(subPixelOptimize(originY + originHeight, lineWidth, false) - shape.y, originHeight === 0 ? 0 : 1); |
| return param; |
| } |
| /** |
| * Sub pixel optimize for canvas |
| * |
| * @param {number} position Coordinate, such as x, y |
| * @param {number} lineWidth Should be nonnegative integer. |
| * @param {boolean=} positiveOrNegative Default false (negative). |
| * @return {number} Optimized position. |
| */ |
| |
| export function subPixelOptimize(position, lineWidth, positiveOrNegative) { |
| // Assure that (position + lineWidth / 2) is near integer edge, |
| // otherwise line will be fuzzy in canvas. |
| var doubledPosition = round(position * 2); |
| return (doubledPosition + round(lineWidth)) % 2 === 0 ? doubledPosition / 2 : (doubledPosition + (positiveOrNegative ? 1 : -1)) / 2; |
| } |
| |
| function hasFillOrStroke(fillOrStroke) { |
| return fillOrStroke != null && fillOrStroke !== 'none'; |
| } // Most lifted color are duplicated. |
| |
| |
| var liftedColorMap = zrUtil.createHashMap(); |
| var liftedColorCount = 0; |
| |
| function liftColor(color) { |
| if (typeof color !== 'string') { |
| return color; |
| } |
| |
| var liftedColor = liftedColorMap.get(color); |
| |
| if (!liftedColor) { |
| liftedColor = colorTool.lift(color, -0.1); |
| |
| if (liftedColorCount < 10000) { |
| liftedColorMap.set(color, liftedColor); |
| liftedColorCount++; |
| } |
| } |
| |
| return liftedColor; |
| } |
| |
| function cacheElementStl(el) { |
| if (!el.__hoverStlDirty) { |
| return; |
| } |
| |
| el.__hoverStlDirty = false; |
| var hoverStyle = el.__hoverStl; |
| |
| if (!hoverStyle) { |
| el.__normalStl = null; |
| return; |
| } |
| |
| var normalStyle = el.__normalStl = {}; |
| var elStyle = el.style; |
| |
| for (var name in hoverStyle) { |
| // See comment in `doSingleEnterHover`. |
| if (hoverStyle[name] != null) { |
| normalStyle[name] = elStyle[name]; |
| } |
| } // Always cache fill and stroke to normalStyle for lifting color. |
| |
| |
| normalStyle.fill = elStyle.fill; |
| normalStyle.stroke = elStyle.stroke; |
| } |
| |
| function doSingleEnterHover(el) { |
| var hoverStl = el.__hoverStl; |
| |
| if (!hoverStl || el.__highlighted) { |
| return; |
| } |
| |
| var useHoverLayer = el.useHoverLayer; |
| el.__highlighted = useHoverLayer ? 'layer' : 'plain'; |
| var zr = el.__zr; |
| |
| if (!zr && useHoverLayer) { |
| return; |
| } |
| |
| var elTarget = el; |
| var targetStyle = el.style; |
| |
| if (useHoverLayer) { |
| elTarget = zr.addHover(el); |
| targetStyle = elTarget.style; |
| } // Consider case: only `position: 'top'` is set on emphasis, then text |
| // color should be returned to `autoColor`, rather than remain '#fff'. |
| // So we should rollback then apply again after style merging. |
| |
| |
| rollbackDefaultTextStyle(targetStyle); |
| |
| if (!useHoverLayer) { |
| cacheElementStl(elTarget); |
| } // styles can be: |
| // { |
| // label: { |
| // show: false, |
| // position: 'outside', |
| // fontSize: 18 |
| // }, |
| // emphasis: { |
| // label: { |
| // show: true |
| // } |
| // } |
| // }, |
| // where properties of `emphasis` may not appear in `normal`. We previously use |
| // module:echarts/util/model#defaultEmphasis to merge `normal` to `emphasis`. |
| // But consider rich text and setOption in merge mode, it is impossible to cover |
| // all properties in merge. So we use merge mode when setting style here, where |
| // only properties that is not `null/undefined` can be set. The disadventage: |
| // null/undefined can not be used to remove style any more in `emphasis`. |
| |
| |
| targetStyle.extendFrom(hoverStl); |
| setDefaultHoverFillStroke(targetStyle, hoverStl, 'fill'); |
| setDefaultHoverFillStroke(targetStyle, hoverStl, 'stroke'); |
| applyDefaultTextStyle(targetStyle); |
| |
| if (!useHoverLayer) { |
| el.dirty(false); |
| el.z2 += 1; |
| } |
| } |
| |
| function setDefaultHoverFillStroke(targetStyle, hoverStyle, prop) { |
| if (!hasFillOrStroke(hoverStyle[prop]) && hasFillOrStroke(targetStyle[prop])) { |
| targetStyle[prop] = liftColor(targetStyle[prop]); |
| } |
| } |
| |
| function doSingleLeaveHover(el) { |
| if (el.__highlighted) { |
| doSingleRestoreHoverStyle(el); |
| el.__highlighted = false; |
| } |
| } |
| |
| function doSingleRestoreHoverStyle(el) { |
| var highlighted = el.__highlighted; |
| |
| if (highlighted === 'layer') { |
| el.__zr && el.__zr.removeHover(el); |
| } else if (highlighted) { |
| var style = el.style; |
| var normalStl = el.__normalStl; |
| |
| if (normalStl) { |
| rollbackDefaultTextStyle(style); // Consider null/undefined value, should use |
| // `setStyle` but not `extendFrom(stl, true)`. |
| |
| el.setStyle(normalStl); |
| applyDefaultTextStyle(style); |
| el.z2 -= 1; |
| } |
| } |
| } |
| |
| function traverseCall(el, method) { |
| el.isGroup ? el.traverse(function (child) { |
| !child.isGroup && method(child); |
| }) : method(el); |
| } |
| /** |
| * Set hover style of element. |
| * |
| * @param {module:zrender/Element} el Should not be `zrender/container/Group`. |
| * @param {Object|boolean} [hoverStl] The specified hover style. |
| * If set as `false`, disable the hover style. |
| * Similarly, The `el.hoverStyle` can alse be set |
| * as `false` to disable the hover style. |
| * Otherwise, use the default hover style if not provided. |
| * @param {Object} [opt] |
| * @param {boolean} [opt.hoverSilentOnTouch=false] See `graphic.setAsHoverStyleTrigger` |
| */ |
| |
| |
| export function setElementHoverStyle(el, hoverStl) { |
| hoverStl = el.__hoverStl = hoverStl !== false && (hoverStl || {}); |
| el.__hoverStlDirty = true; |
| |
| if (el.__highlighted) { |
| doSingleLeaveHover(el); |
| doSingleEnterHover(el); |
| } |
| } |
| /** |
| * Emphasis (called by API) has higher priority than `mouseover`. |
| * When element has been called to be entered emphasis, mouse over |
| * should not trigger the highlight effect (for example, animation |
| * scale) again, and `mouseout` should not downplay the highlight |
| * effect. So the listener of `mouseover` and `mouseout` should |
| * check `isInEmphasis`. |
| * |
| * @param {module:zrender/Element} el |
| * @return {boolean} |
| */ |
| |
| export function isInEmphasis(el) { |
| return el && el.__isEmphasisEntered; |
| } |
| |
| function onElementMouseOver(e) { |
| if (this.__hoverSilentOnTouch && e.zrByTouch) { |
| return; |
| } // Only if element is not in emphasis status |
| |
| |
| !this.__isEmphasisEntered && traverseCall(this, doSingleEnterHover); |
| } |
| |
| function onElementMouseOut(e) { |
| if (this.__hoverSilentOnTouch && e.zrByTouch) { |
| return; |
| } // Only if element is not in emphasis status |
| |
| |
| !this.__isEmphasisEntered && traverseCall(this, doSingleLeaveHover); |
| } |
| |
| function enterEmphasis() { |
| this.__isEmphasisEntered = true; |
| traverseCall(this, doSingleEnterHover); |
| } |
| |
| function leaveEmphasis() { |
| this.__isEmphasisEntered = false; |
| traverseCall(this, doSingleLeaveHover); |
| } |
| /** |
| * Set hover style of element. |
| * |
| * [Caveat]: |
| * This method can be called repeatly and achieve the same result. |
| * |
| * [Usage]: |
| * Call the method for a "root" element once. Do not call it for each descendants. |
| * If the descendants elemenets of a group has itself hover style different from the |
| * root group, we can simply mount the style on `el.hoverStyle` for them, but should |
| * not call this method for them. |
| * |
| * @param {module:zrender/Element} el |
| * @param {Object|boolean} [hoverStyle] See `graphic.setElementHoverStyle`. |
| * @param {Object} [opt] |
| * @param {boolean} [opt.hoverSilentOnTouch=false] See `graphic.setAsHoverStyleTrigger`. |
| */ |
| |
| |
| export function setHoverStyle(el, hoverStyle, opt) { |
| el.isGroup ? el.traverse(function (child) { |
| // If element has sepcified hoverStyle, then use it instead of given hoverStyle |
| // Often used when item group has a label element and it's hoverStyle is different |
| !child.isGroup && setElementHoverStyle(child, child.hoverStyle || hoverStyle); |
| }) : setElementHoverStyle(el, el.hoverStyle || hoverStyle); |
| setAsHoverStyleTrigger(el, opt); |
| } |
| /** |
| * @param {Object|boolean} [opt] If `false`, means disable trigger. |
| * @param {boolean} [opt.hoverSilentOnTouch=false] |
| * In touch device, mouseover event will be trigger on touchstart event |
| * (see module:zrender/dom/HandlerProxy). By this mechanism, we can |
| * conveniently use hoverStyle when tap on touch screen without additional |
| * code for compatibility. |
| * But if the chart/component has select feature, which usually also use |
| * hoverStyle, there might be conflict between 'select-highlight' and |
| * 'hover-highlight' especially when roam is enabled (see geo for example). |
| * In this case, hoverSilentOnTouch should be used to disable hover-highlight |
| * on touch device. |
| */ |
| |
| export function setAsHoverStyleTrigger(el, opt) { |
| var disable = opt === false; |
| el.__hoverSilentOnTouch = opt != null && opt.hoverSilentOnTouch; // Simple optimize, since this method might be |
| // called for each elements of a group in some cases. |
| |
| if (!disable || el.__hoverStyleTrigger) { |
| var method = disable ? 'off' : 'on'; // Duplicated function will be auto-ignored, see Eventful.js. |
| |
| el[method]('mouseover', onElementMouseOver)[method]('mouseout', onElementMouseOut); // Emphasis, normal can be triggered manually |
| |
| el[method]('emphasis', enterEmphasis)[method]('normal', leaveEmphasis); |
| el.__hoverStyleTrigger = !disable; |
| } |
| } |
| /** |
| * @param {Object|module:zrender/graphic/Style} normalStyle |
| * @param {Object} emphasisStyle |
| * @param {module:echarts/model/Model} normalModel |
| * @param {module:echarts/model/Model} emphasisModel |
| * @param {Object} opt Check `opt` of `setTextStyleCommon` to find other props. |
| * @param {string|Function} [opt.defaultText] |
| * @param {module:echarts/model/Model} [opt.labelFetcher] Fetch text by |
| * `opt.labelFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex)` |
| * @param {module:echarts/model/Model} [opt.labelDataIndex] Fetch text by |
| * `opt.textFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex)` |
| * @param {module:echarts/model/Model} [opt.labelDimIndex] Fetch text by |
| * `opt.textFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex)` |
| * @param {Object} [normalSpecified] |
| * @param {Object} [emphasisSpecified] |
| */ |
| |
| export function setLabelStyle(normalStyle, emphasisStyle, normalModel, emphasisModel, opt, normalSpecified, emphasisSpecified) { |
| opt = opt || EMPTY_OBJ; |
| var labelFetcher = opt.labelFetcher; |
| var labelDataIndex = opt.labelDataIndex; |
| var labelDimIndex = opt.labelDimIndex; // This scenario, `label.normal.show = true; label.emphasis.show = false`, |
| // is not supported util someone requests. |
| |
| var showNormal = normalModel.getShallow('show'); |
| var showEmphasis = emphasisModel.getShallow('show'); // Consider performance, only fetch label when necessary. |
| // If `normal.show` is `false` and `emphasis.show` is `true` and `emphasis.formatter` is not set, |
| // label should be displayed, where text is fetched by `normal.formatter` or `opt.defaultText`. |
| |
| var baseText; |
| |
| if (showNormal || showEmphasis) { |
| if (labelFetcher) { |
| baseText = labelFetcher.getFormattedLabel(labelDataIndex, 'normal', null, labelDimIndex); |
| } |
| |
| if (baseText == null) { |
| baseText = zrUtil.isFunction(opt.defaultText) ? opt.defaultText(labelDataIndex, opt) : opt.defaultText; |
| } |
| } |
| |
| var normalStyleText = showNormal ? baseText : null; |
| var emphasisStyleText = showEmphasis ? zrUtil.retrieve2(labelFetcher ? labelFetcher.getFormattedLabel(labelDataIndex, 'emphasis', null, labelDimIndex) : null, baseText) : null; // Optimize: If style.text is null, text will not be drawn. |
| |
| if (normalStyleText != null || emphasisStyleText != null) { |
| // Always set `textStyle` even if `normalStyle.text` is null, because default |
| // values have to be set on `normalStyle`. |
| // If we set default values on `emphasisStyle`, consider case: |
| // Firstly, `setOption(... label: {normal: {text: null}, emphasis: {show: true}} ...);` |
| // Secondly, `setOption(... label: {noraml: {show: true, text: 'abc', color: 'red'} ...);` |
| // Then the 'red' will not work on emphasis. |
| setTextStyle(normalStyle, normalModel, normalSpecified, opt); |
| setTextStyle(emphasisStyle, emphasisModel, emphasisSpecified, opt, true); |
| } |
| |
| normalStyle.text = normalStyleText; |
| emphasisStyle.text = emphasisStyleText; |
| } |
| /** |
| * Set basic textStyle properties. |
| * @param {Object|module:zrender/graphic/Style} textStyle |
| * @param {module:echarts/model/Model} model |
| * @param {Object} [specifiedTextStyle] Can be overrided by settings in model. |
| * @param {Object} [opt] See `opt` of `setTextStyleCommon`. |
| * @param {boolean} [isEmphasis] |
| */ |
| |
| export function setTextStyle(textStyle, textStyleModel, specifiedTextStyle, opt, isEmphasis) { |
| setTextStyleCommon(textStyle, textStyleModel, opt, isEmphasis); |
| specifiedTextStyle && zrUtil.extend(textStyle, specifiedTextStyle); // textStyle.host && textStyle.host.dirty && textStyle.host.dirty(false); |
| |
| return textStyle; |
| } |
| /** |
| * Set text option in the style. |
| * @deprecated |
| * @param {Object} textStyle |
| * @param {module:echarts/model/Model} labelModel |
| * @param {string|boolean} defaultColor Default text color. |
| * If set as false, it will be processed as a emphasis style. |
| */ |
| |
| export function setText(textStyle, labelModel, defaultColor) { |
| var opt = { |
| isRectText: true |
| }; |
| var isEmphasis; |
| |
| if (defaultColor === false) { |
| isEmphasis = true; |
| } else { |
| // Support setting color as 'auto' to get visual color. |
| opt.autoColor = defaultColor; |
| } |
| |
| setTextStyleCommon(textStyle, labelModel, opt, isEmphasis); // textStyle.host && textStyle.host.dirty && textStyle.host.dirty(false); |
| } |
| /** |
| * { |
| * disableBox: boolean, Whether diable drawing box of block (outer most). |
| * isRectText: boolean, |
| * autoColor: string, specify a color when color is 'auto', |
| * for textFill, textStroke, textBackgroundColor, and textBorderColor. |
| * If autoColor specified, it is used as default textFill. |
| * useInsideStyle: |
| * `true`: Use inside style (textFill, textStroke, textStrokeWidth) |
| * if `textFill` is not specified. |
| * `false`: Do not use inside style. |
| * `null/undefined`: use inside style if `isRectText` is true and |
| * `textFill` is not specified and textPosition contains `'inside'`. |
| * forceRich: boolean |
| * } |
| */ |
| |
| function setTextStyleCommon(textStyle, textStyleModel, opt, isEmphasis) { |
| // Consider there will be abnormal when merge hover style to normal style if given default value. |
| opt = opt || EMPTY_OBJ; |
| |
| if (opt.isRectText) { |
| var textPosition = textStyleModel.getShallow('position') || (isEmphasis ? null : 'inside'); // 'outside' is not a valid zr textPostion value, but used |
| // in bar series, and magric type should be considered. |
| |
| textPosition === 'outside' && (textPosition = 'top'); |
| textStyle.textPosition = textPosition; |
| textStyle.textOffset = textStyleModel.getShallow('offset'); |
| var labelRotate = textStyleModel.getShallow('rotate'); |
| labelRotate != null && (labelRotate *= Math.PI / 180); |
| textStyle.textRotation = labelRotate; |
| textStyle.textDistance = zrUtil.retrieve2(textStyleModel.getShallow('distance'), isEmphasis ? null : 5); |
| } |
| |
| var ecModel = textStyleModel.ecModel; |
| var globalTextStyle = ecModel && ecModel.option.textStyle; // Consider case: |
| // { |
| // data: [{ |
| // value: 12, |
| // label: { |
| // rich: { |
| // // no 'a' here but using parent 'a'. |
| // } |
| // } |
| // }], |
| // rich: { |
| // a: { ... } |
| // } |
| // } |
| |
| var richItemNames = getRichItemNames(textStyleModel); |
| var richResult; |
| |
| if (richItemNames) { |
| richResult = {}; |
| |
| for (var name in richItemNames) { |
| if (richItemNames.hasOwnProperty(name)) { |
| // Cascade is supported in rich. |
| var richTextStyle = textStyleModel.getModel(['rich', name]); // In rich, never `disableBox`. |
| |
| setTokenTextStyle(richResult[name] = {}, richTextStyle, globalTextStyle, opt, isEmphasis); |
| } |
| } |
| } |
| |
| textStyle.rich = richResult; |
| setTokenTextStyle(textStyle, textStyleModel, globalTextStyle, opt, isEmphasis, true); |
| |
| if (opt.forceRich && !opt.textStyle) { |
| opt.textStyle = {}; |
| } |
| |
| return textStyle; |
| } // Consider case: |
| // { |
| // data: [{ |
| // value: 12, |
| // label: { |
| // rich: { |
| // // no 'a' here but using parent 'a'. |
| // } |
| // } |
| // }], |
| // rich: { |
| // a: { ... } |
| // } |
| // } |
| |
| |
| function getRichItemNames(textStyleModel) { |
| // Use object to remove duplicated names. |
| var richItemNameMap; |
| |
| while (textStyleModel && textStyleModel !== textStyleModel.ecModel) { |
| var rich = (textStyleModel.option || EMPTY_OBJ).rich; |
| |
| if (rich) { |
| richItemNameMap = richItemNameMap || {}; |
| |
| for (var name in rich) { |
| if (rich.hasOwnProperty(name)) { |
| richItemNameMap[name] = 1; |
| } |
| } |
| } |
| |
| textStyleModel = textStyleModel.parentModel; |
| } |
| |
| return richItemNameMap; |
| } |
| |
| function setTokenTextStyle(textStyle, textStyleModel, globalTextStyle, opt, isEmphasis, isBlock) { |
| // In merge mode, default value should not be given. |
| globalTextStyle = !isEmphasis && globalTextStyle || EMPTY_OBJ; |
| textStyle.textFill = getAutoColor(textStyleModel.getShallow('color'), opt) || globalTextStyle.color; |
| textStyle.textStroke = getAutoColor(textStyleModel.getShallow('textBorderColor'), opt) || globalTextStyle.textBorderColor; |
| textStyle.textStrokeWidth = zrUtil.retrieve2(textStyleModel.getShallow('textBorderWidth'), globalTextStyle.textBorderWidth); // Save original textPosition, because style.textPosition will be repalced by |
| // real location (like [10, 30]) in zrender. |
| |
| textStyle.insideRawTextPosition = textStyle.textPosition; |
| |
| if (!isEmphasis) { |
| if (isBlock) { |
| textStyle.insideRollbackOpt = opt; |
| applyDefaultTextStyle(textStyle); |
| } // Set default finally. |
| |
| |
| if (textStyle.textFill == null) { |
| textStyle.textFill = opt.autoColor; |
| } |
| } // Do not use `getFont` here, because merge should be supported, where |
| // part of these properties may be changed in emphasis style, and the |
| // others should remain their original value got from normal style. |
| |
| |
| textStyle.fontStyle = textStyleModel.getShallow('fontStyle') || globalTextStyle.fontStyle; |
| textStyle.fontWeight = textStyleModel.getShallow('fontWeight') || globalTextStyle.fontWeight; |
| textStyle.fontSize = textStyleModel.getShallow('fontSize') || globalTextStyle.fontSize; |
| textStyle.fontFamily = textStyleModel.getShallow('fontFamily') || globalTextStyle.fontFamily; |
| textStyle.textAlign = textStyleModel.getShallow('align'); |
| textStyle.textVerticalAlign = textStyleModel.getShallow('verticalAlign') || textStyleModel.getShallow('baseline'); |
| textStyle.textLineHeight = textStyleModel.getShallow('lineHeight'); |
| textStyle.textWidth = textStyleModel.getShallow('width'); |
| textStyle.textHeight = textStyleModel.getShallow('height'); |
| textStyle.textTag = textStyleModel.getShallow('tag'); |
| |
| if (!isBlock || !opt.disableBox) { |
| textStyle.textBackgroundColor = getAutoColor(textStyleModel.getShallow('backgroundColor'), opt); |
| textStyle.textPadding = textStyleModel.getShallow('padding'); |
| textStyle.textBorderColor = getAutoColor(textStyleModel.getShallow('borderColor'), opt); |
| textStyle.textBorderWidth = textStyleModel.getShallow('borderWidth'); |
| textStyle.textBorderRadius = textStyleModel.getShallow('borderRadius'); |
| textStyle.textBoxShadowColor = textStyleModel.getShallow('shadowColor'); |
| textStyle.textBoxShadowBlur = textStyleModel.getShallow('shadowBlur'); |
| textStyle.textBoxShadowOffsetX = textStyleModel.getShallow('shadowOffsetX'); |
| textStyle.textBoxShadowOffsetY = textStyleModel.getShallow('shadowOffsetY'); |
| } |
| |
| textStyle.textShadowColor = textStyleModel.getShallow('textShadowColor') || globalTextStyle.textShadowColor; |
| textStyle.textShadowBlur = textStyleModel.getShallow('textShadowBlur') || globalTextStyle.textShadowBlur; |
| textStyle.textShadowOffsetX = textStyleModel.getShallow('textShadowOffsetX') || globalTextStyle.textShadowOffsetX; |
| textStyle.textShadowOffsetY = textStyleModel.getShallow('textShadowOffsetY') || globalTextStyle.textShadowOffsetY; |
| } |
| |
| function getAutoColor(color, opt) { |
| return color !== 'auto' ? color : opt && opt.autoColor ? opt.autoColor : null; |
| } // When text position is `inside` and `textFill` not specified, we |
| // provide a mechanism to auto make text border for better view. But |
| // text position changing when hovering or being emphasis should be |
| // considered, where the `insideRollback` enables to restore the style. |
| |
| |
| function applyDefaultTextStyle(textStyle) { |
| var opt = textStyle.insideRollbackOpt; // Only insideRollbackOpt create (setTextStyleCommon used), |
| // applyDefaultTextStyle works. |
| |
| if (!opt || textStyle.textFill != null) { |
| return; |
| } |
| |
| var useInsideStyle = opt.useInsideStyle; |
| var textPosition = textStyle.insideRawTextPosition; |
| var insideRollback; |
| var autoColor = opt.autoColor; |
| |
| if (useInsideStyle !== false && (useInsideStyle === true || opt.isRectText && textPosition // textPosition can be [10, 30] |
| && typeof textPosition === 'string' && textPosition.indexOf('inside') >= 0)) { |
| insideRollback = { |
| textFill: null, |
| textStroke: textStyle.textStroke, |
| textStrokeWidth: textStyle.textStrokeWidth |
| }; |
| textStyle.textFill = '#fff'; // Consider text with #fff overflow its container. |
| |
| if (textStyle.textStroke == null) { |
| textStyle.textStroke = autoColor; |
| textStyle.textStrokeWidth == null && (textStyle.textStrokeWidth = 2); |
| } |
| } else if (autoColor != null) { |
| insideRollback = { |
| textFill: null |
| }; |
| textStyle.textFill = autoColor; |
| } // Always set `insideRollback`, for clearing previous. |
| |
| |
| if (insideRollback) { |
| textStyle.insideRollback = insideRollback; |
| } |
| } |
| |
| function rollbackDefaultTextStyle(style) { |
| var insideRollback = style.insideRollback; |
| |
| if (insideRollback) { |
| style.textFill = insideRollback.textFill; |
| style.textStroke = insideRollback.textStroke; |
| style.textStrokeWidth = insideRollback.textStrokeWidth; |
| style.insideRollback = null; |
| } |
| } |
| |
| export function getFont(opt, ecModel) { |
| // ecModel or default text style model. |
| var gTextStyleModel = ecModel || ecModel.getModel('textStyle'); |
| return zrUtil.trim([// FIXME in node-canvas fontWeight is before fontStyle |
| opt.fontStyle || gTextStyleModel && gTextStyleModel.getShallow('fontStyle') || '', opt.fontWeight || gTextStyleModel && gTextStyleModel.getShallow('fontWeight') || '', (opt.fontSize || gTextStyleModel && gTextStyleModel.getShallow('fontSize') || 12) + 'px', opt.fontFamily || gTextStyleModel && gTextStyleModel.getShallow('fontFamily') || 'sans-serif'].join(' ')); |
| } |
| |
| function animateOrSetProps(isUpdate, el, props, animatableModel, dataIndex, cb) { |
| if (typeof dataIndex === 'function') { |
| cb = dataIndex; |
| dataIndex = null; |
| } // Do not check 'animation' property directly here. Consider this case: |
| // animation model is an `itemModel`, whose does not have `isAnimationEnabled` |
| // but its parent model (`seriesModel`) does. |
| |
| |
| var animationEnabled = animatableModel && animatableModel.isAnimationEnabled(); |
| |
| if (animationEnabled) { |
| var postfix = isUpdate ? 'Update' : ''; |
| var duration = animatableModel.getShallow('animationDuration' + postfix); |
| var animationEasing = animatableModel.getShallow('animationEasing' + postfix); |
| var animationDelay = animatableModel.getShallow('animationDelay' + postfix); |
| |
| if (typeof animationDelay === 'function') { |
| animationDelay = animationDelay(dataIndex, animatableModel.getAnimationDelayParams ? animatableModel.getAnimationDelayParams(el, dataIndex) : null); |
| } |
| |
| if (typeof duration === 'function') { |
| duration = duration(dataIndex); |
| } |
| |
| duration > 0 ? el.animateTo(props, duration, animationDelay || 0, animationEasing, cb, !!cb) : (el.stopAnimation(), el.attr(props), cb && cb()); |
| } else { |
| el.stopAnimation(); |
| el.attr(props); |
| cb && cb(); |
| } |
| } |
| /** |
| * Update graphic element properties with or without animation according to the |
| * configuration in series. |
| * |
| * Caution: this method will stop previous animation. |
| * So if do not use this method to one element twice before |
| * animation starts, unless you know what you are doing. |
| * |
| * @param {module:zrender/Element} el |
| * @param {Object} props |
| * @param {module:echarts/model/Model} [animatableModel] |
| * @param {number} [dataIndex] |
| * @param {Function} [cb] |
| * @example |
| * graphic.updateProps(el, { |
| * position: [100, 100] |
| * }, seriesModel, dataIndex, function () { console.log('Animation done!'); }); |
| * // Or |
| * graphic.updateProps(el, { |
| * position: [100, 100] |
| * }, seriesModel, function () { console.log('Animation done!'); }); |
| */ |
| |
| |
| export function updateProps(el, props, animatableModel, dataIndex, cb) { |
| animateOrSetProps(true, el, props, animatableModel, dataIndex, cb); |
| } |
| /** |
| * Init graphic element properties with or without animation according to the |
| * configuration in series. |
| * |
| * Caution: this method will stop previous animation. |
| * So if do not use this method to one element twice before |
| * animation starts, unless you know what you are doing. |
| * |
| * @param {module:zrender/Element} el |
| * @param {Object} props |
| * @param {module:echarts/model/Model} [animatableModel] |
| * @param {number} [dataIndex] |
| * @param {Function} cb |
| */ |
| |
| export function initProps(el, props, animatableModel, dataIndex, cb) { |
| animateOrSetProps(false, el, props, animatableModel, dataIndex, cb); |
| } |
| /** |
| * Get transform matrix of target (param target), |
| * in coordinate of its ancestor (param ancestor) |
| * |
| * @param {module:zrender/mixin/Transformable} target |
| * @param {module:zrender/mixin/Transformable} [ancestor] |
| */ |
| |
| export function getTransform(target, ancestor) { |
| var mat = matrix.identity([]); |
| |
| while (target && target !== ancestor) { |
| matrix.mul(mat, target.getLocalTransform(), mat); |
| target = target.parent; |
| } |
| |
| return mat; |
| } |
| /** |
| * Apply transform to an vertex. |
| * @param {Array.<number>} target [x, y] |
| * @param {Array.<number>|TypedArray.<number>|Object} transform Can be: |
| * + Transform matrix: like [1, 0, 0, 1, 0, 0] |
| * + {position, rotation, scale}, the same as `zrender/Transformable`. |
| * @param {boolean=} invert Whether use invert matrix. |
| * @return {Array.<number>} [x, y] |
| */ |
| |
| export function applyTransform(target, transform, invert) { |
| if (transform && !zrUtil.isArrayLike(transform)) { |
| transform = Transformable.getLocalTransform(transform); |
| } |
| |
| if (invert) { |
| transform = matrix.invert([], transform); |
| } |
| |
| return vector.applyTransform([], target, transform); |
| } |
| /** |
| * @param {string} direction 'left' 'right' 'top' 'bottom' |
| * @param {Array.<number>} transform Transform matrix: like [1, 0, 0, 1, 0, 0] |
| * @param {boolean=} invert Whether use invert matrix. |
| * @return {string} Transformed direction. 'left' 'right' 'top' 'bottom' |
| */ |
| |
| export function transformDirection(direction, transform, invert) { |
| // Pick a base, ensure that transform result will not be (0, 0). |
| var hBase = transform[4] === 0 || transform[5] === 0 || transform[0] === 0 ? 1 : Math.abs(2 * transform[4] / transform[0]); |
| var vBase = transform[4] === 0 || transform[5] === 0 || transform[2] === 0 ? 1 : Math.abs(2 * transform[4] / transform[2]); |
| var vertex = [direction === 'left' ? -hBase : direction === 'right' ? hBase : 0, direction === 'top' ? -vBase : direction === 'bottom' ? vBase : 0]; |
| vertex = applyTransform(vertex, transform, invert); |
| return Math.abs(vertex[0]) > Math.abs(vertex[1]) ? vertex[0] > 0 ? 'right' : 'left' : vertex[1] > 0 ? 'bottom' : 'top'; |
| } |
| /** |
| * Apply group transition animation from g1 to g2. |
| * If no animatableModel, no animation. |
| */ |
| |
| export function groupTransition(g1, g2, animatableModel, cb) { |
| if (!g1 || !g2) { |
| return; |
| } |
| |
| function getElMap(g) { |
| var elMap = {}; |
| g.traverse(function (el) { |
| if (!el.isGroup && el.anid) { |
| elMap[el.anid] = el; |
| } |
| }); |
| return elMap; |
| } |
| |
| function getAnimatableProps(el) { |
| var obj = { |
| position: vector.clone(el.position), |
| rotation: el.rotation |
| }; |
| |
| if (el.shape) { |
| obj.shape = zrUtil.extend({}, el.shape); |
| } |
| |
| return obj; |
| } |
| |
| var elMap1 = getElMap(g1); |
| g2.traverse(function (el) { |
| if (!el.isGroup && el.anid) { |
| var oldEl = elMap1[el.anid]; |
| |
| if (oldEl) { |
| var newProp = getAnimatableProps(el); |
| el.attr(getAnimatableProps(oldEl)); |
| updateProps(el, newProp, animatableModel, el.dataIndex); |
| } // else { |
| // if (el.previousProps) { |
| // graphic.updateProps |
| // } |
| // } |
| |
| } |
| }); |
| } |
| /** |
| * @param {Array.<Array.<number>>} points Like: [[23, 44], [53, 66], ...] |
| * @param {Object} rect {x, y, width, height} |
| * @return {Array.<Array.<number>>} A new clipped points. |
| */ |
| |
| export function clipPointsByRect(points, rect) { |
| // FIXME: this way migth be incorrect when grpahic clipped by a corner. |
| // and when element have border. |
| return zrUtil.map(points, function (point) { |
| var x = point[0]; |
| x = mathMax(x, rect.x); |
| x = mathMin(x, rect.x + rect.width); |
| var y = point[1]; |
| y = mathMax(y, rect.y); |
| y = mathMin(y, rect.y + rect.height); |
| return [x, y]; |
| }); |
| } |
| /** |
| * @param {Object} targetRect {x, y, width, height} |
| * @param {Object} rect {x, y, width, height} |
| * @return {Object} A new clipped rect. If rect size are negative, return undefined. |
| */ |
| |
| export function clipRectByRect(targetRect, rect) { |
| var x = mathMax(targetRect.x, rect.x); |
| var x2 = mathMin(targetRect.x + targetRect.width, rect.x + rect.width); |
| var y = mathMax(targetRect.y, rect.y); |
| var y2 = mathMin(targetRect.y + targetRect.height, rect.y + rect.height); // If the total rect is cliped, nothing, including the border, |
| // should be painted. So return undefined. |
| |
| if (x2 >= x && y2 >= y) { |
| return { |
| x: x, |
| y: y, |
| width: x2 - x, |
| height: y2 - y |
| }; |
| } |
| } |
| /** |
| * @param {string} iconStr Support 'image://' or 'path://' or direct svg path. |
| * @param {Object} [opt] Properties of `module:zrender/Element`, except `style`. |
| * @param {Object} [rect] {x, y, width, height} |
| * @return {module:zrender/Element} Icon path or image element. |
| */ |
| |
| export function createIcon(iconStr, opt, rect) { |
| opt = zrUtil.extend({ |
| rectHover: true |
| }, opt); |
| var style = opt.style = { |
| strokeNoScale: true |
| }; |
| rect = rect || { |
| x: -1, |
| y: -1, |
| width: 2, |
| height: 2 |
| }; |
| |
| if (iconStr) { |
| return iconStr.indexOf('image://') === 0 ? (style.image = iconStr.slice(8), zrUtil.defaults(style, rect), new ZImage(opt)) : makePath(iconStr.replace('path://', ''), opt, rect, 'center'); |
| } |
| } |
| export { Group, ZImage as Image, Text, Circle, Sector, Ring, Polygon, Polyline, Rect, Line, BezierCurve, Arc, IncrementalDisplayable, CompoundPath, LinearGradient, RadialGradient, BoundingRect }; |