/* * 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 {__DEV__} from '../../config'; import * as echarts from '../../echarts'; import * as zrUtil from 'zrender/src/core/util'; import * as graphic from '../../util/graphic'; import {setLabel} from './helper'; import Model from '../../model/Model'; import barItemStyle from './barItemStyle'; import Path from 'zrender/src/graphic/Path'; var BAR_BORDER_WIDTH_QUERY = ['itemStyle', 'barBorderWidth']; // FIXME // Just for compatible with ec2. zrUtil.extend(Model.prototype, barItemStyle); export default echarts.extendChartView({ type: 'bar', render: function (seriesModel, ecModel, api) { this._updateDrawMode(seriesModel); var coordinateSystemType = seriesModel.get('coordinateSystem'); if (coordinateSystemType === 'cartesian2d' || coordinateSystemType === 'polar' ) { this._isLargeDraw ? this._renderLarge(seriesModel, ecModel, api) : this._renderNormal(seriesModel, ecModel, api); } else if (__DEV__) { console.warn('Only cartesian2d and polar supported for bar.'); } return this.group; }, incrementalPrepareRender: function (seriesModel, ecModel, api) { this._clear(); this._updateDrawMode(seriesModel); }, incrementalRender: function (params, seriesModel, ecModel, api) { // Do not support progressive in normal mode. this._incrementalRenderLarge(params, seriesModel); }, _updateDrawMode: function (seriesModel) { var isLargeDraw = seriesModel.pipelineContext.large; if (this._isLargeDraw == null || isLargeDraw ^ this._isLargeDraw) { this._isLargeDraw = isLargeDraw; this._clear(); } }, _renderNormal: function (seriesModel, ecModel, api) { var group = this.group; var data = seriesModel.getData(); var oldData = this._data; var coord = seriesModel.coordinateSystem; var baseAxis = coord.getBaseAxis(); var isHorizontalOrRadial; if (coord.type === 'cartesian2d') { isHorizontalOrRadial = baseAxis.isHorizontal(); } else if (coord.type === 'polar') { isHorizontalOrRadial = baseAxis.dim === 'angle'; } var animationModel = seriesModel.isAnimationEnabled() ? seriesModel : null; data.diff(oldData) .add(function (dataIndex) { if (!data.hasValue(dataIndex)) { return; } var itemModel = data.getItemModel(dataIndex); var layout = getLayout[coord.type](data, dataIndex, itemModel); var el = elementCreator[coord.type]( data, dataIndex, itemModel, layout, isHorizontalOrRadial, animationModel ); data.setItemGraphicEl(dataIndex, el); group.add(el); updateStyle( el, data, dataIndex, itemModel, layout, seriesModel, isHorizontalOrRadial, coord.type === 'polar' ); }) .update(function (newIndex, oldIndex) { var el = oldData.getItemGraphicEl(oldIndex); if (!data.hasValue(newIndex)) { group.remove(el); return; } var itemModel = data.getItemModel(newIndex); var layout = getLayout[coord.type](data, newIndex, itemModel); if (el) { graphic.updateProps(el, {shape: layout}, animationModel, newIndex); } else { el = elementCreator[coord.type]( data, newIndex, itemModel, layout, isHorizontalOrRadial, animationModel, true ); } data.setItemGraphicEl(newIndex, el); // Add back group.add(el); updateStyle( el, data, newIndex, itemModel, layout, seriesModel, isHorizontalOrRadial, coord.type === 'polar' ); }) .remove(function (dataIndex) { var el = oldData.getItemGraphicEl(dataIndex); if (coord.type === 'cartesian2d') { el && removeRect(dataIndex, animationModel, el); } else { el && removeSector(dataIndex, animationModel, el); } }) .execute(); this._data = data; }, _renderLarge: function (seriesModel, ecModel, api) { this._clear(); createLarge(seriesModel, this.group); }, _incrementalRenderLarge: function (params, seriesModel) { createLarge(seriesModel, this.group, true); }, dispose: zrUtil.noop, remove: function (ecModel) { this._clear(ecModel); }, _clear: function (ecModel) { var group = this.group; var data = this._data; if (ecModel && ecModel.get('animation') && data && !this._isLargeDraw) { data.eachItemGraphicEl(function (el) { if (el.type === 'sector') { removeSector(el.dataIndex, ecModel, el); } else { removeRect(el.dataIndex, ecModel, el); } }); } else { group.removeAll(); } this._data = null; } }); var elementCreator = { cartesian2d: function ( data, dataIndex, itemModel, layout, isHorizontal, animationModel, isUpdate ) { var rect = new graphic.Rect({shape: zrUtil.extend({}, layout)}); // Animation if (animationModel) { var rectShape = rect.shape; var animateProperty = isHorizontal ? 'height' : 'width'; var animateTarget = {}; rectShape[animateProperty] = 0; animateTarget[animateProperty] = layout[animateProperty]; graphic[isUpdate ? 'updateProps' : 'initProps'](rect, { shape: animateTarget }, animationModel, dataIndex); } return rect; }, polar: function ( data, dataIndex, itemModel, layout, isRadial, animationModel, isUpdate ) { // Keep the same logic with bar in catesion: use end value to control // direction. Notice that if clockwise is true (by default), the sector // will always draw clockwisely, no matter whether endAngle is greater // or less than startAngle. var clockwise = layout.startAngle < layout.endAngle; var sector = new graphic.Sector({ shape: zrUtil.defaults({clockwise: clockwise}, layout) }); // Animation if (animationModel) { var sectorShape = sector.shape; var animateProperty = isRadial ? 'r' : 'endAngle'; var animateTarget = {}; sectorShape[animateProperty] = isRadial ? 0 : layout.startAngle; animateTarget[animateProperty] = layout[animateProperty]; graphic[isUpdate ? 'updateProps' : 'initProps'](sector, { shape: animateTarget }, animationModel, dataIndex); } return sector; } }; function removeRect(dataIndex, animationModel, el) { // Not show text when animating el.style.text = null; graphic.updateProps(el, { shape: { width: 0 } }, animationModel, dataIndex, function () { el.parent && el.parent.remove(el); }); } function removeSector(dataIndex, animationModel, el) { // Not show text when animating el.style.text = null; graphic.updateProps(el, { shape: { r: el.shape.r0 } }, animationModel, dataIndex, function () { el.parent && el.parent.remove(el); }); } var getLayout = { cartesian2d: function (data, dataIndex, itemModel) { var layout = data.getItemLayout(dataIndex); var fixedLineWidth = getLineWidth(itemModel, layout); // fix layout with lineWidth var signX = layout.width > 0 ? 1 : -1; var signY = layout.height > 0 ? 1 : -1; return { x: layout.x + signX * fixedLineWidth / 2, y: layout.y + signY * fixedLineWidth / 2, width: layout.width - signX * fixedLineWidth, height: layout.height - signY * fixedLineWidth }; }, polar: function (data, dataIndex, itemModel) { var layout = data.getItemLayout(dataIndex); return { cx: layout.cx, cy: layout.cy, r0: layout.r0, r: layout.r, startAngle: layout.startAngle, endAngle: layout.endAngle }; } }; function updateStyle( el, data, dataIndex, itemModel, layout, seriesModel, isHorizontal, isPolar ) { var color = data.getItemVisual(dataIndex, 'color'); var opacity = data.getItemVisual(dataIndex, 'opacity'); var itemStyleModel = itemModel.getModel('itemStyle'); var hoverStyle = itemModel.getModel('emphasis.itemStyle').getBarItemStyle(); if (!isPolar) { el.setShape('r', itemStyleModel.get('barBorderRadius') || 0); } el.useStyle(zrUtil.defaults( { fill: color, opacity: opacity }, itemStyleModel.getBarItemStyle() )); var cursorStyle = itemModel.getShallow('cursor'); cursorStyle && el.attr('cursor', cursorStyle); var labelPositionOutside = isHorizontal ? (layout.height > 0 ? 'bottom' : 'top') : (layout.width > 0 ? 'left' : 'right'); if (!isPolar) { setLabel( el.style, hoverStyle, itemModel, color, seriesModel, dataIndex, labelPositionOutside ); } graphic.setHoverStyle(el, hoverStyle); } // In case width or height are too small. function getLineWidth(itemModel, rawLayout) { var lineWidth = itemModel.get(BAR_BORDER_WIDTH_QUERY) || 0; return Math.min(lineWidth, Math.abs(rawLayout.width), Math.abs(rawLayout.height)); } var LargePath = Path.extend({ type: 'largeBar', shape: {points: []}, buildPath: function (ctx, shape) { // Drawing lines is more efficient than drawing // a whole line or drawing rects. var points = shape.points; var startPoint = this.__startPoint; var valueIdx = this.__valueIdx; for (var i = 0; i < points.length; i += 2) { startPoint[this.__valueIdx] = points[i + valueIdx]; ctx.moveTo(startPoint[0], startPoint[1]); ctx.lineTo(points[i], points[i + 1]); } } }); function createLarge(seriesModel, group, incremental) { // TODO support polar var data = seriesModel.getData(); var startPoint = []; var valueIdx = data.getLayout('valueAxisHorizontal') ? 1 : 0; startPoint[1 - valueIdx] = data.getLayout('valueAxisStart'); var el = new LargePath({ shape: {points: data.getLayout('largePoints')}, incremental: !!incremental, __startPoint: startPoint, __valueIdx: valueIdx }); group.add(el); setLargeStyle(el, seriesModel, data); } function setLargeStyle(el, seriesModel, data) { var borderColor = data.getVisual('borderColor') || data.getVisual('color'); var itemStyle = seriesModel.getModel('itemStyle').getItemStyle(['color', 'borderColor']); el.useStyle(itemStyle); el.style.fill = null; el.style.stroke = borderColor; el.style.lineWidth = data.getLayout('barWidth'); }