diff --git a/index.js b/index.js index 5bde6a8a86e488458d221054394e1ee44c10cd23..88f597fc0fb58aa1f53a8771f4da8a182e4e8f06 100644 --- a/index.js +++ b/index.js @@ -22,6 +22,7 @@ require('./lib/chart/candlestick'); require('./lib/chart/effectScatter'); require('./lib/chart/lines'); require('./lib/chart/heatmap'); +require('./lib/chart/pictorialBar'); require('./lib/component/graphic'); require('./lib/component/grid'); diff --git a/src/chart/bar/BarSeries.js b/src/chart/bar/BarSeries.js index 41a4e508151fd4457829a32ae3959a0692a5f3cb..6859422579dee2605f2ef1e3be916fd68a7ad3b6 100644 --- a/src/chart/bar/BarSeries.js +++ b/src/chart/bar/BarSeries.js @@ -1,75 +1,11 @@ define(function(require) { - 'use strict'; - - var SeriesModel = require('../../model/Series'); - var createListFromArray = require('../helper/createListFromArray'); - - return SeriesModel.extend({ + return require('./BaseBarSeries').extend({ type: 'series.bar', dependencies: ['grid', 'polar'], - getInitialData: function (option, ecModel) { - if (__DEV__) { - var coordSys = option.coordinateSystem; - if (coordSys !== 'cartesian2d') { - throw new Error('Bar only support cartesian2d coordinateSystem'); - } - } - return createListFromArray(option.data, this, ecModel); - }, - - getMarkerPosition: function (value) { - var coordSys = this.coordinateSystem; - if (coordSys) { - // PENDING if clamp ? - var pt = coordSys.dataToPoint(value, true); - var data = this.getData(); - var offset = data.getLayout('offset'); - var size = data.getLayout('size'); - var offsetIndex = coordSys.getBaseAxis().isHorizontal() ? 0 : 1; - pt[offsetIndex] += offset + size / 2; - return pt; - } - return [NaN, NaN]; - }, - - brushSelector: 'rect', - - defaultOption: { - zlevel: 0, // 一级层叠 - z: 2, // 二级层叠 - coordinateSystem: 'cartesian2d', - legendHoverLink: true, - // stack: null - - // Cartesian coordinate system - // xAxisIndex: 0, - // yAxisIndex: 0, - - // 最小高度改为0 - barMinHeight: 0, - - // barMaxWidth: null, - // 默认自适应 - // barWidth: null, - // 柱间距离,默认为柱形宽度的30%,可设固定值 - // barGap: '30%', - // 类目间柱形距离,默认为类目间距的20%,可设固定值 - // barCategoryGap: '20%', - // label: { - // normal: { - // show: false - // } - // }, - itemStyle: { - normal: { - // color: '各异' - }, - emphasis: {} - } - } + brushSelector: 'rect' }); }); \ No newline at end of file diff --git a/src/chart/bar/BarView.js b/src/chart/bar/BarView.js index 08feba38fdd055da6670ce93751847ab8b2167bd..220653c375800f648b475ee75b51f4dcfcc4abe7 100644 --- a/src/chart/bar/BarView.js +++ b/src/chart/bar/BarView.js @@ -4,21 +4,13 @@ define(function (require) { var zrUtil = require('zrender/core/util'); var graphic = require('../../util/graphic'); + var helper = require('./helper'); + // FIXME + // Just for compatible with ec2. zrUtil.extend(require('../../model/Model').prototype, require('./barItemStyle')); - function fixLayoutWithLineWidth(layout, lineWidth) { - var signX = layout.width > 0 ? 1 : -1; - var signY = layout.height > 0 ? 1 : -1; - // In case width or height are too small. - lineWidth = Math.min(lineWidth, Math.abs(layout.width), Math.abs(layout.height)); - layout.x += signX * lineWidth / 2; - layout.y += signY * lineWidth / 2; - layout.width -= signX * lineWidth; - layout.height -= signY * lineWidth; - } - - return require('../../echarts').extendChartView({ + var BarView = require('../../echarts').extendChartView({ type: 'bar', @@ -42,170 +34,62 @@ define(function (require) { var cartesian = seriesModel.coordinateSystem; var baseAxis = cartesian.getBaseAxis(); var isHorizontal = baseAxis.isHorizontal(); + var animationModel = seriesModel.isAnimationEnabled() ? seriesModel : null; - var enableAnimation = seriesModel.get('animation'); - - var barBorderWidthQuery = ['itemStyle', 'normal', 'barBorderWidth']; - - function createRect(dataIndex, isUpdate) { - var layout = data.getItemLayout(dataIndex); - var lineWidth = data.getItemModel(dataIndex).get(barBorderWidthQuery) || 0; - fixLayoutWithLineWidth(layout, lineWidth); - - var rect = new graphic.Rect({ - shape: zrUtil.extend({}, layout) - }); - // Animation - if (enableAnimation) { - 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 - }, seriesModel, dataIndex); - } - return rect; - } data.diff(oldData) .add(function (dataIndex) { - // 空数据 if (!data.hasValue(dataIndex)) { return; } - var rect = createRect(dataIndex); - - data.setItemGraphicEl(dataIndex, rect); - - group.add(rect); + var itemModel = data.getItemModel(dataIndex); + var layout = getRectItemLayout(data, dataIndex, itemModel); + var el = createRect(data, dataIndex, itemModel, layout, isHorizontal, animationModel); + data.setItemGraphicEl(dataIndex, el); + group.add(el); + updateStyle(el, data, dataIndex, itemModel, layout, seriesModel, isHorizontal); }) .update(function (newIndex, oldIndex) { - var rect = oldData.getItemGraphicEl(oldIndex); - // 空数据 + var el = oldData.getItemGraphicEl(oldIndex); + if (!data.hasValue(newIndex)) { - group.remove(rect); + group.remove(el); return; } - if (!rect) { - rect = createRect(newIndex, true); - } - - var layout = data.getItemLayout(newIndex); - var lineWidth = data.getItemModel(newIndex).get(barBorderWidthQuery) || 0; - fixLayoutWithLineWidth(layout, lineWidth); - graphic.updateProps(rect, { - shape: layout - }, seriesModel, newIndex); + var itemModel = data.getItemModel(newIndex); + var layout = getRectItemLayout(data, newIndex, itemModel); - data.setItemGraphicEl(newIndex, rect); + if (el) { + graphic.updateProps(el, {shape: layout}, animationModel, newIndex); + } + else { + el = createRect(data, newIndex, itemModel, layout, isHorizontal, animationModel, true); + } + data.setItemGraphicEl(newIndex, el); // Add back - group.add(rect); + group.add(el); + + updateStyle(el, data, newIndex, itemModel, layout, seriesModel, isHorizontal); }) - .remove(function (idx) { - var rect = oldData.getItemGraphicEl(idx); - if (rect) { - // Not show text when animating - rect.style.text = ''; - graphic.updateProps(rect, { - shape: { - width: 0 - } - }, seriesModel, idx, function () { - group.remove(rect); - }); - } + .remove(function (dataIndex) { + var el = oldData.getItemGraphicEl(dataIndex); + el && removeRect(dataIndex, animationModel, el); }) .execute(); - this._updateStyle(seriesModel, data, isHorizontal); - this._data = data; }, - _updateStyle: function (seriesModel, data, isHorizontal) { - function setLabel(style, model, color, labelText, labelPositionOutside) { - graphic.setText(style, model, color); - style.text = labelText; - if (style.textPosition === 'outside') { - style.textPosition = labelPositionOutside; - } - } - - data.eachItemGraphicEl(function (rect, idx) { - var itemModel = data.getItemModel(idx); - var color = data.getItemVisual(idx, 'color'); - var opacity = data.getItemVisual(idx, 'opacity'); - var layout = data.getItemLayout(idx); - var itemStyleModel = itemModel.getModel('itemStyle.normal'); - - var hoverStyle = itemModel.getModel('itemStyle.emphasis').getBarItemStyle(); - - rect.setShape('r', itemStyleModel.get('barBorderRadius') || 0); - - rect.useStyle(zrUtil.defaults( - { - fill: color, - opacity: opacity - }, - itemStyleModel.getBarItemStyle() - )); - - var labelPositionOutside = isHorizontal - ? (layout.height > 0 ? 'bottom' : 'top') - : (layout.width > 0 ? 'left' : 'right'); - - var labelModel = itemModel.getModel('label.normal'); - var hoverLabelModel = itemModel.getModel('label.emphasis'); - var rectStyle = rect.style; - if (labelModel.get('show')) { - setLabel( - rectStyle, labelModel, color, - zrUtil.retrieve( - seriesModel.getFormattedLabel(idx, 'normal'), - seriesModel.getRawValue(idx) - ), - labelPositionOutside - ); - } - else { - rectStyle.text = ''; - } - if (hoverLabelModel.get('show')) { - setLabel( - hoverStyle, hoverLabelModel, color, - zrUtil.retrieve( - seriesModel.getFormattedLabel(idx, 'emphasis'), - seriesModel.getRawValue(idx) - ), - labelPositionOutside - ); - } - else { - hoverStyle.text = ''; - } - graphic.setHoverStyle(rect, hoverStyle); - }); - }, - remove: function (ecModel, api) { var group = this.group; + var data = this._data; if (ecModel.get('animation')) { - if (this._data) { - this._data.eachItemGraphicEl(function (el) { - // Not show text when animating - el.style.text = ''; - graphic.updateProps(el, { - shape: { - width: 0 - } - }, ecModel, el.dataIndex, function () { - group.remove(el); - }); + if (data) { + data.eachItemGraphicEl(function (el) { + removeRect(el.dataIndex, ecModel, el); }); } } @@ -214,4 +98,79 @@ define(function (require) { } } }); + + function createRect(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; + } + + function removeRect(dataIndex, animationModel, el) { + // Not show text when animating + el.style.text = ''; + graphic.updateProps(el, { + shape: { + width: 0 + } + }, animationModel, dataIndex, function () { + el.parent && el.parent.remove(el); + }); + } + + function getRectItemLayout(data, dataIndex, itemModel) { + var layout = data.getItemLayout(dataIndex); + var fixedLineWidth = helper.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 + }; + } + + function updateStyle(el, data, dataIndex, itemModel, layout, seriesModel, isHorizontal) { + var color = data.getItemVisual(dataIndex, 'color'); + var opacity = data.getItemVisual(dataIndex, 'opacity'); + var itemStyleModel = itemModel.getModel('itemStyle.normal'); + var hoverStyle = itemModel.getModel('itemStyle.emphasis').getBarItemStyle(); + + el.setShape('r', itemStyleModel.get('barBorderRadius') || 0); + + el.useStyle(zrUtil.defaults( + { + fill: color, + opacity: opacity + }, + itemStyleModel.getBarItemStyle() + )); + + var labelPositionOutside = isHorizontal + ? (layout.height > 0 ? 'bottom' : 'top') + : (layout.width > 0 ? 'left' : 'right'); + + helper.setLabel( + el.style, hoverStyle, itemModel, color, + seriesModel, dataIndex, labelPositionOutside + ); + + graphic.setHoverStyle(el, hoverStyle); + } + + return BarView; }); \ No newline at end of file diff --git a/src/chart/bar/BaseBarSeries.js b/src/chart/bar/BaseBarSeries.js new file mode 100644 index 0000000000000000000000000000000000000000..4965fa66e5e068dffa72750881f477f50d35802a --- /dev/null +++ b/src/chart/bar/BaseBarSeries.js @@ -0,0 +1,71 @@ +define(function(require) { + + 'use strict'; + + var SeriesModel = require('../../model/Series'); + var createListFromArray = require('../helper/createListFromArray'); + + return SeriesModel.extend({ + + type: 'series.__base_bar__', + + getInitialData: function (option, ecModel) { + if (__DEV__) { + var coordSys = option.coordinateSystem; + if (coordSys !== 'cartesian2d') { + throw new Error('Bar only support cartesian2d coordinateSystem'); + } + } + return createListFromArray(option.data, this, ecModel); + }, + + getMarkerPosition: function (value) { + var coordSys = this.coordinateSystem; + if (coordSys) { + // PENDING if clamp ? + var pt = coordSys.dataToPoint(value, true); + var data = this.getData(); + var offset = data.getLayout('offset'); + var size = data.getLayout('size'); + var offsetIndex = coordSys.getBaseAxis().isHorizontal() ? 0 : 1; + pt[offsetIndex] += offset + size / 2; + return pt; + } + return [NaN, NaN]; + }, + + defaultOption: { + zlevel: 0, // 一级层叠 + z: 2, // 二级层叠 + coordinateSystem: 'cartesian2d', + legendHoverLink: true, + // stack: null + + // Cartesian coordinate system + // xAxisIndex: 0, + // yAxisIndex: 0, + + // 最小高度改为0 + barMinHeight: 0, + + // barMaxWidth: null, + // 默认自适应 + // barWidth: null, + // 柱间距离,默认为柱形宽度的30%,可设固定值 + // barGap: '30%', + // 类目间柱形距离,默认为类目间距的20%,可设固定值 + // barCategoryGap: '20%', + // label: { + // normal: { + // show: false + // } + // }, + itemStyle: { + normal: { + // color: '各异' + }, + emphasis: {} + } + } + }); +}); \ No newline at end of file diff --git a/src/chart/bar/PictorialBarSeries.js b/src/chart/bar/PictorialBarSeries.js new file mode 100644 index 0000000000000000000000000000000000000000..d6f153a1cadf149bd8f386245c8ce61da7f50a2e --- /dev/null +++ b/src/chart/bar/PictorialBarSeries.js @@ -0,0 +1,34 @@ +define(function(require) { + + return require('./BaseBarSeries').extend({ + + type: 'series.pictorialBar', + + dependencies: ['grid'], + + defaultOption: { + symbol: 'circle', // Customized bar shape + symbolSize: null, // Can be ['100%', '100%'], null means auto. + symbolRotate: null, + + symbolPosition: null, // 'start' or 'end' or 'center', null means auto. + symbolOffset: null, + symbolMargin: null, // start margin and end margin. Can be a number or a percent string. + // Auto margin by defualt. + symbolRepeat: false, // Can be a number, specifies repeat times. + // Or false/null/undefined, means no repeat. + symbolClip: false, + symbolBoundingData: null, + + // Disable progressive + progressive: 0, + hoverAnimation: true + + // cases: + // repeat: bg:Y, clip:Y, ani:cliprect, size:symbolSize and calc by gridSize. + // fixed size: bg:Y, clip:Y, ani:cliprect, size:symbolSize. + // stretch: bg:N, clip:Y, ani:position(by layout), size:byRect + // img: bg:N, clip:Y, ani:position(include clip), size:byRect or bySymbolSize. + } + }); +}); \ No newline at end of file diff --git a/src/chart/bar/PictorialBarView.js b/src/chart/bar/PictorialBarView.js new file mode 100644 index 0000000000000000000000000000000000000000..4987706e0b3f79e29114129bb7938f3bc5d4b211 --- /dev/null +++ b/src/chart/bar/PictorialBarView.js @@ -0,0 +1,561 @@ +define(function (require) { + + var zrUtil = require('zrender/core/util'); + var graphic = require('../../util/graphic'); + var symbolUtil = require('../../util/symbol'); + var numberUtil = require('../../util/number'); + var helper = require('./helper'); + + var parsePercent = numberUtil.parsePercent; + + // index: +isHorizontal + var LAYOUT_ATTRS = [ + {xy: 'x', wh: 'width', index: 0, posDesc: ['left', 'right']}, + {xy: 'y', wh: 'height', index: 1, posDesc: ['top', 'bottom']} + ]; + + var BarView = require('../../echarts').extendChartView({ + + type: 'pictorialBar', + + render: function (seriesModel, ecModel, api) { + var group = this.group; + var data = seriesModel.getData(); + var oldData = this._data; + + var cartesian = seriesModel.coordinateSystem; + var baseAxis = cartesian.getBaseAxis(); + var isHorizontal = baseAxis.isHorizontal(); + var coordSysRect = cartesian.grid.getRect(); + + var opt = { + seriesModel: seriesModel, + coordSys: cartesian, + coordSysExtent: [ + [coordSysRect.x, coordSysRect.x + coordSysRect.width], + [coordSysRect.y, coordSysRect.y + coordSysRect.height] + ], + animationModel: seriesModel.isAnimationEnabled() ? seriesModel : null, + isHorizontal: isHorizontal, + valueDim: LAYOUT_ATTRS[+isHorizontal], + categoryDim: LAYOUT_ATTRS[1 - isHorizontal], + hoverAnimation: seriesModel.get('hoverAnimation') + }; + + data.diff(oldData) + .add(function (dataIndex) { + if (!data.hasValue(dataIndex)) { + return; + } + + var itemModel = data.getItemModel(dataIndex); + var symbolMeta = getSymbolMeta(data, dataIndex, itemModel, opt); + + var bar = createBar(data, dataIndex, itemModel, opt, symbolMeta); + bar.__pictorialShapeStr = getShapeStr(data, dataIndex, symbolMeta); + + data.setItemGraphicEl(dataIndex, bar); + group.add(bar); + + updateStyle(bar, dataIndex, itemModel, opt, symbolMeta); + }) + .update(function (newIndex, oldIndex) { + var bar = oldData.getItemGraphicEl(oldIndex); + + if (!data.hasValue(newIndex)) { + group.remove(bar); + return; + } + + var itemModel = data.getItemModel(newIndex); + var symbolMeta = getSymbolMeta(data, newIndex, itemModel, opt); + + var pictorialShapeStr = getShapeStr(data, newIndex, symbolMeta); + if (pictorialShapeStr !== bar.__pictorialShapeStr) { + group.remove(bar); + bar = null; + } + + if (bar) { + updateBar(data, newIndex, itemModel, opt, symbolMeta, bar); + } + else { + bar = createBar(data, newIndex, itemModel, opt, symbolMeta, true); + } + + data.setItemGraphicEl(newIndex, bar); + // Add back + group.add(bar); + + updateStyle(bar, newIndex, itemModel, opt, symbolMeta); + }) + .remove(function (dataIndex) { + var bar = oldData.getItemGraphicEl(dataIndex); + bar && removeBar(dataIndex, opt, bar); + }) + .execute(); + + this._data = data; + + return this.group; + }, + + dispose: zrUtil.noop, + + remove: function (ecModel, api) { + var group = this.group; + var data = this._data; + if (ecModel.get('animation')) { + if (data) { + var opt = {animationModel: ecModel}; + data.eachItemGraphicEl(function (bar) { + removeBar(bar.dataIndex, opt, bar); + }); + } + } + else { + group.removeAll(); + } + } + }); + + + // Set or calculate default value about symbol, and calculate layout info. + function getSymbolMeta(data, dataIndex, itemModel, opt) { + var layout = data.getItemLayout(dataIndex); + var symbolRepeat = itemModel.get('symbolRepeat'); + var symbolClip = itemModel.get('symbolClip'); + var lineWidth = helper.getLineWidth(itemModel, layout); + var symbolPosition = itemModel.get('symbolPosition') + || ((symbolRepeat || symbolClip) ? 'start' : 'center'); + var symbolRotate = itemModel.get('symbolRotate'); + + var symbolMeta = { + layout: layout, + lineWidth: lineWidth, + symbolType: data.getItemVisual(dataIndex, 'symbol') || 'circle', + color: data.getItemVisual(dataIndex, 'color'), + symbolClip: symbolClip, + symbolRepeat: symbolRepeat, + rotation: (symbolRotate || 0) * Math.PI / 180 || 0 + }; + + prepareBarLength(itemModel, symbolRepeat, layout, opt, symbolMeta); + prepareSymbolSize( + data, dataIndex, layout, opt, symbolRepeat, symbolClip, + symbolMeta.barFullLength, symbolMeta + ); + + var symbolSize = symbolMeta.symbolSize; + var symbolOffset = itemModel.get('symbolOffset'); + if (zrUtil.isArray(symbolOffset)) { + symbolOffset = [ + parsePercent(symbolOffset[0], symbolSize[0]), + parsePercent(symbolOffset[1], symbolSize[1]) + ]; + } + + prepareLayoutInfo( + itemModel, symbolSize, layout, symbolRepeat, symbolClip, symbolOffset, + symbolPosition, lineWidth, symbolMeta.barFullLength, symbolMeta.repeatLength, + opt, symbolMeta + ); + + return symbolMeta; + } + + // bar length can be negative. + function prepareBarLength(itemModel, symbolRepeat, layout, opt, output) { + var valueDim = opt.valueDim; + var symbolBoundingData = itemModel.get('symbolBoundingData'); + var valueAxis = opt.coordSys.getOtherAxis(opt.coordSys.getBaseAxis()); + var zeroPx = valueAxis.toGlobalCoord(valueAxis.dataToCoord(0)); + + var barFullLength = output.barFullLength = symbolBoundingData != null + ? valueAxis.toGlobalCoord(valueAxis.dataToCoord(symbolBoundingData)) - zeroPx + : symbolRepeat + ? opt.coordSysExtent[valueDim.index][1 - +(layout[valueDim.wh] <= 0)] - zeroPx + : layout[valueDim.wh]; + + output.repeatLength = symbolBoundingData != null ? barFullLength : layout[valueDim.wh]; + } + + // Support ['100%', '100%'] + function prepareSymbolSize(data, dataIndex, layout, opt, symbolRepeat, symbolClip, barFullLength, output) { + var valueDim = opt.valueDim; + var categoryDim = opt.categoryDim; + var categorySize = Math.abs(layout[categoryDim.wh]); + + var symbolSize = zrUtil.retrieve( + data.getItemVisual(dataIndex, 'symbolSize'), + ['100%', '100%'] + ); + + if (!zrUtil.isArray(symbolSize)) { + symbolSize = [symbolSize, symbolSize]; + } + + symbolSize[categoryDim.index] = parsePercent( + symbolSize[categoryDim.index], + categorySize + ); + symbolSize[valueDim.index] = parsePercent( + symbolSize[valueDim.index], + symbolRepeat ? categorySize : Math.abs(barFullLength) + ); + + output.symbolSize = symbolSize; + } + + function prepareLayoutInfo( + itemModel, symbolSize, layout, symbolRepeat, symbolClip, symbolOffset, + symbolPosition, lineWidth, barFullLength, repeatLength, opt, output + ) { + var categoryDim = opt.categoryDim; + var valueDim = opt.valueDim; + + var symbolMargin = parsePercent( + zrUtil.retrieve(itemModel.get('symbolMargin'), symbolRepeat ? '15%' : 0), + symbolSize[valueDim.index] + ); + + var unitLength = symbolSize[valueDim.index] + lineWidth; + var pathLength; + if (symbolRepeat) { + var unitWithMargin = unitLength + symbolMargin * 2; + var absBarFullLength = Math.abs(barFullLength); + var repeatTimes = numberUtil.isNumeric(symbolRepeat) + ? symbolRepeat : Math.ceil(absBarFullLength / unitWithMargin); + + // Adjust calculate margin, to ensure each symbol is displayed + // entirely in the given layout area. + symbolMargin = (absBarFullLength / repeatTimes - unitLength) / 2; + + // Update repeatTimes if symbolBoundingData not set. + repeatTimes = output.repeatTimes = numberUtil.isNumeric(symbolRepeat) + ? symbolRepeat : Math.ceil(Math.abs(repeatLength) / unitWithMargin); + pathLength = repeatTimes * (unitLength + symbolMargin * 2); + } + else { + pathLength = unitLength + symbolMargin * 2; + } + + output.symbolMargin = symbolMargin; + + var sizeFix = (barFullLength > 0 ? 1 : -1) * (pathLength / 2); + var pathPosition = output.pathPosition = []; + pathPosition[categoryDim.index] = layout[categoryDim.wh] / 2; + pathPosition[valueDim.index] = symbolPosition === 'start' + ? sizeFix + : symbolPosition === 'end' + ? barFullLength - sizeFix + : barFullLength / 2; // 'center' + if (symbolOffset) { + pathPosition[0] += symbolOffset[0]; + pathPosition[1] += symbolOffset[1]; + } + + var bundlePosition = output.bundlePosition = []; + bundlePosition[categoryDim.index] = layout[categoryDim.xy]; + bundlePosition[valueDim.index] = layout[valueDim.xy]; + + var barRectShape = output.barRectShape = zrUtil.extend({}, layout); + barRectShape[valueDim.wh] = barFullLength; + barRectShape[categoryDim.wh] = layout[categoryDim.wh]; + + var clipShape = output.clipShape = {x: 0, y: 0}; + clipShape[valueDim.wh] = layout[valueDim.wh]; + clipShape[categoryDim.wh] = layout[categoryDim.wh]; + + // If x or y is less than zero, show reversed shape. + var symbolScale = output.symbolScale = [symbolSize[0] / 2, symbolSize[1] / 2]; + // Follow convention, 'right' and 'top' is the normal scale. + symbolScale[valueDim.index] *= opt.isHorizontal + ? (barFullLength > 0 ? -1 : 1) + : (barFullLength >= 0 ? 1 : -1); + } + + function createPath(symbolMeta) { + var path = symbolUtil.createSymbol( + symbolMeta.symbolType, -1, -1, 2, 2, symbolMeta.color + ); + path.attr({ + culling: true + }); + return path; + } + + function updateRepeatSymbols(bar, dataIndex, opt, symbolMeta) { + var bundle = bar.__pictorialBundle; + var animationModel = opt.animationModel; + var symbolSize = symbolMeta.symbolSize; + var lineWidth = symbolMeta.lineWidth; + var pathPosition = symbolMeta.pathPosition; + var valueDim = opt.valueDim; + + var repeatTimes = symbolMeta.repeatTimes || 0; + var unit = symbolSize[opt.valueDim.index] + lineWidth + symbolMeta.symbolMargin * 2; + var index = 0; + + eachPath(bar, function (path) { + if (index < repeatTimes) { + graphic.updateProps(path, makeTarget(index), animationModel, dataIndex); + } + else { + graphic.updateProps(path, {scale: [0, 0]}, animationModel, dataIndex, function () { + bundle.remove(path); + }); + } + index++; + }); + + for (; index < repeatTimes; index++) { + var path = createPath(symbolMeta); + var target = makeTarget(index, true); + // FIXME + // start position? + path.attr({position: target.position, scale: [0, 0]}); + graphic.initProps(path, { + scale: target.scale, + rotation: target.rotation + }, animationModel, dataIndex); + + bundle.add(path); + } + + function makeTarget(index) { + var position = pathPosition.slice(); + position[valueDim.index] = unit * (index - repeatTimes / 2 + 0.5) + pathPosition[valueDim.index]; + return { + position: position, + scale: symbolMeta.symbolScale.slice(), + rotation: symbolMeta.rotation + }; + } + } + + // bar rect is used for label and enlarge hover area. + function updateBarRect(bar, dataIndex, opt, symbolMeta) { + var rectShape = zrUtil.extend({}, symbolMeta.barRectShape); + + var barRect = bar.__pictorialBarRect; + if (!barRect) { + barRect = bar.__pictorialBarRect = new graphic.Rect({ + z2: 2, + shape: rectShape, + style: { + stroke: 'transparent', + fill: 'transparent', + lineWidth: 0 + } + }); + + // FIXME + // If all emphasis/normal through action. + barRect + .on('mouseover', onMouseOver) + .on('mouseout', onMouseOut); + + bar.add(barRect); + } + else { + graphic.updateProps(barRect, {shape: rectShape}, opt.animationModel, dataIndex); + } + + function onMouseOver() { + eachPath(bar, function (path) { + path.trigger('emphasis'); + }); + } + function onMouseOut() { + eachPath(bar, function (path) { + path.trigger('normal'); + }); + } + } + + function createBar(data, dataIndex, itemModel, opt, symbolMeta, isUpdate) { + var valueDim = opt.valueDim; + + // bar is the main element for each data. + var bar = new graphic.Group(); + // bundle is used for location and clip. + var bundle = new graphic.Group(); + bar.add(bundle); + bar.__pictorialBundle = bundle; + bundle.attr('position', symbolMeta.bundlePosition.slice()); + + if (symbolMeta.symbolRepeat) { + updateRepeatSymbols(bar, dataIndex, opt, symbolMeta); + } + else { + var path = bar.__pictorialMainPath = createPath(symbolMeta); + bundle.add(path); + path.attr({ + position: symbolMeta.pathPosition.slice(), + scale: symbolMeta.symbolScale.slice(), + rotation: symbolMeta.rotation + }); + } + + var clipPath; + if (symbolMeta.symbolClip) { + clipPath = new graphic.Rect({ + shape: zrUtil.extend({}, symbolMeta.clipShape) + }); + bundle.setClipPath(clipPath); + bar.__pictorialClipPath = clipPath; + } + + updateBarRect(bar, dataIndex, opt, symbolMeta); + + // Three animation types: clip, position, scale. + var animationModel = opt.animationModel; + var updateMethod = isUpdate ? 'updateProps' : 'initProps'; + if (animationModel) { + // clipPath animation + if (clipPath) { + var rectShape = clipPath.shape; + var target = {}; + target[valueDim.wh] = rectShape[valueDim.wh]; + rectShape[valueDim.wh] = 0; + graphic[updateMethod](clipPath, {shape: target}, animationModel, dataIndex); + } + + // FIXME + // animation clip path? + // bar.position[valueDim.index] = layout[valueDim.xy]; + // eachPath(bar, function (path) { + // var target; + // // scale ainmation + // if (symbolMeta.symbolRepeat) { + // target = {scale: path.scale.slice()}; + // path.attr({ + // position: path.position.slice(), + // scale: [0, 0] + // }); + // } + // // // position ainmation + // // else { + // // target = {position: position}; + // // path.attr({ + // // scale: scale + // // }); + // // // FIXME + // // // start pos? + // // path.position[valueDim.index] = layout[valueDim.xy]; + // // } + // graphic[updateMethod](path, target, animationModel, dataIndex); + // }); + } + + return bar; + } + + function updateBar(data, dataIndex, itemModel, opt, symbolMeta, bar) { + var clipPath = bar.__pictorialClipPath; + var mainPath; + var animationModel = opt.animationModel; + var bundle = bar.__pictorialBundle; + + graphic.updateProps( + bundle, {position: symbolMeta.bundlePosition.slice()}, animationModel, dataIndex + ); + + if (symbolMeta.symbolRepeat) { + updateRepeatSymbols(bar, dataIndex, opt, symbolMeta); + } + else { + mainPath = bar.__pictorialMainPath; + } + + updateBarRect(bar, dataIndex, opt, symbolMeta); + + if (clipPath) { + graphic.updateProps( + clipPath, + {shape: zrUtil.extend({}, symbolMeta.clipShape)}, + animationModel, + dataIndex + ); + } + mainPath && graphic.updateProps( + mainPath, + { + position: symbolMeta.pathPosition.slice(), + scale: symbolMeta.symbolScale.slice(), + rotation: symbolMeta.rotation + }, + animationModel, + dataIndex + ); + } + + function removeBar(dataIndex, opt, bar) { + // Not show text when animating + var labelRect = bar.__pictorialBarRect; + labelRect && (labelRect.style.text = ''); + + var clipPath = bar.__pictorialClipPath; + var targetEl = clipPath || bar.__pictorialBundle; + var targetObj = clipPath + ? {shape: {width: 0}} + : {style: {opacity: 0}}; + + graphic.updateProps( + targetEl, targetObj, opt.animationModel, dataIndex, + function () { + bar.parent && bar.parent.remove(bar); + } + ); + } + + function getShapeStr(data, dataIndex, symbolMeta) { + return [ + data.getItemVisual(dataIndex, 'symbol') || 'none', + !!symbolMeta.symbolRepeat, + !!symbolMeta.symbolClip + ].join(':'); + } + + function eachPath(bar, cb, context) { + // Do not use Group#eachChild, because it do not support remove. + zrUtil.each(bar.__pictorialBundle.children(), function (el) { + el !== bar.__pictorialBarRect && cb.call(context, el); + }); + } + + function updateStyle(bar, dataIndex, itemModel, opt, symbolMeta) { + var color = symbolMeta.color; + // Color must be excluded. + // Because symbol provide setColor individually to set fill and stroke + var normalStyle = itemModel.getModel('itemStyle.normal').getItemStyle(['color']); + var hoverStyle = itemModel.getModel('itemStyle.emphasis').getItemStyle(); + + eachPath(bar, function (path) { + // PENDING setColor should be before setStyle!!! + path.setColor(color); + path.setStyle(zrUtil.defaults( + { + fill: color, + opacity: symbolMeta.opacity + }, + normalStyle + )); + graphic.setHoverStyle(path, hoverStyle); + }); + + var barRectHoverStyle = {}; + var barPositionOutside = opt.valueDim.posDesc[+(symbolMeta.barFullLength > 0)]; + var barRect = bar.__pictorialBarRect; + + helper.setLabel( + barRect.style, barRectHoverStyle, itemModel, + color, opt.seriesModel, dataIndex, barPositionOutside + ); + + graphic.setHoverStyle(barRect, barRectHoverStyle); + } + + return BarView; +}); \ No newline at end of file diff --git a/src/chart/bar/helper.js b/src/chart/bar/helper.js new file mode 100644 index 0000000000000000000000000000000000000000..1553e5f7b57e98697e2719af80b4052240c53633 --- /dev/null +++ b/src/chart/bar/helper.js @@ -0,0 +1,60 @@ +define(function (require) { + + var zrUtil = require('zrender/core/util'); + var graphic = require('../../util/graphic'); + + var BAR_BORDER_WIDTH_QUERY = ['itemStyle', 'normal', 'barBorderWidth']; + + var helper = {}; + + helper.setLabel = function ( + normalStyle, hoverStyle, itemModel, color, seriesModel, dataIndex, labelPositionOutside + ) { + var labelModel = itemModel.getModel('label.normal'); + var hoverLabelModel = itemModel.getModel('label.emphasis'); + + if (labelModel.get('show')) { + setLabel( + normalStyle, labelModel, color, + zrUtil.retrieve( + seriesModel.getFormattedLabel(dataIndex, 'normal'), + seriesModel.getRawValue(dataIndex) + ), + labelPositionOutside + ); + } + else { + normalStyle.text = ''; + } + + if (hoverLabelModel.get('show')) { + setLabel( + hoverStyle, hoverLabelModel, color, + zrUtil.retrieve( + seriesModel.getFormattedLabel(dataIndex, 'emphasis'), + seriesModel.getRawValue(dataIndex) + ), + labelPositionOutside + ); + } + else { + hoverStyle.text = ''; + } + }; + + function setLabel(style, model, color, labelText, labelPositionOutside) { + graphic.setText(style, model, color); + style.text = labelText; + if (style.textPosition === 'outside') { + style.textPosition = labelPositionOutside; + } + } + + // In case width or height are too small. + helper.getLineWidth = function (itemModel, rawLayout) { + var lineWidth = itemModel.get(BAR_BORDER_WIDTH_QUERY) || 0; + return Math.min(lineWidth, Math.abs(rawLayout.width), Math.abs(rawLayout.height)); + }; + + return helper; +}); \ No newline at end of file diff --git a/src/chart/pictorialBar.js b/src/chart/pictorialBar.js new file mode 100644 index 0000000000000000000000000000000000000000..6e3a7bb0466f2434340fd5e6a2d5cfb75393b526 --- /dev/null +++ b/src/chart/pictorialBar.js @@ -0,0 +1,21 @@ +define(function (require) { + + var zrUtil = require('zrender/core/util'); + + require('../coord/cartesian/Grid'); + + require('./bar/PictorialBarSeries'); + require('./bar/PictorialBarView'); + + var barLayoutGrid = require('../layout/barGrid'); + var echarts = require('../echarts'); + + echarts.registerLayout(zrUtil.curry(barLayoutGrid, 'pictorialBar')); + + echarts.registerVisual(zrUtil.curry( + require('../visual/symbol'), 'pictorialBar', 'roundRect', null + )); + + // In case developer forget to include grid component + require('../component/grid'); +}); \ No newline at end of file diff --git a/src/util/graphic.js b/src/util/graphic.js index 0754e87c33ac47e9d87f3d9b7a66bf552241d93b..78a8be2cb704274eee2eac09436af0cc69f427c3 100644 --- a/src/util/graphic.js +++ b/src/util/graphic.js @@ -417,12 +417,9 @@ define(function(require) { if (animationEnabled) { var postfix = isUpdate ? 'Update' : ''; - var duration = animatableModel - && animatableModel.getShallow('animationDuration' + postfix); - var animationEasing = animatableModel - && animatableModel.getShallow('animationEasing' + postfix); - var animationDelay = animatableModel - && animatableModel.getShallow('animationDelay' + postfix); + 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); } diff --git a/src/util/number.js b/src/util/number.js index f2b22878c190dbf68f804342789e9c0945707872..1b5bb4181d257fba26482742d54f11b0caf37cde 100644 --- a/src/util/number.js +++ b/src/util/number.js @@ -313,5 +313,16 @@ define(function (require) { } }; + /** + * parseFloat NaNs numeric-cast false positives (null|true|false|"") + * ...but misinterprets leading-number strings, particularly hex literals ("0x...") + * subtraction forces infinities to NaN + * @param {*} v + * @return {boolean} + */ + number.isNumeric = function (v) { + return v - parseFloat(v) >= 0; + }; + return number; }); \ No newline at end of file diff --git a/test/pictorial-repeat.html b/test/pictorial-repeat.html new file mode 100644 index 0000000000000000000000000000000000000000..02055d388819154cc63bd52b48052e66644e1e72 --- /dev/null +++ b/test/pictorial-repeat.html @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + +

vertical | no clip | symbol w/h ratio and margin adjust

+
+

vertical | clip | fixed repeatTimes

+
+

horizontal | clip | positive | rotate

+
+

horizontal | clip | negative | no animation

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/pictorial-single.html b/test/pictorial-single.html new file mode 100644 index 0000000000000000000000000000000000000000..dab8683c85acc7a1741f7d7f7cc00b7ff209b101 --- /dev/null +++ b/test/pictorial-single.html @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + +

horizontal | no clip | symbolOffset

+
+ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file