diff --git a/src/ExtensionAPI.js b/src/ExtensionAPI.js index 3f8c7562631f896d97d4307e055c87e4f48d8bd2..f196b6e96d3e201081cebfa61123a428c0a93b94 100644 --- a/src/ExtensionAPI.js +++ b/src/ExtensionAPI.js @@ -6,7 +6,7 @@ define(function(require) { var echartsAPIList = [ 'getDom', 'getZr', 'getWidth', 'getHeight', 'dispatchAction', - 'on', 'off' + 'on', 'off', 'getDataURL', 'getConnectedDataURL' ]; function ExtensionAPI(chartInstance) { diff --git a/src/component/helper/listComponent.js b/src/component/helper/listComponent.js new file mode 100644 index 0000000000000000000000000000000000000000..ad241c55d394d13d5ad9541eb134c7fa339ff8f6 --- /dev/null +++ b/src/component/helper/listComponent.js @@ -0,0 +1,78 @@ +define(function (require) { + // List layout + var layout = require('../../util/layout'); + var formatUtil = require('../../util/format'); + var graphic = require('../../util/graphic'); + + function positionGroup(group, model, api) { + var x = model.get('x'); + var y = model.get('y'); + var x2 = model.get('x2'); + var y2 = model.get('y2'); + + if (!x && !x2) { + x = 'center'; + } + if (!y && !y2) { + y = 'top'; + } + layout.positionGroup( + group, { + x: x, + y: y, + x2: x2, + y2: y2 + }, + { + width: api.getWidth(), + height: api.getHeight() + }, + model.get('padding') + ); + } + + return { + /** + * Layout list like component. + * It will box layout each items in group of component and then position the whole group in the viewport + * @param {module:zrender/group/Group} group + * @param {module:echarts/model/Component} componentModel + * @param {module:echarts/ExtensionAPI} + */ + layout: function (group, componentModel, api) { + layout.box( + componentModel.get('orient'), + group, + componentModel.get('itemGap'), + api.getWidth(), + api.getHeight() + ); + + positionGroup(group, componentModel, api); + }, + + addBackground: function (group, componentModel) { + var padding = formatUtil.normalizeCssArray( + componentModel.get('padding') + ); + var boundingRect = group.getBoundingRect(); + var rect = new graphic.Rect({ + shape: { + x: boundingRect.x - padding[3], + y: boundingRect.y - padding[0], + width: boundingRect.width + padding[1] + padding[3], + height: boundingRect.height + padding[0] + padding[2] + }, + style: { + stroke: componentModel.get('borderColor'), + fill: componentModel.get('backgroundColor'), + lineWidth: componentModel.get('borderWidth') + }, + silent: true + }); + graphic.subPixelOptimizeRect(rect); + + group.add(rect); + } + } +}); \ No newline at end of file diff --git a/src/component/legend/LegendView.js b/src/component/legend/LegendView.js index 354e782ba8e7c5f10db5627d9e7b651d4aa99b6d..a2d998cd0f7f8cf030eca15e2b6a91aadddaa5e2 100644 --- a/src/component/legend/LegendView.js +++ b/src/component/legend/LegendView.js @@ -3,9 +3,7 @@ define(function (require) { var zrUtil = require('zrender/core/util'); var symbolCreator = require('../../util/symbol'); var graphic = require('../../util/graphic'); - - var layout = require('../../util/layout'); - var formatUtil = require('../../util/format'); + var listComponentHelper = require('../helper/listComponent'); var curry = zrUtil.curry; @@ -34,33 +32,6 @@ define(function (require) { }); } - function positionGroup(group, legendModel, api) { - var x = legendModel.get('x'); - var y = legendModel.get('y'); - var x2 = legendModel.get('x2'); - var y2 = legendModel.get('y2'); - - if (!x && !x2) { - x = 'center'; - } - if (!y && !y2) { - y = 'top'; - } - layout.positionGroup( - group, { - x: x, - y: y, - x2: x2, - y2: y2 - }, - { - width: api.getWidth(), - height: api.getHeight() - }, - legendModel.get('padding') - ); - } - return require('../../echarts').extendComponentView({ type: 'legend', @@ -79,7 +50,9 @@ define(function (require) { group.removeAll(); if (itemAlign === 'auto') { - itemAlign = legendModel.get('x') === 'right' ? 'right' : 'left'; + itemAlign = (legendModel.get('x') === 'right' + && legendModel.get('orient') === 'vertical') + ? 'right' : 'left'; } var legendDataMap = {}; @@ -162,45 +135,10 @@ define(function (require) { } }, this); - layout.box( - legendModel.get('orient'), - group, - legendModel.get('itemGap'), - api.getWidth(), - api.getHeight() - ); - - positionGroup(group, legendModel, api); - - // Render background after group is positioned - // Or will get rect of group with padding + listComponentHelper.layout(group, legendModel, api); + // Render background after group is layout // FIXME - this._renderBG(legendModel, group); - }, - - // FIXME 通用? - _renderBG: function (legendModel, group) { - var padding = formatUtil.normalizeCssArray( - legendModel.get('padding') - ); - var boundingRect = group.getBoundingRect(); - var rect = new graphic.Rect({ - shape: { - x: boundingRect.x - padding[3], - y: boundingRect.y - padding[0], - width: boundingRect.width + padding[1] + padding[3], - height: boundingRect.height + padding[0] + padding[2] - }, - style: { - stroke: legendModel.get('borderColor'), - fill: legendModel.get('backgroundColor'), - lineWidth: legendModel.get('borderWidth') - }, - silent: true - }); - graphic.subPixelOptimizeRect(rect); - - group.add(rect); + listComponentHelper.addBackground(group, legendModel); }, _createItem: function ( diff --git a/src/component/toolbox.js b/src/component/toolbox.js new file mode 100644 index 0000000000000000000000000000000000000000..b84286521f8a7dc7f5ae7d1427ebd640418ffe2a --- /dev/null +++ b/src/component/toolbox.js @@ -0,0 +1,8 @@ +define(function (require) { + + require('./toolbox/ToolboxModel'); + require('./toolbox/ToolboxView'); + + require('./toolbox/feature/SaveAsImage'); + require('./toolbox/feature/MagicType'); +}); \ No newline at end of file diff --git a/src/component/toolbox/ToolboxModel.js b/src/component/toolbox/ToolboxModel.js new file mode 100644 index 0000000000000000000000000000000000000000..4b94b3f8da35fa7d62fba109cf722a11db342363 --- /dev/null +++ b/src/component/toolbox/ToolboxModel.js @@ -0,0 +1,64 @@ +define(function (require) { + + var featureManager = require('./featureManager'); + var zrUtil = require('zrender/core/util'); + + require('../../echarts').extendComponentModel({ + + type: 'toolbox', + + mergeDefaultAndTheme: function (option) { + this.$superApply('mergeDefaultAndTheme', arguments); + + zrUtil.each(this.option.feature, function (featureOpt, featureName) { + var Feature = featureManager.get(featureName); + Feature && zrUtil.merge(featureOpt, Feature.defaultOption); + }); + }, + + defaultOption: { + + show: true, + + z: 6, + + zlevel: 0, + + orient: 'horizontal', + + x: 'right', + + y: 'top', + + // x2 + // y2 + + backgroundColor: 'transparent', + + borderColor: '#ccc', + + borderWidth: 0, + + padding: 10, + + itemGap: 10, + + itemSize: 18, + + showTitle: true, + + iconStyle: { + normal: { + borderColor: '#000', + color: 'none' + }, + emphasis: { + borderColor: '#3E98C5' + } + } + // textStyle: {}, + + // feature + } + }); +}) \ No newline at end of file diff --git a/src/component/toolbox/ToolboxView.js b/src/component/toolbox/ToolboxView.js new file mode 100644 index 0000000000000000000000000000000000000000..b057c2bdb8506d33b958715e420664c2df7691ea --- /dev/null +++ b/src/component/toolbox/ToolboxView.js @@ -0,0 +1,75 @@ +define(function (require) { + + var featureManager = require('./featureManager'); + var zrUtil = require('zrender/core/util'); + var graphic = require('../../util/graphic'); + var Model = require('../../model/Model'); + var listComponentHelper = require('../helper/listComponent'); + + return require('../../echarts').extendComponentView({ + + type: 'toolbox', + + render: function (toolboxModel, ecModel, api) { + var group = this.group; + group.removeAll(); + + if (!toolboxModel.get('show')) { + return; + } + + var itemSize = +toolboxModel.get('itemSize'); + + zrUtil.each(toolboxModel.get('feature'), function (featureOpt, featureName) { + var Feature = featureManager.get(featureName); + if (!Feature) { + return; + } + + var featureModel = new Model(featureOpt, toolboxModel, toolboxModel.ecModel); + var feature = new Feature(featureModel); + if (!featureModel.get('show')) { + return; + } + + var iconStyleModel = featureModel.getModel('iconStyle'); + var normalStyle = iconStyleModel.getModel('normal').getItemStyle(); + var hoverStyle = iconStyleModel.getModel('emphasis').getItemStyle(); + + var icons = feature.getIcons ? feature.getIcons() : featureModel.get('icon'); + if (typeof icons === 'string') { + var icon = icons; + icons = {}; + icons[featureName] = icon; + } + + zrUtil.each(icons, function (icon, iconName) { + var path = graphic.makePath( + icon, { + style: normalStyle, + hoverStyle: hoverStyle, + rectHover: true + }, { + x: -itemSize / 2, + y: -itemSize / 2, + width: itemSize, + height: itemSize + }, 'center' + ); + + graphic.setHoverStyle(path); + + group.add(path); + path.on('click', zrUtil.bind( + feature.onclick, feature, ecModel, api, iconName + )); + }); + }); + + listComponentHelper.layout(group, toolboxModel, api); + // Render background after group is layout + // FIXME + listComponentHelper.addBackground(group, toolboxModel); + } + }); +}); \ No newline at end of file diff --git a/src/component/toolbox/feature/DataView.js b/src/component/toolbox/feature/DataView.js new file mode 100644 index 0000000000000000000000000000000000000000..cd7277f4260c2f724b7788e546777fa05086205b --- /dev/null +++ b/src/component/toolbox/feature/DataView.js @@ -0,0 +1,3 @@ +define(function (require) { + +}); \ No newline at end of file diff --git a/src/component/toolbox/feature/DataZoom.js b/src/component/toolbox/feature/DataZoom.js new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/component/toolbox/feature/MagicType.js b/src/component/toolbox/feature/MagicType.js new file mode 100644 index 0000000000000000000000000000000000000000..b0726b3333c3c9d47970ad2d0e9f2c3a4eee2fcc --- /dev/null +++ b/src/component/toolbox/feature/MagicType.js @@ -0,0 +1,50 @@ +define(function(require) { + 'use strict'; + + var zrUtil = require('zrender/core/util'); + + function MagicType(model) { + this.model = model; + } + + MagicType.defaultOption = { + show: true, + // Icon group + icon: { + line: 'M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4', + bar: 'M8.5,22.9h6.9V50H8.5V22.9z M26.7,12.9h6.9v37h-6.9V12.9z M45,2h6.9v48H45V2z M3.3,58H57', + stack: 'M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z', + tiled: 'M2.3,2.2h22.8V25H2.3V2.2z M35,2.2h22.8V25H35V2.2zM2.3,35h22.8v22.8H2.3V35z M35,35h22.8v22.8H35V35z' + }, + title: { + line: '切换为折线图', + bar: '切换为柱状图', + stack: '切换为堆叠', + tiled: '切换为平铺' + } + }; + + var proto = MagicType.prototype; + + proto.getIcons = function () { + var model = this.model; + var availableIcons = model.get('icon'); + var icons = {}; + zrUtil.each(model.get('option'), function (option, type) { + if (availableIcons[type]) { + icons[type] = availableIcons[type]; + } + }); + return icons; + }; + + proto.onclick = function (ecModel, api, type) { + if (type === 'stack') { + // var seriesModels = + } + } + + require('../featureManager').register('magicType', MagicType); + + return MagicType; +}); \ No newline at end of file diff --git a/src/component/toolbox/feature/SaveAsImage.js b/src/component/toolbox/feature/SaveAsImage.js new file mode 100644 index 0000000000000000000000000000000000000000..ef121c125247d964db6365b5c0c00d1afc74ed76 --- /dev/null +++ b/src/component/toolbox/feature/SaveAsImage.js @@ -0,0 +1,37 @@ +define(function (require) { + + function SaveAsImage (model) { + this.model = model; + }; + + SaveAsImage.defaultOption = { + show: true, + icon: 'M4.7,22.9L29.3,45.5L54.7,23.4M4.6,43.6L4.6,58L53.8,58L53.8,43.6\ + M29.2,45.1L29.2,0', + title: '保存为图片', + type: 'png', + backgroundColor: '#fff', + name: '' + }; + + var proto = SaveAsImage.prototype; + + proto.onclick = function (ecModel, api) { + var title = ecModel.get('title.0.text') || 'echarts'; + var $a = document.createElement('a'); + var type = this.model.get('type', true) || 'png'; + $a.download = title + '.' + type; + $a.target = '_blank'; + $a.href = api.getConnectedDataURL({ + type: type, + backgroundColor: this.model.get('backgroundColor') + }); + $a.click(); + }; + + require('../featureManager').register( + 'saveAsImage', SaveAsImage + ); + + return SaveAsImage; +}); \ No newline at end of file diff --git a/src/component/toolbox/featureManager.js b/src/component/toolbox/featureManager.js new file mode 100644 index 0000000000000000000000000000000000000000..17d810feec457f35bbae9847513643aca6ac13c9 --- /dev/null +++ b/src/component/toolbox/featureManager.js @@ -0,0 +1,15 @@ +define(function(require) { + 'use strict'; + + var features = {}; + + return { + register: function (name, ctor) { + features[name] = ctor; + }, + + get: function (name) { + return features[name]; + } + }; +}); \ No newline at end of file diff --git a/src/util/layout.js b/src/util/layout.js index 88be10f1458ca27cd90a2f8fa9130266e37086ec..9e3ede9aa8c7726e95a8d4cca78408fbc0f4ef37 100644 --- a/src/util/layout.js +++ b/src/util/layout.js @@ -20,11 +20,15 @@ define(function(require) { maxHeight = Infinity; } var currentLineMaxSize = 0; - group.eachChild(function (child) { + group.eachChild(function (child, idx) { var position = child.position; var rect = child.getBoundingRect(); + var nextChild = group.childAt(idx + 1); + var nextChildRect = nextChild && nextChild.getBoundingRect(); + var nextX; + var nextY; if (orient === 'horizontal') { - var nextX = x + rect.width; + nextX = x + rect.width + (nextChildRect ? (-nextChildRect.x + rect.x) : 0); // Wrap if (nextX > maxWidth) { x = 0; @@ -36,7 +40,7 @@ define(function(require) { } } else { - var nextY = y + rect.height; + nextY = y + rect.height + (nextChildRect ? (-nextChildRect.y + rect.y) : 0); // Wrap if (nextY > maxHeight) { x += currentLineMaxSize + gap; @@ -52,8 +56,8 @@ define(function(require) { position[1] = y; orient === 'horizontal' - ? x += rect.width + gap - : y += rect.height + gap; + ? (x = nextX + gap) + : (y = nextY + gap); }); }