/* * 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. */ /** * AUTO-GENERATED FILE. DO NOT MODIFY. */ /* * 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. */ // TODO: move labels out of viewport. import { BoundingRect, updateProps, initProps, isElementRemoved } from '../util/graphic.js'; import { getECData } from '../util/innerStore.js'; import { parsePercent } from '../util/number.js'; import Transformable from 'zrender/lib/core/Transformable.js'; import { updateLabelLinePoints, setLabelLineStyle, getLabelLineStatesModels } from './labelGuideHelper.js'; import { makeInner } from '../util/model.js'; import { retrieve2, each, keys, isFunction, filter, indexOf } from 'zrender/lib/core/util.js'; import { prepareLayoutList, hideOverlap, shiftLayoutOnX, shiftLayoutOnY } from './labelLayoutHelper.js'; import { labelInner, animateLabelValue } from './labelStyle.js'; import { normalizeRadian } from 'zrender/lib/contain/util.js'; function cloneArr(points) { if (points) { var newPoints = []; for (var i = 0; i < points.length; i++) { newPoints.push(points[i].slice()); } return newPoints; } } function prepareLayoutCallbackParams(labelItem, hostEl) { var label = labelItem.label; var labelLine = hostEl && hostEl.getTextGuideLine(); return { dataIndex: labelItem.dataIndex, dataType: labelItem.dataType, seriesIndex: labelItem.seriesModel.seriesIndex, text: labelItem.label.style.text, rect: labelItem.hostRect, labelRect: labelItem.rect, // x: labelAttr.x, // y: labelAttr.y, align: label.style.align, verticalAlign: label.style.verticalAlign, labelLinePoints: cloneArr(labelLine && labelLine.shape.points) }; } var LABEL_OPTION_TO_STYLE_KEYS = ['align', 'verticalAlign', 'width', 'height', 'fontSize']; var dummyTransformable = new Transformable(); var labelLayoutInnerStore = makeInner(); var labelLineAnimationStore = makeInner(); function extendWithKeys(target, source, keys) { for (var i = 0; i < keys.length; i++) { var key = keys[i]; if (source[key] != null) { target[key] = source[key]; } } } var LABEL_LAYOUT_PROPS = ['x', 'y', 'rotation']; var LabelManager = /** @class */ function () { function LabelManager() { this._labelList = []; this._chartViewList = []; } LabelManager.prototype.clearLabels = function () { this._labelList = []; this._chartViewList = []; }; /** * Add label to manager */ LabelManager.prototype._addLabel = function (dataIndex, dataType, seriesModel, label, layoutOption) { var labelStyle = label.style; var hostEl = label.__hostTarget; var textConfig = hostEl.textConfig || {}; // TODO: If label is in other state. var labelTransform = label.getComputedTransform(); var labelRect = label.getBoundingRect().plain(); BoundingRect.applyTransform(labelRect, labelRect, labelTransform); if (labelTransform) { dummyTransformable.setLocalTransform(labelTransform); } else { // Identity transform. dummyTransformable.x = dummyTransformable.y = dummyTransformable.rotation = dummyTransformable.originX = dummyTransformable.originY = 0; dummyTransformable.scaleX = dummyTransformable.scaleY = 1; } dummyTransformable.rotation = normalizeRadian(dummyTransformable.rotation); var host = label.__hostTarget; var hostRect; if (host) { hostRect = host.getBoundingRect().plain(); var transform = host.getComputedTransform(); BoundingRect.applyTransform(hostRect, hostRect, transform); } var labelGuide = hostRect && host.getTextGuideLine(); this._labelList.push({ label: label, labelLine: labelGuide, seriesModel: seriesModel, dataIndex: dataIndex, dataType: dataType, layoutOption: layoutOption, computedLayoutOption: null, rect: labelRect, hostRect: hostRect, // Label with lower priority will be hidden when overlapped // Use rect size as default priority priority: hostRect ? hostRect.width * hostRect.height : 0, // Save default label attributes. // For restore if developers want get back to default value in callback. defaultAttr: { ignore: label.ignore, labelGuideIgnore: labelGuide && labelGuide.ignore, x: dummyTransformable.x, y: dummyTransformable.y, scaleX: dummyTransformable.scaleX, scaleY: dummyTransformable.scaleY, rotation: dummyTransformable.rotation, style: { x: labelStyle.x, y: labelStyle.y, align: labelStyle.align, verticalAlign: labelStyle.verticalAlign, width: labelStyle.width, height: labelStyle.height, fontSize: labelStyle.fontSize }, cursor: label.cursor, attachedPos: textConfig.position, attachedRot: textConfig.rotation } }); }; LabelManager.prototype.addLabelsOfSeries = function (chartView) { var _this = this; this._chartViewList.push(chartView); var seriesModel = chartView.__model; var layoutOption = seriesModel.get('labelLayout'); /** * Ignore layouting if it's not specified anything. */ if (!(isFunction(layoutOption) || keys(layoutOption).length)) { return; } chartView.group.traverse(function (child) { if (child.ignore) { return true; // Stop traverse descendants. } // Only support label being hosted on graphic elements. var textEl = child.getTextContent(); var ecData = getECData(child); // Can only attach the text on the element with dataIndex if (textEl && !textEl.disableLabelLayout) { _this._addLabel(ecData.dataIndex, ecData.dataType, seriesModel, textEl, layoutOption); } }); }; LabelManager.prototype.updateLayoutConfig = function (api) { var width = api.getWidth(); var height = api.getHeight(); function createDragHandler(el, labelLineModel) { return function () { updateLabelLinePoints(el, labelLineModel); }; } for (var i = 0; i < this._labelList.length; i++) { var labelItem = this._labelList[i]; var label = labelItem.label; var hostEl = label.__hostTarget; var defaultLabelAttr = labelItem.defaultAttr; var layoutOption = void 0; // TODO A global layout option? if (isFunction(labelItem.layoutOption)) { layoutOption = labelItem.layoutOption(prepareLayoutCallbackParams(labelItem, hostEl)); } else { layoutOption = labelItem.layoutOption; } layoutOption = layoutOption || {}; labelItem.computedLayoutOption = layoutOption; var degreeToRadian = Math.PI / 180; // TODO hostEl should always exists. // Or label should not have parent because the x, y is all in global space. if (hostEl) { hostEl.setTextConfig({ // Force to set local false. local: false, // Ignore position and rotation config on the host el if x or y is changed. position: layoutOption.x != null || layoutOption.y != null ? null : defaultLabelAttr.attachedPos, // Ignore rotation config on the host el if rotation is changed. rotation: layoutOption.rotate != null ? layoutOption.rotate * degreeToRadian : defaultLabelAttr.attachedRot, offset: [layoutOption.dx || 0, layoutOption.dy || 0] }); } var needsUpdateLabelLine = false; if (layoutOption.x != null) { // TODO width of chart view. label.x = parsePercent(layoutOption.x, width); label.setStyle('x', 0); // Ignore movement in style. TODO: origin. needsUpdateLabelLine = true; } else { label.x = defaultLabelAttr.x; label.setStyle('x', defaultLabelAttr.style.x); } if (layoutOption.y != null) { // TODO height of chart view. label.y = parsePercent(layoutOption.y, height); label.setStyle('y', 0); // Ignore movement in style. needsUpdateLabelLine = true; } else { label.y = defaultLabelAttr.y; label.setStyle('y', defaultLabelAttr.style.y); } if (layoutOption.labelLinePoints) { var guideLine = hostEl.getTextGuideLine(); if (guideLine) { guideLine.setShape({ points: layoutOption.labelLinePoints }); // Not update needsUpdateLabelLine = false; } } var labelLayoutStore = labelLayoutInnerStore(label); labelLayoutStore.needsUpdateLabelLine = needsUpdateLabelLine; label.rotation = layoutOption.rotate != null ? layoutOption.rotate * degreeToRadian : defaultLabelAttr.rotation; label.scaleX = defaultLabelAttr.scaleX; label.scaleY = defaultLabelAttr.scaleY; for (var k = 0; k < LABEL_OPTION_TO_STYLE_KEYS.length; k++) { var key = LABEL_OPTION_TO_STYLE_KEYS[k]; label.setStyle(key, layoutOption[key] != null ? layoutOption[key] : defaultLabelAttr.style[key]); } if (layoutOption.draggable) { label.draggable = true; label.cursor = 'move'; if (hostEl) { var hostModel = labelItem.seriesModel; if (labelItem.dataIndex != null) { var data = labelItem.seriesModel.getData(labelItem.dataType); hostModel = data.getItemModel(labelItem.dataIndex); } label.on('drag', createDragHandler(hostEl, hostModel.getModel('labelLine'))); } } else { // TODO Other drag functions? label.off('drag'); label.cursor = defaultLabelAttr.cursor; } } }; LabelManager.prototype.layout = function (api) { var width = api.getWidth(); var height = api.getHeight(); var labelList = prepareLayoutList(this._labelList); var labelsNeedsAdjustOnX = filter(labelList, function (item) { return item.layoutOption.moveOverlap === 'shiftX'; }); var labelsNeedsAdjustOnY = filter(labelList, function (item) { return item.layoutOption.moveOverlap === 'shiftY'; }); shiftLayoutOnX(labelsNeedsAdjustOnX, 0, width); shiftLayoutOnY(labelsNeedsAdjustOnY, 0, height); var labelsNeedsHideOverlap = filter(labelList, function (item) { return item.layoutOption.hideOverlap; }); hideOverlap(labelsNeedsHideOverlap); }; /** * Process all labels. Not only labels with layoutOption. */ LabelManager.prototype.processLabelsOverall = function () { var _this = this; each(this._chartViewList, function (chartView) { var seriesModel = chartView.__model; var ignoreLabelLineUpdate = chartView.ignoreLabelLineUpdate; var animationEnabled = seriesModel.isAnimationEnabled(); chartView.group.traverse(function (child) { if (child.ignore && !child.forceLabelAnimation) { return true; // Stop traverse descendants. } var needsUpdateLabelLine = !ignoreLabelLineUpdate; var label = child.getTextContent(); if (!needsUpdateLabelLine && label) { needsUpdateLabelLine = labelLayoutInnerStore(label).needsUpdateLabelLine; } if (needsUpdateLabelLine) { _this._updateLabelLine(child, seriesModel); } if (animationEnabled) { _this._animateLabels(child, seriesModel); } }); }); }; LabelManager.prototype._updateLabelLine = function (el, seriesModel) { // Only support label being hosted on graphic elements. var textEl = el.getTextContent(); // Update label line style. var ecData = getECData(el); var dataIndex = ecData.dataIndex; // Only support labelLine on the labels represent data. if (textEl && dataIndex != null) { var data = seriesModel.getData(ecData.dataType); var itemModel = data.getItemModel(dataIndex); var defaultStyle = {}; var visualStyle = data.getItemVisual(dataIndex, 'style'); if (visualStyle) { var visualType = data.getVisual('drawType'); // Default to be same with main color defaultStyle.stroke = visualStyle[visualType]; } var labelLineModel = itemModel.getModel('labelLine'); setLabelLineStyle(el, getLabelLineStatesModels(itemModel), defaultStyle); updateLabelLinePoints(el, labelLineModel); } }; LabelManager.prototype._animateLabels = function (el, seriesModel) { var textEl = el.getTextContent(); var guideLine = el.getTextGuideLine(); // Animate if (textEl // `forceLabelAnimation` has the highest priority && (el.forceLabelAnimation || !textEl.ignore && !textEl.invisible && !el.disableLabelAnimation && !isElementRemoved(el))) { var layoutStore = labelLayoutInnerStore(textEl); var oldLayout = layoutStore.oldLayout; var ecData = getECData(el); var dataIndex = ecData.dataIndex; var newProps = { x: textEl.x, y: textEl.y, rotation: textEl.rotation }; var data = seriesModel.getData(ecData.dataType); if (!oldLayout) { textEl.attr(newProps); // Disable fade in animation if value animation is enabled. if (!labelInner(textEl).valueAnimation) { var oldOpacity = retrieve2(textEl.style.opacity, 1); // Fade in animation textEl.style.opacity = 0; initProps(textEl, { style: { opacity: oldOpacity } }, seriesModel, dataIndex); } } else { textEl.attr(oldLayout); // Make sure the animation from is in the right status. var prevStates = el.prevStates; if (prevStates) { if (indexOf(prevStates, 'select') >= 0) { textEl.attr(layoutStore.oldLayoutSelect); } if (indexOf(prevStates, 'emphasis') >= 0) { textEl.attr(layoutStore.oldLayoutEmphasis); } } updateProps(textEl, newProps, seriesModel, dataIndex); } layoutStore.oldLayout = newProps; if (textEl.states.select) { var layoutSelect = layoutStore.oldLayoutSelect = {}; extendWithKeys(layoutSelect, newProps, LABEL_LAYOUT_PROPS); extendWithKeys(layoutSelect, textEl.states.select, LABEL_LAYOUT_PROPS); } if (textEl.states.emphasis) { var layoutEmphasis = layoutStore.oldLayoutEmphasis = {}; extendWithKeys(layoutEmphasis, newProps, LABEL_LAYOUT_PROPS); extendWithKeys(layoutEmphasis, textEl.states.emphasis, LABEL_LAYOUT_PROPS); } animateLabelValue(textEl, dataIndex, data, seriesModel, seriesModel); } if (guideLine && !guideLine.ignore && !guideLine.invisible) { var layoutStore = labelLineAnimationStore(guideLine); var oldLayout = layoutStore.oldLayout; var newLayout = { points: guideLine.shape.points }; if (!oldLayout) { guideLine.setShape(newLayout); guideLine.style.strokePercent = 0; initProps(guideLine, { style: { strokePercent: 1 } }, seriesModel); } else { guideLine.attr({ shape: oldLayout }); updateProps(guideLine, { shape: newLayout }, seriesModel); } layoutStore.oldLayout = newLayout; } }; return LabelManager; }(); export default LabelManager;