diff --git a/src/chart/bar/BarView.js b/src/chart/bar/BarView.js index 9aa3023832fedd2113a702a3b4f8db6cb2dc747c..e38862e51c3b0dbcd0a80ca177c7c79e0995df4f 100644 --- a/src/chart/bar/BarView.js +++ b/src/chart/bar/BarView.js @@ -24,13 +24,13 @@ define(function (require) { var coordinateSystemType = seriesModel.get('coordinateSystem'); if (coordinateSystemType === 'cartesian2d') { - this._renderCartesian(seriesModel, ecModel, api); + this._renderOnCartesian(seriesModel, ecModel, api); } return this.group; }, - _renderCartesian: function (seriesModel, ecModel, api) { + _renderOnCartesian: function (seriesModel, ecModel, api) { var group = this.group; var data = seriesModel.getData(); var oldData = this._data; @@ -39,7 +39,7 @@ define(function (require) { var baseAxis = cartesian.getBaseAxis(); var isHorizontal = baseAxis.isHorizontal(); - var enableAnimation = ecModel.get('animation'); + var enableAnimation = seriesModel.get('animation'); var barBorderWidthQuery = ['itemStyle', 'normal', 'barBorderWidth']; @@ -153,12 +153,12 @@ define(function (require) { var labelModel = itemModel.getModel('label.normal'); var hoverLabelModel = itemModel.getModel('label.emphasis'); - var labelText = seriesModel.getFormattedLabel(idx, 'normal') - || data.getRawValue(idx); var rectStyle = rect.style; if (labelModel.get('show')) { setLabel( - rectStyle, labelModel, color, labelText, labelPositionOutside + rectStyle, labelModel, color, + seriesModel.getFormattedLabel(idx, 'normal') || data.getRawValue(idx), + labelPositionOutside ); } else { @@ -166,7 +166,9 @@ define(function (require) { } if (hoverLabelModel.get('show')) { setLabel( - hoverStyle, hoverLabelModel, color, labelText, labelPositionOutside + hoverStyle, hoverLabelModel, color, + seriesModel.getFormattedLabel(idx, 'emphasis') || data.getRawValue(idx), + labelPositionOutside ); } else { diff --git a/src/chart/geoLine/GeoLineView.js b/src/chart/geoLine/GeoLineView.js index 01abf5862846a432e2aa49f4dd9a893f60d13b90..572d75c662f2b31956e19b7c839cde88649bfc36 100644 --- a/src/chart/geoLine/GeoLineView.js +++ b/src/chart/geoLine/GeoLineView.js @@ -29,6 +29,8 @@ define(function (require) { var trailLength = seriesModel.get('effect.trailLength'); var zr = api.getZr(); + // Avoid the drag cause ghost shadow + // FIXME Better way ? zr.painter.getLayer(zlevel).clear(true); // Config layer with motion blur if (this._lastZlevel != null) { diff --git a/src/chart/graph.js b/src/chart/graph.js index 0390bf45a8463de1e9ae285377e0d126e207d10d..df9e4e9c5f33fa795049ba50f584792eee44ba25 100644 --- a/src/chart/graph.js +++ b/src/chart/graph.js @@ -17,6 +17,7 @@ define(function (require) { echarts.registerLayout(require('./graph/simpleLayout')); echarts.registerLayout(require('./graph/circularLayout')); + echarts.registerLayout(require('./graph/forceLayout')); // Graph view coordinate system echarts.registerCoordinateSystem('graphView', { diff --git a/src/chart/graph/GraphSeries.js b/src/chart/graph/GraphSeries.js index cd1d74217f308d899324fc6f506613d3764df17c..8e71e397ae636dd1903aad36168b8bbc490bbe1e 100644 --- a/src/chart/graph/GraphSeries.js +++ b/src/chart/graph/GraphSeries.js @@ -112,6 +112,14 @@ define(function (require) { layout: null, + // Configuration of force + force: { + initLayout: null, + repulsion: 50, + gravity: 0.1, + edgeLength: 30 + }, + x: 'center', y: 'center', x2: null, @@ -122,8 +130,9 @@ define(function (require) { symbol: 'circle', symbolSize: 10, - // roam: false, + draggable: false, + roam: false, roamDetail: { x: 0, y: 0, diff --git a/src/chart/graph/GraphView.js b/src/chart/graph/GraphView.js index bc2a9a290037af3f41e6bdbcf98f57ae2393ad40..7916f6d0a13134ed736ea43672dfda7891cd15e5 100644 --- a/src/chart/graph/GraphView.js +++ b/src/chart/graph/GraphView.js @@ -62,7 +62,6 @@ define(function (require) { }); }); - // Save the original lineWidth data.graph.eachEdge(function (edge) { edge.__lineWidth = edge.getModel('lineStyle.normal').get('width'); @@ -80,6 +79,42 @@ define(function (require) { this._updateNodeAndLinkScale(); this._updateController(seriesModel, coordSys, api); + + clearTimeout(this._layoutTimeout); + + var forceLayout = seriesModel.forceLayout; + if (forceLayout) { + this._startForceLayoutIteration(forceLayout); + } + + // Update draggable + data.eachItemGraphicEl(function (el, idx) { + var draggable = data.getItemModel(idx).get('draggable'); + if (draggable && forceLayout) { + el.on('drag', function () { + forceLayout.warmUp(); + forceLayout.setFixed(idx); + // Write position back to layout + data.setItemLayout(idx, el.position); + }).on('dragend', function () { + forceLayout.setUnfixed(idx); + }); + } + else { + el.off('drag'); + } + el.setDraggable(draggable); + }, this); + }, + + _startForceLayoutIteration: function (forceLayout) { + var self = this; + (function step() { + forceLayout.step(function (stopped) { + self.updateLayout(); + !stopped && (self._layoutTimeout = setTimeout(step, 16)); + }); + })(); }, _updateController: function (seriesModel, coordSys, api) { diff --git a/src/chart/graph/circularLayout.js b/src/chart/graph/circularLayout.js index a8b35d5aac0fb694430910b0a70bf0a3459ca429..b9291dab35758863dafa8f029818ded8b699ef99 100644 --- a/src/chart/graph/circularLayout.js +++ b/src/chart/graph/circularLayout.js @@ -1,48 +1,9 @@ define(function (require) { + var circularLayoutHelper = require('./circularLayoutHelper'); return function (ecModel, api) { ecModel.eachSeriesByType('graph', function (seriesModel) { if (seriesModel.get('layout') === 'circular') { - var coordSys = seriesModel.coordinateSystem; - if (coordSys && coordSys.type !== 'view') { - return; - } - - var rect = coordSys.getBoundingRect(); - - var nodeData = seriesModel.getData(); - var graph = nodeData.graph; - - var angle = 0; - var unitAngle = Math.PI * 2 / nodeData.getSum('value'); - - var cx = rect.width / 2 + rect.x; - var cy = rect.height / 2 + rect.y; - - var r = Math.min(rect.width, rect.height) / 2; - - graph.eachNode(function (node) { - var value = node.getValue('value'); - - angle += unitAngle * value / 2; - - node.setLayout([ - r * Math.cos(angle) + cx, - r * Math.sin(angle) + cy - ]); - - angle += unitAngle * value / 2; - }); - - graph.eachEdge(function (edge) { - var curveness = edge.getModel().get('lineStyle.normal.curveness'); - var p1 = edge.node1.getLayout(); - var p2 = edge.node2.getLayout(); - var cp1; - if (curveness > 0) { - cp1 = [cx, cy]; - } - edge.setLayout([p1, p2, cp1]); - }); + circularLayoutHelper(seriesModel); } }); }; diff --git a/src/chart/graph/circularLayoutHelper.js b/src/chart/graph/circularLayoutHelper.js new file mode 100644 index 0000000000000000000000000000000000000000..6389c81ac56e6e00976aa846d25b51a41f44dd75 --- /dev/null +++ b/src/chart/graph/circularLayoutHelper.js @@ -0,0 +1,46 @@ +define(function (require) { + return function (seriesModel) { + var coordSys = seriesModel.coordinateSystem; + if (coordSys && coordSys.type !== 'view') { + return; + } + + var rect = coordSys.getBoundingRect(); + + var nodeData = seriesModel.getData(); + var graph = nodeData.graph; + + var angle = 0; + var sum = nodeData.getSum('value'); + var unitAngle = Math.PI * 2 / (sum || nodeData.count()); + + var cx = rect.width / 2 + rect.x; + var cy = rect.height / 2 + rect.y; + + var r = Math.min(rect.width, rect.height) / 2; + + graph.eachNode(function (node) { + var value = node.getValue('value'); + + angle += unitAngle * (sum ? value : 2) / 2; + + node.setLayout([ + r * Math.cos(angle) + cx, + r * Math.sin(angle) + cy + ]); + + angle += unitAngle * (sum ? value : 2) / 2; + }); + + graph.eachEdge(function (edge) { + var curveness = edge.getModel().get('lineStyle.normal.curveness') || 0; + var p1 = edge.node1.getLayout(); + var p2 = edge.node2.getLayout(); + var cp1; + if (curveness > 0) { + cp1 = [cx, cy]; + } + edge.setLayout([p1, p2, cp1]); + }); + }; +}); \ No newline at end of file diff --git a/src/chart/graph/forceHelper.js b/src/chart/graph/forceHelper.js new file mode 100644 index 0000000000000000000000000000000000000000..a9c0718626c55f9aedb9598137d69bbc9558c36c --- /dev/null +++ b/src/chart/graph/forceHelper.js @@ -0,0 +1,137 @@ +define(function (require) { + + var vec2 = require('zrender/core/vector'); + var scaleAndAdd = vec2.scaleAndAdd; + + // function adjacentNode(n, e) { + // return e.n1 === n ? e.n2 : e.n1; + // } + + return function (nodes, edges, opts) { + var rect = opts.rect; + var width = rect.width; + var height = rect.height; + var center = [rect.x + width / 2, rect.y + height / 2]; + // var scale = opts.scale || 1; + var gravity = opts.gravity == null ? 0.1 : opts.gravity; + + // for (var i = 0; i < edges.length; i++) { + // var e = edges[i]; + // var n1 = e.n1; + // var n2 = e.n2; + // n1.edges = n1.edges || []; + // n2.edges = n2.edges || []; + // n1.edges.push(e); + // n2.edges.push(e); + // } + // Init position + for (var i = 0; i < nodes.length; i++) { + var n = nodes[i]; + if (!n.p) { + // Use the position from first adjecent node with defined position + // Or use a random position + // From d3 + // if (n.edges) { + // var j = -1; + // while (++j < n.edges.length) { + // var e = n.edges[j]; + // var other = adjacentNode(n, e); + // if (other.p) { + // n.p = vec2.clone(other.p); + // break; + // } + // } + // } + // if (!n.p) { + n.p = vec2.create( + width * (Math.random() - 0.5) + center[0], + height * (Math.random() - 0.5) + center[1] + ); + // } + } + n.pp = vec2.clone(n.p); + n.edges = null; + } + + // Formula in 'Graph Drawing by Force-directed Placement' + // var k = scale * Math.sqrt(width * height / nodes.length); + // var k2 = k * k; + + var friction = 0.6; + + return { + warmUp: function () { + friction = 0.5; + }, + + setFixed: function (idx) { + nodes[idx].fixed = true; + }, + + setUnfixed: function (idx) { + nodes[idx].fixed = false; + }, + + step: function (cb) { + var v12 = []; + var nLen = nodes.length; + for (var i = 0; i < edges.length; i++) { + var e = edges[i]; + var n1 = e.n1; + var n2 = e.n2; + + vec2.sub(v12, n2.p, n1.p); + var d = vec2.len(v12) - e.d; + var w = n2.w / (n1.w + n2.w); + vec2.normalize(v12, v12); + + !n1.fixed && scaleAndAdd(n1.p, n1.p, v12, w * d * friction); + !n2.fixed && scaleAndAdd(n2.p, n2.p, v12, -(1 - w) * d * friction); + } + // Gravity + for (var i = 0; i < nLen; i++) { + var n = nodes[i]; + if (!n.fixed) { + vec2.sub(v12, center, n.p); + // var d = vec2.len(v12); + // vec2.scale(v12, v12, 1 / d); + // var gravityFactor = gravity; + vec2.scaleAndAdd(n.p, n.p, v12, gravity * friction); + } + } + + // Repulsive + // PENDING + for (var i = 0; i < nLen; i++) { + var n1 = nodes[i]; + for (var j = i + 1; j < nLen; j++) { + var n2 = nodes[j]; + vec2.sub(v12, n2.p, n1.p); + var d = vec2.len(v12); + if (d === 0) { + // Random repulse + vec2.set(v12, Math.random() - 0.5, Math.random() - 0.5); + d = 1; + } + var repFact = (n1.rep + n2.rep) / d / d; + !n1.fixed && scaleAndAdd(n1.pp, n1.pp, v12, repFact); + !n2.fixed && scaleAndAdd(n2.pp, n2.pp, v12, -repFact); + } + } + var v = []; + for (var i = 0; i < nLen; i++) { + var n = nodes[i]; + if (!n.fixed) { + vec2.sub(v, n.p, n.pp); + vec2.scaleAndAdd(n.p, n.p, v, friction); + vec2.copy(n.pp, n.p); + } + } + + friction = friction * 0.995; + + cb && cb(nodes, edges, friction < 0.01); + } + }; + }; +}); \ No newline at end of file diff --git a/src/chart/graph/forceLayout.js b/src/chart/graph/forceLayout.js new file mode 100644 index 0000000000000000000000000000000000000000..d2c0282d4a619735c3e245abb390f537e3279fc0 --- /dev/null +++ b/src/chart/graph/forceLayout.js @@ -0,0 +1,107 @@ +define(function (require) { + + var forceHelper = require('./forceHelper'); + var numberUtil = require('../../util/number'); + var simpleLayoutHelper = require('./simpleLayoutHelper'); + var circularLayoutHelper = require('./circularLayoutHelper'); + var vec2 = require('zrender/core/vector'); + + return function (ecModel, api) { + ecModel.eachSeriesByType('graph', function (graphSeries) { + if (graphSeries.get('layout') === 'force') { + var preservedPoints = graphSeries.preservedPoints || {}; + var graph = graphSeries.getGraph(); + var nodeData = graph.data; + var edgeData = graph.edgeData; + var forceModel = graphSeries.getModel('force'); + var initLayout = forceModel.get('initLayout'); + if (graphSeries.preservedPoints) { + nodeData.each(function (idx) { + var id = nodeData.getId(idx); + nodeData.setItemLayout(idx, preservedPoints[id] || [NaN, NaN]); + }); + } + else if (!initLayout || initLayout === 'none') { + simpleLayoutHelper(graphSeries); + } + else if (initLayout === 'circular') { + circularLayoutHelper(graphSeries); + } + + var nodeDataExtent = nodeData.getDataExtent('value'); + // var edgeDataExtent = edgeData.getDataExtent('value'); + var repulsion = forceModel.get('repulsion'); + var edgeLength = forceModel.get('edgeLength'); + var nodes = nodeData.mapArray('value', function (value, idx) { + var point = nodeData.getItemLayout(idx); + // var w = numberUtil.linearMap(value, nodeDataExtent, [0, 50]); + var rep = numberUtil.linearMap(value, nodeDataExtent, [0, repulsion]) || (repulsion / 2); + return { + w: rep, + rep: rep, + p: (!point || isNaN(point[0]) || isNaN(point[1])) ? null : point + }; + }); + var edges = edgeData.mapArray('value', function (value, idx) { + var edge = graph.getEdgeByIndex(idx); + // var w = numberUtil.linearMap(value, edgeDataExtent, [0, 100]); + return { + n1: nodes[edge.node1.dataIndex], + n2: nodes[edge.node2.dataIndex], + d: edgeLength, + curveness: edge.getModel().get('lineStyle.normal.curveness') || 0 + }; + }); + + var coordSys = graphSeries.coordinateSystem; + var rect = coordSys.getBoundingRect(); + var forceInstance = forceHelper(nodes, edges, { + rect: rect, + gravity: forceModel.get('gravity') + }); + var oldStep = forceInstance.step; + forceInstance.step = function (cb) { + for (var i = 0, l = nodes.length; i < l; i++) { + if (nodes[i].fixed) { + // Write back to layout instance + vec2.copy(nodes[i].p, graph.getNodeByIndex(i).getLayout()); + } + } + oldStep(function (nodes, edges, stopped) { + for (var i = 0, l = nodes.length; i < l; i++) { + if (!nodes[i].fixed) { + graph.getNodeByIndex(i).setLayout(nodes[i].p); + } + preservedPoints[nodeData.getId(i)] = nodes[i].p; + } + for (var i = 0, l = edges.length; i < l; i++) { + var e = edges[i]; + var p1 = e.n1.p; + var p2 = e.n2.p; + var points = [p1, p2]; + if (e.curveness > 0) { + points.push([ + (p1[0] + p2[0]) / 2 - (p1[1] - p2[1]) * e.curveness, + (p1[1] + p2[1]) / 2 - (p2[0] - p1[0]) * e.curveness + ]); + } + graph.getEdgeByIndex(i).setLayout(points); + } + // Update layout + + cb && cb(stopped); + }); + }; + graphSeries.forceLayout = forceInstance; + graphSeries.preservedPoints = preservedPoints; + + // Step to get the layout + forceInstance.step(); + } + else { + // Remove prev injected forceLayout instance + graphSeries.forceLayout = null; + } + }); + }; +}); \ No newline at end of file diff --git a/src/chart/graph/simpleLayout.js b/src/chart/graph/simpleLayout.js index 87f240cd63bf7a2ddc6be8d163c793f68a5ee2fb..0c0fa9478be70b87010a18d04fbb314354a4a28e 100644 --- a/src/chart/graph/simpleLayout.js +++ b/src/chart/graph/simpleLayout.js @@ -1,35 +1,11 @@ define(function (require) { + var simpleLayoutHelper = require('./simpleLayoutHelper'); return function (ecModel, api) { ecModel.eachSeriesByType('graph', function (seriesModel) { var layout = seriesModel.get('layout'); if (!layout || layout === 'none') { - var coordSys = seriesModel.coordinateSystem; - - if (coordSys && coordSys.type !== 'view') { - return; - } - var graph = seriesModel.getGraph(); - - graph.eachNode(function (node) { - var model = node.getModel(); - node.setLayout([+model.get('x'), +model.get('y')]); - }); - - graph.eachEdge(function (edge) { - var curveness = edge.getModel().get('lineStyle.normal.curveness'); - var p1 = edge.node1.getLayout(); - var p2 = edge.node2.getLayout(); - var cp1; - if (curveness > 0) { - cp1 = [ - (p1[0] + p2[0]) / 2 - (p1[1] - p2[1]) * curveness, - (p1[1] + p2[1]) / 2 - (p2[0] - p1[0]) * curveness - ]; - } - edge.setLayout([p1, p2, cp1]); - }); - + simpleLayoutHelper(seriesModel); } }); }; diff --git a/src/chart/graph/simpleLayoutHelper.js b/src/chart/graph/simpleLayoutHelper.js new file mode 100644 index 0000000000000000000000000000000000000000..2c115a4b767deb42d8049e81f0c410f4df582831 --- /dev/null +++ b/src/chart/graph/simpleLayoutHelper.js @@ -0,0 +1,28 @@ +define(function (require) { + return function (seriesModel) { + var coordSys = seriesModel.coordinateSystem; + if (coordSys && coordSys.type !== 'view') { + return; + } + var graph = seriesModel.getGraph(); + + graph.eachNode(function (node) { + var model = node.getModel(); + node.setLayout([+model.get('x'), +model.get('y')]); + }); + + graph.eachEdge(function (edge) { + var curveness = edge.getModel().get('lineStyle.normal.curveness') || 0; + var p1 = edge.node1.getLayout(); + var p2 = edge.node2.getLayout(); + var cp1; + if (curveness > 0) { + cp1 = [ + (p1[0] + p2[0]) / 2 - (p1[1] - p2[1]) * curveness, + (p1[1] + p2[1]) / 2 - (p2[0] - p1[0]) * curveness + ]; + } + edge.setLayout([p1, p2, cp1]); + }); + }; +}); \ No newline at end of file diff --git a/src/chart/heatmap.js b/src/chart/heatmap.js new file mode 100644 index 0000000000000000000000000000000000000000..b16b31a7ee8fcd45f0c0d08d828b95bf2e2351da --- /dev/null +++ b/src/chart/heatmap.js @@ -0,0 +1,5 @@ +define(function (require) { + + require('./heatmap/HeatmapSeries'); + require('./heatmap/HeatmapView'); +}); \ No newline at end of file diff --git a/src/chart/heatmap/HeatmapSeries.js b/src/chart/heatmap/HeatmapSeries.js new file mode 100644 index 0000000000000000000000000000000000000000..07235f7321fa38ae1ed1a6b8a6e19e304cca1168 --- /dev/null +++ b/src/chart/heatmap/HeatmapSeries.js @@ -0,0 +1,34 @@ +define(function (require) { + + var SeriesModel = require('../../model/Series'); + var createListFromArray = require('../helper/createListFromArray'); + + return SeriesModel.extend({ + type: 'series.heatmap', + + getInitialData: function (option, ecModel) { + return createListFromArray(option.data, this, ecModel); + }, + + defaultOption: { + + // Cartesian2D or geo + coordinateSystem: 'cartesian2d', + + zlevel: 0, + + z: 2, + + // Cartesian coordinate system + xAxisIndex: 0, + yAxisIndex: 0, + + // Geo coordinate system + geoIndex: 0, + + // No blur + // Available when heatmap is on geo + blurSize: 0 + } + }); +}); \ No newline at end of file diff --git a/src/chart/heatmap/HeatmapView.js b/src/chart/heatmap/HeatmapView.js new file mode 100644 index 0000000000000000000000000000000000000000..15bb7ea11c4885fff32f0db65ac8e583f36b3e07 --- /dev/null +++ b/src/chart/heatmap/HeatmapView.js @@ -0,0 +1,81 @@ +define(function (require) { + + var graphic = require('../../util/graphic'); + + return require('../../echarts').extendChartView({ + + type: 'heatmap', + + render: function (seriesModel, ecModel, api) { + var coordSys = seriesModel.coordinateSystem; + if (coordSys.type === 'cartesian2d') { + this._renderOnCartesian(coordSys, seriesModel, ecModel, api); + } + else if (coordSys.type === 'geo') { + this._renderOnGeo(coordSys, seriesModel, ecModel, api); + } + }, + + _renderOnCartesian: function (cartesian, seriesModel, ecModel, api) { + var xAxis = cartesian.getAxis('x'); + var yAxis = cartesian.getAxis('y'); + var group = this.group; + group.removeAll(); + + if (!(xAxis.type === 'category' && yAxis.type === 'category')) { + throw new Error('Heatmap on cartesian must have two category axes'); + } + if (!(xAxis.onBand && yAxis.onBand)) { + throw new Error('Heatmap on cartesian must have two axes with boundaryGap true'); + } + var width = xAxis.getBandWidth(); + var height = yAxis.getBandWidth(); + + var data = seriesModel.getData(); + data.each(['x', 'y', 'z'], function (x, y, z, idx) { + var itemModel = data.getItemModel(idx); + var point = cartesian.dataToPoint([x, y]); + var rect = new graphic.Rect({ + shape: { + x: point[0] - width / 2, + y: point[1] - height / 2, + width: width, + height: height + }, + style: { + fill: data.getItemVisual(idx, 'color') + } + }); + var style = itemModel.getModel('itemStyle.normal').getItemStyle(['color']); + var hoverStl = itemModel.getModel('itemStyle.emphasis').getItemStyle(); + var labelModel = itemModel.getModel('label.normal'); + var hoverLabelModel = itemModel.getModel('label.emphasis'); + + var rawValue = data.getRawValue(idx); + var defaultText = '-'; + if (rawValue && rawValue[2] != null) { + defaultText = rawValue[2]; + } + if (labelModel.get('show')) { + graphic.setText(style, labelModel); + style.text = seriesModel.getFormattedLabel(idx, 'normal') || defaultText; + } + if (hoverLabelModel.get('show')) { + graphic.setText(hoverStl, hoverLabelModel); + hoverStl.text = seriesModel.getFormattedLabel(idx, 'emphasis') || defaultText; + } + + rect.setStyle(style); + + graphic.setHoverStyle(rect, hoverStl); + + group.add(rect); + data.setItemGraphicEl(idx, rect); + }); + }, + + _renderOnGeo: function (geo, seriesModel, ecModel, api) { + + } + }); +}); \ No newline at end of file diff --git a/src/chart/helper/EffectLine.js b/src/chart/helper/EffectLine.js index 8e3703caa340ce4c02ac43aa0d52b5c4cd240014..7f07f1709671da80245a03299196503a812684dc 100644 --- a/src/chart/helper/EffectLine.js +++ b/src/chart/helper/EffectLine.js @@ -51,6 +51,8 @@ define(function (require) { var ty = quadraticDerivativeAt(p1[1], cp1[1], p2[1], t); this.rotation = -Math.atan2(ty, tx) - Math.PI / 2; + + this.ignore = false; } effectLineProto._updateEffectSymbol = function (lineData, idx) { @@ -68,6 +70,7 @@ define(function (require) { symbol = symbolUtil.createSymbol( symbolType, -0.5, -0.5, 1, 1, color ); + symbol.ignore = true; symbol.z2 = 100; this._symbolType = symbolType; this._period = period; diff --git a/src/chart/helper/Line.js b/src/chart/helper/Line.js index b2b9b654c18a3791000d654745d5d9025a13f4c1..b7314dfe1b16f3560aa15f493e6de38cddbd1626 100644 --- a/src/chart/helper/Line.js +++ b/src/chart/helper/Line.js @@ -36,7 +36,10 @@ define(function (require) { function createLine(points) { var line = new LinePath({ - name: 'line' + name: 'line', + style: { + strokeNoScale: true + } }); setLinePoints(line.shape, points); return line; diff --git a/src/chart/helper/Symbol.js b/src/chart/helper/Symbol.js index b1e79cd67faf051657b1107270f0fb797fe7cda0..353a78d80ea88d792e6be9738221e2beab333edb 100644 --- a/src/chart/helper/Symbol.js +++ b/src/chart/helper/Symbol.js @@ -29,6 +29,10 @@ define(function (require) { var symbolProto = Symbol.prototype; + function driftSymbol(dx, dy) { + this.parent.drift(dx, dy); + } + symbolProto._createSymbol = function (symbolType, data, idx) { // Remove paths created before this.removeAll(); @@ -47,6 +51,8 @@ define(function (require) { z2: 100, scale: [0, 0] }); + // Rewrite drift method + symbolPath.drift = driftSymbol; var size = normalizeSymbolSize(data.getItemVisual(idx, 'symbolSize')); @@ -99,6 +105,12 @@ define(function (require) { symbolPath.zlevel = zlevel; symbolPath.z = z; }; + + symbolProto.setDraggable = function (draggable) { + var symbolPath = this.childAt(0); + symbolPath.draggable = draggable; + symbolPath.cursor = draggable ? 'move' : 'pointer'; + }; /** * Update symbol properties * @param {module:echarts/data/List} data diff --git a/src/chart/helper/SymbolDraw.js b/src/chart/helper/SymbolDraw.js index adec0d9850b612c95a7330ad04d8414e9f6d6a4f..79bb35cceac8f73e34af4a616aad29cea08ce53f 100644 --- a/src/chart/helper/SymbolDraw.js +++ b/src/chart/helper/SymbolDraw.js @@ -19,6 +19,11 @@ define(function (require) { var symbolDrawProto = SymbolDraw.prototype; + function symbolNeedsDraw(data, idx, isIgnore) { + var point = data.getItemLayout(idx); + return point && !isNaN(point[0]) && !isNaN(point[1]) && !(isIgnore && isIgnore(idx)) + && data.getItemVisual(idx, 'symbol') !== 'none'; + } /** * Update symbols draw by new data * @param {module:echarts/data/List} data @@ -33,26 +38,21 @@ define(function (require) { data.diff(oldData) .add(function (newIdx) { - if ( - data.hasValue(newIdx) && !(isIgnore && isIgnore(newIdx)) - && data.getItemVisual(newIdx, 'symbol') !== 'none' - ) { + var point = data.getItemLayout(newIdx); + if (symbolNeedsDraw(data, newIdx, isIgnore)) { var symbolEl = new SymbolCtor(data, newIdx); - symbolEl.attr('position', data.getItemLayout(newIdx)); + symbolEl.attr('position', point); data.setItemGraphicEl(newIdx, symbolEl); group.add(symbolEl); } }) .update(function (newIdx, oldIdx) { var symbolEl = oldData.getItemGraphicEl(oldIdx); - // Empty data - if (!data.hasValue(newIdx) || (isIgnore && isIgnore(newIdx)) - || data.getItemVisual(newIdx, 'symbol') === 'none' - ) { + var point = data.getItemLayout(newIdx); + if (!symbolNeedsDraw(data, newIdx, isIgnore)) { group.remove(symbolEl); return; } - var point = data.getItemLayout(newIdx); if (!symbolEl) { symbolEl = new SymbolCtor(data, newIdx); symbolEl.attr('position', point); diff --git a/src/chart/helper/createGraphFromNodeEdge.js b/src/chart/helper/createGraphFromNodeEdge.js index e1f4b7d068e220333c0dc963206f2ac9e0a06ea8..982d1b11d727a02281cf58379222dcc38649ef56 100644 --- a/src/chart/helper/createGraphFromNodeEdge.js +++ b/src/chart/helper/createGraphFromNodeEdge.js @@ -4,18 +4,23 @@ define(function (require) { var Graph = require('../../data/Graph'); var linkList = require('../../data/helper/linkList'); var completeDimensions = require('../../data/helper/completeDimensions'); + var zrUtil = require('zrender/core/util'); return function (nodes, edges, hostModel, directed) { var graph = new Graph(directed); for (var i = 0; i < nodes.length; i++) { - graph.addNode(nodes[i].id || nodes[i].name, i); + graph.addNode(zrUtil.retrieve(nodes[i].id, nodes[i].name), i); } var linkNameList = []; + var validEdges = []; for (var i = 0; i < edges.length; i++) { var link = edges[i]; - linkNameList[i] = link.id || link.source + ' - ' + link.target; - graph.addEdge(link.source, link.target, i); + // addEdge may fail when source or target not exists + if (graph.addEdge(link.source, link.target, i)) { + validEdges.push(link); + linkNameList.push(zrUtil.retrieve(link.id, link.source + ' - ' + link.target)); + } } // FIXME @@ -25,11 +30,13 @@ define(function (require) { var edgeData = new List(['value'], hostModel); nodeData.initData(nodes); - edgeData.initData(edges, linkNameList); + edgeData.initData(validEdges, linkNameList); graph.setEdgeData(edgeData); linkList.linkToGraph(nodeData, graph); + // Update dataIndex of nodes and edges because invalid edge may be removed + graph.update(); return graph; }; diff --git a/src/chart/line/LineView.js b/src/chart/line/LineView.js index ed3805ae7ba5e6818e9accfcb8fcb91af351a010..de22b5062079174c740bc9bdc7ceca533d4e4ffa 100644 --- a/src/chart/line/LineView.js +++ b/src/chart/line/LineView.js @@ -187,7 +187,7 @@ define(function(require) { var lineGroup = this._lineGroup; - var hasAnimation = ecModel.get('animation'); + var hasAnimation = seriesModel.get('animation'); var isAreaChart = !areaStyleModel.isEmpty(); var stackedOnPoints = getStackedOnPoints(coordSys, data); @@ -329,7 +329,7 @@ define(function(require) { data.setItemGraphicEl(dataIndex, symbol); // Stop scale animation - symbol.stopAnimation(true); + symbol.stopSymbolAnimation(true); this.group.add(symbol); } diff --git a/src/chart/pie/PieView.js b/src/chart/pie/PieView.js index dd6f1f36c25549b6e014c7f2f6f9247acfcdc01c..b3ab6eb35bbefa8b3311eb3d047cf6a0a22e2fcc 100644 --- a/src/chart/pie/PieView.js +++ b/src/chart/pie/PieView.js @@ -214,10 +214,13 @@ define(function (require) { data.getItemLayout(idx), itemModel.get('selected'), seriesModel.get('selectedOffset'), - seriesModel.ecModel.get('animation') + seriesModel.get('animation') ); function onEmphasis() { + // Sector may has animation of updating data. Force to move to the last frame + // Or it may stopped on the wrong shape + sector.stopAnimation(true); sector.animateTo({ shape: { r: layout.r + 10 @@ -225,6 +228,7 @@ define(function (require) { }, 300, 'elasticOut'); } function onNormal() { + sector.stopAnimation(true); sector.animateTo({ shape: { r: layout.r diff --git a/src/component/dataRange/visualCoding.js b/src/component/dataRange/visualCoding.js index 3f1ec26e6824b0449c8e0c9c3a5acb4b5fc0333f..d81ed80b78f9fe765dd30b6c3625644b0c8de905 100644 --- a/src/component/dataRange/visualCoding.js +++ b/src/component/dataRange/visualCoding.js @@ -5,6 +5,7 @@ define(function (require) { var echarts = require('../../echarts'); var VisualMapping = require('../../visual/VisualMapping'); + var zrUtil = require('zrender/core/util'); echarts.registerVisualCoding('component', function (ecModel) { ecModel.eachComponent('dataRange', function (dataRangeModel) { @@ -13,8 +14,29 @@ define(function (require) { }); function processSingleDataRange(dataRangeModel, ecModel) { + var visualMappings = dataRangeModel.targetVisuals; + var visualTypesMap = {}; + var colorFuncsMap = {}; + zrUtil.each(['inRange', 'outOfRange'], function (state) { + var visualTypes = VisualMapping.prepareVisualTypes(visualMappings[state]); + var colorFunc = zrUtil.filter(zrUtil.map(visualTypes, function (visualType) { + return visualMappings[state][visualType].getColorMapper; + }), function (func) { + return !!func; + })[0]; + visualTypesMap[state] = visualTypes; + colorFuncsMap[state] = colorFunc; + }); + + // Cache color func + function colorFunc(value, out) { + var valueState = dataRangeModel.getValueState(value); + var colorFunc = colorFuncsMap[valueState]; + // PENDING + return colorFunc && colorFunc(value, out); + } + dataRangeModel.eachTargetSeries(function (seriesModel) { - var visualMappings = dataRangeModel.targetVisuals; var data = seriesModel.getData(); var dimension = dataRangeModel.getDataDimension(data); var dataIndex; @@ -27,11 +49,14 @@ define(function (require) { data.setItemVisual(dataIndex, key, value); } + data.setVisual('colorFunc', colorFunc); + data.each([dimension], function (value, index) { // For performance consideration, do not use curry. dataIndex = index; - var mappings = visualMappings[dataRangeModel.getValueState(value)]; - var visualTypes = VisualMapping.prepareVisualTypes(mappings); + var valueState = dataRangeModel.getValueState(value); + var mappings = visualMappings[valueState]; + var visualTypes = visualTypesMap[valueState]; for (var i = 0, len = visualTypes.length; i < len; i++) { var type = visualTypes[i]; mappings[type] && mappings[type].applyVisual(value, getVisual, setVisual); diff --git a/src/component/tooltip.js b/src/component/tooltip.js index c4c0ffd9bbd89540e5c7c62eee471afe47b9433c..5bf984c5ac47892b8baa62d3a8e8dde7744815b2 100644 --- a/src/component/tooltip.js +++ b/src/component/tooltip.js @@ -120,6 +120,10 @@ define(function (require) { var rectWidth = rect.width; var rectHeight = rect.height; switch (position) { + case 'inside': + x = rect.x + rectWidth / 2 - domWidth / 2; + y = rect.y + rectHeight / 2 - domHeight / 2; + break; case 'top': x = rect.x + rectWidth / 2 - domWidth / 2; y = rect.y - domHeight - gap; @@ -463,8 +467,8 @@ define(function (require) { var moveAnimation = axisPointerType !== 'cross'; if (axisPointerType === 'cross') { - moveGridLine('x', point, cartesian.getAxis('y').getExtent()); - moveGridLine('y', point, cartesian.getAxis('x').getExtent()); + moveGridLine('x', point, cartesian.getAxis('y').getGlobalExtent()); + moveGridLine('y', point, cartesian.getAxis('x').getGlobalExtent()); this._updateCrossText(cartesian, point, axisPointerModel); } diff --git a/src/coord/cartesian/Grid.js b/src/coord/cartesian/Grid.js index a5bf74fbbf9f0e318e28bfe9e78237ec0ba96a11..1c57a85244a14db422cf05efafdff07c93780bd2 100644 --- a/src/coord/cartesian/Grid.js +++ b/src/coord/cartesian/Grid.js @@ -107,16 +107,18 @@ define(function(require, factory) { // Minus label size if (gridModel.get('containLabel')) { each(axesList, function (axis) { - var labelUnionRect = getLabelUnionRect(axis); - if (labelUnionRect) { - var dim = axis.isHorizontal() ? 'height' : 'width'; - var margin = axis.model.get('axisLabel.margin'); - gridRect[dim] -= labelUnionRect[dim] + margin; - if (axis.position === 'top') { - gridRect.y += labelUnionRect.height + margin; - } - else if (axis.position === 'left') { - gridRect.x += labelUnionRect.width + margin; + if (!axis.model.get('axisLabel.inside')) { + var labelUnionRect = getLabelUnionRect(axis); + if (labelUnionRect) { + var dim = axis.isHorizontal() ? 'height' : 'width'; + var margin = axis.model.get('axisLabel.margin'); + gridRect[dim] -= labelUnionRect[dim] + margin; + if (axis.position === 'top') { + gridRect.y += labelUnionRect.height + margin; + } + else if (axis.position === 'left') { + gridRect.x += labelUnionRect.width + margin; + } } } }); diff --git a/src/data/Graph.js b/src/data/Graph.js index 56795111bdb61a98dc5d92db1baacd823fa4e094..d4f97999cf256ed3d903536afe5aa2dca6245d77 100644 --- a/src/data/Graph.js +++ b/src/data/Graph.js @@ -81,8 +81,13 @@ define(function(require) { graphProto.addNode = function (id, dataIndex) { var nodesMap = this._nodesMap; + // Assign dataIndex as id if not exists + if (id == null) { + id = dataIndex; + } + if (nodesMap[id]) { - return nodesMap[id]; + return; } var node = new Node(id, dataIndex); @@ -94,6 +99,15 @@ define(function(require) { return node; }; + /** + * Get node by data index + * @param {number} dataIndex + * @return {module:echarts/data/Graph~Node} + */ + graphProto.getNodeByIndex = function (dataIndex) { + var rawIdx = this.data.getRawIndex(dataIndex); + return this.nodes[rawIdx]; + }; /** * Get node by id * @param {string} id @@ -114,10 +128,10 @@ define(function(require) { var nodesMap = this._nodesMap; var edgesMap = this._edgesMap; - if (typeof n1 == 'string') { + if (!(n1 instanceof Node)) { n1 = nodesMap[n1]; } - if (typeof n2 == 'string') { + if (!(n2 instanceof Node)) { n2 = nodesMap[n2]; } if (!n1 || !n2) { @@ -125,8 +139,9 @@ define(function(require) { } var key = n1.id + '-' + n2.id; + // PENDING if (edgesMap[key]) { - return edgesMap[key]; + return; } var edge = new Edge(n1, n2, dataIndex); @@ -148,16 +163,25 @@ define(function(require) { }; /** - * Get edge by two nodes + * Get edge by data index + * @param {number} dataIndex + * @return {module:echarts/data/Graph~Node} + */ + graphProto.getEdgeByIndex = function (dataIndex) { + var rawIdx = this.edgeData.getRawIndex(dataIndex); + return this.edges[rawIdx]; + }; + /** + * Get edge by two linked nodes * @param {module:echarts/data/Graph.Node|string} n1 * @param {module:echarts/data/Graph.Node|string} n2 * @return {module:echarts/data/Graph.Edge} */ graphProto.getEdge = function (n1, n2) { - if (typeof(n1) !== 'string') { + if (n1 instanceof Node) { n1 = n1.id; } - if (typeof(n2) !== 'string') { + if (n2 instanceof Node) { n2 = n2.id; } @@ -214,7 +238,7 @@ define(function(require) { graphProto.breadthFirstTraverse = function ( cb, startNode, direction, context ) { - if (typeof(startNode) === 'string') { + if (!startNode instanceof Node) { startNode = this._nodesMap[startNode]; } if (!startNode) { @@ -254,11 +278,11 @@ define(function(require) { }; // TODO - graphProto.depthFirstTraverse = function ( - cb, startNode, direction, context - ) { + // graphProto.depthFirstTraverse = function ( + // cb, startNode, direction, context + // ) { - }; + // }; // Filter update graphProto.update = function () { @@ -326,7 +350,7 @@ define(function(require) { /** * @type {string} */ - this.id = id || ''; + this.id = id == null ? '' : id; /** * @type {Array.} diff --git a/src/echarts.js b/src/echarts.js index f5fe330a08664f2cad56a8eefa67df9af9d50d61..5a36add6d6aeac55a5e3f71607cb0dc1e1f852ee 100644 --- a/src/echarts.js +++ b/src/echarts.js @@ -73,7 +73,8 @@ define(function (require) { * @private */ this._zr = zrender.init(dom, { - renderer: opts.renderer || 'canvas' + renderer: opts.renderer || 'canvas', + devicePixelRatio: opts.devicePixelRatio }); /** @@ -736,6 +737,9 @@ define(function (require) { chart.id = idBase++; instances[chart.id] = chart; + dom.setAttribute && + dom.setAttribute(DOM_ATTRIBUTE_KEY, chart.id); + // Connecting zrUtil.each(eventActionMap, function (actionType, eventType) { // FIXME diff --git a/src/util/number.js b/src/util/number.js index 96e9a11ce02de1e826412414dc0177a2d86f8fe6..b181856cc362a0cd702c7704dcc58682b59a221d 100644 --- a/src/util/number.js +++ b/src/util/number.js @@ -31,8 +31,7 @@ define(function (require) { var sub = domain[1] - domain[0]; if (sub === 0) { - // FIXME - return range[0]; + return (range[0] + range[1]) / 2; } var t = (val - domain[0]) / sub; diff --git a/src/visual/VisualMapping.js b/src/visual/VisualMapping.js index 7c044cb287c93fd6467e107bfc4b1f0c36b37bcc..8a48f24059d9bb58036151677940057cfeb1e78b 100644 --- a/src/visual/VisualMapping.js +++ b/src/visual/VisualMapping.js @@ -96,6 +96,29 @@ define(function (require) { applyVisual: defaultApplyColor, + /** + * Create a mapper function + * @return {Function} + */ + getColorMapper: function () { + var visual = isCategory(this) + ? this.option.visual + : zrUtil.map(this.option.visual, zrUtil.parse); + return isCategory(this) + ? function (value) { + return getVisualForCategory(this, visual, this._normalizeData(value)); + } + : function (value, out) { + // If output rgb array + // which will be much faster and useful in pixel manipulation + var returnRGBArray = !!out; + out = zrColor.fastMapToColor( + this._normalizeData(value), visual, out + ); + return returnRGBArray ? out : zrUtil.stringify(out, 'rgba'); + }; + }, + // value: // (1) {number} // (2) {Array.} Represents a interval, for colorStops. @@ -113,7 +136,7 @@ define(function (require) { this._normalizeData(value[1]) ]; - // For creating graduate color list. + // For creating gradient color list. return zrColor.mapIntervalToColor(value, visual); } else { diff --git a/test/force.html b/test/force.html new file mode 100644 index 0000000000000000000000000000000000000000..be1dffdc6b48050a229641523b1711f4001ce639 --- /dev/null +++ b/test/force.html @@ -0,0 +1,96 @@ + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/test/force2.html b/test/force2.html new file mode 100644 index 0000000000000000000000000000000000000000..c3075bdbc7543aca858919de61f25fbba14ddc18 --- /dev/null +++ b/test/force2.html @@ -0,0 +1,104 @@ + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/test/force3.html b/test/force3.html new file mode 100644 index 0000000000000000000000000000000000000000..484334e00b09b1c69565465eb5620d13d5871dee --- /dev/null +++ b/test/force3.html @@ -0,0 +1,81 @@ + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/test/graph.html b/test/graph.html index d91db3cd89e14d74319a367c7aa52e3f0ebf8ff9..ca7114d153643d6ab4138d135b268e4dd8273b4a 100644 --- a/test/graph.html +++ b/test/graph.html @@ -64,7 +64,7 @@ } }, animationDurationUpdate: 1500, - animationEasing: 'quinticInOut', + animationEasingUpdate: 'quinticInOut', series : [ { name: 'Les Miserables', diff --git a/test/heatmap.html b/test/heatmap.html new file mode 100644 index 0000000000000000000000000000000000000000..09c483a2c6ea03ea06f8135b9b03064c56ae1b0d --- /dev/null +++ b/test/heatmap.html @@ -0,0 +1,88 @@ + + + + + + + + + +
+ + + \ No newline at end of file