1
0
mirror of https://gitlab.com/JKANetwork/CheckServer.git synced 2026-03-28 13:22:01 +01:00

Start again

This commit is contained in:
2020-10-04 17:14:00 +02:00
parent c0d3912413
commit 091f119048
4382 changed files with 1762543 additions and 9606 deletions

View File

@@ -0,0 +1,7 @@
define(function(require) {
'use strict';
require('../coord/polar/polarCreator');
require('./axis/AngleAxisView');
});

8
vendors/echarts/src/component/axis.js vendored Normal file
View File

@@ -0,0 +1,8 @@
// TODO boundaryGap
define(function(require) {
'use strict';
require('../coord/cartesian/AxisModel');
require('./axis/AxisView');
});

View File

@@ -0,0 +1,224 @@
define(function (require) {
'use strict';
var zrUtil = require('zrender/core/util');
var graphic = require('../../util/graphic');
var Model = require('../../model/Model');
var elementList = ['axisLine', 'axisLabel', 'axisTick', 'splitLine', 'splitArea'];
function getAxisLineShape(polar, r0, r, angle) {
var start = polar.coordToPoint([r0, angle]);
var end = polar.coordToPoint([r, angle]);
return {
x1: start[0],
y1: start[1],
x2: end[0],
y2: end[1]
};
}
require('../../echarts').extendComponentView({
type: 'angleAxis',
render: function (angleAxisModel, ecModel) {
this.group.removeAll();
if (!angleAxisModel.get('show')) {
return;
}
var polarModel = ecModel.getComponent('polar', angleAxisModel.get('polarIndex'));
var angleAxis = angleAxisModel.axis;
var polar = polarModel.coordinateSystem;
var radiusExtent = polar.getRadiusAxis().getExtent();
var ticksAngles = angleAxis.getTicksCoords();
if (angleAxis.type !== 'category') {
// Remove the last tick which will overlap the first tick
ticksAngles.pop();
}
zrUtil.each(elementList, function (name) {
if (angleAxisModel.get(name +'.show')) {
this['_' + name](angleAxisModel, polar, ticksAngles, radiusExtent);
}
}, this);
},
/**
* @private
*/
_axisLine: function (angleAxisModel, polar, ticksAngles, radiusExtent) {
var lineStyleModel = angleAxisModel.getModel('axisLine.lineStyle');
var circle = new graphic.Circle({
shape: {
cx: polar.cx,
cy: polar.cy,
r: radiusExtent[1]
},
style: lineStyleModel.getLineStyle(),
z2: 1,
silent: true
});
circle.style.fill = null;
this.group.add(circle);
},
/**
* @private
*/
_axisTick: function (angleAxisModel, polar, ticksAngles, radiusExtent) {
var tickModel = angleAxisModel.getModel('axisTick');
var tickLen = (tickModel.get('inside') ? -1 : 1) * tickModel.get('length');
var lines = zrUtil.map(ticksAngles, function (tickAngle) {
return new graphic.Line({
shape: getAxisLineShape(polar, radiusExtent[1], radiusExtent[1] + tickLen, tickAngle)
});
});
this.group.add(graphic.mergePath(
lines, {
style: tickModel.getModel('lineStyle').getLineStyle()
}
));
},
/**
* @private
*/
_axisLabel: function (angleAxisModel, polar, ticksAngles, radiusExtent) {
var axis = angleAxisModel.axis;
var categoryData = angleAxisModel.get('data');
var labelModel = angleAxisModel.getModel('axisLabel');
var axisTextStyleModel = labelModel.getModel('textStyle');
var labels = angleAxisModel.getFormattedLabels();
var labelMargin = labelModel.get('margin');
var labelsAngles = axis.getLabelsCoords();
// Use length of ticksAngles because it may remove the last tick to avoid overlapping
for (var i = 0; i < ticksAngles.length; i++) {
var r = radiusExtent[1];
var p = polar.coordToPoint([r + labelMargin, labelsAngles[i]]);
var cx = polar.cx;
var cy = polar.cy;
var labelTextAlign = Math.abs(p[0] - cx) / r < 0.3
? 'center' : (p[0] > cx ? 'left' : 'right');
var labelTextBaseline = Math.abs(p[1] - cy) / r < 0.3
? 'middle' : (p[1] > cy ? 'top' : 'bottom');
var textStyleModel = axisTextStyleModel;
if (categoryData && categoryData[i] && categoryData[i].textStyle) {
textStyleModel = new Model(
categoryData[i].textStyle, axisTextStyleModel
);
}
this.group.add(new graphic.Text({
style: {
x: p[0],
y: p[1],
fill: textStyleModel.getTextColor(),
text: labels[i],
textAlign: labelTextAlign,
textVerticalAlign: labelTextBaseline,
textFont: textStyleModel.getFont()
},
silent: true
}));
}
},
/**
* @private
*/
_splitLine: function (angleAxisModel, polar, ticksAngles, radiusExtent) {
var splitLineModel = angleAxisModel.getModel('splitLine');
var lineStyleModel = splitLineModel.getModel('lineStyle');
var lineColors = lineStyleModel.get('color');
var lineCount = 0;
lineColors = lineColors instanceof Array ? lineColors : [lineColors];
var splitLines = [];
for (var i = 0; i < ticksAngles.length; i++) {
var colorIndex = (lineCount++) % lineColors.length;
splitLines[colorIndex] = splitLines[colorIndex] || [];
splitLines[colorIndex].push(new graphic.Line({
shape: getAxisLineShape(polar, radiusExtent[0], radiusExtent[1], ticksAngles[i])
}));
}
// Simple optimization
// Batching the lines if color are the same
for (var i = 0; i < splitLines.length; i++) {
this.group.add(graphic.mergePath(splitLines[i], {
style: zrUtil.defaults({
stroke: lineColors[i % lineColors.length]
}, lineStyleModel.getLineStyle()),
silent: true,
z: angleAxisModel.get('z')
}));
}
},
/**
* @private
*/
_splitArea: function (angleAxisModel, polar, ticksAngles, radiusExtent) {
var splitAreaModel = angleAxisModel.getModel('splitArea');
var areaStyleModel = splitAreaModel.getModel('areaStyle');
var areaColors = areaStyleModel.get('color');
var lineCount = 0;
areaColors = areaColors instanceof Array ? areaColors : [areaColors];
var splitAreas = [];
var RADIAN = Math.PI / 180;
var prevAngle = -ticksAngles[0] * RADIAN;
var r0 = Math.min(radiusExtent[0], radiusExtent[1]);
var r1 = Math.max(radiusExtent[0], radiusExtent[1]);
var clockwise = angleAxisModel.get('clockwise');
for (var i = 1; i < ticksAngles.length; i++) {
var colorIndex = (lineCount++) % areaColors.length;
splitAreas[colorIndex] = splitAreas[colorIndex] || [];
splitAreas[colorIndex].push(new graphic.Sector({
shape: {
cx: polar.cx,
cy: polar.cy,
r0: r0,
r: r1,
startAngle: prevAngle,
endAngle: -ticksAngles[i] * RADIAN,
clockwise: clockwise
},
silent: true
}));
prevAngle = -ticksAngles[i] * RADIAN;
}
// Simple optimization
// Batching the lines if color are the same
for (var i = 0; i < splitAreas.length; i++) {
this.group.add(graphic.mergePath(splitAreas[i], {
style: zrUtil.defaults({
fill: areaColors[i % areaColors.length]
}, areaStyleModel.getAreaStyle()),
silent: true
}));
}
}
});
});

View File

@@ -0,0 +1,463 @@
define(function (require) {
var zrUtil = require('zrender/core/util');
var graphic = require('../../util/graphic');
var Model = require('../../model/Model');
var numberUtil = require('../../util/number');
var remRadian = numberUtil.remRadian;
var isRadianAroundZero = numberUtil.isRadianAroundZero;
var PI = Math.PI;
function makeAxisEventDataBase(axisModel) {
var eventData = {
componentType: axisModel.mainType
};
eventData[axisModel.mainType + 'Index'] = axisModel.componentIndex;
return eventData;
}
/**
* A final axis is translated and rotated from a "standard axis".
* So opt.position and opt.rotation is required.
*
* A standard axis is and axis from [0, 0] to [0, axisExtent[1]],
* for example: (0, 0) ------------> (0, 50)
*
* nameDirection or tickDirection or labelDirection is 1 means tick
* or label is below the standard axis, whereas is -1 means above
* the standard axis. labelOffset means offset between label and axis,
* which is useful when 'onZero', where axisLabel is in the grid and
* label in outside grid.
*
* Tips: like always,
* positive rotation represents anticlockwise, and negative rotation
* represents clockwise.
* The direction of position coordinate is the same as the direction
* of screen coordinate.
*
* Do not need to consider axis 'inverse', which is auto processed by
* axis extent.
*
* @param {module:zrender/container/Group} group
* @param {Object} axisModel
* @param {Object} opt Standard axis parameters.
* @param {Array.<number>} opt.position [x, y]
* @param {number} opt.rotation by radian
* @param {number} [opt.nameDirection=1] 1 or -1 Used when nameLocation is 'middle'.
* @param {number} [opt.tickDirection=1] 1 or -1
* @param {number} [opt.labelDirection=1] 1 or -1
* @param {number} [opt.labelOffset=0] Usefull when onZero.
* @param {string} [opt.axisName] default get from axisModel.
* @param {number} [opt.labelRotation] by degree, default get from axisModel.
* @param {number} [opt.labelInterval] Default label interval when label
* interval from model is null or 'auto'.
* @param {number} [opt.strokeContainThreshold] Default label interval when label
* @param {number} [opt.axisLineSilent=true] If axis line is silent
*/
var AxisBuilder = function (axisModel, opt) {
/**
* @readOnly
*/
this.opt = opt;
/**
* @readOnly
*/
this.axisModel = axisModel;
// Default value
zrUtil.defaults(
opt,
{
labelOffset: 0,
nameDirection: 1,
tickDirection: 1,
labelDirection: 1,
silent: true
}
);
/**
* @readOnly
*/
this.group = new graphic.Group({
position: opt.position.slice(),
rotation: opt.rotation
});
};
AxisBuilder.prototype = {
constructor: AxisBuilder,
hasBuilder: function (name) {
return !!builders[name];
},
add: function (name) {
builders[name].call(this);
},
getGroup: function () {
return this.group;
}
};
var builders = {
/**
* @private
*/
axisLine: function () {
var opt = this.opt;
var axisModel = this.axisModel;
if (!axisModel.get('axisLine.show')) {
return;
}
var extent = this.axisModel.axis.getExtent();
this.group.add(new graphic.Line({
shape: {
x1: extent[0],
y1: 0,
x2: extent[1],
y2: 0
},
style: zrUtil.extend(
{lineCap: 'round'},
axisModel.getModel('axisLine.lineStyle').getLineStyle()
),
strokeContainThreshold: opt.strokeContainThreshold,
silent: !!opt.axisLineSilent,
z2: 1
}));
},
/**
* @private
*/
axisTick: function () {
var axisModel = this.axisModel;
if (!axisModel.get('axisTick.show')) {
return;
}
var axis = axisModel.axis;
var tickModel = axisModel.getModel('axisTick');
var opt = this.opt;
var lineStyleModel = tickModel.getModel('lineStyle');
var tickLen = tickModel.get('length');
var tickInterval = getInterval(tickModel, opt.labelInterval);
var ticksCoords = axis.getTicksCoords();
var tickLines = [];
for (var i = 0; i < ticksCoords.length; i++) {
// Only ordinal scale support tick interval
if (ifIgnoreOnTick(axis, i, tickInterval)) {
continue;
}
var tickCoord = ticksCoords[i];
// Tick line
tickLines.push(new graphic.Line(graphic.subPixelOptimizeLine({
shape: {
x1: tickCoord,
y1: 0,
x2: tickCoord,
y2: opt.tickDirection * tickLen
},
style: {
lineWidth: lineStyleModel.get('width')
},
silent: true
})));
}
this.group.add(graphic.mergePath(tickLines, {
style: lineStyleModel.getLineStyle(),
z2: 2,
silent: true
}));
},
/**
* @param {module:echarts/coord/cartesian/AxisModel} axisModel
* @param {module:echarts/coord/cartesian/GridModel} gridModel
* @private
*/
axisLabel: function () {
var axisModel = this.axisModel;
if (!axisModel.get('axisLabel.show')) {
return;
}
var opt = this.opt;
var axis = axisModel.axis;
var labelModel = axisModel.getModel('axisLabel');
var textStyleModel = labelModel.getModel('textStyle');
var labelMargin = labelModel.get('margin');
var ticks = axis.scale.getTicks();
var labels = axisModel.getFormattedLabels();
// Special label rotate.
var labelRotation = opt.labelRotation;
if (labelRotation == null) {
labelRotation = labelModel.get('rotate') || 0;
}
// To radian.
labelRotation = labelRotation * PI / 180;
var labelLayout = innerTextLayout(opt, labelRotation, opt.labelDirection);
var categoryData = axisModel.get('data');
var textEls = [];
var isSilent = axisModel.get('silent');
for (var i = 0; i < ticks.length; i++) {
if (ifIgnoreOnTick(axis, i, opt.labelInterval)) {
continue;
}
var itemTextStyleModel = textStyleModel;
if (categoryData && categoryData[i] && categoryData[i].textStyle) {
itemTextStyleModel = new Model(
categoryData[i].textStyle, textStyleModel, axisModel.ecModel
);
}
var textColor = itemTextStyleModel.getTextColor();
var tickCoord = axis.dataToCoord(ticks[i]);
var pos = [
tickCoord,
opt.labelOffset + opt.labelDirection * labelMargin
];
var labelBeforeFormat = axis.scale.getLabel(ticks[i]);
var textEl = new graphic.Text({
style: {
text: labels[i],
textAlign: itemTextStyleModel.get('align', true) || labelLayout.textAlign,
textVerticalAlign: itemTextStyleModel.get('baseline', true) || labelLayout.verticalAlign,
textFont: itemTextStyleModel.getFont(),
fill: typeof textColor === 'function' ? textColor(labelBeforeFormat) : textColor
},
position: pos,
rotation: labelLayout.rotation,
silent: isSilent,
z2: 10
});
// Pack data for mouse event
textEl.eventData = makeAxisEventDataBase(axisModel);
textEl.eventData.targetType = 'axisLabel';
textEl.eventData.value = labelBeforeFormat;
textEls.push(textEl);
this.group.add(textEl);
}
function isTwoLabelOverlapped(current, next) {
var firstRect = current && current.getBoundingRect().clone();
var nextRect = next && next.getBoundingRect().clone();
if (firstRect && nextRect) {
firstRect.applyTransform(current.getLocalTransform());
nextRect.applyTransform(next.getLocalTransform());
return firstRect.intersect(nextRect);
}
}
if (axis.type !== 'category') {
// If min or max are user set, we need to check
// If the tick on min(max) are overlap on their neighbour tick
// If they are overlapped, we need to hide the min(max) tick label
if (axisModel.getMin ? axisModel.getMin() : axisModel.get('min')) {
var firstLabel = textEls[0];
var nextLabel = textEls[1];
if (isTwoLabelOverlapped(firstLabel, nextLabel)) {
firstLabel.ignore = true;
}
}
if (axisModel.getMax ? axisModel.getMax() : axisModel.get('max')) {
var lastLabel = textEls[textEls.length - 1];
var prevLabel = textEls[textEls.length - 2];
if (isTwoLabelOverlapped(prevLabel, lastLabel)) {
lastLabel.ignore = true;
}
}
}
},
/**
* @private
*/
axisName: function () {
var opt = this.opt;
var axisModel = this.axisModel;
var name = this.opt.axisName;
// If name is '', do not get name from axisMode.
if (name == null) {
name = axisModel.get('name');
}
if (!name) {
return;
}
var nameLocation = axisModel.get('nameLocation');
var nameDirection = opt.nameDirection;
var textStyleModel = axisModel.getModel('nameTextStyle');
var gap = axisModel.get('nameGap') || 0;
var extent = this.axisModel.axis.getExtent();
var gapSignal = extent[0] > extent[1] ? -1 : 1;
var pos = [
nameLocation === 'start'
? extent[0] - gapSignal * gap
: nameLocation === 'end'
? extent[1] + gapSignal * gap
: (extent[0] + extent[1]) / 2, // 'middle'
// Reuse labelOffset.
nameLocation === 'middle' ? opt.labelOffset + nameDirection * gap : 0
];
var labelLayout;
if (nameLocation === 'middle') {
labelLayout = innerTextLayout(opt, opt.rotation, nameDirection);
}
else {
labelLayout = endTextLayout(opt, nameLocation, extent);
}
var textEl = new graphic.Text({
style: {
text: name,
textFont: textStyleModel.getFont(),
fill: textStyleModel.getTextColor()
|| axisModel.get('axisLine.lineStyle.color'),
textAlign: labelLayout.textAlign,
textVerticalAlign: labelLayout.verticalAlign
},
position: pos,
rotation: labelLayout.rotation,
silent: axisModel.get('silent'),
z2: 1
});
textEl.eventData = makeAxisEventDataBase(axisModel);
textEl.eventData.targetType = 'axisName';
textEl.eventData.name = name;
this.group.add(textEl);
}
};
/**
* @inner
*/
function innerTextLayout(opt, textRotation, direction) {
var rotationDiff = remRadian(textRotation - opt.rotation);
var textAlign;
var verticalAlign;
if (isRadianAroundZero(rotationDiff)) { // Label is parallel with axis line.
verticalAlign = direction > 0 ? 'top' : 'bottom';
textAlign = 'center';
}
else if (isRadianAroundZero(rotationDiff - PI)) { // Label is inverse parallel with axis line.
verticalAlign = direction > 0 ? 'bottom' : 'top';
textAlign = 'center';
}
else {
verticalAlign = 'middle';
if (rotationDiff > 0 && rotationDiff < PI) {
textAlign = direction > 0 ? 'right' : 'left';
}
else {
textAlign = direction > 0 ? 'left' : 'right';
}
}
return {
rotation: rotationDiff,
textAlign: textAlign,
verticalAlign: verticalAlign
};
}
/**
* @inner
*/
function endTextLayout(opt, textPosition, extent) {
var rotationDiff = remRadian(-opt.rotation);
var textAlign;
var verticalAlign;
var inverse = extent[0] > extent[1];
var onLeft = (textPosition === 'start' && !inverse)
|| (textPosition !== 'start' && inverse);
if (isRadianAroundZero(rotationDiff - PI / 2)) {
verticalAlign = onLeft ? 'bottom' : 'top';
textAlign = 'center';
}
else if (isRadianAroundZero(rotationDiff - PI * 1.5)) {
verticalAlign = onLeft ? 'top' : 'bottom';
textAlign = 'center';
}
else {
verticalAlign = 'middle';
if (rotationDiff < PI * 1.5 && rotationDiff > PI / 2) {
textAlign = onLeft ? 'left' : 'right';
}
else {
textAlign = onLeft ? 'right' : 'left';
}
}
return {
rotation: rotationDiff,
textAlign: textAlign,
verticalAlign: verticalAlign
};
}
/**
* @static
*/
var ifIgnoreOnTick = AxisBuilder.ifIgnoreOnTick = function (axis, i, interval) {
var rawTick;
var scale = axis.scale;
return scale.type === 'ordinal'
&& (
typeof interval === 'function'
? (
rawTick = scale.getTicks()[i],
!interval(rawTick, scale.getLabel(rawTick))
)
: i % (interval + 1)
);
};
/**
* @static
*/
var getInterval = AxisBuilder.getInterval = function (model, labelInterval) {
var interval = model.get('interval');
if (interval == null || interval == 'auto') {
interval = labelInterval;
}
return interval;
};
return AxisBuilder;
});

View File

@@ -0,0 +1,274 @@
define(function (require) {
var zrUtil = require('zrender/core/util');
var graphic = require('../../util/graphic');
var AxisBuilder = require('./AxisBuilder');
var ifIgnoreOnTick = AxisBuilder.ifIgnoreOnTick;
var getInterval = AxisBuilder.getInterval;
var axisBuilderAttrs = [
'axisLine', 'axisLabel', 'axisTick', 'axisName'
];
var selfBuilderAttrs = [
'splitLine', 'splitArea'
];
var AxisView = require('../../echarts').extendComponentView({
type: 'axis',
render: function (axisModel, ecModel) {
this.group.removeAll();
if (!axisModel.get('show')) {
return;
}
var gridModel = ecModel.getComponent('grid', axisModel.get('gridIndex'));
var layout = layoutAxis(gridModel, axisModel);
var axisBuilder = new AxisBuilder(axisModel, layout);
zrUtil.each(axisBuilderAttrs, axisBuilder.add, axisBuilder);
this.group.add(axisBuilder.getGroup());
zrUtil.each(selfBuilderAttrs, function (name) {
if (axisModel.get(name +'.show')) {
this['_' + name](axisModel, gridModel, layout.labelInterval);
}
}, this);
},
/**
* @param {module:echarts/coord/cartesian/AxisModel} axisModel
* @param {module:echarts/coord/cartesian/GridModel} gridModel
* @param {number|Function} labelInterval
* @private
*/
_splitLine: function (axisModel, gridModel, labelInterval) {
var axis = axisModel.axis;
var splitLineModel = axisModel.getModel('splitLine');
var lineStyleModel = splitLineModel.getModel('lineStyle');
var lineWidth = lineStyleModel.get('width');
var lineColors = lineStyleModel.get('color');
var lineInterval = getInterval(splitLineModel, labelInterval);
lineColors = zrUtil.isArray(lineColors) ? lineColors : [lineColors];
var gridRect = gridModel.coordinateSystem.getRect();
var isHorizontal = axis.isHorizontal();
var splitLines = [];
var lineCount = 0;
var ticksCoords = axis.getTicksCoords();
var p1 = [];
var p2 = [];
for (var i = 0; i < ticksCoords.length; i++) {
if (ifIgnoreOnTick(axis, i, lineInterval)) {
continue;
}
var tickCoord = axis.toGlobalCoord(ticksCoords[i]);
if (isHorizontal) {
p1[0] = tickCoord;
p1[1] = gridRect.y;
p2[0] = tickCoord;
p2[1] = gridRect.y + gridRect.height;
}
else {
p1[0] = gridRect.x;
p1[1] = tickCoord;
p2[0] = gridRect.x + gridRect.width;
p2[1] = tickCoord;
}
var colorIndex = (lineCount++) % lineColors.length;
splitLines[colorIndex] = splitLines[colorIndex] || [];
splitLines[colorIndex].push(new graphic.Line(graphic.subPixelOptimizeLine({
shape: {
x1: p1[0],
y1: p1[1],
x2: p2[0],
y2: p2[1]
},
style: {
lineWidth: lineWidth
},
silent: true
})));
}
// Simple optimization
// Batching the lines if color are the same
var lineStyle = lineStyleModel.getLineStyle();
for (var i = 0; i < splitLines.length; i++) {
this.group.add(graphic.mergePath(splitLines[i], {
style: zrUtil.defaults({
stroke: lineColors[i % lineColors.length]
}, lineStyle),
silent: true
}));
}
},
/**
* @param {module:echarts/coord/cartesian/AxisModel} axisModel
* @param {module:echarts/coord/cartesian/GridModel} gridModel
* @param {number|Function} labelInterval
* @private
*/
_splitArea: function (axisModel, gridModel, labelInterval) {
var axis = axisModel.axis;
var splitAreaModel = axisModel.getModel('splitArea');
var areaStyleModel = splitAreaModel.getModel('areaStyle');
var areaColors = areaStyleModel.get('color');
var gridRect = gridModel.coordinateSystem.getRect();
var ticksCoords = axis.getTicksCoords();
var prevX = axis.toGlobalCoord(ticksCoords[0]);
var prevY = axis.toGlobalCoord(ticksCoords[0]);
var splitAreaRects = [];
var count = 0;
var areaInterval = getInterval(splitAreaModel, labelInterval);
areaColors = zrUtil.isArray(areaColors) ? areaColors : [areaColors];
for (var i = 1; i < ticksCoords.length; i++) {
if (ifIgnoreOnTick(axis, i, areaInterval)) {
continue;
}
var tickCoord = axis.toGlobalCoord(ticksCoords[i]);
var x;
var y;
var width;
var height;
if (axis.isHorizontal()) {
x = prevX;
y = gridRect.y;
width = tickCoord - x;
height = gridRect.height;
}
else {
x = gridRect.x;
y = prevY;
width = gridRect.width;
height = tickCoord - y;
}
var colorIndex = (count++) % areaColors.length;
splitAreaRects[colorIndex] = splitAreaRects[colorIndex] || [];
splitAreaRects[colorIndex].push(new graphic.Rect({
shape: {
x: x,
y: y,
width: width,
height: height
},
silent: true
}));
prevX = x + width;
prevY = y + height;
}
// Simple optimization
// Batching the rects if color are the same
var areaStyle = areaStyleModel.getAreaStyle();
for (var i = 0; i < splitAreaRects.length; i++) {
this.group.add(graphic.mergePath(splitAreaRects[i], {
style: zrUtil.defaults({
fill: areaColors[i % areaColors.length]
}, areaStyle),
silent: true
}));
}
}
});
AxisView.extend({
type: 'xAxis'
});
AxisView.extend({
type: 'yAxis'
});
/**
* @inner
*/
function layoutAxis(gridModel, axisModel) {
var grid = gridModel.coordinateSystem;
var axis = axisModel.axis;
var layout = {};
var rawAxisPosition = axis.position;
var axisPosition = axis.onZero ? 'onZero' : rawAxisPosition;
var axisDim = axis.dim;
// [left, right, top, bottom]
var rect = grid.getRect();
var rectBound = [rect.x, rect.x + rect.width, rect.y, rect.y + rect.height];
var posMap = {
x: {top: rectBound[2], bottom: rectBound[3]},
y: {left: rectBound[0], right: rectBound[1]}
};
posMap.x.onZero = Math.max(Math.min(getZero('y'), posMap.x.bottom), posMap.x.top);
posMap.y.onZero = Math.max(Math.min(getZero('x'), posMap.y.right), posMap.y.left);
function getZero(dim, val) {
var theAxis = grid.getAxis(dim);
return theAxis.toGlobalCoord(theAxis.dataToCoord(0));
}
// Axis position
layout.position = [
axisDim === 'y' ? posMap.y[axisPosition] : rectBound[0],
axisDim === 'x' ? posMap.x[axisPosition] : rectBound[3]
];
// Axis rotation
var r = {x: 0, y: 1};
layout.rotation = Math.PI / 2 * r[axisDim];
// Tick and label direction, x y is axisDim
var dirMap = {top: -1, bottom: 1, left: -1, right: 1};
layout.labelDirection = layout.tickDirection = layout.nameDirection = dirMap[rawAxisPosition];
if (axis.onZero) {
layout.labelOffset = posMap[axisDim][rawAxisPosition] - posMap[axisDim].onZero;
}
if (axisModel.getModel('axisTick').get('inside')) {
layout.tickDirection = -layout.tickDirection;
}
if (axisModel.getModel('axisLabel').get('inside')) {
layout.labelDirection = -layout.labelDirection;
}
// Special label rotation
var labelRotation = axisModel.getModel('axisLabel').get('rotate');
layout.labelRotation = axisPosition === 'top' ? -labelRotation : labelRotation;
// label interval when auto mode.
layout.labelInterval = axis.getLabelInterval();
// Over splitLine and splitArea
layout.z2 = 1;
return layout;
}
});

View File

@@ -0,0 +1,137 @@
define(function (require) {
var zrUtil = require('zrender/core/util');
var AxisBuilder = require('./AxisBuilder');
var SelectController = require('../helper/SelectController');
var elementList = ['axisLine', 'axisLabel', 'axisTick', 'axisName'];
var AxisView = require('../../echarts').extendComponentView({
type: 'parallelAxis',
/**
* @type {module:echarts/component/helper/SelectController}
*/
_selectController: null,
/**
* @override
*/
render: function (axisModel, ecModel, api, payload) {
if (fromAxisAreaSelect(axisModel, ecModel, payload)) {
return;
}
this.axisModel = axisModel;
this.api = api;
this.group.removeAll();
if (!axisModel.get('show')) {
return;
}
var coordSys = ecModel.getComponent(
'parallel', axisModel.get('parallelIndex')
).coordinateSystem;
var areaSelectStyle = axisModel.getAreaSelectStyle();
var areaWidth = areaSelectStyle.width;
var axisLayout = coordSys.getAxisLayout(axisModel.axis.dim);
var builderOpt = zrUtil.extend(
{
strokeContainThreshold: areaWidth,
// lineWidth === 0 or no value.
axisLineSilent: !(areaWidth > 0) // jshint ignore:line
},
axisLayout
);
var axisBuilder = new AxisBuilder(axisModel, builderOpt);
zrUtil.each(elementList, axisBuilder.add, axisBuilder);
var axisGroup = axisBuilder.getGroup();
this.group.add(axisGroup);
this._buildSelectController(
axisGroup, areaSelectStyle, axisModel, api
);
},
_buildSelectController: function (axisGroup, areaSelectStyle, axisModel, api) {
var axis = axisModel.axis;
var selectController = this._selectController;
if (!selectController) {
selectController = this._selectController = new SelectController(
'line',
api.getZr(),
areaSelectStyle
);
selectController.on('selected', zrUtil.bind(this._onSelected, this));
}
selectController.enable(axisGroup);
// After filtering, axis may change, select area needs to be update.
var ranges = zrUtil.map(axisModel.activeIntervals, function (interval) {
return [
axis.dataToCoord(interval[0], true),
axis.dataToCoord(interval[1], true)
];
});
selectController.update(ranges);
},
_onSelected: function (ranges) {
// Do not cache these object, because the mey be changed.
var axisModel = this.axisModel;
var axis = axisModel.axis;
var intervals = zrUtil.map(ranges, function (range) {
return [
axis.coordToData(range[0], true),
axis.coordToData(range[1], true)
];
});
this.api.dispatchAction({
type: 'axisAreaSelect',
parallelAxisId: axisModel.id,
intervals: intervals
});
},
/**
* @override
*/
remove: function () {
this._selectController && this._selectController.disable();
},
/**
* @override
*/
dispose: function () {
if (this._selectController) {
this._selectController.dispose();
this._selectController = null;
}
}
});
function fromAxisAreaSelect(axisModel, ecModel, payload) {
return payload
&& payload.type === 'axisAreaSelect'
&& ecModel.findComponents(
{mainType: 'parallelAxis', query: payload}
)[0] === axisModel;
}
return AxisView;
});

View File

@@ -0,0 +1,144 @@
define(function (require) {
'use strict';
var zrUtil = require('zrender/core/util');
var graphic = require('../../util/graphic');
var AxisBuilder = require('./AxisBuilder');
var axisBuilderAttrs = [
'axisLine', 'axisLabel', 'axisTick', 'axisName'
];
var selfBuilderAttrs = [
'splitLine', 'splitArea'
];
require('../../echarts').extendComponentView({
type: 'radiusAxis',
render: function (radiusAxisModel, ecModel) {
this.group.removeAll();
if (!radiusAxisModel.get('show')) {
return;
}
var polarModel = ecModel.getComponent('polar', radiusAxisModel.get('polarIndex'));
var angleAxis = polarModel.coordinateSystem.getAngleAxis();
var radiusAxis = radiusAxisModel.axis;
var polar = polarModel.coordinateSystem;
var ticksCoords = radiusAxis.getTicksCoords();
var axisAngle = angleAxis.getExtent()[0];
var radiusExtent = radiusAxis.getExtent();
var layout = layoutAxis(polar, radiusAxisModel, axisAngle);
var axisBuilder = new AxisBuilder(radiusAxisModel, layout);
zrUtil.each(axisBuilderAttrs, axisBuilder.add, axisBuilder);
this.group.add(axisBuilder.getGroup());
zrUtil.each(selfBuilderAttrs, function (name) {
if (radiusAxisModel.get(name +'.show')) {
this['_' + name](radiusAxisModel, polar, axisAngle, radiusExtent, ticksCoords);
}
}, this);
},
/**
* @private
*/
_splitLine: function (radiusAxisModel, polar, axisAngle, radiusExtent, ticksCoords) {
var splitLineModel = radiusAxisModel.getModel('splitLine');
var lineStyleModel = splitLineModel.getModel('lineStyle');
var lineColors = lineStyleModel.get('color');
var lineCount = 0;
lineColors = lineColors instanceof Array ? lineColors : [lineColors];
var splitLines = [];
for (var i = 0; i < ticksCoords.length; i++) {
var colorIndex = (lineCount++) % lineColors.length;
splitLines[colorIndex] = splitLines[colorIndex] || [];
splitLines[colorIndex].push(new graphic.Circle({
shape: {
cx: polar.cx,
cy: polar.cy,
r: ticksCoords[i]
},
silent: true
}));
}
// Simple optimization
// Batching the lines if color are the same
for (var i = 0; i < splitLines.length; i++) {
this.group.add(graphic.mergePath(splitLines[i], {
style: zrUtil.defaults({
stroke: lineColors[i % lineColors.length],
fill: null
}, lineStyleModel.getLineStyle()),
silent: true
}));
}
},
/**
* @private
*/
_splitArea: function (radiusAxisModel, polar, axisAngle, radiusExtent, ticksCoords) {
var splitAreaModel = radiusAxisModel.getModel('splitArea');
var areaStyleModel = splitAreaModel.getModel('areaStyle');
var areaColors = areaStyleModel.get('color');
var lineCount = 0;
areaColors = areaColors instanceof Array ? areaColors : [areaColors];
var splitAreas = [];
var prevRadius = ticksCoords[0];
for (var i = 1; i < ticksCoords.length; i++) {
var colorIndex = (lineCount++) % areaColors.length;
splitAreas[colorIndex] = splitAreas[colorIndex] || [];
splitAreas[colorIndex].push(new graphic.Sector({
shape: {
cx: polar.cx,
cy: polar.cy,
r0: prevRadius,
r: ticksCoords[i],
startAngle: 0,
endAngle: Math.PI * 2
},
silent: true
}));
prevRadius = ticksCoords[i];
}
// Simple optimization
// Batching the lines if color are the same
for (var i = 0; i < splitAreas.length; i++) {
this.group.add(graphic.mergePath(splitAreas[i], {
style: zrUtil.defaults({
fill: areaColors[i % areaColors.length]
}, areaStyleModel.getAreaStyle()),
silent: true
}));
}
}
});
/**
* @inner
*/
function layoutAxis(polar, radiusAxisModel, axisAngle) {
return {
position: [polar.cx, polar.cy],
rotation: axisAngle / 180 * Math.PI,
labelDirection: -1,
tickDirection: -1,
nameDirection: 1,
labelRotation: radiusAxisModel.getModel('axisLabel').get('rotate'),
// Over splitLine and splitArea
z2: 1
};
}
});

View File

@@ -0,0 +1,161 @@
define(function (require) {
var AxisBuilder = require('./AxisBuilder');
var zrUtil = require('zrender/core/util');
var graphic = require('../../util/graphic');
var getInterval = AxisBuilder.getInterval;
var ifIgnoreOnTick = AxisBuilder.ifIgnoreOnTick;
var axisBuilderAttrs = [
'axisLine', 'axisLabel', 'axisTick', 'axisName'
];
var selfBuilderAttr = 'splitLine';
var AxisView = require('../../echarts').extendComponentView({
type: 'singleAxis',
render: function (axisModel, ecModel) {
var group = this.group;
group.removeAll();
var layout = axisLayout(axisModel);
var axisBuilder = new AxisBuilder(axisModel, layout);
zrUtil.each(axisBuilderAttrs, axisBuilder.add, axisBuilder);
group.add(axisBuilder.getGroup());
if (axisModel.get(selfBuilderAttr + '.show')) {
this['_' + selfBuilderAttr](axisModel, layout.labelInterval);
}
},
_splitLine: function(axisModel, labelInterval) {
var axis = axisModel.axis;
var splitLineModel = axisModel.getModel('splitLine');
var lineStyleModel = splitLineModel.getModel('lineStyle');
var lineWidth = lineStyleModel.get('width');
var lineColors = lineStyleModel.get('color');
var lineInterval = getInterval(splitLineModel, labelInterval);
lineColors = lineColors instanceof Array ? lineColors : [lineColors];
var gridRect = axisModel.coordinateSystem.getRect();
var isHorizontal = axis.isHorizontal();
var splitLines = [];
var lineCount = 0;
var ticksCoords = axis.getTicksCoords();
var p1 = [];
var p2 = [];
for (var i = 0; i < ticksCoords.length; ++i) {
if (ifIgnoreOnTick(axis, i, lineInterval)) {
continue;
}
var tickCoord = axis.toGlobalCoord(ticksCoords[i]);
if (isHorizontal) {
p1[0] = tickCoord;
p1[1] = gridRect.y;
p2[0] = tickCoord;
p2[1] = gridRect.y + gridRect.height;
}
else {
p1[0] = gridRect.x;
p1[1] = tickCoord;
p2[0] = gridRect.x + gridRect.width;
p2[1] = tickCoord;
}
var colorIndex = (lineCount++) % lineColors.length;
splitLines[colorIndex] = splitLines[colorIndex] || [];
splitLines[colorIndex].push(new graphic.Line(
graphic.subPixelOptimizeLine({
shape: {
x1: p1[0],
y1: p1[1],
x2: p2[0],
y2: p2[1]
},
style: {
lineWidth: lineWidth
},
silent: true
})));
}
for (var i = 0; i < splitLines.length; ++i) {
this.group.add(graphic.mergePath(splitLines[i], {
style: {
stroke: lineColors[i % lineColors.length],
lineDash: lineStyleModel.getLineDash(),
lineWidth: lineWidth
},
silent: true
}));
}
}
});
function axisLayout(axisModel) {
var single = axisModel.coordinateSystem;
var axis = axisModel.axis;
var layout = {};
var axisPosition = axis.position;
var orient = axis.orient;
var rect = single.getRect();
var rectBound = [rect.x, rect.x + rect.width, rect.y, rect.y + rect.height];
var positionMap = {
horizontal: {top: rectBound[2], bottom: rectBound[3]},
vertical: {left: rectBound[0], right: rectBound[1]}
};
layout.position = [
orient === 'vertical'
? positionMap.vertical[axisPosition]
: rectBound[0],
orient === 'horizontal'
? positionMap.horizontal[axisPosition]
: rectBound[3]
];
var r = {horizontal: 0, vertical: 1};
layout.rotation = Math.PI / 2 * r[orient];
var directionMap = {top: -1, bottom: 1, right: 1, left: -1};
layout.labelDirection = layout.tickDirection
= layout.nameDirection
= directionMap[axisPosition];
if (axisModel.getModel('axisTick').get('inside')) {
layout.tickDirection = -layout.tickDirection;
}
if (axisModel.getModel('axisLabel').get('inside')) {
layout.labelDirection = -layout.labelDirection;
}
var labelRotation = axisModel.getModel('axisLabel').get('rotate');
layout.labelRotation = axisPosition === 'top' ? -labelRotation : labelRotation;
layout.labelInterval = axis.getLabelInterval();
layout.z2 = 1;
return layout;
}
return AxisView;
});

View File

@@ -0,0 +1,25 @@
define(function (require) {
var echarts = require('../../echarts');
var actionInfo = {
type: 'axisAreaSelect',
event: 'axisAreaSelected',
update: 'updateVisual'
};
/**
* @payload
* @property {string} parallelAxisId
* @property {Array.<Array.<number>>} intervals
*/
echarts.registerAction(actionInfo, function (payload, ecModel) {
ecModel.eachComponent(
{mainType: 'parallelAxis', query: payload},
function (parallelAxisModel) {
parallelAxisModel.axis.model.setActiveIntervals(payload.intervals);
}
);
});
});

View File

@@ -0,0 +1,20 @@
/**
* DataZoom component entry
*/
define(function (require) {
require('./dataZoom/typeDefaulter');
require('./dataZoom/DataZoomModel');
require('./dataZoom/DataZoomView');
require('./dataZoom/SliderZoomModel');
require('./dataZoom/SliderZoomView');
require('./dataZoom/InsideZoomModel');
require('./dataZoom/InsideZoomView');
require('./dataZoom/dataZoomProcessor');
require('./dataZoom/dataZoomAction');
});

View File

@@ -0,0 +1,357 @@
/**
* @file Axis operator
*/
define(function(require) {
var zrUtil = require('zrender/core/util');
var numberUtil = require('../../util/number');
var each = zrUtil.each;
var asc = numberUtil.asc;
/**
* Operate single axis.
* One axis can only operated by one axis operator.
* Different dataZoomModels may be defined to operate the same axis.
* (i.e. 'inside' data zoom and 'slider' data zoom components)
* So dataZoomModels share one axisProxy in that case.
*
* @class
*/
var AxisProxy = function (dimName, axisIndex, dataZoomModel, ecModel) {
/**
* @private
* @type {string}
*/
this._dimName = dimName;
/**
* @private
*/
this._axisIndex = axisIndex;
/**
* @private
* @type {Array.<number>}
*/
this._valueWindow;
/**
* @private
* @type {Array.<number>}
*/
this._percentWindow;
/**
* @private
* @type {Array.<number>}
*/
this._dataExtent;
/**
* @readOnly
* @type {module: echarts/model/Global}
*/
this.ecModel = ecModel;
/**
* @private
* @type {module: echarts/component/dataZoom/DataZoomModel}
*/
this._dataZoomModel = dataZoomModel;
};
AxisProxy.prototype = {
constructor: AxisProxy,
/**
* Whether the axisProxy is hosted by dataZoomModel.
*
* @public
* @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel
* @return {boolean}
*/
hostedBy: function (dataZoomModel) {
return this._dataZoomModel === dataZoomModel;
},
/**
* @return {Array.<number>}
*/
getDataExtent: function () {
return this._dataExtent.slice();
},
/**
* @return {Array.<number>}
*/
getDataValueWindow: function () {
return this._valueWindow.slice();
},
/**
* @return {Array.<number>}
*/
getDataPercentWindow: function () {
return this._percentWindow.slice();
},
/**
* @public
* @param {number} axisIndex
* @return {Array} seriesModels
*/
getTargetSeriesModels: function () {
var seriesModels = [];
this.ecModel.eachSeries(function (seriesModel) {
if (this._axisIndex === seriesModel.get(this._dimName + 'AxisIndex')) {
seriesModels.push(seriesModel);
}
}, this);
return seriesModels;
},
getAxisModel: function () {
return this.ecModel.getComponent(this._dimName + 'Axis', this._axisIndex);
},
getOtherAxisModel: function () {
var axisDim = this._dimName;
var ecModel = this.ecModel;
var axisModel = this.getAxisModel();
var isCartesian = axisDim === 'x' || axisDim === 'y';
var otherAxisDim;
var coordSysIndexName;
if (isCartesian) {
coordSysIndexName = 'gridIndex';
otherAxisDim = axisDim === 'x' ? 'y' : 'x';
}
else {
coordSysIndexName = 'polarIndex';
otherAxisDim = axisDim === 'angle' ? 'radius' : 'angle';
}
var foundOtherAxisModel;
ecModel.eachComponent(otherAxisDim + 'Axis', function (otherAxisModel) {
if ((otherAxisModel.get(coordSysIndexName) || 0)
=== (axisModel.get(coordSysIndexName) || 0)
) {
foundOtherAxisModel = otherAxisModel;
}
});
return foundOtherAxisModel;
},
/**
* Notice: reset should not be called before series.restoreData() called,
* so it is recommanded to be called in "process stage" but not "model init
* stage".
*
* @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel
*/
reset: function (dataZoomModel) {
if (dataZoomModel !== this._dataZoomModel) {
return;
}
// Culculate data window and data extent, and record them.
var dataExtent = this._dataExtent = calculateDataExtent(
this._dimName, this.getTargetSeriesModels()
);
var dataWindow = calculateDataWindow(
dataZoomModel.option, dataExtent, this
);
this._valueWindow = dataWindow.valueWindow;
this._percentWindow = dataWindow.percentWindow;
// Update axis setting then.
setAxisModel(this);
},
/**
* @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel
*/
restore: function (dataZoomModel) {
if (dataZoomModel !== this._dataZoomModel) {
return;
}
this._valueWindow = this._percentWindow = null;
setAxisModel(this, true);
},
/**
* @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel
*/
filterData: function (dataZoomModel) {
if (dataZoomModel !== this._dataZoomModel) {
return;
}
var axisDim = this._dimName;
var seriesModels = this.getTargetSeriesModels();
var filterMode = dataZoomModel.get('filterMode');
var valueWindow = this._valueWindow;
// FIXME
// Toolbox may has dataZoom injected. And if there are stacked bar chart
// with NaN data, NaN will be filtered and stack will be wrong.
// So we need to force the mode to be set empty.
// In fect, it is not a big deal that do not support filterMode-'filter'
// when using toolbox#dataZoom, utill tooltip#dataZoom support "single axis
// selection" some day, which might need "adapt to data extent on the
// otherAxis", which is disabled by filterMode-'empty'.
var otherAxisModel = this.getOtherAxisModel();
if (dataZoomModel.get('$fromToolbox')
&& otherAxisModel
&& otherAxisModel.get('type') === 'category'
) {
filterMode = 'empty';
}
// Process series data
each(seriesModels, function (seriesModel) {
var seriesData = seriesModel.getData();
seriesData && each(seriesModel.coordDimToDataDim(axisDim), function (dim) {
if (filterMode === 'empty') {
seriesModel.setData(
seriesData.map(dim, function (value) {
return !isInWindow(value) ? NaN : value;
})
);
}
else {
seriesData.filterSelf(dim, isInWindow);
}
});
});
function isInWindow(value) {
return value >= valueWindow[0] && value <= valueWindow[1];
}
}
};
function calculateDataExtent(axisDim, seriesModels) {
var dataExtent = [Infinity, -Infinity];
each(seriesModels, function (seriesModel) {
var seriesData = seriesModel.getData();
if (seriesData) {
each(seriesModel.coordDimToDataDim(axisDim), function (dim) {
var seriesExtent = seriesData.getDataExtent(dim);
seriesExtent[0] < dataExtent[0] && (dataExtent[0] = seriesExtent[0]);
seriesExtent[1] > dataExtent[1] && (dataExtent[1] = seriesExtent[1]);
});
}
}, this);
return dataExtent;
}
function calculateDataWindow(opt, dataExtent, axisProxy) {
var axisModel = axisProxy.getAxisModel();
var scale = axisModel.axis.scale;
var percentExtent = [0, 100];
var percentWindow = [
opt.start,
opt.end
];
var valueWindow = [];
// In percent range is used and axis min/max/scale is set,
// window should be based on min/max/0, but should not be
// based on the extent of filtered data.
dataExtent = dataExtent.slice();
fixExtendByAxis(dataExtent, axisModel, scale);
each(['startValue', 'endValue'], function (prop) {
valueWindow.push(
opt[prop] != null
? scale.parse(opt[prop])
: null
);
});
// Normalize bound.
each([0, 1], function (idx) {
var boundValue = valueWindow[idx];
var boundPercent = percentWindow[idx];
// start/end has higher priority over startValue/endValue,
// because start/end can be consistent among different type
// of axis but startValue/endValue not.
if (boundPercent != null || boundValue == null) {
if (boundPercent == null) {
boundPercent = percentExtent[idx];
}
// Use scale.parse to math round for category or time axis.
boundValue = scale.parse(numberUtil.linearMap(
boundPercent, percentExtent, dataExtent, true
));
}
else { // boundPercent == null && boundValue != null
boundPercent = numberUtil.linearMap(
boundValue, dataExtent, percentExtent, true
);
}
// valueWindow[idx] = round(boundValue);
// percentWindow[idx] = round(boundPercent);
valueWindow[idx] = boundValue;
percentWindow[idx] = boundPercent;
});
return {
valueWindow: asc(valueWindow),
percentWindow: asc(percentWindow)
};
}
function fixExtendByAxis(dataExtent, axisModel, scale) {
each(['min', 'max'], function (minMax, index) {
var axisMax = axisModel.get(minMax, true);
// Consider 'dataMin', 'dataMax'
if (axisMax != null && (axisMax + '').toLowerCase() !== 'data' + minMax) {
dataExtent[index] = scale.parse(axisMax);
}
});
if (!axisModel.get('scale', true)) {
dataExtent[0] > 0 && (dataExtent[0] = 0);
dataExtent[1] < 0 && (dataExtent[1] = 0);
}
return dataExtent;
}
function setAxisModel(axisProxy, isRestore) {
var axisModel = axisProxy.getAxisModel();
var percentWindow = axisProxy._percentWindow;
var valueWindow = axisProxy._valueWindow;
if (!percentWindow) {
return;
}
var isFull = isRestore || (percentWindow[0] === 0 && percentWindow[1] === 100);
// [0, 500]: arbitrary value, guess axis extent.
var precision = !isRestore && numberUtil.getPixelPrecision(valueWindow, [0, 500]);
// toFixed() digits argument must be between 0 and 20
var invalidPrecision = !isRestore && !(precision < 20 && precision >= 0);
var useOrigin = isRestore || isFull || invalidPrecision;
axisModel.setRange && axisModel.setRange(
useOrigin ? null : +valueWindow[0].toFixed(precision),
useOrigin ? null : +valueWindow[1].toFixed(precision)
);
}
return AxisProxy;
});

View File

@@ -0,0 +1,432 @@
/**
* @file Data zoom model
*/
define(function(require) {
var zrUtil = require('zrender/core/util');
var env = require('zrender/core/env');
var echarts = require('../../echarts');
var modelUtil = require('../../util/model');
var AxisProxy = require('./AxisProxy');
var each = zrUtil.each;
var eachAxisDim = modelUtil.eachAxisDim;
var DataZoomModel = echarts.extendComponentModel({
type: 'dataZoom',
dependencies: [
'xAxis', 'yAxis', 'zAxis', 'radiusAxis', 'angleAxis', 'series'
],
/**
* @protected
*/
defaultOption: {
zlevel: 0,
z: 4, // Higher than normal component (z: 2).
orient: null, // Default auto by axisIndex. Possible value: 'horizontal', 'vertical'.
xAxisIndex: null, // Default all horizontal category axis.
yAxisIndex: null, // Default all vertical category axis.
angleAxisIndex: null,
radiusAxisIndex: null,
filterMode: 'filter', // Possible values: 'filter' or 'empty'.
// 'filter': data items which are out of window will be removed.
// This option is applicable when filtering outliers.
// 'empty': data items which are out of window will be set to empty.
// This option is applicable when user should not neglect
// that there are some data items out of window.
// Taking line chart as an example, line will be broken in
// the filtered points when filterModel is set to 'empty', but
// be connected when set to 'filter'.
throttle: 100, // Dispatch action by the fixed rate, avoid frequency.
// default 100. Do not throttle when use null/undefined.
start: 0, // Start percent. 0 ~ 100
end: 100, // End percent. 0 ~ 100
startValue: null, // Start value. If startValue specified, start is ignored.
endValue: null // End value. If endValue specified, end is ignored.
},
/**
* @override
*/
init: function (option, parentModel, ecModel) {
/**
* key like x_0, y_1
* @private
* @type {Object}
*/
this._dataIntervalByAxis = {};
/**
* @private
*/
this._dataInfo = {};
/**
* key like x_0, y_1
* @private
*/
this._axisProxies = {};
/**
* @readOnly
*/
this.textStyleModel;
var rawOption = retrieveRaw(option);
this.mergeDefaultAndTheme(option, ecModel);
this.doInit(rawOption);
},
/**
* @override
*/
mergeOption: function (newOption) {
var rawOption = retrieveRaw(newOption);
//FIX #2591
zrUtil.merge(this.option, newOption, true);
this.doInit(rawOption);
},
/**
* @protected
*/
doInit: function (rawOption) {
var thisOption = this.option;
// Disable realtime view update if canvas is not supported.
if (!env.canvasSupported) {
thisOption.realtime = false;
}
processRangeProp('start', 'startValue', rawOption, thisOption);
processRangeProp('end', 'endValue', rawOption, thisOption);
this.textStyleModel = this.getModel('textStyle');
this._resetTarget();
this._giveAxisProxies();
},
/**
* @private
*/
_giveAxisProxies: function () {
var axisProxies = this._axisProxies;
this.eachTargetAxis(function (dimNames, axisIndex, dataZoomModel, ecModel) {
var axisModel = this.dependentModels[dimNames.axis][axisIndex];
// If exists, share axisProxy with other dataZoomModels.
var axisProxy = axisModel.__dzAxisProxy || (
// Use the first dataZoomModel as the main model of axisProxy.
axisModel.__dzAxisProxy = new AxisProxy(
dimNames.name, axisIndex, this, ecModel
)
);
// FIXME
// dispose __dzAxisProxy
axisProxies[dimNames.name + '_' + axisIndex] = axisProxy;
}, this);
},
/**
* @private
*/
_resetTarget: function () {
var thisOption = this.option;
var autoMode = this._judgeAutoMode();
eachAxisDim(function (dimNames) {
var axisIndexName = dimNames.axisIndex;
thisOption[axisIndexName] = modelUtil.normalizeToArray(
thisOption[axisIndexName]
);
}, this);
if (autoMode === 'axisIndex') {
this._autoSetAxisIndex();
}
else if (autoMode === 'orient') {
this._autoSetOrient();
}
},
/**
* @private
*/
_judgeAutoMode: function () {
// Auto set only works for setOption at the first time.
// The following is user's reponsibility. So using merged
// option is OK.
var thisOption = this.option;
var hasIndexSpecified = false;
eachAxisDim(function (dimNames) {
// When user set axisIndex as a empty array, we think that user specify axisIndex
// but do not want use auto mode. Because empty array may be encountered when
// some error occured.
if (thisOption[dimNames.axisIndex] != null) {
hasIndexSpecified = true;
}
}, this);
var orient = thisOption.orient;
if (orient == null && hasIndexSpecified) {
return 'orient';
}
else if (!hasIndexSpecified) {
if (orient == null) {
thisOption.orient = 'horizontal';
}
return 'axisIndex';
}
},
/**
* @private
*/
_autoSetAxisIndex: function () {
var autoAxisIndex = true;
var orient = this.get('orient', true);
var thisOption = this.option;
if (autoAxisIndex) {
// Find axis that parallel to dataZoom as default.
var dimNames = orient === 'vertical'
? {dim: 'y', axisIndex: 'yAxisIndex', axis: 'yAxis'}
: {dim: 'x', axisIndex: 'xAxisIndex', axis: 'xAxis'};
if (this.dependentModels[dimNames.axis].length) {
thisOption[dimNames.axisIndex] = [0];
autoAxisIndex = false;
}
}
if (autoAxisIndex) {
// Find the first category axis as default. (consider polar)
eachAxisDim(function (dimNames) {
if (!autoAxisIndex) {
return;
}
var axisIndices = [];
var axisModels = this.dependentModels[dimNames.axis];
if (axisModels.length && !axisIndices.length) {
for (var i = 0, len = axisModels.length; i < len; i++) {
if (axisModels[i].get('type') === 'category') {
axisIndices.push(i);
}
}
}
thisOption[dimNames.axisIndex] = axisIndices;
if (axisIndices.length) {
autoAxisIndex = false;
}
}, this);
}
if (autoAxisIndex) {
// FIXME
// 这里是兼容ec2的写法没指定xAxisIndex和yAxisIndex时把scatter和双数值轴折柱纳入dataZoom控制
// 但是实际是否需要Grid.js#getScaleByOption来判断考虑timelog等axis type
// If both dataZoom.xAxisIndex and dataZoom.yAxisIndex is not specified,
// dataZoom component auto adopts series that reference to
// both xAxis and yAxis which type is 'value'.
this.ecModel.eachSeries(function (seriesModel) {
if (this._isSeriesHasAllAxesTypeOf(seriesModel, 'value')) {
eachAxisDim(function (dimNames) {
var axisIndices = thisOption[dimNames.axisIndex];
var axisIndex = seriesModel.get(dimNames.axisIndex);
if (zrUtil.indexOf(axisIndices, axisIndex) < 0) {
axisIndices.push(axisIndex);
}
});
}
}, this);
}
},
/**
* @private
*/
_autoSetOrient: function () {
var dim;
// Find the first axis
this.eachTargetAxis(function (dimNames) {
!dim && (dim = dimNames.name);
}, this);
this.option.orient = dim === 'y' ? 'vertical' : 'horizontal';
},
/**
* @private
*/
_isSeriesHasAllAxesTypeOf: function (seriesModel, axisType) {
// FIXME
// 需要series的xAxisIndex和yAxisIndex都首先自动设置上。
// 例如series.type === scatter时。
var is = true;
eachAxisDim(function (dimNames) {
var seriesAxisIndex = seriesModel.get(dimNames.axisIndex);
var axisModel = this.dependentModels[dimNames.axis][seriesAxisIndex];
if (!axisModel || axisModel.get('type') !== axisType) {
is = false;
}
}, this);
return is;
},
/**
* @public
*/
getFirstTargetAxisModel: function () {
var firstAxisModel;
eachAxisDim(function (dimNames) {
if (firstAxisModel == null) {
var indices = this.get(dimNames.axisIndex);
if (indices.length) {
firstAxisModel = this.dependentModels[dimNames.axis][indices[0]];
}
}
}, this);
return firstAxisModel;
},
/**
* @public
* @param {Function} callback param: axisModel, dimNames, axisIndex, dataZoomModel, ecModel
*/
eachTargetAxis: function (callback, context) {
var ecModel = this.ecModel;
eachAxisDim(function (dimNames) {
each(
this.get(dimNames.axisIndex),
function (axisIndex) {
callback.call(context, dimNames, axisIndex, this, ecModel);
},
this
);
}, this);
},
getAxisProxy: function (dimName, axisIndex) {
return this._axisProxies[dimName + '_' + axisIndex];
},
/**
* If not specified, set to undefined.
*
* @public
* @param {Object} opt
* @param {number} [opt.start]
* @param {number} [opt.end]
* @param {number} [opt.startValue]
* @param {number} [opt.endValue]
*/
setRawRange: function (opt) {
each(['start', 'end', 'startValue', 'endValue'], function (name) {
// If any of those prop is null/undefined, we should alos set
// them, because only one pair between start/end and
// startValue/endValue can work.
this.option[name] = opt[name];
}, this);
},
/**
* @public
* @return {Array.<number>} [startPercent, endPercent]
*/
getPercentRange: function () {
var axisProxy = this.findRepresentativeAxisProxy();
if (axisProxy) {
return axisProxy.getDataPercentWindow();
}
},
/**
* @public
* For example, chart.getModel().getComponent('dataZoom').getValueRange('y', 0);
*
* @param {string} [axisDimName]
* @param {number} [axisIndex]
* @return {Array.<number>} [startValue, endValue]
*/
getValueRange: function (axisDimName, axisIndex) {
if (axisDimName == null && axisIndex == null) {
var axisProxy = this.findRepresentativeAxisProxy();
if (axisProxy) {
return axisProxy.getDataValueWindow();
}
}
else {
return this.getAxisProxy(axisDimName, axisIndex).getDataValueWindow();
}
},
/**
* @public
* @return {module:echarts/component/dataZoom/AxisProxy}
*/
findRepresentativeAxisProxy: function () {
// Find the first hosted axisProxy
var axisProxies = this._axisProxies;
for (var key in axisProxies) {
if (axisProxies.hasOwnProperty(key) && axisProxies[key].hostedBy(this)) {
return axisProxies[key];
}
}
// If no hosted axis find not hosted axisProxy.
// Consider this case: dataZoomModel1 and dataZoomModel2 control the same axis,
// and the option.start or option.end settings are different. The percentRange
// should follow axisProxy.
// (We encounter this problem in toolbox data zoom.)
for (var key in axisProxies) {
if (axisProxies.hasOwnProperty(key) && !axisProxies[key].hostedBy(this)) {
return axisProxies[key];
}
}
}
});
function retrieveRaw(option) {
var ret = {};
each(
['start', 'end', 'startValue', 'endValue'],
function (name) {
ret[name] = option[name];
}
);
return ret;
}
function processRangeProp(percentProp, valueProp, rawOption, thisOption) {
// start/end has higher priority over startValue/endValue,
// but we should make chart.setOption({endValue: 1000}) effective,
// rather than chart.setOption({endValue: 1000, end: null}).
if (rawOption[valueProp] != null && rawOption[percentProp] == null) {
thisOption[percentProp] = null;
}
// Otherwise do nothing and use the merge result.
}
return DataZoomModel;
});

View File

@@ -0,0 +1,84 @@
define(function (require) {
var ComponentView = require('../../view/Component');
return ComponentView.extend({
type: 'dataZoom',
render: function (dataZoomModel, ecModel, api, payload) {
this.dataZoomModel = dataZoomModel;
this.ecModel = ecModel;
this.api = api;
},
/**
* Find the first target coordinate system.
*
* @protected
* @return {Object} {
* cartesians: [
* {model: coord0, axisModels: [axis1, axis3], coordIndex: 1},
* {model: coord1, axisModels: [axis0, axis2], coordIndex: 0},
* ...
* ], // cartesians must not be null/undefined.
* polars: [
* {model: coord0, axisModels: [axis4], coordIndex: 0},
* ...
* ], // polars must not be null/undefined.
* axisModels: [axis0, axis1, axis2, axis3, axis4]
* // axisModels must not be null/undefined.
* }
*/
getTargetInfo: function () {
var dataZoomModel = this.dataZoomModel;
var ecModel = this.ecModel;
var cartesians = [];
var polars = [];
var axisModels = [];
dataZoomModel.eachTargetAxis(function (dimNames, axisIndex) {
var axisModel = ecModel.getComponent(dimNames.axis, axisIndex);
if (axisModel) {
axisModels.push(axisModel);
var gridIndex = axisModel.get('gridIndex');
var polarIndex = axisModel.get('polarIndex');
if (gridIndex != null) {
var coordModel = ecModel.getComponent('grid', gridIndex);
save(coordModel, axisModel, cartesians, gridIndex);
}
else if (polarIndex != null) {
var coordModel = ecModel.getComponent('polar', polarIndex);
save(coordModel, axisModel, polars, polarIndex);
}
}
}, this);
function save(coordModel, axisModel, store, coordIndex) {
var item;
for (var i = 0; i < store.length; i++) {
if (store[i].model === coordModel) {
item = store[i];
break;
}
}
if (!item) {
store.push(item = {
model: coordModel, axisModels: [], coordIndex: coordIndex
});
}
item.axisModels.push(axisModel);
}
return {
cartesians: cartesians,
polars: polars,
axisModels: axisModels
};
}
});
});

View File

@@ -0,0 +1,17 @@
/**
* @file Data zoom model
*/
define(function(require) {
return require('./DataZoomModel').extend({
type: 'dataZoom.inside',
/**
* @protected
*/
defaultOption: {
zoomLock: false // Whether disable zoom but only pan.
}
});
});

View File

@@ -0,0 +1,194 @@
define(function (require) {
var DataZoomView = require('./DataZoomView');
var zrUtil = require('zrender/core/util');
var sliderMove = require('../helper/sliderMove');
var roams = require('./roams');
var bind = zrUtil.bind;
var InsideZoomView = DataZoomView.extend({
type: 'dataZoom.inside',
/**
* @override
*/
init: function (ecModel, api) {
/**
* 'throttle' is used in this.dispatchAction, so we save range
* to avoid missing some 'pan' info.
* @private
* @type {Array.<number>}
*/
this._range;
},
/**
* @override
*/
render: function (dataZoomModel, ecModel, api, payload) {
InsideZoomView.superApply(this, 'render', arguments);
// Notice: origin this._range should be maintained, and should not be re-fetched
// from dataZoomModel when payload.type is 'dataZoom', otherwise 'pan' or 'zoom'
// info will be missed because of 'throttle' of this.dispatchAction.
if (roams.shouldRecordRange(payload, dataZoomModel.id)) {
this._range = dataZoomModel.getPercentRange();
}
// Reset controllers.
var coordInfoList = this.getTargetInfo().cartesians;
var allCoordIds = zrUtil.map(coordInfoList, function (coordInfo) {
return roams.generateCoordId(coordInfo.model);
});
zrUtil.each(coordInfoList, function (coordInfo) {
var coordModel = coordInfo.model;
roams.register(
api,
{
coordId: roams.generateCoordId(coordModel),
allCoordIds: allCoordIds,
coordinateSystem: coordModel.coordinateSystem,
dataZoomId: dataZoomModel.id,
throttleRage: dataZoomModel.get('throttle', true),
panGetRange: bind(this._onPan, this, coordInfo),
zoomGetRange: bind(this._onZoom, this, coordInfo)
}
);
}, this);
// TODO
// polar支持
},
/**
* @override
*/
remove: function () {
roams.unregister(this.api, this.dataZoomModel.id);
InsideZoomView.superApply(this, 'remove', arguments);
this._range = null;
},
/**
* @override
*/
dispose: function () {
roams.unregister(this.api, this.dataZoomModel.id);
InsideZoomView.superApply(this, 'dispose', arguments);
this._range = null;
},
/**
* @private
*/
_onPan: function (coordInfo, controller, dx, dy) {
return (
this._range = panCartesian(
[dx, dy], this._range, controller, coordInfo
)
);
},
/**
* @private
*/
_onZoom: function (coordInfo, controller, scale, mouseX, mouseY) {
var dataZoomModel = this.dataZoomModel;
if (dataZoomModel.option.zoomLock) {
return this._range;
}
return (
this._range = scaleCartesian(
1 / scale, [mouseX, mouseY], this._range,
controller, coordInfo, dataZoomModel
)
);
}
});
function panCartesian(pixelDeltas, range, controller, coordInfo) {
range = range.slice();
// Calculate transform by the first axis.
var axisModel = coordInfo.axisModels[0];
if (!axisModel) {
return;
}
var directionInfo = getDirectionInfo(pixelDeltas, axisModel, controller);
var percentDelta = directionInfo.signal
* (range[1] - range[0])
* directionInfo.pixel / directionInfo.pixelLength;
sliderMove(
percentDelta,
range,
[0, 100],
'rigid'
);
return range;
}
function scaleCartesian(scale, mousePoint, range, controller, coordInfo, dataZoomModel) {
range = range.slice();
// Calculate transform by the first axis.
var axisModel = coordInfo.axisModels[0];
if (!axisModel) {
return;
}
var directionInfo = getDirectionInfo(mousePoint, axisModel, controller);
var mouse = directionInfo.pixel - directionInfo.pixelStart;
var percentPoint = mouse / directionInfo.pixelLength * (range[1] - range[0]) + range[0];
scale = Math.max(scale, 0);
range[0] = (range[0] - percentPoint) * scale + percentPoint;
range[1] = (range[1] - percentPoint) * scale + percentPoint;
return fixRange(range);
}
function getDirectionInfo(xy, axisModel, controller) {
var axis = axisModel.axis;
var rect = controller.rectProvider();
var ret = {};
if (axis.dim === 'x') {
ret.pixel = xy[0];
ret.pixelLength = rect.width;
ret.pixelStart = rect.x;
ret.signal = axis.inverse ? 1 : -1;
}
else { // axis.dim === 'y'
ret.pixel = xy[1];
ret.pixelLength = rect.height;
ret.pixelStart = rect.y;
ret.signal = axis.inverse ? -1 : 1;
}
return ret;
}
function fixRange(range) {
// Clamp, using !(<= or >=) to handle NaN.
// jshint ignore:start
var bound = [0, 100];
!(range[0] <= bound[1]) && (range[0] = bound[1]);
!(range[1] <= bound[1]) && (range[1] = bound[1]);
!(range[0] >= bound[0]) && (range[0] = bound[0]);
!(range[1] >= bound[0]) && (range[1] = bound[0]);
// jshint ignore:end
return range;
}
return InsideZoomView;
});

View File

@@ -0,0 +1,14 @@
/**
* @file Data zoom model
*/
define(function(require) {
var DataZoomModel = require('./DataZoomModel');
return DataZoomModel.extend({
type: 'dataZoom.select'
});
});

View File

@@ -0,0 +1,9 @@
define(function (require) {
return require('./DataZoomView').extend({
type: 'dataZoom.select'
});
});

View File

@@ -0,0 +1,57 @@
/**
* @file Data zoom model
*/
define(function(require) {
var DataZoomModel = require('./DataZoomModel');
var SliderZoomModel = DataZoomModel.extend({
type: 'dataZoom.slider',
layoutMode: 'box',
/**
* @protected
*/
defaultOption: {
show: true,
// ph => placeholder. Using placehoder here because
// deault value can only be drived in view stage.
right: 'ph', // Default align to grid rect.
top: 'ph', // Default align to grid rect.
width: 'ph', // Default align to grid rect.
height: 'ph', // Default align to grid rect.
left: null, // Default align to grid rect.
bottom: null, // Default align to grid rect.
backgroundColor: 'rgba(47,69,84,0)', // Background of slider zoom component.
dataBackgroundColor: '#ddd', // Background of data shadow.
fillerColor: 'rgba(47,69,84,0.15)', // Color of selected area.
handleColor: 'rgba(148,164,165,0.95)', // Color of handle.
handleSize: 10,
labelPrecision: null,
labelFormatter: null,
showDetail: true,
showDataShadow: 'auto', // Default auto decision.
realtime: true,
zoomLock: false, // Whether disable zoom.
textStyle: {
color: '#333'
}
},
/**
* @override
*/
mergeOption: function (option) {
SliderZoomModel.superApply(this, 'mergeOption', arguments);
}
});
return SliderZoomModel;
});

View File

@@ -0,0 +1,700 @@
define(function (require) {
var zrUtil = require('zrender/core/util');
var graphic = require('../../util/graphic');
var throttle = require('../../util/throttle');
var DataZoomView = require('./DataZoomView');
var Rect = graphic.Rect;
var numberUtil = require('../../util/number');
var linearMap = numberUtil.linearMap;
var layout = require('../../util/layout');
var sliderMove = require('../helper/sliderMove');
var asc = numberUtil.asc;
var bind = zrUtil.bind;
var mathRound = Math.round;
var mathMax = Math.max;
var each = zrUtil.each;
// Constants
var DEFAULT_LOCATION_EDGE_GAP = 7;
var DEFAULT_FRAME_BORDER_WIDTH = 1;
var DEFAULT_FILLER_SIZE = 30;
var HORIZONTAL = 'horizontal';
var VERTICAL = 'vertical';
var LABEL_GAP = 5;
var SHOW_DATA_SHADOW_SERIES_TYPE = ['line', 'bar', 'candlestick', 'scatter'];
var SliderZoomView = DataZoomView.extend({
type: 'dataZoom.slider',
init: function (ecModel, api) {
/**
* @private
* @type {Object}
*/
this._displayables = {};
/**
* @private
* @type {string}
*/
this._orient;
/**
* [0, 100]
* @private
*/
this._range;
/**
* [coord of the first handle, coord of the second handle]
* @private
*/
this._handleEnds;
/**
* [length, thick]
* @private
* @type {Array.<number>}
*/
this._size;
/**
* @private
* @type {number}
*/
this._halfHandleSize;
/**
* @private
*/
this._location;
/**
* @private
*/
this._dragging;
/**
* @private
*/
this._dataShadowInfo;
this.api = api;
},
/**
* @override
*/
render: function (dataZoomModel, ecModel, api, payload) {
SliderZoomView.superApply(this, 'render', arguments);
throttle.createOrUpdate(
this,
'_dispatchZoomAction',
this.dataZoomModel.get('throttle'),
'fixRate'
);
this._orient = dataZoomModel.get('orient');
this._halfHandleSize = mathRound(dataZoomModel.get('handleSize') / 2);
if (this.dataZoomModel.get('show') === false) {
this.group.removeAll();
return;
}
// Notice: this._resetInterval() should not be executed when payload.type
// is 'dataZoom', origin this._range should be maintained, otherwise 'pan'
// or 'zoom' info will be missed because of 'throttle' of this.dispatchAction,
if (!payload || payload.type !== 'dataZoom' || payload.from !== this.uid) {
this._buildView();
}
this._updateView();
},
/**
* @override
*/
remove: function () {
SliderZoomView.superApply(this, 'remove', arguments);
throttle.clear(this, '_dispatchZoomAction');
},
/**
* @override
*/
dispose: function () {
SliderZoomView.superApply(this, 'dispose', arguments);
throttle.clear(this, '_dispatchZoomAction');
},
_buildView: function () {
var thisGroup = this.group;
thisGroup.removeAll();
this._resetLocation();
this._resetInterval();
var barGroup = this._displayables.barGroup = new graphic.Group();
this._renderBackground();
this._renderDataShadow();
this._renderHandle();
thisGroup.add(barGroup);
this._positionGroup();
},
/**
* @private
*/
_resetLocation: function () {
var dataZoomModel = this.dataZoomModel;
var api = this.api;
// If some of x/y/width/height are not specified,
// auto-adapt according to target grid.
var coordRect = this._findCoordRect();
var ecSize = {width: api.getWidth(), height: api.getHeight()};
// Default align by coordinate system rect.
var positionInfo = this._orient === HORIZONTAL
? {
// Why using 'right', because right should be used in vertical,
// and it is better to be consistent for dealing with position param merge.
right: ecSize.width - coordRect.x - coordRect.width,
top: (ecSize.height - DEFAULT_FILLER_SIZE - DEFAULT_LOCATION_EDGE_GAP),
width: coordRect.width,
height: DEFAULT_FILLER_SIZE
}
: { // vertical
right: DEFAULT_LOCATION_EDGE_GAP,
top: coordRect.y,
width: DEFAULT_FILLER_SIZE,
height: coordRect.height
};
// Do not write back to option and replace value 'ph', because
// the 'ph' value should be recalculated when resize.
var layoutParams = layout.getLayoutParams(dataZoomModel.option);
// Replace the placeholder value.
zrUtil.each(['right', 'top', 'width', 'height'], function (name) {
if (layoutParams[name] === 'ph') {
layoutParams[name] = positionInfo[name];
}
});
var layoutRect = layout.getLayoutRect(
layoutParams,
ecSize,
dataZoomModel.padding
);
this._location = {x: layoutRect.x, y: layoutRect.y};
this._size = [layoutRect.width, layoutRect.height];
this._orient === VERTICAL && this._size.reverse();
},
/**
* @private
*/
_positionGroup: function () {
var thisGroup = this.group;
var location = this._location;
var orient = this._orient;
// Just use the first axis to determine mapping.
var targetAxisModel = this.dataZoomModel.getFirstTargetAxisModel();
var inverse = targetAxisModel && targetAxisModel.get('inverse');
var barGroup = this._displayables.barGroup;
var otherAxisInverse = (this._dataShadowInfo || {}).otherAxisInverse;
// Transform barGroup.
barGroup.attr(
(orient === HORIZONTAL && !inverse)
? {scale: otherAxisInverse ? [1, 1] : [1, -1]}
: (orient === HORIZONTAL && inverse)
? {scale: otherAxisInverse ? [-1, 1] : [-1, -1]}
: (orient === VERTICAL && !inverse)
? {scale: otherAxisInverse ? [1, -1] : [1, 1], rotation: Math.PI / 2}
// Dont use Math.PI, considering shadow direction.
: {scale: otherAxisInverse ? [-1, -1] : [-1, 1], rotation: Math.PI / 2}
);
// Position barGroup
var rect = thisGroup.getBoundingRect([barGroup]);
thisGroup.position[0] = location.x - rect.x;
thisGroup.position[1] = location.y - rect.y;
},
/**
* @private
*/
_getViewExtent: function () {
// View total length.
var halfHandleSize = this._halfHandleSize;
var totalLength = mathMax(this._size[0], halfHandleSize * 4);
var extent = [halfHandleSize, totalLength - halfHandleSize];
return extent;
},
_renderBackground : function () {
var dataZoomModel = this.dataZoomModel;
var size = this._size;
this._displayables.barGroup.add(new Rect({
silent: true,
shape: {
x: 0, y: 0, width: size[0], height: size[1]
},
style: {
fill: dataZoomModel.get('backgroundColor')
}
}));
},
_renderDataShadow: function () {
var info = this._dataShadowInfo = this._prepareDataShadowInfo();
if (!info) {
return;
}
var size = this._size;
var seriesModel = info.series;
var data = seriesModel.getRawData();
var otherDim = seriesModel.getShadowDim
? seriesModel.getShadowDim() // @see candlestick
: info.otherDim;
var otherDataExtent = data.getDataExtent(otherDim);
// Nice extent.
var otherOffset = (otherDataExtent[1] - otherDataExtent[0]) * 0.3;
otherDataExtent = [
otherDataExtent[0] - otherOffset,
otherDataExtent[1] + otherOffset
];
var otherShadowExtent = [0, size[1]];
var thisShadowExtent = [0, size[0]];
var points = [[size[0], 0], [0, 0]];
var step = thisShadowExtent[1] / (data.count() - 1);
var thisCoord = 0;
// Optimize for large data shadow
var stride = Math.round(data.count() / size[0]);
data.each([otherDim], function (value, index) {
if (stride > 0 && (index % stride)) {
thisCoord += step;
return;
}
// FIXME
// 应该使用统计的空判断还是在list里进行空判断
var otherCoord = (value == null || isNaN(value) || value === '')
? null
: linearMap(value, otherDataExtent, otherShadowExtent, true);
otherCoord != null && points.push([thisCoord, otherCoord]);
thisCoord += step;
});
this._displayables.barGroup.add(new graphic.Polyline({
shape: {points: points},
style: {fill: this.dataZoomModel.get('dataBackgroundColor'), lineWidth: 0},
silent: true,
z2: -20
}));
},
_prepareDataShadowInfo: function () {
var dataZoomModel = this.dataZoomModel;
var showDataShadow = dataZoomModel.get('showDataShadow');
if (showDataShadow === false) {
return;
}
// Find a representative series.
var result;
var ecModel = this.ecModel;
dataZoomModel.eachTargetAxis(function (dimNames, axisIndex) {
var seriesModels = dataZoomModel
.getAxisProxy(dimNames.name, axisIndex)
.getTargetSeriesModels();
zrUtil.each(seriesModels, function (seriesModel) {
if (result) {
return;
}
if (showDataShadow !== true && zrUtil.indexOf(
SHOW_DATA_SHADOW_SERIES_TYPE, seriesModel.get('type')
) < 0
) {
return;
}
var otherDim = getOtherDim(dimNames.name);
var thisAxis = ecModel.getComponent(dimNames.axis, axisIndex).axis;
result = {
thisAxis: thisAxis,
series: seriesModel,
thisDim: dimNames.name,
otherDim: otherDim,
otherAxisInverse: seriesModel
.coordinateSystem.getOtherAxis(thisAxis).inverse
};
}, this);
}, this);
return result;
},
_renderHandle: function () {
var displaybles = this._displayables;
var handles = displaybles.handles = [];
var handleLabels = displaybles.handleLabels = [];
var barGroup = this._displayables.barGroup;
var size = this._size;
barGroup.add(displaybles.filler = new Rect({
draggable: true,
cursor: 'move',
drift: bind(this._onDragMove, this, 'all'),
ondragend: bind(this._onDragEnd, this),
onmouseover: bind(this._showDataInfo, this, true),
onmouseout: bind(this._showDataInfo, this, false),
style: {
fill: this.dataZoomModel.get('fillerColor'),
// text: ':::',
textPosition : 'inside'
}
}));
// Frame border.
barGroup.add(new Rect(graphic.subPixelOptimizeRect({
silent: true,
shape: {
x: 0,
y: 0,
width: size[0],
height: size[1]
},
style: {
stroke: this.dataZoomModel.get('dataBackgroundColor'),
lineWidth: DEFAULT_FRAME_BORDER_WIDTH,
fill: 'rgba(0,0,0,0)'
}
})));
each([0, 1], function (handleIndex) {
barGroup.add(handles[handleIndex] = new Rect({
style: {
fill: this.dataZoomModel.get('handleColor')
},
cursor: 'move',
draggable: true,
drift: bind(this._onDragMove, this, handleIndex),
ondragend: bind(this._onDragEnd, this),
onmouseover: bind(this._showDataInfo, this, true),
onmouseout: bind(this._showDataInfo, this, false)
}));
var textStyleModel = this.dataZoomModel.textStyleModel;
this.group.add(
handleLabels[handleIndex] = new graphic.Text({
silent: true,
invisible: true,
style: {
x: 0, y: 0, text: '',
textVerticalAlign: 'middle',
textAlign: 'center',
fill: textStyleModel.getTextColor(),
textFont: textStyleModel.getFont()
}
}));
}, this);
},
/**
* @private
*/
_resetInterval: function () {
var range = this._range = this.dataZoomModel.getPercentRange();
var viewExtent = this._getViewExtent();
this._handleEnds = [
linearMap(range[0], [0, 100], viewExtent, true),
linearMap(range[1], [0, 100], viewExtent, true)
];
},
/**
* @private
* @param {(number|string)} handleIndex 0 or 1 or 'all'
* @param {number} dx
* @param {number} dy
*/
_updateInterval: function (handleIndex, delta) {
var handleEnds = this._handleEnds;
var viewExtend = this._getViewExtent();
sliderMove(
delta,
handleEnds,
viewExtend,
(handleIndex === 'all' || this.dataZoomModel.get('zoomLock'))
? 'rigid' : 'cross',
handleIndex
);
this._range = asc([
linearMap(handleEnds[0], viewExtend, [0, 100], true),
linearMap(handleEnds[1], viewExtend, [0, 100], true)
]);
},
/**
* @private
*/
_updateView: function () {
var displaybles = this._displayables;
var handleEnds = this._handleEnds;
var handleInterval = asc(handleEnds.slice());
var size = this._size;
var halfHandleSize = this._halfHandleSize;
each([0, 1], function (handleIndex) {
// Handles
var handle = displaybles.handles[handleIndex];
handle.setShape({
x: handleEnds[handleIndex] - halfHandleSize,
y: -1,
width: halfHandleSize * 2,
height: size[1] + 2,
r: 1
});
}, this);
// Filler
displaybles.filler.setShape({
x: handleInterval[0],
y: 0,
width: handleInterval[1] - handleInterval[0],
height: this._size[1]
});
this._updateDataInfo();
},
/**
* @private
*/
_updateDataInfo: function () {
var dataZoomModel = this.dataZoomModel;
var displaybles = this._displayables;
var handleLabels = displaybles.handleLabels;
var orient = this._orient;
var labelTexts = ['', ''];
// FIXME
// date型支持formatterautoformatterec2 date.getAutoFormatter
if (dataZoomModel.get('showDetail')) {
var dataInterval;
var axis;
dataZoomModel.eachTargetAxis(function (dimNames, axisIndex) {
// Using dataInterval of the first axis.
if (!dataInterval) {
dataInterval = dataZoomModel
.getAxisProxy(dimNames.name, axisIndex)
.getDataValueWindow();
axis = this.ecModel.getComponent(dimNames.axis, axisIndex).axis;
}
}, this);
if (dataInterval) {
labelTexts = [
this._formatLabel(dataInterval[0], axis),
this._formatLabel(dataInterval[1], axis)
];
}
}
var orderedHandleEnds = asc(this._handleEnds.slice());
setLabel.call(this, 0);
setLabel.call(this, 1);
function setLabel(handleIndex) {
// Label
// Text should not transform by barGroup.
var barTransform = graphic.getTransform(
displaybles.handles[handleIndex], this.group
);
var direction = graphic.transformDirection(
handleIndex === 0 ? 'right' : 'left', barTransform
);
var offset = this._halfHandleSize + LABEL_GAP;
var textPoint = graphic.applyTransform(
[
orderedHandleEnds[handleIndex] + (handleIndex === 0 ? -offset : offset),
this._size[1] / 2
],
barTransform
);
handleLabels[handleIndex].setStyle({
x: textPoint[0],
y: textPoint[1],
textVerticalAlign: orient === HORIZONTAL ? 'middle' : direction,
textAlign: orient === HORIZONTAL ? direction : 'center',
text: labelTexts[handleIndex]
});
}
},
/**
* @private
*/
_formatLabel: function (value, axis) {
var dataZoomModel = this.dataZoomModel;
var labelFormatter = dataZoomModel.get('labelFormatter');
if (zrUtil.isFunction(labelFormatter)) {
return labelFormatter(value);
}
var labelPrecision = dataZoomModel.get('labelPrecision');
if (labelPrecision == null || labelPrecision === 'auto') {
labelPrecision = axis.getPixelPrecision();
}
value = (value == null && isNaN(value))
? ''
// FIXME Glue code
: (axis.type === 'category' || axis.type === 'time')
? axis.scale.getLabel(Math.round(value))
// param of toFixed should less then 20.
: value.toFixed(Math.min(labelPrecision, 20));
if (zrUtil.isString(labelFormatter)) {
value = labelFormatter.replace('{value}', value);
}
return value;
},
/**
* @private
* @param {boolean} showOrHide true: show, false: hide
*/
_showDataInfo: function (showOrHide) {
// Always show when drgging.
showOrHide = this._dragging || showOrHide;
var handleLabels = this._displayables.handleLabels;
handleLabels[0].attr('invisible', !showOrHide);
handleLabels[1].attr('invisible', !showOrHide);
},
_onDragMove: function (handleIndex, dx, dy) {
this._dragging = true;
// Transform dx, dy to bar coordination.
var vertex = this._applyBarTransform([dx, dy], true);
this._updateInterval(handleIndex, vertex[0]);
this._updateView();
if (this.dataZoomModel.get('realtime')) {
this._dispatchZoomAction();
}
},
_onDragEnd: function () {
this._dragging = false;
this._showDataInfo(false);
this._dispatchZoomAction();
},
/**
* This action will be throttled.
* @private
*/
_dispatchZoomAction: function () {
var range = this._range;
this.api.dispatchAction({
type: 'dataZoom',
from: this.uid,
dataZoomId: this.dataZoomModel.id,
start: range[0],
end: range[1]
});
},
/**
* @private
*/
_applyBarTransform: function (vertex, inverse) {
var barTransform = this._displayables.barGroup.getLocalTransform();
return graphic.applyTransform(vertex, barTransform, inverse);
},
/**
* @private
*/
_findCoordRect: function () {
// Find the grid coresponding to the first axis referred by dataZoom.
var targetInfo = this.getTargetInfo();
// FIXME
// 判断是catesian还是polar
var rect;
if (targetInfo.cartesians.length) {
rect = targetInfo.cartesians[0].model.coordinateSystem.getRect();
}
else { // Polar
// FIXME
// 暂时随便写的
var width = this.api.getWidth();
var height = this.api.getHeight();
rect = {
x: width * 0.2,
y: height * 0.2,
width: width * 0.6,
height: height * 0.6
};
}
return rect;
}
});
function getOtherDim(thisDim) {
// FIXME
// 这个逻辑和getOtherAxis里一致但是写在这里是否不好
return thisDim === 'x' ? 'y' : 'x';
}
return SliderZoomView;
});

View File

@@ -0,0 +1,43 @@
/**
* @file Data zoom action
*/
define(function(require) {
var zrUtil = require('zrender/core/util');
var modelUtil = require('../../util/model');
var echarts = require('../../echarts');
echarts.registerAction('dataZoom', function (payload, ecModel) {
var linkedNodesFinder = modelUtil.createLinkedNodesFinder(
zrUtil.bind(ecModel.eachComponent, ecModel, 'dataZoom'),
modelUtil.eachAxisDim,
function (model, dimNames) {
return model.get(dimNames.axisIndex);
}
);
var effectedModels = [];
ecModel.eachComponent(
{mainType: 'dataZoom', query: payload},
function (model, index) {
effectedModels.push.apply(
effectedModels, linkedNodesFinder(model).nodes
);
}
);
zrUtil.each(effectedModels, function (dataZoomModel, index) {
dataZoomModel.setRawRange({
start: payload.start,
end: payload.end,
startValue: payload.startValue,
endValue: payload.endValue
});
});
});
});

View File

@@ -0,0 +1,57 @@
/**
* @file Data zoom processor
*/
define(function (require) {
var echarts = require('../../echarts');
echarts.registerProcessor('filter', function (ecModel, api) {
ecModel.eachComponent('dataZoom', function (dataZoomModel) {
// We calculate window and reset axis here but not in model
// init stage and not after action dispatch handler, because
// reset should be called after seriesData.restoreData.
dataZoomModel.eachTargetAxis(resetSingleAxis);
// Caution: data zoom filtering is order sensitive when using
// percent range and no min/max/scale set on axis.
// For example, we have dataZoom definition:
// [
// {xAxisIndex: 0, start: 30, end: 70},
// {yAxisIndex: 0, start: 20, end: 80}
// ]
// In this case, [20, 80] of y-dataZoom should be based on data
// that have filtered by x-dataZoom using range of [30, 70],
// but should not be based on full raw data. Thus sliding
// x-dataZoom will change both ranges of xAxis and yAxis,
// while sliding y-dataZoom will only change the range of yAxis.
// So we should filter x-axis after reset x-axis immediately,
// and then reset y-axis and filter y-axis.
dataZoomModel.eachTargetAxis(filterSingleAxis);
});
ecModel.eachComponent('dataZoom', function (dataZoomModel) {
// Fullfill all of the range props so that user
// is able to get them from chart.getOption().
var axisProxy = dataZoomModel.findRepresentativeAxisProxy();
var percentRange = axisProxy.getDataPercentWindow();
var valueRange = axisProxy.getDataValueWindow();
dataZoomModel.setRawRange({
start: percentRange[0],
end: percentRange[1],
startValue: valueRange[0],
endValue: valueRange[1]
});
});
});
function resetSingleAxis(dimNames, axisIndex, dataZoomModel) {
dataZoomModel.getAxisProxy(dimNames.name, axisIndex).reset(dataZoomModel);
}
function filterSingleAxis(dimNames, axisIndex, dataZoomModel) {
dataZoomModel.getAxisProxy(dimNames.name, axisIndex).filterData(dataZoomModel);
}
});

View File

@@ -0,0 +1,109 @@
/**
* @file History manager.
*/
define(function(require) {
var zrUtil = require('zrender/core/util');
var each = zrUtil.each;
var ATTR = '\0_ec_hist_store';
var history = {
/**
* @public
* @param {module:echarts/model/Global} ecModel
* @param {Object} newSnapshot {dataZoomId, batch: [payloadInfo, ...]}
*/
push: function (ecModel, newSnapshot) {
var store = giveStore(ecModel);
// If previous dataZoom can not be found,
// complete an range with current range.
each(newSnapshot, function (batchItem, dataZoomId) {
var i = store.length - 1;
for (; i >= 0; i--) {
var snapshot = store[i];
if (snapshot[dataZoomId]) {
break;
}
}
if (i < 0) {
// No origin range set, create one by current range.
var dataZoomModel = ecModel.queryComponents(
{mainType: 'dataZoom', subType: 'select', id: dataZoomId}
)[0];
if (dataZoomModel) {
var percentRange = dataZoomModel.getPercentRange();
store[0][dataZoomId] = {
dataZoomId: dataZoomId,
start: percentRange[0],
end: percentRange[1]
};
}
}
});
store.push(newSnapshot);
},
/**
* @public
* @param {module:echarts/model/Global} ecModel
* @return {Object} snapshot
*/
pop: function (ecModel) {
var store = giveStore(ecModel);
var head = store[store.length - 1];
store.length > 1 && store.pop();
// Find top for all dataZoom.
var snapshot = {};
each(head, function (batchItem, dataZoomId) {
for (var i = store.length - 1; i >= 0; i--) {
var batchItem = store[i][dataZoomId];
if (batchItem) {
snapshot[dataZoomId] = batchItem;
break;
}
}
});
return snapshot;
},
/**
* @public
*/
clear: function (ecModel) {
ecModel[ATTR] = null;
},
/**
* @public
* @param {module:echarts/model/Global} ecModel
* @return {number} records. always >= 1.
*/
count: function (ecModel) {
return giveStore(ecModel).length;
}
};
/**
* [{key: dataZoomId, value: {dataZoomId, range}}, ...]
* History length of each dataZoom may be different.
* this._history[0] is used to store origin range.
* @type {Array.<Object>}
*/
function giveStore(ecModel) {
var store = ecModel[ATTR];
if (!store) {
store = ecModel[ATTR] = [{}];
}
return store;
}
return history;
});

View File

@@ -0,0 +1,192 @@
/**
* @file Roam controller manager.
*/
define(function(require) {
// Only create one roam controller for each coordinate system.
// one roam controller might be refered by two inside data zoom
// components (for example, one for x and one for y). When user
// pan or zoom, only dispatch one action for those data zoom
// components.
var zrUtil = require('zrender/core/util');
var RoamController = require('../../component/helper/RoamController');
var throttle = require('../../util/throttle');
var curry = zrUtil.curry;
var ATTR = '\0_ec_dataZoom_roams';
var roams = {
/**
* @public
* @param {module:echarts/ExtensionAPI} api
* @param {Object} dataZoomInfo
* @param {string} dataZoomInfo.coordId
* @param {Object} dataZoomInfo.coordinateSystem
* @param {Array.<string>} dataZoomInfo.allCoordIds
* @param {string} dataZoomInfo.dataZoomId
* @param {number} dataZoomInfo.throttleRate
* @param {Function} dataZoomInfo.panGetRange
* @param {Function} dataZoomInfo.zoomGetRange
*/
register: function (api, dataZoomInfo) {
var store = giveStore(api);
var theDataZoomId = dataZoomInfo.dataZoomId;
var theCoordId = dataZoomInfo.coordId;
// Do clean when a dataZoom changes its target coordnate system.
zrUtil.each(store, function (record, coordId) {
var dataZoomInfos = record.dataZoomInfos;
if (dataZoomInfos[theDataZoomId]
&& zrUtil.indexOf(dataZoomInfo.allCoordIds, theCoordId) < 0
) {
delete dataZoomInfos[theDataZoomId];
record.count--;
}
});
cleanStore(store);
var record = store[theCoordId];
// Create if needed.
if (!record) {
record = store[theCoordId] = {
coordId: theCoordId,
dataZoomInfos: {},
count: 0
};
record.controller = createController(api, dataZoomInfo, record);
record.dispatchAction = zrUtil.curry(dispatchAction, api);
}
// Consider resize, area should be always updated.
var rect = dataZoomInfo.coordinateSystem.getRect().clone();
record.controller.rectProvider = function () {
return rect;
};
// Update throttle.
throttle.createOrUpdate(
record,
'dispatchAction',
dataZoomInfo.throttleRate,
'fixRate'
);
// Update reference of dataZoom.
!(record.dataZoomInfos[theDataZoomId]) && record.count++;
record.dataZoomInfos[theDataZoomId] = dataZoomInfo;
},
/**
* @public
* @param {module:echarts/ExtensionAPI} api
* @param {string} dataZoomId
*/
unregister: function (api, dataZoomId) {
var store = giveStore(api);
zrUtil.each(store, function (record) {
var dataZoomInfos = record.dataZoomInfos;
if (dataZoomInfos[dataZoomId]) {
delete dataZoomInfos[dataZoomId];
record.count--;
}
});
cleanStore(store);
},
/**
* @public
*/
shouldRecordRange: function (payload, dataZoomId) {
if (payload && payload.type === 'dataZoom' && payload.batch) {
for (var i = 0, len = payload.batch.length; i < len; i++) {
if (payload.batch[i].dataZoomId === dataZoomId) {
return false;
}
}
}
return true;
},
/**
* @public
*/
generateCoordId: function (coordModel) {
return coordModel.type + '\0_' + coordModel.id;
}
};
/**
* Key: coordId, value: {dataZoomInfos: [], count, controller}
* @type {Array.<Object>}
*/
function giveStore(api) {
// Mount store on zrender instance, so that we do not
// need to worry about dispose.
var zr = api.getZr();
return zr[ATTR] || (zr[ATTR] = {});
}
function createController(api, dataZoomInfo, newRecord) {
var controller = new RoamController(api.getZr());
controller.enable();
controller.on('pan', curry(onPan, newRecord));
controller.on('zoom', curry(onZoom, newRecord));
return controller;
}
function cleanStore(store) {
zrUtil.each(store, function (record, coordId) {
if (!record.count) {
record.controller.off('pan').off('zoom');
delete store[coordId];
}
});
}
function onPan(record, dx, dy) {
wrapAndDispatch(record, function (info) {
return info.panGetRange(record.controller, dx, dy);
});
}
function onZoom(record, scale, mouseX, mouseY) {
wrapAndDispatch(record, function (info) {
return info.zoomGetRange(record.controller, scale, mouseX, mouseY);
});
}
function wrapAndDispatch(record, getRange) {
var batch = [];
zrUtil.each(record.dataZoomInfos, function (info) {
var range = getRange(info);
range && batch.push({
dataZoomId: info.dataZoomId,
start: range[0],
end: range[1]
});
});
record.dispatchAction(batch);
}
/**
* This action will be throttled.
*/
function dispatchAction(api, batch) {
api.dispatchAction({
type: 'dataZoom',
batch: batch
});
}
return roams;
});

View File

@@ -0,0 +1,8 @@
define(function (require) {
require('../../model/Component').registerSubTypeDefaulter('dataZoom', function (option) {
// Default 'slider' when no type specified.
return 'slider';
});
});

View File

@@ -0,0 +1,17 @@
/**
* DataZoom component entry
*/
define(function (require) {
require('./dataZoom/typeDefaulter');
require('./dataZoom/DataZoomModel');
require('./dataZoom/DataZoomView');
require('./dataZoom/InsideZoomModel');
require('./dataZoom/InsideZoomView');
require('./dataZoom/dataZoomProcessor');
require('./dataZoom/dataZoomAction');
});

View File

@@ -0,0 +1,17 @@
/**
* DataZoom component entry
*/
define(function (require) {
require('./dataZoom/typeDefaulter');
require('./dataZoom/DataZoomModel');
require('./dataZoom/DataZoomView');
require('./dataZoom/SelectZoomModel');
require('./dataZoom/SelectZoomView');
require('./dataZoom/dataZoomProcessor');
require('./dataZoom/dataZoomAction');
});

49
vendors/echarts/src/component/geo.js vendored Normal file
View File

@@ -0,0 +1,49 @@
define(function (require) {
require('../coord/geo/GeoModel');
require('../coord/geo/geoCreator');
require('./geo/GeoView');
require('../action/geoRoam');
var echarts = require('../echarts');
var zrUtil = require('zrender/core/util');
function makeAction(method, actionInfo) {
actionInfo.update = 'updateView';
echarts.registerAction(actionInfo, function (payload, ecModel) {
var selected = {};
ecModel.eachComponent(
{ mainType: 'geo', query: payload},
function (geoModel) {
geoModel[method](payload.name);
var geo = geoModel.coordinateSystem;
zrUtil.each(geo.regions, function (region) {
selected[region.name] = geoModel.isSelected(region.name) || false;
});
}
);
return {
selected: selected,
name: payload.name
}
});
}
makeAction('toggleSelected', {
type: 'geoToggleSelect',
event: 'geoselectchanged'
});
makeAction('select', {
type: 'geoSelect',
event: 'geoselected'
});
makeAction('unSelect', {
type: 'geoUnSelect',
event: 'geounselected'
});
});

View File

@@ -0,0 +1,35 @@
define(function (require) {
'use strict';
var MapDraw = require('../helper/MapDraw');
return require('../../echarts').extendComponentView({
type: 'geo',
init: function (ecModel, api) {
var mapDraw = new MapDraw(api, true);
this._mapDraw = mapDraw;
this.group.add(mapDraw.group);
},
render: function (geoModel, ecModel, api, payload) {
// Not render if it is an toggleSelect action from self
if (payload && payload.type === 'geoToggleSelect'
&& payload.from === this.uid
) {
return;
}
var mapDraw = this._mapDraw;
if (geoModel.get('show')) {
mapDraw.draw(geoModel, ecModel, api, this, payload);
}
else {
this._mapDraw.group.removeAll();
}
}
});
});

29
vendors/echarts/src/component/grid.js vendored Normal file
View File

@@ -0,0 +1,29 @@
define(function(require) {
'use strict';
var graphic = require('../util/graphic');
var zrUtil = require('zrender/core/util');
require('../coord/cartesian/Grid');
require('./axis');
// Grid view
require('../echarts').extendComponentView({
type: 'grid',
render: function (gridModel, ecModel) {
this.group.removeAll();
if (gridModel.get('show')) {
this.group.add(new graphic.Rect({
shape:gridModel.coordinateSystem.getRect(),
style: zrUtil.defaults({
fill: gridModel.get('backgroundColor')
}, gridModel.getItemStyle()),
silent: true
}));
}
}
});
});

View File

@@ -0,0 +1,295 @@
/**
* @module echarts/component/helper/MapDraw
*/
define(function (require) {
var RoamController = require('./RoamController');
var graphic = require('../../util/graphic');
var zrUtil = require('zrender/core/util');
function getFixedItemStyle(model, scale) {
var itemStyle = model.getItemStyle();
var areaColor = model.get('areaColor');
if (areaColor) {
itemStyle.fill = areaColor;
}
return itemStyle;
}
function updateMapSelectHandler(mapOrGeoModel, group, api, fromView) {
group.off('click');
mapOrGeoModel.get('selectedMode')
&& group.on('click', function (e) {
var el = e.target;
while (!el.__region) {
el = el.parent;
}
if (!el) {
return;
}
var region = el.__region;
var action = {
type: (mapOrGeoModel.mainType === 'geo' ? 'geo' : 'map') + 'ToggleSelect',
name: region.name,
from: fromView.uid
};
action[mapOrGeoModel.mainType + 'Id'] = mapOrGeoModel.id;
api.dispatchAction(action);
updateMapSelected(mapOrGeoModel, group);
});
}
function updateMapSelected(mapOrGeoModel, group) {
// FIXME
group.eachChild(function (otherRegionEl) {
if (otherRegionEl.__region) {
otherRegionEl.trigger(mapOrGeoModel.isSelected(otherRegionEl.__region.name) ? 'emphasis' : 'normal');
}
});
}
/**
* @alias module:echarts/component/helper/MapDraw
* @param {module:echarts/ExtensionAPI} api
* @param {boolean} updateGroup
*/
function MapDraw(api, updateGroup) {
var group = new graphic.Group();
/**
* @type {module:echarts/component/helper/RoamController}
* @private
*/
this._controller = new RoamController(
api.getZr(), updateGroup ? group : null, null
);
/**
* @type {module:zrender/container/Group}
* @readOnly
*/
this.group = group;
/**
* @type {boolean}
* @private
*/
this._updateGroup = updateGroup;
}
MapDraw.prototype = {
constructor: MapDraw,
draw: function (mapOrGeoModel, ecModel, api, fromView, payload) {
// geoModel has no data
var data = mapOrGeoModel.getData && mapOrGeoModel.getData();
var geo = mapOrGeoModel.coordinateSystem;
var group = this.group;
var scale = geo.scale;
var groupNewProp = {
position: geo.position,
scale: scale
};
// No animation when first draw or in action
if (!group.childAt(0) || payload) {
group.attr(groupNewProp);
}
else {
graphic.updateProps(group, groupNewProp, mapOrGeoModel);
}
group.removeAll();
var itemStyleAccessPath = ['itemStyle', 'normal'];
var hoverItemStyleAccessPath = ['itemStyle', 'emphasis'];
var labelAccessPath = ['label', 'normal'];
var hoverLabelAccessPath = ['label', 'emphasis'];
zrUtil.each(geo.regions, function (region) {
var regionGroup = new graphic.Group();
var compoundPath = new graphic.CompoundPath({
shape: {
paths: []
}
});
regionGroup.add(compoundPath);
var regionModel = mapOrGeoModel.getRegionModel(region.name) || mapOrGeoModel;
var itemStyleModel = regionModel.getModel(itemStyleAccessPath);
var hoverItemStyleModel = regionModel.getModel(hoverItemStyleAccessPath);
var itemStyle = getFixedItemStyle(itemStyleModel, scale);
var hoverItemStyle = getFixedItemStyle(hoverItemStyleModel, scale);
var labelModel = regionModel.getModel(labelAccessPath);
var hoverLabelModel = regionModel.getModel(hoverLabelAccessPath);
var dataIdx;
// Use the itemStyle in data if has data
if (data) {
dataIdx = data.indexOfName(region.name);
// Only visual color of each item will be used. It can be encoded by dataRange
// But visual color of series is used in symbol drawing
//
// Visual color for each series is for the symbol draw
var visualColor = data.getItemVisual(dataIdx, 'color', true);
if (visualColor) {
itemStyle.fill = visualColor;
}
}
var textStyleModel = labelModel.getModel('textStyle');
var hoverTextStyleModel = hoverLabelModel.getModel('textStyle');
zrUtil.each(region.contours, function (contour) {
var polygon = new graphic.Polygon({
shape: {
points: contour
}
});
compoundPath.shape.paths.push(polygon);
});
compoundPath.setStyle(itemStyle);
compoundPath.style.strokeNoScale = true;
compoundPath.culling = true;
// Label
var showLabel = labelModel.get('show');
var hoverShowLabel = hoverLabelModel.get('show');
var isDataNaN = data && isNaN(data.get('value', dataIdx));
var itemLayout = data && data.getItemLayout(dataIdx);
// In the following cases label will be drawn
// 1. In map series and data value is NaN
// 2. In geo component
// 4. Region has no series legendSymbol, which will be add a showLabel flag in mapSymbolLayout
if (
(!data || isDataNaN && (showLabel || hoverShowLabel))
|| (itemLayout && itemLayout.showLabel)
) {
var query = data ? dataIdx : region.name;
var formattedStr = mapOrGeoModel.getFormattedLabel(query, 'normal');
var hoverFormattedStr = mapOrGeoModel.getFormattedLabel(query, 'emphasis');
var text = new graphic.Text({
style: {
text: showLabel ? (formattedStr || region.name) : '',
fill: textStyleModel.getTextColor(),
textFont: textStyleModel.getFont(),
textAlign: 'center',
textVerticalAlign: 'middle'
},
hoverStyle: {
text: hoverShowLabel ? (hoverFormattedStr || region.name) : '',
fill: hoverTextStyleModel.getTextColor(),
textFont: hoverTextStyleModel.getFont()
},
position: region.center.slice(),
scale: [1 / scale[0], 1 / scale[1]],
z2: 10,
silent: true
});
regionGroup.add(text);
}
// setItemGraphicEl, setHoverStyle after all polygons and labels
// are added to the rigionGroup
if (data) {
data.setItemGraphicEl(dataIdx, regionGroup);
}
else {
var regionModel = mapOrGeoModel.getRegionModel(region.name);
// Package custom mouse event for geo component
compoundPath.eventData = {
componentType: 'geo',
geoIndex: mapOrGeoModel.componentIndex,
name: region.name,
region: (regionModel && regionModel.option) || {}
};
}
regionGroup.__region = region;
graphic.setHoverStyle(regionGroup, hoverItemStyle);
group.add(regionGroup);
});
this._updateController(mapOrGeoModel, ecModel, api);
updateMapSelectHandler(mapOrGeoModel, group, api, fromView);
updateMapSelected(mapOrGeoModel, group);
},
remove: function () {
this.group.removeAll();
this._controller.dispose();
},
_updateController: function (mapOrGeoModel, ecModel, api) {
var geo = mapOrGeoModel.coordinateSystem;
var controller = this._controller;
controller.zoomLimit = mapOrGeoModel.get('scaleLimit');
// Update zoom from model
controller.zoom = geo.getZoom();
// roamType is will be set default true if it is null
controller.enable(mapOrGeoModel.get('roam') || false);
var mainType = mapOrGeoModel.mainType;
function makeActionBase() {
var action = {
type: 'geoRoam',
componentType: mainType
};
action[mainType + 'Id'] = mapOrGeoModel.id;
return action;
}
controller.off('pan')
.on('pan', function (dx, dy) {
api.dispatchAction(zrUtil.extend(makeActionBase(), {
dx: dx,
dy: dy
}));
});
controller.off('zoom')
.on('zoom', function (zoom, mouseX, mouseY) {
api.dispatchAction(zrUtil.extend(makeActionBase(), {
zoom: zoom,
originX: mouseX,
originY: mouseY
}));
if (this._updateGroup) {
var group = this.group;
var scale = group.scale;
group.traverse(function (el) {
if (el.type === 'text') {
el.attr('scale', [1 / scale[0], 1 / scale[1]]);
}
});
}
}, this);
controller.rectProvider = function () {
return geo.getViewRectAfterRoam();
};
}
};
return MapDraw;
});

View File

@@ -0,0 +1,221 @@
/**
* @module echarts/component/helper/RoamController
*/
define(function (require) {
var Eventful = require('zrender/mixin/Eventful');
var zrUtil = require('zrender/core/util');
var eventTool = require('zrender/core/event');
var interactionMutex = require('./interactionMutex');
function mousedown(e) {
if (e.target && e.target.draggable) {
return;
}
var x = e.offsetX;
var y = e.offsetY;
var rect = this.rectProvider && this.rectProvider();
if (rect && rect.contain(x, y)) {
this._x = x;
this._y = y;
this._dragging = true;
}
}
function mousemove(e) {
if (!this._dragging) {
return;
}
eventTool.stop(e.event);
if (e.gestureEvent !== 'pinch') {
if (interactionMutex.isTaken('globalPan', this._zr)) {
return;
}
var x = e.offsetX;
var y = e.offsetY;
var dx = x - this._x;
var dy = y - this._y;
this._x = x;
this._y = y;
var target = this.target;
if (target) {
var pos = target.position;
pos[0] += dx;
pos[1] += dy;
target.dirty();
}
eventTool.stop(e.event);
this.trigger('pan', dx, dy);
}
}
function mouseup(e) {
this._dragging = false;
}
function mousewheel(e) {
// Convenience:
// Mac and VM Windows on Mac: scroll up: zoom out.
// Windows: scroll up: zoom in.
var zoomDelta = e.wheelDelta > 0 ? 1.1 : 1 / 1.1;
zoom.call(this, e, zoomDelta, e.offsetX, e.offsetY);
}
function pinch(e) {
if (interactionMutex.isTaken('globalPan', this._zr)) {
return;
}
var zoomDelta = e.pinchScale > 1 ? 1.1 : 1 / 1.1;
zoom.call(this, e, zoomDelta, e.pinchX, e.pinchY);
}
function zoom(e, zoomDelta, zoomX, zoomY) {
var rect = this.rectProvider && this.rectProvider();
if (rect && rect.contain(zoomX, zoomY)) {
// When mouse is out of roamController rect,
// default befavoius should be be disabled, otherwise
// page sliding is disabled, contrary to expectation.
eventTool.stop(e.event);
var target = this.target;
var zoomLimit = this.zoomLimit;
if (target) {
var pos = target.position;
var scale = target.scale;
var newZoom = this.zoom = this.zoom || 1;
newZoom *= zoomDelta;
if (zoomLimit) {
var zoomMin = zoomLimit.min || 0;
var zoomMax = zoomLimit.max || Infinity;
newZoom = Math.max(
Math.min(zoomMax, newZoom),
zoomMin
);
}
var zoomScale = newZoom / this.zoom;
this.zoom = newZoom;
// Keep the mouse center when scaling
pos[0] -= (zoomX - pos[0]) * (zoomScale - 1);
pos[1] -= (zoomY - pos[1]) * (zoomScale - 1);
scale[0] *= zoomScale;
scale[1] *= zoomScale;
target.dirty();
}
this.trigger('zoom', zoomDelta, zoomX, zoomY);
}
}
/**
* @alias module:echarts/component/helper/RoamController
* @constructor
* @mixin {module:zrender/mixin/Eventful}
*
* @param {module:zrender/zrender~ZRender} zr
* @param {module:zrender/Element} target
* @param {Function} rectProvider
*/
function RoamController(zr, target, rectProvider) {
/**
* @type {module:zrender/Element}
*/
this.target = target;
/**
* @type {Function}
*/
this.rectProvider = rectProvider;
/**
* { min: 1, max: 2 }
* @type {Object}
*/
this.zoomLimit;
/**
* @type {number}
*/
this.zoom;
/**
* @type {module:zrender}
*/
this._zr = zr;
// Avoid two roamController bind the same handler
var bind = zrUtil.bind;
var mousedownHandler = bind(mousedown, this);
var mousemoveHandler = bind(mousemove, this);
var mouseupHandler = bind(mouseup, this);
var mousewheelHandler = bind(mousewheel, this);
var pinchHandler = bind(pinch, this);
Eventful.call(this);
/**
* Notice: only enable needed types. For example, if 'zoom'
* is not needed, 'zoom' should not be enabled, otherwise
* default mousewheel behaviour (scroll page) will be disabled.
*
* @param {boolean|string} [controlType=true] Specify the control type,
* which can be null/undefined or true/false
* or 'pan/move' or 'zoom'/'scale'
*/
this.enable = function (controlType) {
// Disable previous first
this.disable();
if (controlType == null) {
controlType = true;
}
if (controlType === true || (controlType === 'move' || controlType === 'pan')) {
zr.on('mousedown', mousedownHandler);
zr.on('mousemove', mousemoveHandler);
zr.on('mouseup', mouseupHandler);
}
if (controlType === true || (controlType === 'scale' || controlType === 'zoom')) {
zr.on('mousewheel', mousewheelHandler);
zr.on('pinch', pinchHandler);
}
};
this.disable = function () {
zr.off('mousedown', mousedownHandler);
zr.off('mousemove', mousemoveHandler);
zr.off('mouseup', mouseupHandler);
zr.off('mousewheel', mousewheelHandler);
zr.off('pinch', pinchHandler);
};
this.dispose = this.disable;
this.isDragging = function () {
return this._dragging;
};
this.isPinching = function () {
return this._pinching;
};
}
zrUtil.mixin(RoamController, Eventful);
return RoamController;
});

View File

@@ -0,0 +1,373 @@
/**
* Box selection tool.
*
* @module echarts/component/helper/SelectController
*/
define(function (require) {
var Eventful = require('zrender/mixin/Eventful');
var zrUtil = require('zrender/core/util');
var graphic = require('../../util/graphic');
var bind = zrUtil.bind;
var each = zrUtil.each;
var mathMin = Math.min;
var mathMax = Math.max;
var mathPow = Math.pow;
var COVER_Z = 10000;
var UNSELECT_THRESHOLD = 2;
var EVENTS = ['mousedown', 'mousemove', 'mouseup'];
/**
* @alias module:echarts/component/helper/SelectController
* @constructor
* @mixin {module:zrender/mixin/Eventful}
*
* @param {string} type 'line', 'rect'
* @param {module:zrender/zrender~ZRender} zr
* @param {Object} [opt]
* @param {number} [opt.width]
* @param {number} [opt.lineWidth]
* @param {string} [opt.stroke]
* @param {string} [opt.fill]
*/
function SelectController(type, zr, opt) {
Eventful.call(this);
/**
* @type {string}
* @readOnly
*/
this.type = type;
/**
* @type {module:zrender/zrender~ZRender}
*/
this.zr = zr;
/**
* @type {Object}
* @readOnly
*/
this.opt = zrUtil.clone(opt);
/**
* @type {module:zrender/container/Group}
* @readOnly
*/
this.group = new graphic.Group();
/**
* @type {module:zrender/core/BoundingRect}
*/
this._containerRect = null;
/**
* @type {Array.<nubmer>}
* @private
*/
this._track = [];
/**
* @type {boolean}
*/
this._dragging;
/**
* @type {module:zrender/Element}
* @private
*/
this._cover;
/**
* @type {boolean}
* @private
*/
this._disabled = true;
/**
* @type {Object}
* @private
*/
this._handlers = {
mousedown: bind(mousedown, this),
mousemove: bind(mousemove, this),
mouseup: bind(mouseup, this)
};
each(EVENTS, function (eventName) {
this.zr.on(eventName, this._handlers[eventName]);
}, this);
}
SelectController.prototype = {
constructor: SelectController,
/**
* @param {module:zrender/mixin/Transformable} container
* @param {module:zrender/core/BoundingRect|boolean} [rect] If not specified,
* use container.getBoundingRect().
* If false, do not use containerRect.
*/
enable: function (container, rect) {
this._disabled = false;
// Remove from old container.
removeGroup.call(this);
// boundingRect will change when dragging, so we have
// to keep initial boundingRect.
this._containerRect = rect !== false
? (rect || container.getBoundingRect()) : null;
// Add to new container.
container.add(this.group);
},
/**
* Update cover location.
* @param {Array.<number>|Object} ranges If null/undefined, remove cover.
*/
update: function (ranges) {
// TODO
// Only support one interval yet.
renderCover.call(this, ranges && zrUtil.clone(ranges));
},
disable: function () {
this._disabled = true;
removeGroup.call(this);
},
dispose: function () {
this.disable();
each(EVENTS, function (eventName) {
this.zr.off(eventName, this._handlers[eventName]);
}, this);
}
};
zrUtil.mixin(SelectController, Eventful);
function updateZ(group) {
group.traverse(function (el) {
el.z = COVER_Z;
});
}
function isInContainer(x, y) {
var localPos = this.group.transformCoordToLocal(x, y);
return !this._containerRect
|| this._containerRect.contain(localPos[0], localPos[1]);
}
function preventDefault(e) {
var rawE = e.event;
rawE.preventDefault && rawE.preventDefault();
}
function mousedown(e) {
if (this._disabled || (e.target && e.target.draggable)) {
return;
}
preventDefault(e);
var x = e.offsetX;
var y = e.offsetY;
if (isInContainer.call(this, x, y)) {
this._dragging = true;
this._track = [[x, y]];
}
}
function mousemove(e) {
if (!this._dragging || this._disabled) {
return;
}
preventDefault(e);
updateViewByCursor.call(this, e);
}
function mouseup(e) {
if (!this._dragging || this._disabled) {
return;
}
preventDefault(e);
updateViewByCursor.call(this, e, true);
this._dragging = false;
this._track = [];
}
function updateViewByCursor(e, isEnd) {
var x = e.offsetX;
var y = e.offsetY;
if (isInContainer.call(this, x, y)) {
this._track.push([x, y]);
// Create or update cover.
var ranges = shouldShowCover.call(this)
? coverRenderers[this.type].getRanges.call(this)
// Remove cover.
: [];
renderCover.call(this, ranges);
this.trigger('selected', zrUtil.clone(ranges));
if (isEnd) {
this.trigger('selectEnd', zrUtil.clone(ranges));
}
}
}
function shouldShowCover() {
var track = this._track;
if (!track.length) {
return false;
}
var p2 = track[track.length - 1];
var p1 = track[0];
var dx = p2[0] - p1[0];
var dy = p2[1] - p1[1];
var dist = mathPow(dx * dx + dy * dy, 0.5);
return dist > UNSELECT_THRESHOLD;
}
function renderCover(ranges) {
var coverRenderer = coverRenderers[this.type];
if (ranges && ranges.length) {
if (!this._cover) {
this._cover = coverRenderer.create.call(this);
this.group.add(this._cover);
}
coverRenderer.update.call(this, ranges);
}
else {
this.group.remove(this._cover);
this._cover = null;
}
updateZ(this.group);
}
function removeGroup() {
// container may 'removeAll' outside.
var group = this.group;
var container = group.parent;
if (container) {
container.remove(group);
}
}
function createRectCover() {
var opt = this.opt;
return new graphic.Rect({
// FIXME
// customize style.
style: {
stroke: opt.stroke,
fill: opt.fill,
lineWidth: opt.lineWidth,
opacity: opt.opacity
}
});
}
function getLocalTrack() {
return zrUtil.map(this._track, function (point) {
return this.group.transformCoordToLocal(point[0], point[1]);
}, this);
}
function getLocalTrackEnds() {
var localTrack = getLocalTrack.call(this);
var tail = localTrack.length - 1;
tail < 0 && (tail = 0);
return [localTrack[0], localTrack[tail]];
}
/**
* key: this.type
* @type {Object}
*/
var coverRenderers = {
line: {
create: createRectCover,
getRanges: function () {
var ends = getLocalTrackEnds.call(this);
var min = mathMin(ends[0][0], ends[1][0]);
var max = mathMax(ends[0][0], ends[1][0]);
return [[min, max]];
},
update: function (ranges) {
var range = ranges[0];
var width = this.opt.width;
this._cover.setShape({
x: range[0],
y: -width / 2,
width: range[1] - range[0],
height: width
});
}
},
rect: {
create: createRectCover,
getRanges: function () {
var ends = getLocalTrackEnds.call(this);
var min = [
mathMin(ends[1][0], ends[0][0]),
mathMin(ends[1][1], ends[0][1])
];
var max = [
mathMax(ends[1][0], ends[0][0]),
mathMax(ends[1][1], ends[0][1])
];
return [[
[min[0], max[0]], // x range
[min[1], max[1]] // y range
]];
},
update: function (ranges) {
var range = ranges[0];
this._cover.setShape({
x: range[0][0],
y: range[1][0],
width: range[0][1] - range[0][0],
height: range[1][1] - range[1][0]
});
}
}
};
return SelectController;
});

View File

@@ -0,0 +1,25 @@
define(function (require) {
var ATTR = '\0_ec_interaction_mutex';
var interactionMutex = {
take: function (key, zr) {
getStore(zr)[key] = true;
},
release: function (key, zr) {
getStore(zr)[key] = false;
},
isTaken: function (key, zr) {
return !!getStore(zr)[key];
}
};
function getStore(zr) {
return zr[ATTR] || (zr[ATTR] = {});
}
return interactionMutex;
});

View File

@@ -0,0 +1,65 @@
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) {
layout.positionGroup(
group, model.getBoxLayoutParams(),
{
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) {
var rect = layout.getLayoutRect(componentModel.getBoxLayoutParams(), {
width: api.getWidth(),
height: api.getHeight()
}, componentModel.get('padding'));
layout.box(
componentModel.get('orient'),
group,
componentModel.get('itemGap'),
rect.width,
rect.height
);
positionGroup(group, componentModel, api);
},
addBackground: function (group, componentModel) {
var padding = formatUtil.normalizeCssArray(
componentModel.get('padding')
);
var boundingRect = group.getBoundingRect();
var style = componentModel.getItemStyle(['color', 'opacity']);
style.fill = componentModel.get('backgroundColor');
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: style,
silent: true,
z2: -1
});
graphic.subPixelOptimizeRect(rect);
group.add(rect);
}
};
});

View File

@@ -0,0 +1,65 @@
/**
* Data selectable mixin for chart series.
* To eanble data select, option of series must have `selectedMode`.
* And each data item will use `selected` to toggle itself selected status
*
* @module echarts/chart/helper/DataSelectable
*/
define(function (require) {
var zrUtil = require('zrender/core/util');
return {
updateSelectedMap: function (targetList) {
this._selectTargetMap = zrUtil.reduce(targetList || [], function (targetMap, target) {
targetMap[target.name] = target;
return targetMap;
}, {});
},
/**
* @param {string} name
*/
// PENGING If selectedMode is null ?
select: function (name) {
var targetMap = this._selectTargetMap;
var target = targetMap[name];
var selectedMode = this.get('selectedMode');
if (selectedMode === 'single') {
zrUtil.each(targetMap, function (target) {
target.selected = false;
});
}
target && (target.selected = true);
},
/**
* @param {string} name
*/
unSelect: function (name) {
var target = this._selectTargetMap[name];
// var selectedMode = this.get('selectedMode');
// selectedMode !== 'single' && target && (target.selected = false);
target && (target.selected = false);
},
/**
* @param {string} name
*/
toggleSelected: function (name) {
var target = this._selectTargetMap[name];
if (target != null) {
this[target.selected ? 'unSelect' : 'select'](name);
return target.selected;
}
},
/**
* @param {string} name
*/
isSelected: function (name) {
var target = this._selectTargetMap[name];
return target && target.selected;
}
};
});

View File

@@ -0,0 +1,54 @@
define(function (require) {
/**
* Calculate slider move result.
*
* @param {number} delta Move length.
* @param {Array.<number>} handleEnds handleEnds[0] and be bigger then handleEnds[1].
* handleEnds will be modified in this method.
* @param {Array.<number>} extent handleEnds is restricted by extent.
* extent[0] should less or equals than extent[1].
* @param {string} mode 'rigid': Math.abs(handleEnds[0] - handleEnds[1]) remain unchanged,
* 'cross' handleEnds[0] can be bigger then handleEnds[1],
* 'push' handleEnds[0] can not be bigger then handleEnds[1],
* when they touch, one push other.
* @param {number} handleIndex If mode is 'rigid', handleIndex is not required.
* @param {Array.<number>} The input handleEnds.
*/
return function (delta, handleEnds, extent, mode, handleIndex) {
if (!delta) {
return handleEnds;
}
if (mode === 'rigid') {
delta = getRealDelta(delta, handleEnds, extent);
handleEnds[0] += delta;
handleEnds[1] += delta;
}
else {
delta = getRealDelta(delta, handleEnds[handleIndex], extent);
handleEnds[handleIndex] += delta;
if (mode === 'push' && handleEnds[0] > handleEnds[1]) {
handleEnds[1 - handleIndex] = handleEnds[handleIndex];
}
}
return handleEnds;
function getRealDelta(delta, handleEnds, extent) {
var handleMinMax = !handleEnds.length
? [handleEnds, handleEnds]
: handleEnds.slice();
handleEnds[0] > handleEnds[1] && handleMinMax.reverse();
if (delta < 0 && handleMinMax[0] + delta < extent[0]) {
delta = extent[0] - handleMinMax[0];
}
if (delta > 0 && handleMinMax[1] + delta > extent[1]) {
delta = extent[1] - handleMinMax[1];
}
return delta;
}
};
});

13
vendors/echarts/src/component/legend.js vendored Normal file
View File

@@ -0,0 +1,13 @@
/**
* Legend component entry file8
*/
define(function (require) {
require('./legend/LegendModel');
require('./legend/legendAction');
require('./legend/LegendView');
var echarts = require('../echarts');
// Series Filter
echarts.registerProcessor('filter', require('./legend/legendFilter'));
});

View File

@@ -0,0 +1,180 @@
define(function(require) {
'use strict';
var zrUtil = require('zrender/core/util');
var Model = require('../../model/Model');
var LegendModel = require('../../echarts').extendComponentModel({
type: 'legend',
dependencies: ['series'],
layoutMode: {
type: 'box',
ignoreSize: true
},
init: function (option, parentModel, ecModel) {
this.mergeDefaultAndTheme(option, ecModel);
option.selected = option.selected || {};
this._updateData(ecModel);
var legendData = this._data;
// If has any selected in option.selected
var selectedMap = this.option.selected;
// If selectedMode is single, try to select one
if (legendData[0] && this.get('selectedMode') === 'single') {
var hasSelected = false;
for (var name in selectedMap) {
if (selectedMap[name]) {
this.select(name);
hasSelected = true;
}
}
// Try select the first if selectedMode is single
!hasSelected && this.select(legendData[0].get('name'));
}
},
mergeOption: function (option) {
LegendModel.superCall(this, 'mergeOption', option);
this._updateData(this.ecModel);
},
_updateData: function (ecModel) {
var legendData = zrUtil.map(this.get('data') || [], function (dataItem) {
if (typeof dataItem === 'string') {
dataItem = {
name: dataItem
};
}
return new Model(dataItem, this, this.ecModel);
}, this);
this._data = legendData;
var availableNames = zrUtil.map(ecModel.getSeries(), function (series) {
return series.name;
});
ecModel.eachSeries(function (seriesModel) {
if (seriesModel.legendDataProvider) {
var data = seriesModel.legendDataProvider();
availableNames = availableNames.concat(data.mapArray(data.getName));
}
});
/**
* @type {Array.<string>}
* @private
*/
this._availableNames = availableNames;
},
/**
* @return {Array.<module:echarts/model/Model>}
*/
getData: function () {
return this._data;
},
/**
* @param {string} name
*/
select: function (name) {
var selected = this.option.selected;
var selectedMode = this.get('selectedMode');
if (selectedMode === 'single') {
var data = this._data;
zrUtil.each(data, function (dataItem) {
selected[dataItem.get('name')] = false;
});
}
selected[name] = true;
},
/**
* @param {string} name
*/
unSelect: function (name) {
if (this.get('selectedMode') !== 'single') {
this.option.selected[name] = false;
}
},
/**
* @param {string} name
*/
toggleSelected: function (name) {
var selected = this.option.selected;
// Default is true
if (!(name in selected)) {
selected[name] = true;
}
this[selected[name] ? 'unSelect' : 'select'](name);
},
/**
* @param {string} name
*/
isSelected: function (name) {
var selected = this.option.selected;
return !((name in selected) && !selected[name])
&& zrUtil.indexOf(this._availableNames, name) >= 0;
},
defaultOption: {
// 一级层叠
zlevel: 0,
// 二级层叠
z: 4,
show: true,
// 布局方式,默认为水平布局,可选为:
// 'horizontal' | 'vertical'
orient: 'horizontal',
left: 'center',
// right: 'center',
top: 'top',
// bottom: 'top',
// 水平对齐
// 'auto' | 'left' | 'right'
// 默认为 'auto', 根据 x 的位置判断是左对齐还是右对齐
align: 'auto',
backgroundColor: 'rgba(0,0,0,0)',
// 图例边框颜色
borderColor: '#ccc',
// 图例边框线宽单位px默认为0无边框
borderWidth: 0,
// 图例内边距单位px默认各方向内边距为5
// 接受数组分别设定上右下左边距同css
padding: 5,
// 各个item之间的间隔单位px默认为10
// 横向布局时为水平间隔,纵向布局时为纵向间隔
itemGap: 10,
// 图例图形宽度
itemWidth: 25,
// 图例图形高度
itemHeight: 14,
textStyle: {
// 图例文字颜色
color: '#333'
},
// formatter: '',
// 选择模式,默认开启图例开关
selectedMode: true
// 配置默认选中状态可配合LEGEND.SELECTED事件做动态数据载入
// selected: null,
// 图例内容详见legend.data数组中每一项代表一个item
// data: [],
}
});
return LegendModel;
});

View File

@@ -0,0 +1,230 @@
define(function (require) {
var zrUtil = require('zrender/core/util');
var symbolCreator = require('../../util/symbol');
var graphic = require('../../util/graphic');
var listComponentHelper = require('../helper/listComponent');
var curry = zrUtil.curry;
var LEGEND_DISABLE_COLOR = '#ccc';
function dispatchSelectAction(name, api) {
api.dispatchAction({
type: 'legendToggleSelect',
name: name
});
}
function dispatchHighlightAction(seriesModel, dataName, api) {
seriesModel.get('legendHoverLink') && api.dispatchAction({
type: 'highlight',
seriesName: seriesModel.name,
name: dataName
});
}
function dispatchDownplayAction(seriesModel, dataName, api) {
seriesModel.get('legendHoverLink') && api.dispatchAction({
type: 'downplay',
seriesName: seriesModel.name,
name: dataName
});
}
return require('../../echarts').extendComponentView({
type: 'legend',
init: function () {
this._symbolTypeStore = {};
},
render: function (legendModel, ecModel, api) {
var group = this.group;
group.removeAll();
if (!legendModel.get('show')) {
return;
}
var selectMode = legendModel.get('selectedMode');
var itemAlign = legendModel.get('align');
if (itemAlign === 'auto') {
itemAlign = (legendModel.get('left') === 'right'
&& legendModel.get('orient') === 'vertical')
? 'right' : 'left';
}
var legendDrawedMap = {};
zrUtil.each(legendModel.getData(), function (itemModel) {
var name = itemModel.get('name');
// Use empty string or \n as a newline string
if (name === '' || name === '\n') {
group.add(new graphic.Group({
newline: true
}));
return;
}
var seriesModel = ecModel.getSeriesByName(name)[0];
if (legendDrawedMap[name]) {
// Series not exists
return;
}
// Series legend
if (seriesModel) {
var data = seriesModel.getData();
var color = data.getVisual('color');
// If color is a callback function
if (typeof color === 'function') {
// Use the first data
color = color(seriesModel.getDataParams(0));
}
// Using rect symbol defaultly
var legendSymbolType = data.getVisual('legendSymbol') || 'roundRect';
var symbolType = data.getVisual('symbol');
var itemGroup = this._createItem(
name, itemModel, legendModel,
legendSymbolType, symbolType,
itemAlign, color,
selectMode
);
itemGroup.on('click', curry(dispatchSelectAction, name, api))
.on('mouseover', curry(dispatchHighlightAction, seriesModel, '', api))
.on('mouseout', curry(dispatchDownplayAction, seriesModel, '', api));
legendDrawedMap[name] = true;
}
else {
// Data legend of pie, funnel
ecModel.eachRawSeries(function (seriesModel) {
// In case multiple series has same data name
if (legendDrawedMap[name]) {
return;
}
if (seriesModel.legendDataProvider) {
var data = seriesModel.legendDataProvider();
var idx = data.indexOfName(name);
if (idx < 0) {
return;
}
var color = data.getItemVisual(idx, 'color');
var legendSymbolType = 'roundRect';
var itemGroup = this._createItem(
name, itemModel, legendModel,
legendSymbolType, null,
itemAlign, color,
selectMode
);
itemGroup.on('click', curry(dispatchSelectAction, name, api))
// FIXME Should not specify the series name
.on('mouseover', curry(dispatchHighlightAction, seriesModel, name, api))
.on('mouseout', curry(dispatchDownplayAction, seriesModel, name, api));
legendDrawedMap[name] = true;
}
}, this);
}
}, this);
listComponentHelper.layout(group, legendModel, api);
// Render background after group is layout
// FIXME
listComponentHelper.addBackground(group, legendModel);
},
_createItem: function (
name, itemModel, legendModel,
legendSymbolType, symbolType,
itemAlign, color, selectMode
) {
var itemWidth = legendModel.get('itemWidth');
var itemHeight = legendModel.get('itemHeight');
var isSelected = legendModel.isSelected(name);
var itemGroup = new graphic.Group();
var textStyleModel = itemModel.getModel('textStyle');
var itemIcon = itemModel.get('icon');
// Use user given icon first
legendSymbolType = itemIcon || legendSymbolType;
itemGroup.add(symbolCreator.createSymbol(
legendSymbolType, 0, 0, itemWidth, itemHeight, isSelected ? color : LEGEND_DISABLE_COLOR
));
// Compose symbols
// PENDING
if (!itemIcon && symbolType
// At least show one symbol, can't be all none
&& ((symbolType !== legendSymbolType) || symbolType == 'none')
) {
var size = itemHeight * 0.8;
if (symbolType === 'none') {
symbolType = 'circle';
}
// Put symbol in the center
itemGroup.add(symbolCreator.createSymbol(
symbolType, (itemWidth - size) / 2, (itemHeight - size) / 2, size, size,
isSelected ? color : LEGEND_DISABLE_COLOR
));
}
// Text
var textX = itemAlign === 'left' ? itemWidth + 5 : -5;
var textAlign = itemAlign;
var formatter = legendModel.get('formatter');
if (typeof formatter === 'string' && formatter) {
name = formatter.replace('{name}', name);
}
else if (typeof formatter === 'function') {
name = formatter(name);
}
var text = new graphic.Text({
style: {
text: name,
x: textX,
y: itemHeight / 2,
fill: isSelected ? textStyleModel.getTextColor() : LEGEND_DISABLE_COLOR,
textFont: textStyleModel.getFont(),
textAlign: textAlign,
textVerticalAlign: 'middle'
}
});
itemGroup.add(text);
// Add a invisible rect to increase the area of mouse hover
itemGroup.add(new graphic.Rect({
shape: itemGroup.getBoundingRect(),
invisible: true
}));
itemGroup.eachChild(function (child) {
child.silent = !selectMode;
});
this.group.add(itemGroup);
graphic.setHoverStyle(itemGroup);
return itemGroup;
}
});
});

View File

@@ -0,0 +1,82 @@
/**
* @file Legend action
*/
define(function(require) {
var echarts = require('../../echarts');
var zrUtil = require('zrender/core/util');
function legendSelectActionHandler(methodName, payload, ecModel) {
var selectedMap = {};
var isToggleSelect = methodName === 'toggleSelected';
var isSelected;
// Update all legend components
ecModel.eachComponent('legend', function (legendModel) {
if (isToggleSelect && isSelected != null) {
// Force other legend has same selected status
// Or the first is toggled to true and other are toggled to false
// In the case one legend has some item unSelected in option. And if other legend
// doesn't has the item, they will assume it is selected.
legendModel[isSelected ? 'select' : 'unSelect'](payload.name);
}
else {
legendModel[methodName](payload.name);
isSelected = legendModel.isSelected(payload.name);
}
var legendData = legendModel.getData();
zrUtil.each(legendData, function (model) {
var name = model.get('name');
// Wrap element
if (name === '\n' || name === '') {
return;
}
var isItemSelected = legendModel.isSelected(name);
if (name in selectedMap) {
// Unselected if any legend is unselected
selectedMap[name] = selectedMap[name] && isItemSelected;
}
else {
selectedMap[name] = isItemSelected;
}
});
});
// Return the event explicitly
return {
name: payload.name,
selected: selectedMap
};
}
/**
* @event legendToggleSelect
* @type {Object}
* @property {string} type 'legendToggleSelect'
* @property {string} [from]
* @property {string} name Series name or data item name
*/
echarts.registerAction(
'legendToggleSelect', 'legendselectchanged',
zrUtil.curry(legendSelectActionHandler, 'toggleSelected')
);
/**
* @event legendSelect
* @type {Object}
* @property {string} type 'legendSelect'
* @property {string} name Series name or data item name
*/
echarts.registerAction(
'legendSelect', 'legendselected',
zrUtil.curry(legendSelectActionHandler, 'select')
);
/**
* @event legendUnSelect
* @type {Object}
* @property {string} type 'legendUnSelect'
* @property {string} name Series name or data item name
*/
echarts.registerAction(
'legendUnSelect', 'legendunselected',
zrUtil.curry(legendSelectActionHandler, 'unSelect')
);
});

View File

@@ -0,0 +1,19 @@
define(function () {
return function (ecModel) {
var legendModels = ecModel.findComponents({
mainType: 'legend'
});
if (legendModels && legendModels.length) {
ecModel.filterSeries(function (series) {
// If in any legend component the status is not selected.
// Because in legend series is assumed selected when it is not in the legend data.
for (var i = 0; i < legendModels.length; i++) {
if (!legendModels[i].isSelected(series.name)) {
return false;
}
}
return true;
});
}
};
});

View File

@@ -0,0 +1,10 @@
define(function (require) {
require('./marker/MarkLineModel');
require('./marker/MarkLineView');
require('../echarts').registerPreprocessor(function (opt) {
// Make sure markLine component is enabled
opt.markLine = opt.markLine || {};
});
});

View File

@@ -0,0 +1,11 @@
// HINT Markpoint can't be used too much
define(function (require) {
require('./marker/MarkPointModel');
require('./marker/MarkPointView');
require('../echarts').registerPreprocessor(function (opt) {
// Make sure markPoint component is enabled
opt.markPoint = opt.markPoint || {};
});
});

View File

@@ -0,0 +1,103 @@
define(function (require) {
var modelUtil = require('../../util/model');
var zrUtil = require('zrender/core/util');
function fillLabel(opt) {
modelUtil.defaultEmphasis(
opt.label,
modelUtil.LABEL_OPTIONS
);
}
var MarkLineModel = require('../../echarts').extendComponentModel({
type: 'markLine',
dependencies: ['series', 'grid', 'polar', 'geo'],
/**
* @overrite
*/
init: function (option, parentModel, ecModel, extraOpt) {
this.mergeDefaultAndTheme(option, ecModel);
this.mergeOption(option, ecModel, extraOpt.createdBySelf, true);
},
mergeOption: function (newOpt, ecModel, createdBySelf, isInit) {
if (!createdBySelf) {
ecModel.eachSeries(function (seriesModel) {
var markLineOpt = seriesModel.get('markLine');
var mlModel = seriesModel.markLineModel;
if (!markLineOpt || !markLineOpt.data) {
seriesModel.markLineModel = null;
return;
}
if (!mlModel) {
if (isInit) {
// Default label emphasis `position` and `show`
fillLabel(markLineOpt);
}
zrUtil.each(markLineOpt.data, function (item) {
if (item instanceof Array) {
fillLabel(item[0]);
fillLabel(item[1]);
}
else {
fillLabel(item);
}
});
var opt = {
mainType: 'markLine',
// Use the same series index and name
seriesIndex: seriesModel.seriesIndex,
name: seriesModel.name,
createdBySelf: true
};
mlModel = new MarkLineModel(
markLineOpt, this, ecModel, opt
);
}
else {
mlModel.mergeOption(markLineOpt, ecModel, true);
}
seriesModel.markLineModel = mlModel;
}, this);
}
},
defaultOption: {
zlevel: 0,
z: 5,
symbol: ['circle', 'arrow'],
symbolSize: [8, 16],
//symbolRotate: 0,
precision: 2,
tooltip: {
trigger: 'item'
},
label: {
normal: {
show: true,
position: 'end'
},
emphasis: {
show: true
}
},
lineStyle: {
normal: {
type: 'dashed'
},
emphasis: {
width: 3
}
},
animationEasing: 'linear'
}
});
return MarkLineModel;
});

View File

@@ -0,0 +1,407 @@
define(function (require) {
var zrUtil = require('zrender/core/util');
var List = require('../../data/List');
var formatUtil = require('../../util/format');
var modelUtil = require('../../util/model');
var numberUtil = require('../../util/number');
var addCommas = formatUtil.addCommas;
var encodeHTML = formatUtil.encodeHTML;
var markerHelper = require('./markerHelper');
var LineDraw = require('../../chart/helper/LineDraw');
var markLineTransform = function (seriesModel, coordSys, mlModel, item) {
var data = seriesModel.getData();
// Special type markLine like 'min', 'max', 'average'
var mlType = item.type;
if (!zrUtil.isArray(item)
&& (
mlType === 'min' || mlType === 'max' || mlType === 'average'
// In case
// data: [{
// yAxis: 10
// }]
|| (item.xAxis != null || item.yAxis != null)
)
) {
var valueAxis;
var valueDataDim;
var value;
if (item.yAxis != null || item.xAxis != null) {
valueDataDim = item.yAxis != null ? 'y' : 'x';
valueAxis = coordSys.getAxis(valueDataDim);
value = zrUtil.retrieve(item.yAxis, item.xAxis);
}
else {
var axisInfo = markerHelper.getAxisInfo(item, data, coordSys, seriesModel);
valueDataDim = axisInfo.valueDataDim;
valueAxis = axisInfo.valueAxis;
value = markerHelper.numCalculate(data, valueDataDim, mlType);
}
var valueIndex = valueDataDim === 'x' ? 0 : 1;
var baseIndex = 1 - valueIndex;
var mlFrom = zrUtil.clone(item);
var mlTo = {};
mlFrom.type = null;
mlFrom.coord = [];
mlTo.coord = [];
mlFrom.coord[baseIndex] = -Infinity;
mlTo.coord[baseIndex] = Infinity;
var precision = mlModel.get('precision');
if (precision >= 0) {
value = +value.toFixed(precision);
}
mlFrom.coord[valueIndex] = mlTo.coord[valueIndex] = value;
item = [mlFrom, mlTo, { // Extra option for tooltip and label
type: mlType,
valueIndex: item.valueIndex,
// Force to use the value of calculated value.
value: value
}];
}
item = [
markerHelper.dataTransform(seriesModel, item[0]),
markerHelper.dataTransform(seriesModel, item[1]),
zrUtil.extend({}, item[2])
];
// Avoid line data type is extended by from(to) data type
item[2].type = item[2].type || '';
// Merge from option and to option into line option
zrUtil.merge(item[2], item[0]);
zrUtil.merge(item[2], item[1]);
return item;
};
function isInifinity(val) {
return !isNaN(val) && !isFinite(val);
}
// If a markLine has one dim
function ifMarkLineHasOnlyDim(dimIndex, fromCoord, toCoord, coordSys) {
var otherDimIndex = 1 - dimIndex;
var dimName = coordSys.dimensions[dimIndex];
return isInifinity(fromCoord[otherDimIndex]) && isInifinity(toCoord[otherDimIndex])
&& fromCoord[dimIndex] === toCoord[dimIndex] && coordSys.getAxis(dimName).containData(fromCoord[dimIndex]);
}
function markLineFilter(coordSys, item) {
if (coordSys.type === 'cartesian2d') {
var fromCoord = item[0].coord;
var toCoord = item[1].coord;
// In case
// {
// markLine: {
// data: [{ yAxis: 2 }]
// }
// }
if (
fromCoord && toCoord &&
(ifMarkLineHasOnlyDim(1, fromCoord, toCoord, coordSys)
|| ifMarkLineHasOnlyDim(0, fromCoord, toCoord, coordSys))
) {
return true;
}
}
return markerHelper.dataFilter(coordSys, item[0])
&& markerHelper.dataFilter(coordSys, item[1]);
}
function updateSingleMarkerEndLayout(
data, idx, isFrom, mlType, valueIndex, seriesModel, api
) {
var coordSys = seriesModel.coordinateSystem;
var itemModel = data.getItemModel(idx);
var point;
var xPx = itemModel.get('x');
var yPx = itemModel.get('y');
if (xPx != null && yPx != null) {
point = [
numberUtil.parsePercent(xPx, api.getWidth()),
numberUtil.parsePercent(yPx, api.getHeight())
];
}
else {
// Chart like bar may have there own marker positioning logic
if (seriesModel.getMarkerPosition) {
// Use the getMarkerPoisition
point = seriesModel.getMarkerPosition(
data.getValues(data.dimensions, idx)
);
}
else {
var dims = coordSys.dimensions;
var x = data.get(dims[0], idx);
var y = data.get(dims[1], idx);
point = coordSys.dataToPoint([x, y]);
}
// Expand line to the edge of grid if value on one axis is Inifnity
// In case
// markLine: {
// data: [{
// yAxis: 2
// // or
// type: 'average'
// }]
// }
if (coordSys.type === 'cartesian2d') {
var xAxis = coordSys.getAxis('x');
var yAxis = coordSys.getAxis('y');
var dims = coordSys.dimensions;
if (isInifinity(data.get(dims[0], idx))) {
point[0] = xAxis.toGlobalCoord(xAxis.getExtent()[isFrom ? 0 : 1]);
}
else if (isInifinity(data.get(dims[1], idx))) {
point[1] = yAxis.toGlobalCoord(yAxis.getExtent()[isFrom ? 0 : 1]);
}
}
}
data.setItemLayout(idx, point);
}
var markLineFormatMixin = {
formatTooltip: function (dataIndex) {
var data = this._data;
var value = this.getRawValue(dataIndex);
var formattedValue = zrUtil.isArray(value)
? zrUtil.map(value, addCommas).join(', ') : addCommas(value);
var name = data.getName(dataIndex);
return this.name + '<br />'
+ ((name ? encodeHTML(name) + ' : ' : '') + formattedValue);
},
getData: function () {
return this._data;
},
setData: function (data) {
this._data = data;
}
};
zrUtil.defaults(markLineFormatMixin, modelUtil.dataFormatMixin);
require('../../echarts').extendComponentView({
type: 'markLine',
init: function () {
/**
* Markline grouped by series
* @private
* @type {Object}
*/
this._markLineMap = {};
},
render: function (markLineModel, ecModel, api) {
var lineDrawMap = this._markLineMap;
for (var name in lineDrawMap) {
lineDrawMap[name].__keep = false;
}
ecModel.eachSeries(function (seriesModel) {
var mlModel = seriesModel.markLineModel;
mlModel && this._renderSeriesML(seriesModel, mlModel, ecModel, api);
}, this);
for (var name in lineDrawMap) {
if (!lineDrawMap[name].__keep) {
this.group.remove(lineDrawMap[name].group);
}
}
},
updateLayout: function (markLineModel, ecModel, api) {
ecModel.eachSeries(function (seriesModel) {
var mlModel = seriesModel.markLineModel;
if (mlModel) {
var mlData = mlModel.getData();
var fromData = mlModel.__from;
var toData = mlModel.__to;
// Update visual and layout of from symbol and to symbol
fromData.each(function (idx) {
var lineModel = mlData.getItemModel(idx);
var mlType = lineModel.get('type');
var valueIndex = lineModel.get('valueIndex');
updateSingleMarkerEndLayout(fromData, idx, true, mlType, valueIndex, seriesModel, api);
updateSingleMarkerEndLayout(toData, idx, false, mlType, valueIndex, seriesModel, api);
});
// Update layout of line
mlData.each(function (idx) {
mlData.setItemLayout(idx, [
fromData.getItemLayout(idx),
toData.getItemLayout(idx)
]);
});
this._markLineMap[seriesModel.name].updateLayout();
}
}, this);
},
_renderSeriesML: function (seriesModel, mlModel, ecModel, api) {
var coordSys = seriesModel.coordinateSystem;
var seriesName = seriesModel.name;
var seriesData = seriesModel.getData();
var lineDrawMap = this._markLineMap;
var lineDraw = lineDrawMap[seriesName];
if (!lineDraw) {
lineDraw = lineDrawMap[seriesName] = new LineDraw();
}
this.group.add(lineDraw.group);
var mlData = createList(coordSys, seriesModel, mlModel);
var fromData = mlData.from;
var toData = mlData.to;
var lineData = mlData.line;
mlModel.__from = fromData;
mlModel.__to = toData;
// Line data for tooltip and formatter
zrUtil.extend(mlModel, markLineFormatMixin);
mlModel.setData(lineData);
var symbolType = mlModel.get('symbol');
var symbolSize = mlModel.get('symbolSize');
if (!zrUtil.isArray(symbolType)) {
symbolType = [symbolType, symbolType];
}
if (typeof symbolSize === 'number') {
symbolSize = [symbolSize, symbolSize];
}
// Update visual and layout of from symbol and to symbol
mlData.from.each(function (idx) {
var lineModel = lineData.getItemModel(idx);
var mlType = lineModel.get('type');
var valueIndex = lineModel.get('valueIndex');
updateDataVisualAndLayout(fromData, idx, true, mlType, valueIndex);
updateDataVisualAndLayout(toData, idx, false, mlType, valueIndex);
});
// Update visual and layout of line
lineData.each(function (idx) {
var lineColor = lineData.getItemModel(idx).get('lineStyle.normal.color');
lineData.setItemVisual(idx, {
color: lineColor || fromData.getItemVisual(idx, 'color')
});
lineData.setItemLayout(idx, [
fromData.getItemLayout(idx),
toData.getItemLayout(idx)
]);
lineData.setItemVisual(idx, {
'fromSymbolSize': fromData.getItemVisual(idx, 'symbolSize'),
'fromSymbol': fromData.getItemVisual(idx, 'symbol'),
'toSymbolSize': toData.getItemVisual(idx, 'symbolSize'),
'toSymbol': toData.getItemVisual(idx, 'symbol')
});
});
lineDraw.updateData(lineData);
// Set host model for tooltip
// FIXME
mlData.line.eachItemGraphicEl(function (el, idx) {
el.traverse(function (child) {
child.dataModel = mlModel;
});
});
function updateDataVisualAndLayout(data, idx, isFrom, mlType, valueIndex) {
var itemModel = data.getItemModel(idx);
updateSingleMarkerEndLayout(
data, idx, isFrom, mlType, valueIndex, seriesModel, api
);
data.setItemVisual(idx, {
symbolSize: itemModel.get('symbolSize') || symbolSize[isFrom ? 0 : 1],
symbol: itemModel.get('symbol', true) || symbolType[isFrom ? 0 : 1],
color: itemModel.get('itemStyle.normal.color') || seriesData.getVisual('color')
});
}
lineDraw.__keep = true;
}
});
/**
* @inner
* @param {module:echarts/coord/*} coordSys
* @param {module:echarts/model/Series} seriesModel
* @param {module:echarts/model/Model} mpModel
*/
function createList(coordSys, seriesModel, mlModel) {
var coordDimsInfos;
if (coordSys) {
coordDimsInfos = zrUtil.map(coordSys && coordSys.dimensions, function (coordDim) {
var info = seriesModel.getData().getDimensionInfo(
seriesModel.coordDimToDataDim(coordDim)[0]
) || {}; // In map series data don't have lng and lat dimension. Fallback to same with coordSys
info.name = coordDim;
return info;
});
}
else {
coordDimsInfos =[{
name: 'value',
type: 'float'
}];
}
var fromData = new List(coordDimsInfos, mlModel);
var toData = new List(coordDimsInfos, mlModel);
// No dimensions
var lineData = new List([], mlModel);
var optData = zrUtil.map(mlModel.get('data'), zrUtil.curry(
markLineTransform, seriesModel, coordSys, mlModel
));
if (coordSys) {
optData = zrUtil.filter(
optData, zrUtil.curry(markLineFilter, coordSys)
);
}
var dimValueGetter = coordSys ? markerHelper.dimValueGetter : function (item) {
return item.value;
};
fromData.initData(
zrUtil.map(optData, function (item) { return item[0]; }),
null, dimValueGetter
);
toData.initData(
zrUtil.map(optData, function (item) { return item[1]; }),
null, dimValueGetter
);
lineData.initData(
zrUtil.map(optData, function (item) { return item[2]; })
);
return {
from: fromData,
to: toData,
line: lineData
};
}
});

View File

@@ -0,0 +1,87 @@
define(function (require) {
var modelUtil = require('../../util/model');
var zrUtil = require('zrender/core/util');
function fillLabel(opt) {
modelUtil.defaultEmphasis(
opt.label,
modelUtil.LABEL_OPTIONS
);
}
var MarkPointModel = require('../../echarts').extendComponentModel({
type: 'markPoint',
dependencies: ['series', 'grid', 'polar'],
/**
* @overrite
*/
init: function (option, parentModel, ecModel, extraOpt) {
this.mergeDefaultAndTheme(option, ecModel);
this.mergeOption(option, ecModel, extraOpt.createdBySelf, true);
},
mergeOption: function (newOpt, ecModel, createdBySelf, isInit) {
if (!createdBySelf) {
ecModel.eachSeries(function (seriesModel) {
var markPointOpt = seriesModel.get('markPoint');
var mpModel = seriesModel.markPointModel;
if (!markPointOpt || !markPointOpt.data) {
seriesModel.markPointModel = null;
return;
}
if (!mpModel) {
if (isInit) {
// Default label emphasis `position` and `show`
fillLabel(markPointOpt);
}
zrUtil.each(markPointOpt.data, fillLabel);
var opt = {
mainType: 'markPoint',
// Use the same series index and name
seriesIndex: seriesModel.seriesIndex,
name: seriesModel.name,
createdBySelf: true
};
mpModel = new MarkPointModel(
markPointOpt, this, ecModel, opt
);
}
else {
mpModel.mergeOption(markPointOpt, ecModel, true);
}
seriesModel.markPointModel = mpModel;
}, this);
}
},
defaultOption: {
zlevel: 0,
z: 5,
symbol: 'pin',
symbolSize: 50,
//symbolRotate: 0,
//symbolOffset: [0, 0]
tooltip: {
trigger: 'item'
},
label: {
normal: {
show: true,
position: 'inside'
},
emphasis: {
show: true
}
},
itemStyle: {
normal: {
borderWidth: 2
}
}
}
});
return MarkPointModel;
});

View File

@@ -0,0 +1,200 @@
define(function (require) {
var SymbolDraw = require('../../chart/helper/SymbolDraw');
var zrUtil = require('zrender/core/util');
var formatUtil = require('../../util/format');
var modelUtil = require('../../util/model');
var numberUtil = require('../../util/number');
var addCommas = formatUtil.addCommas;
var encodeHTML = formatUtil.encodeHTML;
var List = require('../../data/List');
var markerHelper = require('./markerHelper');
function updateMarkerLayout(mpData, seriesModel, api) {
var coordSys = seriesModel.coordinateSystem;
mpData.each(function (idx) {
var itemModel = mpData.getItemModel(idx);
var point;
var xPx = itemModel.getShallow('x');
var yPx = itemModel.getShallow('y');
if (xPx != null && yPx != null) {
point = [
numberUtil.parsePercent(xPx, api.getWidth()),
numberUtil.parsePercent(yPx, api.getHeight())
];
}
// Chart like bar may have there own marker positioning logic
else if (seriesModel.getMarkerPosition) {
// Use the getMarkerPoisition
point = seriesModel.getMarkerPosition(
mpData.getValues(mpData.dimensions, idx)
);
}
else if (coordSys) {
var x = mpData.get(coordSys.dimensions[0], idx);
var y = mpData.get(coordSys.dimensions[1], idx);
point = coordSys.dataToPoint([x, y]);
}
mpData.setItemLayout(idx, point);
});
}
// FIXME
var markPointFormatMixin = {
formatTooltip: function (dataIndex) {
var data = this.getData();
var value = this.getRawValue(dataIndex);
var formattedValue = zrUtil.isArray(value)
? zrUtil.map(value, addCommas).join(', ') : addCommas(value);
var name = data.getName(dataIndex);
return this.name + '<br />'
+ ((name ? encodeHTML(name) + ' : ' : '') + formattedValue);
},
getData: function () {
return this._data;
},
setData: function (data) {
this._data = data;
}
};
zrUtil.defaults(markPointFormatMixin, modelUtil.dataFormatMixin);
require('../../echarts').extendComponentView({
type: 'markPoint',
init: function () {
this._symbolDrawMap = {};
},
render: function (markPointModel, ecModel, api) {
var symbolDrawMap = this._symbolDrawMap;
for (var name in symbolDrawMap) {
symbolDrawMap[name].__keep = false;
}
ecModel.eachSeries(function (seriesModel) {
var mpModel = seriesModel.markPointModel;
mpModel && this._renderSeriesMP(seriesModel, mpModel, api);
}, this);
for (var name in symbolDrawMap) {
if (!symbolDrawMap[name].__keep) {
symbolDrawMap[name].remove();
this.group.remove(symbolDrawMap[name].group);
}
}
},
updateLayout: function (markPointModel, ecModel, api) {
ecModel.eachSeries(function (seriesModel) {
var mpModel = seriesModel.markPointModel;
if (mpModel) {
updateMarkerLayout(mpModel.getData(), seriesModel, api);
this._symbolDrawMap[seriesModel.name].updateLayout(mpModel);
}
}, this);
},
_renderSeriesMP: function (seriesModel, mpModel, api) {
var coordSys = seriesModel.coordinateSystem;
var seriesName = seriesModel.name;
var seriesData = seriesModel.getData();
var symbolDrawMap = this._symbolDrawMap;
var symbolDraw = symbolDrawMap[seriesName];
if (!symbolDraw) {
symbolDraw = symbolDrawMap[seriesName] = new SymbolDraw();
}
var mpData = createList(coordSys, seriesModel, mpModel);
// FIXME
zrUtil.mixin(mpModel, markPointFormatMixin);
mpModel.setData(mpData);
updateMarkerLayout(mpModel.getData(), seriesModel, api);
mpData.each(function (idx) {
var itemModel = mpData.getItemModel(idx);
var symbolSize = itemModel.getShallow('symbolSize');
if (typeof symbolSize === 'function') {
// FIXME 这里不兼容 ECharts 2.x2.x 貌似参数是整个数据?
symbolSize = symbolSize(
mpModel.getRawValue(idx), mpModel.getDataParams(idx)
);
}
mpData.setItemVisual(idx, {
symbolSize: symbolSize,
color: itemModel.get('itemStyle.normal.color')
|| seriesData.getVisual('color'),
symbol: itemModel.getShallow('symbol')
});
});
// TODO Text are wrong
symbolDraw.updateData(mpData);
this.group.add(symbolDraw.group);
// Set host model for tooltip
// FIXME
mpData.eachItemGraphicEl(function (el) {
el.traverse(function (child) {
child.dataModel = mpModel;
});
});
symbolDraw.__keep = true;
}
});
/**
* @inner
* @param {module:echarts/coord/*} [coordSys]
* @param {module:echarts/model/Series} seriesModel
* @param {module:echarts/model/Model} mpModel
*/
function createList(coordSys, seriesModel, mpModel) {
var coordDimsInfos;
if (coordSys) {
coordDimsInfos = zrUtil.map(coordSys && coordSys.dimensions, function (coordDim) {
var info = seriesModel.getData().getDimensionInfo(
seriesModel.coordDimToDataDim(coordDim)[0]
) || {}; // In map series data don't have lng and lat dimension. Fallback to same with coordSys
info.name = coordDim;
return info;
});
}
else {
coordDimsInfos =[{
name: 'value',
type: 'float'
}];
}
var mpData = new List(coordDimsInfos, mpModel);
var dataOpt = zrUtil.map(mpModel.get('data'), zrUtil.curry(
markerHelper.dataTransform, seriesModel
));
if (coordSys) {
dataOpt = zrUtil.filter(
dataOpt, zrUtil.curry(markerHelper.dataFilter, coordSys)
);
}
mpData.initData(dataOpt, null,
coordSys ? markerHelper.dimValueGetter : function (item) {
return item.value;
}
);
return mpData;
}
});

View File

@@ -0,0 +1,174 @@
define(function (require) {
var zrUtil = require('zrender/core/util');
var numberUtil = require('../../util/number');
var indexOf = zrUtil.indexOf;
function getPrecision(data, valueAxisDim, dataIndex) {
var precision = -1;
do {
precision = Math.max(
numberUtil.getPrecision(data.get(
valueAxisDim, dataIndex
)),
precision
);
data = data.stackedOn;
} while (data);
return precision;
}
function markerTypeCalculatorWithExtent(
mlType, data, baseDataDim, valueDataDim, baseCoordIndex, valueCoordIndex
) {
var coordArr = [];
var value = numCalculate(data, valueDataDim, mlType);
var dataIndex = data.indexOfNearest(valueDataDim, value, true);
coordArr[baseCoordIndex] = data.get(baseDataDim, dataIndex, true);
coordArr[valueCoordIndex] = data.get(valueDataDim, dataIndex, true);
var precision = getPrecision(data, valueDataDim, dataIndex);
if (precision >= 0) {
coordArr[valueCoordIndex] = +coordArr[valueCoordIndex].toFixed(precision);
}
return coordArr;
}
var curry = zrUtil.curry;
// TODO Specified percent
var markerTypeCalculator = {
/**
* @method
* @param {module:echarts/data/List} data
* @param {string} baseAxisDim
* @param {string} valueAxisDim
*/
min: curry(markerTypeCalculatorWithExtent, 'min'),
/**
* @method
* @param {module:echarts/data/List} data
* @param {string} baseAxisDim
* @param {string} valueAxisDim
*/
max: curry(markerTypeCalculatorWithExtent, 'max'),
/**
* @method
* @param {module:echarts/data/List} data
* @param {string} baseAxisDim
* @param {string} valueAxisDim
*/
average: curry(markerTypeCalculatorWithExtent, 'average')
};
/**
* Transform markPoint data item to format used in List by do the following
* 1. Calculate statistic like `max`, `min`, `average`
* 2. Convert `item.xAxis`, `item.yAxis` to `item.coord` array
* @param {module:echarts/model/Series} seriesModel
* @param {module:echarts/coord/*} [coordSys]
* @param {Object} item
* @return {Object}
*/
var dataTransform = function (seriesModel, item) {
var data = seriesModel.getData();
var coordSys = seriesModel.coordinateSystem;
// 1. If not specify the position with pixel directly
// 2. If `coord` is not a data array. Which uses `xAxis`,
// `yAxis` to specify the coord on each dimension
// parseFloat first because item.x and item.y can be percent string like '20%'
if (item && (isNaN(parseFloat(item.x)) || isNaN(parseFloat(item.y)))
&& !zrUtil.isArray(item.coord)
&& coordSys
) {
var axisInfo = getAxisInfo(item, data, coordSys, seriesModel);
// Clone the option
// Transform the properties xAxis, yAxis, radiusAxis, angleAxis, geoCoord to value
item = zrUtil.clone(item);
if (item.type
&& markerTypeCalculator[item.type]
&& axisInfo.baseAxis && axisInfo.valueAxis
) {
var dims = coordSys.dimensions;
var baseCoordIndex = indexOf(dims, axisInfo.baseAxis.dim);
var valueCoordIndex = indexOf(dims, axisInfo.valueAxis.dim);
item.coord = markerTypeCalculator[item.type](
data, axisInfo.baseDataDim, axisInfo.valueDataDim,
baseCoordIndex, valueCoordIndex
);
// Force to use the value of calculated value.
item.value = item.coord[valueCoordIndex];
}
else {
// FIXME Only has one of xAxis and yAxis.
item.coord = [
item.xAxis != null ? item.xAxis : item.radiusAxis,
item.yAxis != null ? item.yAxis : item.angleAxis
];
}
}
return item;
};
var getAxisInfo = function (item, data, coordSys, seriesModel) {
var ret = {};
if (item.valueIndex != null || item.valueDim != null) {
ret.valueDataDim = item.valueIndex != null
? data.getDimension(item.valueIndex) : item.valueDim;
ret.valueAxis = coordSys.getAxis(seriesModel.dataDimToCoordDim(ret.valueDataDim));
ret.baseAxis = coordSys.getOtherAxis(ret.valueAxis);
ret.baseDataDim = seriesModel.coordDimToDataDim(ret.baseAxis.dim)[0];
}
else {
ret.baseAxis = seriesModel.getBaseAxis();
ret.valueAxis = coordSys.getOtherAxis(ret.baseAxis);
ret.baseDataDim = seriesModel.coordDimToDataDim(ret.baseAxis.dim)[0];
ret.valueDataDim = seriesModel.coordDimToDataDim(ret.valueAxis.dim)[0];
}
return ret;
};
/**
* Filter data which is out of coordinateSystem range
* [dataFilter description]
* @param {module:echarts/coord/*} [coordSys]
* @param {Object} item
* @return {boolean}
*/
var dataFilter = function (coordSys, item) {
// Alwalys return true if there is no coordSys
return (coordSys && coordSys.containData && item.coord && (item.x == null || item.y == null))
? coordSys.containData(item.coord) : true;
};
var dimValueGetter = function (item, dimName, dataIndex, dimIndex) {
// x, y, radius, angle
if (dimIndex < 2) {
return item.coord && item.coord[dimIndex];
}
return item.value;
};
var numCalculate = function (data, valueDataDim, mlType) {
return mlType === 'average'
? data.getSum(valueDataDim, true) / data.count()
: data.getDataExtent(valueDataDim, true)[mlType === 'max' ? 1 : 0];
};
return {
dataTransform: dataTransform,
dataFilter: dataFilter,
dimValueGetter: dimValueGetter,
getAxisInfo: getAxisInfo,
numCalculate: numCalculate
};
});

View File

@@ -0,0 +1,18 @@
define(function(require) {
require('../coord/parallel/parallelCreator');
require('../coord/parallel/ParallelModel');
require('./parallelAxis');
var echarts = require('../echarts');
// Parallel view
echarts.extendComponentView({
type: 'parallel'
});
echarts.registerPreprocessor(
require('../coord/parallel/parallelPreprocessor')
);
});

View File

@@ -0,0 +1,7 @@
define(function(require) {
require('../coord/parallel/parallelCreator');
require('./axis/parallelAxisAction');
require('./axis/ParallelAxisView');
});

12
vendors/echarts/src/component/polar.js vendored Normal file
View File

@@ -0,0 +1,12 @@
define(function(require) {
'use strict';
require('../coord/polar/polarCreator');
require('./angleAxis');
require('./radiusAxis');
// Polar view
require('../echarts').extendComponentView({
type: 'polar'
});
});

View File

@@ -0,0 +1,7 @@
define(function (require) {
require('../coord/radar/Radar');
require('../coord/radar/RadarModel');
require('./radar/RadarView');
});

View File

@@ -0,0 +1,167 @@
define(function (require) {
var AxisBuilder = require('../axis/AxisBuilder');
var zrUtil = require('zrender/core/util');
var graphic = require('../../util/graphic');
var axisBuilderAttrs = [
'axisLine', 'axisLabel', 'axisTick', 'axisName'
];
return require('../../echarts').extendComponentView({
type: 'radar',
render: function (radarModel, ecModel, api) {
var group = this.group;
group.removeAll();
this._buildAxes(radarModel);
this._buildSplitLineAndArea(radarModel);
},
_buildAxes: function (radarModel) {
var radar = radarModel.coordinateSystem;
var indicatorAxes = radar.getIndicatorAxes();
var axisBuilders = zrUtil.map(indicatorAxes, function (indicatorAxis) {
var axisBuilder = new AxisBuilder(indicatorAxis.model, {
position: [radar.cx, radar.cy],
rotation: indicatorAxis.angle,
labelDirection: -1,
tickDirection: -1,
nameDirection: 1
});
return axisBuilder;
});
zrUtil.each(axisBuilders, function (axisBuilder) {
zrUtil.each(axisBuilderAttrs, axisBuilder.add, axisBuilder);
this.group.add(axisBuilder.getGroup());
}, this);
},
_buildSplitLineAndArea: function (radarModel) {
var radar = radarModel.coordinateSystem;
var splitNumber = radarModel.get('splitNumber');
var indicatorAxes = radar.getIndicatorAxes();
if (!indicatorAxes.length) {
return;
}
var shape = radarModel.get('shape');
var splitLineModel = radarModel.getModel('splitLine');
var splitAreaModel = radarModel.getModel('splitArea');
var lineStyleModel = splitLineModel.getModel('lineStyle');
var areaStyleModel = splitAreaModel.getModel('areaStyle');
var showSplitLine = splitLineModel.get('show');
var showSplitArea = splitAreaModel.get('show');
var splitLineColors = lineStyleModel.get('color');
var splitAreaColors = areaStyleModel.get('color');
splitLineColors = zrUtil.isArray(splitLineColors) ? splitLineColors : [splitLineColors];
splitAreaColors = zrUtil.isArray(splitAreaColors) ? splitAreaColors : [splitAreaColors];
var splitLines = [];
var splitAreas = [];
function getColorIndex(areaOrLine, areaOrLineColorList, idx) {
var colorIndex = idx % areaOrLineColorList.length;
areaOrLine[colorIndex] = areaOrLine[colorIndex] || [];
return colorIndex;
}
if (shape === 'circle') {
var ticksRadius = indicatorAxes[0].getTicksCoords();
var cx = radar.cx;
var cy = radar.cy;
for (var i = 0; i < ticksRadius.length; i++) {
if (showSplitLine) {
var colorIndex = getColorIndex(splitLines, splitLineColors, i);
splitLines[colorIndex].push(new graphic.Circle({
shape: {
cx: cx,
cy: cy,
r: ticksRadius[i]
}
}));
}
if (showSplitArea && i < ticksRadius.length - 1) {
var colorIndex = getColorIndex(splitAreas, splitAreaColors, i);
splitAreas[colorIndex].push(new graphic.Ring({
shape: {
cx: cx,
cy: cy,
r0: ticksRadius[i],
r: ticksRadius[i + 1]
}
}));
}
}
}
// Polyyon
else {
var axesTicksPoints = zrUtil.map(indicatorAxes, function (indicatorAxis, idx) {
var ticksCoords = indicatorAxis.getTicksCoords();
return zrUtil.map(ticksCoords, function (tickCoord) {
return radar.coordToPoint(tickCoord, idx);
});
});
var prevPoints = [];
for (var i = 0; i <= splitNumber; i++) {
var points = [];
for (var j = 0; j < indicatorAxes.length; j++) {
points.push(axesTicksPoints[j][i]);
}
// Close
points.push(points[0].slice());
if (showSplitLine) {
var colorIndex = getColorIndex(splitLines, splitLineColors, i);
splitLines[colorIndex].push(new graphic.Polyline({
shape: {
points: points
}
}));
}
if (showSplitArea && prevPoints) {
var colorIndex = getColorIndex(splitAreas, splitAreaColors, i - 1);
splitAreas[colorIndex].push(new graphic.Polygon({
shape: {
points: points.concat(prevPoints)
}
}));
}
prevPoints = points.slice().reverse();
}
}
var lineStyle = lineStyleModel.getLineStyle();
var areaStyle = areaStyleModel.getAreaStyle();
// Add splitArea before splitLine
zrUtil.each(splitAreas, function (splitAreas, idx) {
this.group.add(graphic.mergePath(
splitAreas, {
style: zrUtil.defaults({
stroke: 'none',
fill: splitAreaColors[idx % splitAreaColors.length]
}, areaStyle),
silent: true
}
));
}, this);
zrUtil.each(splitLines, function (splitLines, idx) {
this.group.add(graphic.mergePath(
splitLines, {
style: zrUtil.defaults({
fill: 'none',
stroke: splitLineColors[idx % splitLineColors.length]
}, lineStyle),
silent: true
}
));
}, this);
}
});
});

View File

@@ -0,0 +1,6 @@
define(function(require) {
require('../coord/polar/polarCreator');
require('./axis/RadiusAxisView');
});

12
vendors/echarts/src/component/single.js vendored Normal file
View File

@@ -0,0 +1,12 @@
define(function (require) {
require('../coord/single/singleCreator');
require('./singleAxis');
var echarts = require('../echarts');
echarts.extendComponentView({
type: 'single'
});
});

View File

@@ -0,0 +1,7 @@
define(function (require) {
require('../coord/single/singleCreator');
require('./axis/SingleAxisView');
require('../coord/single/AxisModel');
});

View File

@@ -0,0 +1,15 @@
/**
* DataZoom component entry
*/
define(function (require) {
var echarts = require('../echarts');
echarts.registerPreprocessor(require('./timeline/preprocessor'));
require('./timeline/typeDefaulter');
require('./timeline/timelineAction');
require('./timeline/SliderTimelineModel');
require('./timeline/SliderTimelineView');
});

View File

@@ -0,0 +1,111 @@
/**
* @file Silder timeline model
*/
define(function(require) {
var TimelineModel = require('./TimelineModel');
var zrUtil = require('zrender/core/util');
var modelUtil = require('../../util/model');
var SliderTimelineModel = TimelineModel.extend({
type: 'timeline.slider',
/**
* @protected
*/
defaultOption: {
backgroundColor: 'rgba(0,0,0,0)', // 时间轴背景颜色
borderColor: '#ccc', // 时间轴边框颜色
borderWidth: 0, // 时间轴边框线宽单位px默认为0无边框
orient: 'horizontal', // 'vertical'
inverse: false,
tooltip: { // boolean or Object
trigger: 'item' // data item may also have tootip attr.
},
symbol: 'emptyCircle',
symbolSize: 10,
lineStyle: {
show: true,
width: 2,
color: '#304654'
},
label: { // 文本标签
position: 'auto', // auto left right top bottom
// When using number, label position is not
// restricted by viewRect.
// positive: right/bottom, negative: left/top
normal: {
show: true,
interval: 'auto',
rotate: 0,
// formatter: null,
textStyle: { // 其余属性默认使用全局文本样式详见TEXTSTYLE
color: '#304654'
}
},
emphasis: {
show: true,
textStyle: { // 其余属性默认使用全局文本样式详见TEXTSTYLE
color: '#c23531'
}
}
},
itemStyle: {
normal: {
color: '#304654',
borderWidth: 1
},
emphasis: {
color: '#c23531'
}
},
checkpointStyle: {
symbol: 'circle',
symbolSize: 13,
color: '#c23531',
borderWidth: 5,
borderColor: 'rgba(194,53,49, 0.5)',
animation: true,
animationDuration: 300,
animationEasing: 'quinticInOut'
},
controlStyle: {
show: true,
showPlayBtn: true,
showPrevBtn: true,
showNextBtn: true,
itemSize: 22,
itemGap: 12,
position: 'left', // 'left' 'right' 'top' 'bottom'
playIcon: 'path://M31.6,53C17.5,53,6,41.5,6,27.4S17.5,1.8,31.6,1.8C45.7,1.8,57.2,13.3,57.2,27.4S45.7,53,31.6,53z M31.6,3.3 C18.4,3.3,7.5,14.1,7.5,27.4c0,13.3,10.8,24.1,24.1,24.1C44.9,51.5,55.7,40.7,55.7,27.4C55.7,14.1,44.9,3.3,31.6,3.3z M24.9,21.3 c0-2.2,1.6-3.1,3.5-2l10.5,6.1c1.899,1.1,1.899,2.9,0,4l-10.5,6.1c-1.9,1.1-3.5,0.2-3.5-2V21.3z', // jshint ignore:line
stopIcon: 'path://M30.9,53.2C16.8,53.2,5.3,41.7,5.3,27.6S16.8,2,30.9,2C45,2,56.4,13.5,56.4,27.6S45,53.2,30.9,53.2z M30.9,3.5C17.6,3.5,6.8,14.4,6.8,27.6c0,13.3,10.8,24.1,24.101,24.1C44.2,51.7,55,40.9,55,27.6C54.9,14.4,44.1,3.5,30.9,3.5z M36.9,35.8c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H36c0.5,0,0.9,0.4,0.9,1V35.8z M27.8,35.8 c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H27c0.5,0,0.9,0.4,0.9,1L27.8,35.8L27.8,35.8z', // jshint ignore:line
nextIcon: 'path://M18.6,50.8l22.5-22.5c0.2-0.2,0.3-0.4,0.3-0.7c0-0.3-0.1-0.5-0.3-0.7L18.7,4.4c-0.1-0.1-0.2-0.3-0.2-0.5 c0-0.4,0.3-0.8,0.8-0.8c0.2,0,0.5,0.1,0.6,0.3l23.5,23.5l0,0c0.2,0.2,0.3,0.4,0.3,0.7c0,0.3-0.1,0.5-0.3,0.7l-0.1,0.1L19.7,52 c-0.1,0.1-0.3,0.2-0.5,0.2c-0.4,0-0.8-0.3-0.8-0.8C18.4,51.2,18.5,51,18.6,50.8z', // jshint ignore:line
prevIcon: 'path://M43,52.8L20.4,30.3c-0.2-0.2-0.3-0.4-0.3-0.7c0-0.3,0.1-0.5,0.3-0.7L42.9,6.4c0.1-0.1,0.2-0.3,0.2-0.5 c0-0.4-0.3-0.8-0.8-0.8c-0.2,0-0.5,0.1-0.6,0.3L18.3,28.8l0,0c-0.2,0.2-0.3,0.4-0.3,0.7c0,0.3,0.1,0.5,0.3,0.7l0.1,0.1L41.9,54 c0.1,0.1,0.3,0.2,0.5,0.2c0.4,0,0.8-0.3,0.8-0.8C43.2,53.2,43.1,53,43,52.8z', // jshint ignore:line
normal: {
color: '#304654',
borderColor: '#304654',
borderWidth: 1
},
emphasis: {
color: '#c23531',
borderColor: '#c23531',
borderWidth: 2
}
},
data: []
}
});
zrUtil.mixin(SliderTimelineModel, modelUtil.dataFormatMixin);
return SliderTimelineModel;
});

View File

@@ -0,0 +1,688 @@
/**
* @file Silder timeline view
*/
define(function (require) {
var zrUtil = require('zrender/core/util');
var graphic = require('../../util/graphic');
var layout = require('../../util/layout');
var TimelineView = require('./TimelineView');
var TimelineAxis = require('./TimelineAxis');
var symbolUtil = require('../../util/symbol');
var axisHelper = require('../../coord/axisHelper');
var BoundingRect = require('zrender/core/BoundingRect');
var matrix = require('zrender/core/matrix');
var numberUtil = require('../../util/number');
var formatUtil = require('../../util/format');
var encodeHTML = formatUtil.encodeHTML;
var bind = zrUtil.bind;
var each = zrUtil.each;
var PI = Math.PI;
return TimelineView.extend({
type: 'timeline.slider',
init: function (ecModel, api) {
this.api = api;
/**
* @private
* @type {module:echarts/component/timeline/TimelineAxis}
*/
this._axis;
/**
* @private
* @type {module:zrender/core/BoundingRect}
*/
this._viewRect;
/**
* @type {number}
*/
this._timer;
/**
* @type {module:zrende/Element}
*/
this._currentPointer;
/**
* @type {module:zrender/container/Group}
*/
this._mainGroup;
/**
* @type {module:zrender/container/Group}
*/
this._labelGroup;
},
/**
* @override
*/
render: function (timelineModel, ecModel, api, payload) {
this.model = timelineModel;
this.api = api;
this.ecModel = ecModel;
this.group.removeAll();
if (timelineModel.get('show', true)) {
var layoutInfo = this._layout(timelineModel, api);
var mainGroup = this._createGroup('mainGroup');
var labelGroup = this._createGroup('labelGroup');
/**
* @private
* @type {module:echarts/component/timeline/TimelineAxis}
*/
var axis = this._axis = this._createAxis(layoutInfo, timelineModel);
timelineModel.formatTooltip = function (dataIndex) {
return encodeHTML(axis.scale.getLabel(dataIndex));
};
each(
['AxisLine', 'AxisTick', 'Control', 'CurrentPointer'],
function (name) {
this['_render' + name](layoutInfo, mainGroup, axis, timelineModel);
},
this
);
this._renderAxisLabel(layoutInfo, labelGroup, axis, timelineModel);
this._position(layoutInfo, timelineModel);
}
this._doPlayStop();
},
/**
* @override
*/
remove: function () {
this._clearTimer();
this.group.removeAll();
},
/**
* @override
*/
dispose: function () {
this._clearTimer();
},
_layout: function (timelineModel, api) {
var labelPosOpt = timelineModel.get('label.normal.position');
var orient = timelineModel.get('orient');
var viewRect = getViewRect(timelineModel, api);
// Auto label offset.
if (labelPosOpt == null || labelPosOpt === 'auto') {
labelPosOpt = orient === 'horizontal'
? ((viewRect.y + viewRect.height / 2) < api.getHeight() / 2 ? '-' : '+')
: ((viewRect.x + viewRect.width / 2) < api.getWidth() / 2 ? '+' : '-');
}
else if (isNaN(labelPosOpt)) {
labelPosOpt = ({
horizontal: {top: '-', bottom: '+'},
vertical: {left: '-', right: '+'}
})[orient][labelPosOpt];
}
// FIXME
// 暂没有实现用户传入
// var labelAlign = timelineModel.get('label.normal.textStyle.align');
// var labelBaseline = timelineModel.get('label.normal.textStyle.baseline');
var labelAlignMap = {
horizontal: 'center',
vertical: (labelPosOpt >= 0 || labelPosOpt === '+') ? 'left' : 'right'
};
var labelBaselineMap = {
horizontal: (labelPosOpt >= 0 || labelPosOpt === '+') ? 'top' : 'bottom',
vertical: 'middle'
};
var rotationMap = {
horizontal: 0,
vertical: PI / 2
};
// Position
var mainLength = orient === 'vertical' ? viewRect.height : viewRect.width;
var controlModel = timelineModel.getModel('controlStyle');
var showControl = controlModel.get('show');
var controlSize = showControl ? controlModel.get('itemSize') : 0;
var controlGap = showControl ? controlModel.get('itemGap') : 0;
var sizePlusGap = controlSize + controlGap;
// Special label rotate.
var labelRotation = timelineModel.get('label.normal.rotate') || 0;
labelRotation = labelRotation * PI / 180; // To radian.
var playPosition;
var prevBtnPosition;
var nextBtnPosition;
var axisExtent;
var controlPosition = controlModel.get('position', true);
var showControl = controlModel.get('show', true);
var showPlayBtn = showControl && controlModel.get('showPlayBtn', true);
var showPrevBtn = showControl && controlModel.get('showPrevBtn', true);
var showNextBtn = showControl && controlModel.get('showNextBtn', true);
var xLeft = 0;
var xRight = mainLength;
// position[0] means left, position[1] means middle.
if (controlPosition === 'left' || controlPosition === 'bottom') {
showPlayBtn && (playPosition = [0, 0], xLeft += sizePlusGap);
showPrevBtn && (prevBtnPosition = [xLeft, 0], xLeft += sizePlusGap);
showNextBtn && (nextBtnPosition = [xRight - controlSize, 0], xRight -= sizePlusGap);
}
else { // 'top' 'right'
showPlayBtn && (playPosition = [xRight - controlSize, 0], xRight -= sizePlusGap);
showPrevBtn && (prevBtnPosition = [0, 0], xLeft += sizePlusGap);
showNextBtn && (nextBtnPosition = [xRight - controlSize, 0], xRight -= sizePlusGap);
}
axisExtent = [xLeft, xRight];
if (timelineModel.get('inverse')) {
axisExtent.reverse();
}
return {
viewRect: viewRect,
mainLength: mainLength,
orient: orient,
rotation: rotationMap[orient],
labelRotation: labelRotation,
labelPosOpt: labelPosOpt,
labelAlign: labelAlignMap[orient],
labelBaseline: labelBaselineMap[orient],
// Based on mainGroup.
playPosition: playPosition,
prevBtnPosition: prevBtnPosition,
nextBtnPosition: nextBtnPosition,
axisExtent: axisExtent,
controlSize: controlSize,
controlGap: controlGap
};
},
_position: function (layoutInfo, timelineModel) {
// Position is be called finally, because bounding rect is needed for
// adapt content to fill viewRect (auto adapt offset).
// Timeline may be not all in the viewRect when 'offset' is specified
// as a number, because it is more appropriate that label aligns at
// 'offset' but not the other edge defined by viewRect.
var mainGroup = this._mainGroup;
var labelGroup = this._labelGroup;
var viewRect = layoutInfo.viewRect;
if (layoutInfo.orient === 'vertical') {
// transfrom to horizontal, inverse rotate by left-top point.
var m = matrix.create();
var rotateOriginX = viewRect.x;
var rotateOriginY = viewRect.y + viewRect.height;
matrix.translate(m, m, [-rotateOriginX, -rotateOriginY]);
matrix.rotate(m, m, -PI / 2);
matrix.translate(m, m, [rotateOriginX, rotateOriginY]);
viewRect = viewRect.clone();
viewRect.applyTransform(m);
}
var viewBound = getBound(viewRect);
var mainBound = getBound(mainGroup.getBoundingRect());
var labelBound = getBound(labelGroup.getBoundingRect());
var mainPosition = mainGroup.position;
var labelsPosition = labelGroup.position;
labelsPosition[0] = mainPosition[0] = viewBound[0][0];
var labelPosOpt = layoutInfo.labelPosOpt;
if (isNaN(labelPosOpt)) { // '+' or '-'
var mainBoundIdx = labelPosOpt === '+' ? 0 : 1;
toBound(mainPosition, mainBound, viewBound, 1, mainBoundIdx);
toBound(labelsPosition, labelBound, viewBound, 1, 1 - mainBoundIdx);
}
else {
var mainBoundIdx = labelPosOpt >= 0 ? 0 : 1;
toBound(mainPosition, mainBound, viewBound, 1, mainBoundIdx);
labelsPosition[1] = mainPosition[1] + labelPosOpt;
}
mainGroup.position = mainPosition;
labelGroup.position = labelsPosition;
mainGroup.rotation = labelGroup.rotation = layoutInfo.rotation;
setOrigin(mainGroup);
setOrigin(labelGroup);
function setOrigin(targetGroup) {
var pos = targetGroup.position;
targetGroup.origin = [
viewBound[0][0] - pos[0],
viewBound[1][0] - pos[1]
];
}
function getBound(rect) {
// [[xmin, xmax], [ymin, ymax]]
return [
[rect.x, rect.x + rect.width],
[rect.y, rect.y + rect.height]
];
}
function toBound(fromPos, from, to, dimIdx, boundIdx) {
fromPos[dimIdx] += to[dimIdx][boundIdx] - from[dimIdx][boundIdx];
}
},
_createAxis: function (layoutInfo, timelineModel) {
var data = timelineModel.getData();
var axisType = timelineModel.get('axisType');
var scale = axisHelper.createScaleByModel(timelineModel, axisType);
var dataExtent = data.getDataExtent('value');
scale.setExtent(dataExtent[0], dataExtent[1]);
this._customizeScale(scale, data);
scale.niceTicks();
var axis = new TimelineAxis('value', scale, layoutInfo.axisExtent, axisType);
axis.model = timelineModel;
return axis;
},
_customizeScale: function (scale, data) {
scale.getTicks = function () {
return data.mapArray(['value'], function (value) {
return value;
});
};
scale.getTicksLabels = function () {
return zrUtil.map(this.getTicks(), scale.getLabel, scale);
};
},
_createGroup: function (name) {
var newGroup = this['_' + name] = new graphic.Group();
this.group.add(newGroup);
return newGroup;
},
_renderAxisLine: function (layoutInfo, group, axis, timelineModel) {
var axisExtent = axis.getExtent();
if (!timelineModel.get('lineStyle.show')) {
return;
}
group.add(new graphic.Line({
shape: {
x1: axisExtent[0], y1: 0,
x2: axisExtent[1], y2: 0
},
style: zrUtil.extend(
{lineCap: 'round'},
timelineModel.getModel('lineStyle').getLineStyle()
),
silent: true,
z2: 1
}));
},
/**
* @private
*/
_renderAxisTick: function (layoutInfo, group, axis, timelineModel) {
var data = timelineModel.getData();
var ticks = axis.scale.getTicks();
each(ticks, function (value, dataIndex) {
var tickCoord = axis.dataToCoord(value);
var itemModel = data.getItemModel(dataIndex);
var itemStyleModel = itemModel.getModel('itemStyle.normal');
var hoverStyleModel = itemModel.getModel('itemStyle.emphasis');
var symbolOpt = {
position: [tickCoord, 0],
onclick: bind(this._changeTimeline, this, dataIndex)
};
var el = giveSymbol(itemModel, itemStyleModel, group, symbolOpt);
graphic.setHoverStyle(el, hoverStyleModel.getItemStyle());
if (itemModel.get('tooltip')) {
el.dataIndex = dataIndex;
el.dataModel = timelineModel;
}
else {
el.dataIndex = el.dataModel = null;
}
}, this);
},
/**
* @private
*/
_renderAxisLabel: function (layoutInfo, group, axis, timelineModel) {
var labelModel = timelineModel.getModel('label.normal');
if (!labelModel.get('show')) {
return;
}
var data = timelineModel.getData();
var ticks = axis.scale.getTicks();
var labels = axisHelper.getFormattedLabels(
axis, labelModel.get('formatter')
);
var labelInterval = axis.getLabelInterval();
each(ticks, function (tick, dataIndex) {
if (axis.isLabelIgnored(dataIndex, labelInterval)) {
return;
}
var itemModel = data.getItemModel(dataIndex);
var itemTextStyleModel = itemModel.getModel('label.normal.textStyle');
var hoverTextStyleModel = itemModel.getModel('label.emphasis.textStyle');
var tickCoord = axis.dataToCoord(tick);
var textEl = new graphic.Text({
style: {
text: labels[dataIndex],
textAlign: layoutInfo.labelAlign,
textVerticalAlign: layoutInfo.labelBaseline,
textFont: itemTextStyleModel.getFont(),
fill: itemTextStyleModel.getTextColor()
},
position: [tickCoord, 0],
rotation: layoutInfo.labelRotation - layoutInfo.rotation,
onclick: bind(this._changeTimeline, this, dataIndex),
silent: false
});
group.add(textEl);
graphic.setHoverStyle(textEl, hoverTextStyleModel.getItemStyle());
}, this);
},
/**
* @private
*/
_renderControl: function (layoutInfo, group, axis, timelineModel) {
var controlSize = layoutInfo.controlSize;
var rotation = layoutInfo.rotation;
var itemStyle = timelineModel.getModel('controlStyle.normal').getItemStyle();
var hoverStyle = timelineModel.getModel('controlStyle.emphasis').getItemStyle();
var rect = [0, -controlSize / 2, controlSize, controlSize];
var playState = timelineModel.getPlayState();
var inverse = timelineModel.get('inverse', true);
makeBtn(
layoutInfo.nextBtnPosition,
'controlStyle.nextIcon',
bind(this._changeTimeline, this, inverse ? '-' : '+')
);
makeBtn(
layoutInfo.prevBtnPosition,
'controlStyle.prevIcon',
bind(this._changeTimeline, this, inverse ? '+' : '-')
);
makeBtn(
layoutInfo.playPosition,
'controlStyle.' + (playState ? 'stopIcon' : 'playIcon'),
bind(this._handlePlayClick, this, !playState),
true
);
function makeBtn(position, iconPath, onclick, willRotate) {
if (!position) {
return;
}
var opt = {
position: position,
origin: [controlSize / 2, 0],
rotation: willRotate ? -rotation : 0,
rectHover: true,
style: itemStyle,
onclick: onclick
};
var btn = makeIcon(timelineModel, iconPath, rect, opt);
group.add(btn);
graphic.setHoverStyle(btn, hoverStyle);
}
},
_renderCurrentPointer: function (layoutInfo, group, axis, timelineModel) {
var data = timelineModel.getData();
var currentIndex = timelineModel.getCurrentIndex();
var pointerModel = data.getItemModel(currentIndex).getModel('checkpointStyle');
var me = this;
var callback = {
onCreate: function (pointer) {
pointer.draggable = true;
pointer.drift = bind(me._handlePointerDrag, me);
pointer.ondragend = bind(me._handlePointerDragend, me);
pointerMoveTo(pointer, currentIndex, axis, timelineModel, true);
},
onUpdate: function (pointer) {
pointerMoveTo(pointer, currentIndex, axis, timelineModel);
}
};
// Reuse when exists, for animation and drag.
this._currentPointer = giveSymbol(
pointerModel, pointerModel, this._mainGroup, {}, this._currentPointer, callback
);
},
_handlePlayClick: function (nextState) {
this._clearTimer();
this.api.dispatchAction({
type: 'timelinePlayChange',
playState: nextState,
from: this.uid
});
},
_handlePointerDrag: function (dx, dy, e) {
this._clearTimer();
this._pointerChangeTimeline([e.offsetX, e.offsetY]);
},
_handlePointerDragend: function (e) {
this._pointerChangeTimeline([e.offsetX, e.offsetY], true);
},
_pointerChangeTimeline: function (mousePos, trigger) {
var toCoord = this._toAxisCoord(mousePos)[0];
var axis = this._axis;
var axisExtent = numberUtil.asc(axis.getExtent().slice());
toCoord > axisExtent[1] && (toCoord = axisExtent[1]);
toCoord < axisExtent[0] && (toCoord = axisExtent[0]);
this._currentPointer.position[0] = toCoord;
this._currentPointer.dirty();
var targetDataIndex = this._findNearestTick(toCoord);
var timelineModel = this.model;
if (trigger || (
targetDataIndex !== timelineModel.getCurrentIndex()
&& timelineModel.get('realtime')
)) {
this._changeTimeline(targetDataIndex);
}
},
_doPlayStop: function () {
this._clearTimer();
if (this.model.getPlayState()) {
this._timer = setTimeout(
bind(handleFrame, this),
this.model.get('playInterval')
);
}
function handleFrame() {
// Do not cache
var timelineModel = this.model;
this._changeTimeline(
timelineModel.getCurrentIndex()
+ (timelineModel.get('rewind', true) ? -1 : 1)
);
}
},
_toAxisCoord: function (vertex) {
var trans = this._mainGroup.getLocalTransform();
return graphic.applyTransform(vertex, trans, true);
},
_findNearestTick: function (axisCoord) {
var data = this.model.getData();
var dist = Infinity;
var targetDataIndex;
var axis = this._axis;
data.each(['value'], function (value, dataIndex) {
var coord = axis.dataToCoord(value);
var d = Math.abs(coord - axisCoord);
if (d < dist) {
dist = d;
targetDataIndex = dataIndex;
}
});
return targetDataIndex;
},
_clearTimer: function () {
if (this._timer) {
clearTimeout(this._timer);
this._timer = null;
}
},
_changeTimeline: function (nextIndex) {
var currentIndex = this.model.getCurrentIndex();
if (nextIndex === '+') {
nextIndex = currentIndex + 1;
}
else if (nextIndex === '-') {
nextIndex = currentIndex - 1;
}
this.api.dispatchAction({
type: 'timelineChange',
currentIndex: nextIndex,
from: this.uid
});
}
});
function getViewRect(model, api) {
return layout.getLayoutRect(
model.getBoxLayoutParams(),
{
width: api.getWidth(),
height: api.getHeight()
},
model.get('padding')
);
}
function makeIcon(timelineModel, objPath, rect, opts) {
var icon = graphic.makePath(
timelineModel.get(objPath).replace(/^path:\/\//, ''),
zrUtil.clone(opts || {}),
new BoundingRect(rect[0], rect[1], rect[2], rect[3]),
'center'
);
return icon;
}
/**
* Create symbol or update symbol
*/
function giveSymbol(hostModel, itemStyleModel, group, opt, symbol, callback) {
var symbolType = hostModel.get('symbol');
var color = itemStyleModel.get('color');
var symbolSize = hostModel.get('symbolSize');
var halfSymbolSize = symbolSize / 2;
var itemStyle = itemStyleModel.getItemStyle(['color', 'symbol', 'symbolSize']);
if (!symbol) {
symbol = symbolUtil.createSymbol(
symbolType, -halfSymbolSize, -halfSymbolSize, symbolSize, symbolSize, color
);
group.add(symbol);
callback && callback.onCreate(symbol);
}
else {
symbol.setStyle(itemStyle);
symbol.setColor(color);
group.add(symbol); // Group may be new, also need to add.
callback && callback.onUpdate(symbol);
}
opt = zrUtil.merge({
rectHover: true,
style: itemStyle,
z2: 100
}, opt, true);
symbol.attr(opt);
return symbol;
}
function pointerMoveTo(pointer, dataIndex, axis, timelineModel, noAnimation) {
if (pointer.dragging) {
return;
}
var pointerModel = timelineModel.getModel('checkpointStyle');
var toCoord = axis.dataToCoord(timelineModel.getData().get(['value'], dataIndex));
if (noAnimation || !pointerModel.get('animation', true)) {
pointer.attr({position: [toCoord, 0]});
}
else {
pointer.stopAnimation(true);
pointer.animateTo(
{position: [toCoord, 0]},
pointerModel.get('animationDuration', true),
pointerModel.get('animationEasing', true)
);
}
}
});

View File

@@ -0,0 +1,96 @@
define(function (require) {
var zrUtil = require('zrender/core/util');
var Axis = require('../../coord/Axis');
var axisHelper = require('../../coord/axisHelper');
/**
* Extend axis 2d
* @constructor module:echarts/coord/cartesian/Axis2D
* @extends {module:echarts/coord/cartesian/Axis}
* @param {string} dim
* @param {*} scale
* @param {Array.<number>} coordExtent
* @param {string} axisType
* @param {string} position
*/
var TimelineAxis = function (dim, scale, coordExtent, axisType) {
Axis.call(this, dim, scale, coordExtent);
/**
* Axis type
* - 'category'
* - 'value'
* - 'time'
* - 'log'
* @type {string}
*/
this.type = axisType || 'value';
/**
* @private
* @type {number}
*/
this._autoLabelInterval;
/**
* Axis model
* @param {module:echarts/component/TimelineModel}
*/
this.model = null;
};
TimelineAxis.prototype = {
constructor: TimelineAxis,
/**
* @public
* @return {number}
*/
getLabelInterval: function () {
var timelineModel = this.model;
var labelModel = timelineModel.getModel('label.normal');
var labelInterval = labelModel.get('interval');
if (labelInterval != null && labelInterval != 'auto') {
return labelInterval;
}
var labelInterval = this._autoLabelInterval;
if (!labelInterval) {
labelInterval = this._autoLabelInterval = axisHelper.getAxisLabelInterval(
zrUtil.map(this.scale.getTicks(), this.dataToCoord, this),
axisHelper.getFormattedLabels(this, labelModel.get('formatter')),
labelModel.getModel('textStyle').getFont(),
timelineModel.get('orient') === 'horizontal'
);
}
return labelInterval;
},
/**
* If label is ignored.
* Automatically used when axis is category and label can not be all shown
* @public
* @param {number} idx
* @return {boolean}
*/
isLabelIgnored: function (idx) {
if (this.type === 'category') {
var labelInterval = this.getLabelInterval();
return ((typeof labelInterval === 'function')
&& !labelInterval(idx, this.scale.getLabel(idx)))
|| idx % (labelInterval + 1);
}
}
};
zrUtil.inherits(TimelineAxis, Axis);
return TimelineAxis;
});

View File

@@ -0,0 +1,197 @@
/**
* @file Timeline model
*/
define(function(require) {
var ComponentModel = require('../../model/Component');
var List = require('../../data/List');
var zrUtil = require('zrender/core/util');
var modelUtil = require('../../util/model');
var TimelineModel = ComponentModel.extend({
type: 'timeline',
layoutMode: 'box',
/**
* @protected
*/
defaultOption: {
zlevel: 0, // 一级层叠
z: 4, // 二级层叠
show: true,
axisType: 'time', // 模式是时间类型,支持 value, category
realtime: true,
left: '20%',
top: null,
right: '20%',
bottom: 0,
width: null,
height: 40,
padding: 5,
controlPosition: 'left', // 'left' 'right' 'top' 'bottom' 'none'
autoPlay: false,
rewind: false, // 反向播放
loop: true,
playInterval: 2000, // 播放时间间隔单位ms
currentIndex: 0,
itemStyle: {
normal: {},
emphasis: {}
},
label: {
normal: {
textStyle: {
color: '#000'
}
},
emphasis: {}
},
data: []
},
/**
* @override
*/
init: function (option, parentModel, ecModel) {
/**
* @private
* @type {module:echarts/data/List}
*/
this._data;
/**
* @private
* @type {Array.<string>}
*/
this._names;
this.mergeDefaultAndTheme(option, ecModel);
this._initData();
},
/**
* @override
*/
mergeOption: function (option) {
TimelineModel.superApply(this, 'mergeOption', arguments);
this._initData();
},
/**
* @param {number} [currentIndex]
*/
setCurrentIndex: function (currentIndex) {
if (currentIndex == null) {
currentIndex = this.option.currentIndex;
}
var count = this._data.count();
if (this.option.loop) {
currentIndex = (currentIndex % count + count) % count;
}
else {
currentIndex >= count && (currentIndex = count - 1);
currentIndex < 0 && (currentIndex = 0);
}
this.option.currentIndex = currentIndex;
},
/**
* @return {number} currentIndex
*/
getCurrentIndex: function () {
return this.option.currentIndex;
},
/**
* @return {boolean}
*/
isIndexMax: function () {
return this.getCurrentIndex() >= this._data.count() - 1;
},
/**
* @param {boolean} state true: play, false: stop
*/
setPlayState: function (state) {
this.option.autoPlay = !!state;
},
/**
* @return {boolean} true: play, false: stop
*/
getPlayState: function () {
return !!this.option.autoPlay;
},
/**
* @private
*/
_initData: function () {
var thisOption = this.option;
var dataArr = thisOption.data || [];
var axisType = thisOption.axisType;
var names = this._names = [];
if (axisType === 'category') {
var idxArr = [];
zrUtil.each(dataArr, function (item, index) {
var value = modelUtil.getDataItemValue(item);
var newItem;
if (zrUtil.isObject(item)) {
newItem = zrUtil.clone(item);
newItem.value = index;
}
else {
newItem = index;
}
idxArr.push(newItem);
if (!zrUtil.isString(value) && (value == null || isNaN(value))) {
value = '';
}
names.push(value + '');
});
dataArr = idxArr;
}
var dimType = ({category: 'ordinal', time: 'time'})[axisType] || 'number';
var data = this._data = new List([{name: 'value', type: dimType}], this);
data.initData(dataArr, names);
},
getData: function () {
return this._data;
},
/**
* @public
* @return {Array.<string>} categoreis
*/
getCategories: function () {
if (this.get('axisType') === 'category') {
return this._names.slice();
}
}
});
return TimelineModel;
});

View File

@@ -0,0 +1,15 @@
/**
* @file Timeline view
*/
define(function (require) {
// var zrUtil = require('zrender/core/util');
// var graphic = require('../../util/graphic');
var ComponentView = require('../../view/Component');
return ComponentView.extend({
type: 'timeline'
});
});

View File

@@ -0,0 +1,86 @@
/**
* @file Timeline preprocessor
*/
define(function(require) {
var zrUtil = require('zrender/core/util');
return function (option) {
var timelineOpt = option && option.timeline;
if (!zrUtil.isArray(timelineOpt)) {
timelineOpt = timelineOpt ? [timelineOpt] : [];
}
zrUtil.each(timelineOpt, function (opt) {
if (!opt) {
return;
}
compatibleEC2(opt);
});
};
function compatibleEC2(opt) {
var type = opt.type;
var ec2Types = {'number': 'value', 'time': 'time'};
// Compatible with ec2
if (ec2Types[type]) {
opt.axisType = ec2Types[type];
delete opt.type;
}
transferItem(opt);
if (has(opt, 'controlPosition')) {
var controlStyle = opt.controlStyle || (opt.controlStyle = {});
if (!has(controlStyle, 'position')) {
controlStyle.position = opt.controlPosition;
}
if (controlStyle.position === 'none' && !has(controlStyle, 'show')) {
controlStyle.show = false;
delete controlStyle.position;
}
delete opt.controlPosition;
}
zrUtil.each(opt.data || [], function (dataItem) {
if (zrUtil.isObject(dataItem) && !zrUtil.isArray(dataItem)) {
if (!has(dataItem, 'value') && has(dataItem, 'name')) {
// In ec2, using name as value.
dataItem.value = dataItem.name;
}
transferItem(dataItem);
}
});
}
function transferItem(opt) {
var itemStyle = opt.itemStyle || (opt.itemStyle = {});
var itemStyleEmphasis = itemStyle.emphasis || (itemStyle.emphasis = {});
// Transfer label out
var label = opt.label || (opt.label || {});
var labelNormal = label.normal || (label.normal = {});
var excludeLabelAttr = {normal: 1, emphasis: 1};
zrUtil.each(label, function (value, name) {
if (!excludeLabelAttr[name] && !has(labelNormal, name)) {
labelNormal[name] = value;
}
});
if (itemStyleEmphasis.label && !has(label, 'emphasis')) {
label.emphasis = itemStyleEmphasis.label;
delete itemStyleEmphasis.label;
}
}
function has(obj, attr) {
return obj.hasOwnProperty(attr);
}
});

View File

@@ -0,0 +1,39 @@
/**
* @file Timeilne action
*/
define(function(require) {
var echarts = require('../../echarts');
echarts.registerAction(
{type: 'timelineChange', event: 'timelineChanged', update: 'prepareAndUpdate'},
function (payload, ecModel) {
var timelineModel = ecModel.getComponent('timeline');
if (timelineModel && payload.currentIndex != null) {
timelineModel.setCurrentIndex(payload.currentIndex);
if (!timelineModel.get('loop', true) && timelineModel.isIndexMax()) {
timelineModel.setPlayState(false);
}
}
ecModel.resetOption('timeline');
}
);
echarts.registerAction(
{type: 'timelinePlayChange', event: 'timelinePlayChanged', update: 'update'},
function (payload, ecModel) {
var timelineModel = ecModel.getComponent('timeline');
if (timelineModel && payload.playState != null) {
timelineModel.setPlayState(payload.playState);
}
}
);
});

View File

@@ -0,0 +1,8 @@
define(function (require) {
require('../../model/Component').registerSubTypeDefaulter('timeline', function () {
// Only slider now.
return 'slider';
});
});

188
vendors/echarts/src/component/title.js vendored Normal file
View File

@@ -0,0 +1,188 @@
define(function(require) {
'use strict';
var echarts = require('../echarts');
var graphic = require('../util/graphic');
var layout = require('../util/layout');
// Model
echarts.extendComponentModel({
type: 'title',
layoutMode: {type: 'box', ignoreSize: true},
defaultOption: {
// 一级层叠
zlevel: 0,
// 二级层叠
z: 6,
show: true,
text: '',
// 超链接跳转
// link: null,
// 仅支持self | blank
target: 'blank',
subtext: '',
// 超链接跳转
// sublink: null,
// 仅支持self | blank
subtarget: 'blank',
// 'center' ¦ 'left' ¦ 'right'
// ¦ {number}x坐标单位px
left: 0,
// 'top' ¦ 'bottom' ¦ 'center'
// ¦ {number}y坐标单位px
top: 0,
// 水平对齐
// 'auto' | 'left' | 'right'
// 默认根据 x 的位置判断是左对齐还是右对齐
//textAlign: null
backgroundColor: 'rgba(0,0,0,0)',
// 标题边框颜色
borderColor: '#ccc',
// 标题边框线宽单位px默认为0无边框
borderWidth: 0,
// 标题内边距单位px默认各方向内边距为5
// 接受数组分别设定上右下左边距同css
padding: 5,
// 主副标题纵向间隔单位px默认为10
itemGap: 10,
textStyle: {
fontSize: 18,
fontWeight: 'bolder',
// 主标题文字颜色
color: '#333'
},
subtextStyle: {
// 副标题文字颜色
color: '#aaa'
}
}
});
// View
echarts.extendComponentView({
type: 'title',
render: function (titleModel, ecModel, api) {
this.group.removeAll();
if (!titleModel.get('show')) {
return;
}
var group = this.group;
var textStyleModel = titleModel.getModel('textStyle');
var subtextStyleModel = titleModel.getModel('subtextStyle');
var textAlign = titleModel.get('textAlign');
var textEl = new graphic.Text({
style: {
text: titleModel.get('text'),
textFont: textStyleModel.getFont(),
fill: textStyleModel.getTextColor(),
textBaseline: 'top'
},
z2: 10
});
var textRect = textEl.getBoundingRect();
var subText = titleModel.get('subtext');
var subTextEl = new graphic.Text({
style: {
text: subText,
textFont: subtextStyleModel.getFont(),
fill: subtextStyleModel.getTextColor(),
y: textRect.height + titleModel.get('itemGap'),
textBaseline: 'top'
},
z2: 10
});
var link = titleModel.get('link');
var sublink = titleModel.get('sublink');
textEl.silent = !link;
subTextEl.silent = !sublink;
if (link) {
textEl.on('click', function () {
window.open(link, '_' + titleModel.get('target'));
});
}
if (sublink) {
subTextEl.on('click', function () {
window.open(sublink, '_' + titleModel.get('subtarget'));
});
}
group.add(textEl);
subText && group.add(subTextEl);
// If no subText, but add subTextEl, there will be an empty line.
var groupRect = group.getBoundingRect();
var layoutOption = titleModel.getBoxLayoutParams();
layoutOption.width = groupRect.width;
layoutOption.height = groupRect.height;
var layoutRect = layout.getLayoutRect(
layoutOption, {
width: api.getWidth(),
height: api.getHeight()
}, titleModel.get('padding')
);
// Adjust text align based on position
if (!textAlign) {
// Align left if title is on the left. center and right is same
textAlign = titleModel.get('left') || titleModel.get('right');
if (textAlign === 'middle') {
textAlign = 'center';
}
// Adjust layout by text align
if (textAlign === 'right') {
layoutRect.x += layoutRect.width;
}
else if (textAlign === 'center') {
layoutRect.x += layoutRect.width / 2;
}
}
group.position = [layoutRect.x, layoutRect.y];
textEl.setStyle('textAlign', textAlign);
subTextEl.setStyle('textAlign', textAlign);
// Render background
// Get groupRect again because textAlign has been changed
groupRect = group.getBoundingRect();
var padding = layoutRect.margin;
var style = titleModel.getItemStyle(['color', 'opacity']);
style.fill = titleModel.get('backgroundColor');
var rect = new graphic.Rect({
shape: {
x: groupRect.x - padding[3],
y: groupRect.y - padding[0],
width: groupRect.width + padding[1] + padding[3],
height: groupRect.height + padding[0] + padding[2]
},
style: style,
silent: true
});
graphic.subPixelOptimizeRect(rect);
group.add(rect);
}
});
});

View File

@@ -0,0 +1,11 @@
define(function (require) {
require('./toolbox/ToolboxModel');
require('./toolbox/ToolboxView');
require('./toolbox/feature/SaveAsImage');
require('./toolbox/feature/MagicType');
require('./toolbox/feature/DataView');
require('./toolbox/feature/DataZoom');
require('./toolbox/feature/Restore');
});

View File

@@ -0,0 +1,71 @@
define(function (require) {
var featureManager = require('./featureManager');
var zrUtil = require('zrender/core/util');
var ToolboxModel = require('../../echarts').extendComponentModel({
type: 'toolbox',
layoutMode: {
type: 'box',
ignoreSize: true
},
mergeDefaultAndTheme: function (option) {
ToolboxModel.superApply(this, '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',
left: 'right',
top: 'top',
// right
// bottom
backgroundColor: 'transparent',
borderColor: '#ccc',
borderWidth: 0,
padding: 5,
itemSize: 15,
itemGap: 8,
showTitle: true,
iconStyle: {
normal: {
borderColor: '#666',
color: 'none'
},
emphasis: {
borderColor: '#3E98C5'
}
}
// textStyle: {},
// feature
}
});
return ToolboxModel;
});

View File

@@ -0,0 +1,233 @@
define(function (require) {
var featureManager = require('./featureManager');
var zrUtil = require('zrender/core/util');
var graphic = require('../../util/graphic');
var Model = require('../../model/Model');
var DataDiffer = require('../../data/DataDiffer');
var listComponentHelper = require('../helper/listComponent');
var textContain = require('zrender/contain/text');
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');
var featureOpts = toolboxModel.get('feature') || {};
var features = this._features || (this._features = {});
var featureNames = [];
zrUtil.each(featureOpts, function (opt, name) {
featureNames.push(name);
});
(new DataDiffer(this._featureNames || [], featureNames))
.add(process)
.update(process)
.remove(zrUtil.curry(process, null))
.execute();
// Keep for diff.
this._featureNames = featureNames;
function process(newIndex, oldIndex) {
var featureName = featureNames[newIndex];
var oldName = featureNames[oldIndex];
var featureOpt = featureOpts[featureName];
var featureModel = new Model(featureOpt, toolboxModel, toolboxModel.ecModel);
var feature;
if (featureName && !oldName) { // Create
if (isUserFeatureName(featureName)) {
feature = {
model: featureModel,
onclick: featureModel.option.onclick,
featureName: featureName
};
}
else {
var Feature = featureManager.get(featureName);
if (!Feature) {
return;
}
feature = new Feature(featureModel);
}
features[featureName] = feature;
}
else {
feature = features[oldName];
// If feature does not exsit.
if (!feature) {
return;
}
feature.model = featureModel;
}
if (!featureName && oldName) {
feature.dispose && feature.dispose(ecModel, api);
return;
}
if (!featureModel.get('show') || feature.unusable) {
feature.remove && feature.remove(ecModel, api);
return;
}
createIconPaths(featureModel, feature, featureName);
featureModel.setIconStatus = function (iconName, status) {
var option = this.option;
var iconPaths = this.iconPaths;
option.iconStatus = option.iconStatus || {};
option.iconStatus[iconName] = status;
// FIXME
iconPaths[iconName] && iconPaths[iconName].trigger(status);
};
if (feature.render) {
feature.render(featureModel, ecModel, api);
}
}
function createIconPaths(featureModel, feature, featureName) {
var iconStyleModel = featureModel.getModel('iconStyle');
// If one feature has mutiple icon. they are orginaized as
// {
// icon: {
// foo: '',
// bar: ''
// },
// title: {
// foo: '',
// bar: ''
// }
// }
var icons = feature.getIcons ? feature.getIcons() : featureModel.get('icon');
var titles = featureModel.get('title') || {};
if (typeof icons === 'string') {
var icon = icons;
var title = titles;
icons = {};
titles = {};
icons[featureName] = icon;
titles[featureName] = title;
}
var iconPaths = featureModel.iconPaths = {};
zrUtil.each(icons, function (icon, iconName) {
var normalStyle = iconStyleModel.getModel('normal').getItemStyle();
var hoverStyle = iconStyleModel.getModel('emphasis').getItemStyle();
var style = {
x: -itemSize / 2,
y: -itemSize / 2,
width: itemSize,
height: itemSize
};
var path = icon.indexOf('image://') === 0
? (
style.image = icon.slice(8),
new graphic.Image({style: style})
)
: graphic.makePath(
icon.replace('path://', ''),
{
style: normalStyle,
hoverStyle: hoverStyle,
rectHover: true
},
style,
'center'
);
graphic.setHoverStyle(path);
if (toolboxModel.get('showTitle')) {
path.__title = titles[iconName];
path.on('mouseover', function () {
path.setStyle({
text: titles[iconName],
textPosition: hoverStyle.textPosition || 'bottom',
textFill: hoverStyle.fill || hoverStyle.stroke || '#000',
textAlign: hoverStyle.textAlign || 'center'
});
})
.on('mouseout', function () {
path.setStyle({
textFill: null
});
});
}
path.trigger(featureModel.get('iconStatus.' + iconName) || 'normal');
group.add(path);
path.on('click', zrUtil.bind(
feature.onclick, feature, ecModel, api, iconName
));
iconPaths[iconName] = path;
});
}
listComponentHelper.layout(group, toolboxModel, api);
// Render background after group is layout
// FIXME
listComponentHelper.addBackground(group, toolboxModel);
// Adjust icon title positions to avoid them out of screen
group.eachChild(function (icon) {
var titleText = icon.__title;
var hoverStyle = icon.hoverStyle;
// May be background element
if (hoverStyle && titleText) {
var rect = textContain.getBoundingRect(
titleText, hoverStyle.font
);
var offsetX = icon.position[0] + group.position[0];
var offsetY = icon.position[1] + group.position[1] + itemSize;
var needPutOnTop = false;
if (offsetY + rect.height > api.getHeight()) {
hoverStyle.textPosition = 'top';
needPutOnTop = true;
}
var topOffset = needPutOnTop ? (-5 - rect.height) : (itemSize + 8);
if (offsetX + rect.width / 2 > api.getWidth()) {
hoverStyle.textPosition = ['100%', topOffset];
hoverStyle.textAlign = 'right';
}
else if (offsetX - rect.width / 2 < 0) {
hoverStyle.textPosition = [0, topOffset];
hoverStyle.textAlign = 'left';
}
}
});
},
remove: function (ecModel, api) {
zrUtil.each(this._features, function (feature) {
feature.remove && feature.remove(ecModel, api);
});
this.group.removeAll();
},
dispose: function (ecModel, api) {
zrUtil.each(this._features, function (feature) {
feature.dispose && feature.dispose(ecModel, api);
});
}
});
function isUserFeatureName(featureName) {
return featureName.indexOf('my') === 0;
}
});

View File

@@ -0,0 +1,478 @@
/**
* @module echarts/component/toolbox/feature/DataView
*/
define(function (require) {
var zrUtil = require('zrender/core/util');
var eventTool = require('zrender/core/event');
var BLOCK_SPLITER = new Array(60).join('-');
var ITEM_SPLITER = '\t';
/**
* Group series into two types
* 1. on category axis, like line, bar
* 2. others, like scatter, pie
* @param {module:echarts/model/Global} ecModel
* @return {Object}
* @inner
*/
function groupSeries(ecModel) {
var seriesGroupByCategoryAxis = {};
var otherSeries = [];
var meta = [];
ecModel.eachRawSeries(function (seriesModel) {
var coordSys = seriesModel.coordinateSystem;
if (coordSys && (coordSys.type === 'cartesian2d' || coordSys.type === 'polar')) {
var baseAxis = coordSys.getBaseAxis();
if (baseAxis.type === 'category') {
var key = baseAxis.dim + '_' + baseAxis.index;
if (!seriesGroupByCategoryAxis[key]) {
seriesGroupByCategoryAxis[key] = {
categoryAxis: baseAxis,
valueAxis: coordSys.getOtherAxis(baseAxis),
series: []
};
meta.push({
axisDim: baseAxis.dim,
axisIndex: baseAxis.index
});
}
seriesGroupByCategoryAxis[key].series.push(seriesModel);
}
else {
otherSeries.push(seriesModel);
}
}
else {
otherSeries.push(seriesModel);
}
});
return {
seriesGroupByCategoryAxis: seriesGroupByCategoryAxis,
other: otherSeries,
meta: meta
};
}
/**
* Assemble content of series on cateogory axis
* @param {Array.<module:echarts/model/Series>} series
* @return {string}
* @inner
*/
function assembleSeriesWithCategoryAxis(series) {
var tables = [];
zrUtil.each(series, function (group, key) {
var categoryAxis = group.categoryAxis;
var valueAxis = group.valueAxis;
var valueAxisDim = valueAxis.dim;
var headers = [' '].concat(zrUtil.map(group.series, function (series) {
return series.name;
}));
var columns = [categoryAxis.model.getCategories()];
zrUtil.each(group.series, function (series) {
columns.push(series.getRawData().mapArray(valueAxisDim, function (val) {
return val;
}));
});
// Assemble table content
var lines = [headers.join(ITEM_SPLITER)];
for (var i = 0; i < columns[0].length; i++) {
var items = [];
for (var j = 0; j < columns.length; j++) {
items.push(columns[j][i]);
}
lines.push(items.join(ITEM_SPLITER));
}
tables.push(lines.join('\n'));
});
return tables.join('\n\n' + BLOCK_SPLITER + '\n\n');
}
/**
* Assemble content of other series
* @param {Array.<module:echarts/model/Series>} series
* @return {string}
* @inner
*/
function assembleOtherSeries(series) {
return zrUtil.map(series, function (series) {
var data = series.getRawData();
var lines = [series.name];
var vals = [];
data.each(data.dimensions, function () {
var argLen = arguments.length;
var dataIndex = arguments[argLen - 1];
var name = data.getName(dataIndex);
for (var i = 0; i < argLen - 1; i++) {
vals[i] = arguments[i];
}
lines.push((name ? (name + ITEM_SPLITER) : '') + vals.join(ITEM_SPLITER));
});
return lines.join('\n');
}).join('\n\n' + BLOCK_SPLITER + '\n\n');
}
/**
* @param {module:echarts/model/Global}
* @return {string}
* @inner
*/
function getContentFromModel(ecModel) {
var result = groupSeries(ecModel);
return {
value: zrUtil.filter([
assembleSeriesWithCategoryAxis(result.seriesGroupByCategoryAxis),
assembleOtherSeries(result.other)
], function (str) {
return str.replace(/[\n\t\s]/g, '');
}).join('\n\n' + BLOCK_SPLITER + '\n\n'),
meta: result.meta
};
}
function trim(str) {
return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
}
/**
* If a block is tsv format
*/
function isTSVFormat(block) {
// Simple method to find out if a block is tsv format
var firstLine = block.slice(0, block.indexOf('\n'));
if (firstLine.indexOf(ITEM_SPLITER) >= 0) {
return true;
}
}
var itemSplitRegex = new RegExp('[' + ITEM_SPLITER + ']+', 'g');
/**
* @param {string} tsv
* @return {Array.<Object>}
*/
function parseTSVContents(tsv) {
var tsvLines = tsv.split(/\n+/g);
var headers = trim(tsvLines.shift()).split(itemSplitRegex);
var categories = [];
var series = zrUtil.map(headers, function (header) {
return {
name: header,
data: []
};
});
for (var i = 0; i < tsvLines.length; i++) {
var items = trim(tsvLines[i]).split(itemSplitRegex);
categories.push(items.shift());
for (var j = 0; j < items.length; j++) {
series[j] && (series[j].data[i] = items[j]);
}
}
return {
series: series,
categories: categories
};
}
/**
* @param {string} str
* @return {Array.<Object>}
* @inner
*/
function parseListContents(str) {
var lines = str.split(/\n+/g);
var seriesName = trim(lines.shift());
var data = [];
for (var i = 0; i < lines.length; i++) {
var items = trim(lines[i]).split(itemSplitRegex);
var name = '';
var value;
var hasName = false;
if (isNaN(items[0])) { // First item is name
hasName = true;
name = items[0];
items = items.slice(1);
data[i] = {
name: name,
value: []
};
value = data[i].value;
}
else {
value = data[i] = [];
}
for (var j = 0; j < items.length; j++) {
value.push(+items[j]);
}
if (value.length === 1) {
hasName ? (data[i].value = value[0]) : (data[i] = value[0]);
}
}
return {
name: seriesName,
data: data
};
}
/**
* @param {string} str
* @param {Array.<Object>} blockMetaList
* @return {Object}
* @inner
*/
function parseContents(str, blockMetaList) {
var blocks = str.split(new RegExp('\n*' + BLOCK_SPLITER + '\n*', 'g'));
var newOption = {
series: []
};
zrUtil.each(blocks, function (block, idx) {
if (isTSVFormat(block)) {
var result = parseTSVContents(block);
var blockMeta = blockMetaList[idx];
var axisKey = blockMeta.axisDim + 'Axis';
if (blockMeta) {
newOption[axisKey] = newOption[axisKey] || [];
newOption[axisKey][blockMeta.axisIndex] = {
data: result.categories
};
newOption.series = newOption.series.concat(result.series);
}
}
else {
var result = parseListContents(block);
newOption.series.push(result);
}
});
return newOption;
}
/**
* @alias {module:echarts/component/toolbox/feature/DataView}
* @constructor
* @param {module:echarts/model/Model} model
*/
function DataView(model) {
this._dom = null;
this.model = model;
}
DataView.defaultOption = {
show: true,
readOnly: false,
optionToContent: null,
contentToOption: null,
icon: 'M17.5,17.3H33 M17.5,17.3H33 M45.4,29.5h-28 M11.5,2v56H51V14.8L38.4,2H11.5z M38.4,2.2v12.7H51 M45.4,41.7h-28',
title: '数据视图',
lang: ['数据视图', '关闭', '刷新'],
backgroundColor: '#fff',
textColor: '#000',
textareaColor: '#fff',
textareaBorderColor: '#333',
buttonColor: '#c23531',
buttonTextColor: '#fff'
};
DataView.prototype.onclick = function (ecModel, api) {
var container = api.getDom();
var model = this.model;
if (this._dom) {
container.removeChild(this._dom);
}
var root = document.createElement('div');
root.style.cssText = 'position:absolute;left:5px;top:5px;bottom:5px;right:5px;';
root.style.backgroundColor = model.get('backgroundColor') || '#fff';
// Create elements
var header = document.createElement('h4');
var lang = model.get('lang') || [];
header.innerHTML = lang[0] || model.get('title');
header.style.cssText = 'margin: 10px 20px;';
header.style.color = model.get('textColor');
var viewMain = document.createElement('div');
var textarea = document.createElement('textarea');
viewMain.style.cssText = 'display:block;width:100%;overflow:hidden;';
var optionToContent = model.get('optionToContent');
var contentToOption = model.get('contentToOption');
var result = getContentFromModel(ecModel);
if (typeof optionToContent === 'function') {
var htmlOrDom = optionToContent(api.getOption());
if (typeof htmlOrDom === 'string') {
viewMain.innerHTML = htmlOrDom;
}
else if (zrUtil.isDom(htmlOrDom)) {
viewMain.appendChild(htmlOrDom);
}
}
else {
// Use default textarea
viewMain.appendChild(textarea);
textarea.readOnly = model.get('readOnly');
textarea.style.cssText = 'width:100%;height:100%;font-family:monospace;font-size:14px;line-height:1.6rem;';
textarea.style.color = model.get('textColor');
textarea.style.borderColor = model.get('textareaBorderColor');
textarea.style.backgroundColor = model.get('textareaColor');
textarea.value = result.value;
}
var blockMetaList = result.meta;
var buttonContainer = document.createElement('div');
buttonContainer.style.cssText = 'position:absolute;bottom:0;left:0;right:0;';
var buttonStyle = 'float:right;margin-right:20px;border:none;'
+ 'cursor:pointer;padding:2px 5px;font-size:12px;border-radius:3px';
var closeButton = document.createElement('div');
var refreshButton = document.createElement('div');
buttonStyle += ';background-color:' + model.get('buttonColor');
buttonStyle += ';color:' + model.get('buttonTextColor');
var self = this;
function close() {
container.removeChild(root);
self._dom = null;
}
eventTool.addEventListener(closeButton, 'click', close);
eventTool.addEventListener(refreshButton, 'click', function () {
var newOption;
try {
if (typeof contentToOption === 'function') {
newOption = contentToOption(viewMain, api.getOption());
}
else {
newOption = parseContents(textarea.value, blockMetaList);
}
}
catch (e) {
close();
throw new Error('Data view format error ' + e);
}
if (newOption) {
api.dispatchAction({
type: 'changeDataView',
newOption: newOption
});
}
close();
});
closeButton.innerHTML = lang[1];
refreshButton.innerHTML = lang[2];
refreshButton.style.cssText = buttonStyle;
closeButton.style.cssText = buttonStyle;
!model.get('readOnly') && buttonContainer.appendChild(refreshButton);
buttonContainer.appendChild(closeButton);
// http://stackoverflow.com/questions/6637341/use-tab-to-indent-in-textarea
eventTool.addEventListener(textarea, 'keydown', function (e) {
if ((e.keyCode || e.which) === 9) {
// get caret position/selection
var val = this.value;
var start = this.selectionStart;
var end = this.selectionEnd;
// set textarea value to: text before caret + tab + text after caret
this.value = val.substring(0, start) + ITEM_SPLITER + val.substring(end);
// put caret at right position again
this.selectionStart = this.selectionEnd = start + 1;
// prevent the focus lose
eventTool.stop(e);
}
});
root.appendChild(header);
root.appendChild(viewMain);
root.appendChild(buttonContainer);
viewMain.style.height = (container.clientHeight - 80) + 'px';
container.appendChild(root);
this._dom = root;
};
DataView.prototype.remove = function (ecModel, api) {
this._dom && api.getDom().removeChild(this._dom);
};
DataView.prototype.dispose = function (ecModel, api) {
this.remove(ecModel, api);
};
/**
* @inner
*/
function tryMergeDataOption(newData, originalData) {
return zrUtil.map(newData, function (newVal, idx) {
var original = originalData && originalData[idx];
if (zrUtil.isObject(original) && !zrUtil.isArray(original)) {
if (zrUtil.isObject(newVal) && !zrUtil.isArray(newVal)) {
newVal = newVal.value;
}
// Original data has option
return zrUtil.defaults({
value: newVal
}, original);
}
else {
return newVal;
}
});
}
require('../featureManager').register('dataView', DataView);
require('../../../echarts').registerAction({
type: 'changeDataView',
event: 'dataViewChanged',
update: 'prepareAndUpdate'
}, function (payload, ecModel) {
var newSeriesOptList = [];
zrUtil.each(payload.newOption.series, function (seriesOpt) {
var seriesModel = ecModel.getSeriesByName(seriesOpt.name)[0];
if (!seriesModel) {
// New created series
// Geuss the series type
newSeriesOptList.push(zrUtil.extend({
// Default is scatter
type: 'scatter'
}, seriesOpt));
}
else {
var originalData = seriesModel.get('data');
newSeriesOptList.push({
name: seriesOpt.name,
data: tryMergeDataOption(seriesOpt.data, originalData)
});
}
});
ecModel.mergeOption(zrUtil.defaults({
series: newSeriesOptList
}, payload.newOption));
});
return DataView;
});

View File

@@ -0,0 +1,343 @@
define(function(require) {
'use strict';
var zrUtil = require('zrender/core/util');
var numberUtil = require('../../../util/number');
var SelectController = require('../../helper/SelectController');
var BoundingRect = require('zrender/core/BoundingRect');
var Group = require('zrender/container/Group');
var history = require('../../dataZoom/history');
var interactionMutex = require('../../helper/interactionMutex');
var each = zrUtil.each;
var asc = numberUtil.asc;
// Use dataZoomSelect
require('../../dataZoomSelect');
// Spectial component id start with \0ec\0, see echarts/model/Global.js~hasInnerId
var DATA_ZOOM_ID_BASE = '\0_ec_\0toolbox-dataZoom_';
function DataZoom(model) {
this.model = model;
/**
* @private
* @type {module:zrender/container/Group}
*/
this._controllerGroup;
/**
* @private
* @type {module:echarts/component/helper/SelectController}
*/
this._controller;
/**
* Is zoom active.
* @private
* @type {Object}
*/
this._isZoomActive;
}
DataZoom.defaultOption = {
show: true,
// Icon group
icon: {
zoom: 'M0,13.5h26.9 M13.5,26.9V0 M32.1,13.5H58V58H13.5 V32.1',
back: 'M22,1.4L9.9,13.5l12.3,12.3 M10.3,13.5H54.9v44.6 H10.3v-26'
},
title: {
zoom: '区域缩放',
back: '区域缩放还原'
}
};
var proto = DataZoom.prototype;
proto.render = function (featureModel, ecModel, api) {
updateBackBtnStatus(featureModel, ecModel);
};
proto.onclick = function (ecModel, api, type) {
var controllerGroup = this._controllerGroup;
if (!this._controllerGroup) {
controllerGroup = this._controllerGroup = new Group();
api.getZr().add(controllerGroup);
}
handlers[type].call(this, controllerGroup, this.model, ecModel, api);
};
proto.remove = function (ecModel, api) {
this._disposeController();
interactionMutex.release('globalPan', api.getZr());
};
proto.dispose = function (ecModel, api) {
var zr = api.getZr();
interactionMutex.release('globalPan', zr);
this._disposeController();
this._controllerGroup && zr.remove(this._controllerGroup);
};
/**
* @private
*/
var handlers = {
zoom: function (controllerGroup, featureModel, ecModel, api) {
var isZoomActive = this._isZoomActive = !this._isZoomActive;
var zr = api.getZr();
interactionMutex[isZoomActive ? 'take' : 'release']('globalPan', zr);
featureModel.setIconStatus('zoom', isZoomActive ? 'emphasis' : 'normal');
if (isZoomActive) {
zr.setDefaultCursorStyle('crosshair');
this._createController(
controllerGroup, featureModel, ecModel, api
);
}
else {
zr.setDefaultCursorStyle('default');
this._disposeController();
}
},
back: function (controllerGroup, featureModel, ecModel, api) {
this._dispatchAction(history.pop(ecModel), api);
}
};
/**
* @private
*/
proto._createController = function (
controllerGroup, featureModel, ecModel, api
) {
var controller = this._controller = new SelectController(
'rect',
api.getZr(),
{
// FIXME
lineWidth: 3,
stroke: '#333',
fill: 'rgba(0,0,0,0.2)'
}
);
controller.on(
'selectEnd',
zrUtil.bind(
this._onSelected, this, controller,
featureModel, ecModel, api
)
);
controller.enable(controllerGroup, false);
};
proto._disposeController = function () {
var controller = this._controller;
if (controller) {
controller.off('selected');
controller.dispose();
}
};
function prepareCoordInfo(grid, ecModel) {
// Default use the first axis.
// FIXME
var coordInfo = [
{axisModel: grid.getAxis('x').model, axisIndex: 0}, // x
{axisModel: grid.getAxis('y').model, axisIndex: 0} // y
];
coordInfo.grid = grid;
ecModel.eachComponent(
{mainType: 'dataZoom', subType: 'select'},
function (dzModel, dataZoomIndex) {
if (isTheAxis('xAxis', coordInfo[0].axisModel, dzModel, ecModel)) {
coordInfo[0].dataZoomModel = dzModel;
}
if (isTheAxis('yAxis', coordInfo[1].axisModel, dzModel, ecModel)) {
coordInfo[1].dataZoomModel = dzModel;
}
}
);
return coordInfo;
}
function isTheAxis(axisName, axisModel, dataZoomModel, ecModel) {
var axisIndex = dataZoomModel.get(axisName + 'Index');
return axisIndex != null
&& ecModel.getComponent(axisName, axisIndex) === axisModel;
}
/**
* @private
*/
proto._onSelected = function (controller, featureModel, ecModel, api, selRanges) {
if (!selRanges.length) {
return;
}
var selRange = selRanges[0];
controller.update(); // remove cover
var snapshot = {};
// FIXME
// polar
ecModel.eachComponent('grid', function (gridModel, gridIndex) {
var grid = gridModel.coordinateSystem;
var coordInfo = prepareCoordInfo(grid, ecModel);
var selDataRange = pointToDataInCartesian(selRange, coordInfo);
if (selDataRange) {
var xBatchItem = scaleCartesianAxis(selDataRange, coordInfo, 0, 'x');
var yBatchItem = scaleCartesianAxis(selDataRange, coordInfo, 1, 'y');
xBatchItem && (snapshot[xBatchItem.dataZoomId] = xBatchItem);
yBatchItem && (snapshot[yBatchItem.dataZoomId] = yBatchItem);
}
}, this);
history.push(ecModel, snapshot);
this._dispatchAction(snapshot, api);
};
function pointToDataInCartesian(selRange, coordInfo) {
var grid = coordInfo.grid;
var selRect = new BoundingRect(
selRange[0][0],
selRange[1][0],
selRange[0][1] - selRange[0][0],
selRange[1][1] - selRange[1][0]
);
if (!selRect.intersect(grid.getRect())) {
return;
}
var cartesian = grid.getCartesian(coordInfo[0].axisIndex, coordInfo[1].axisIndex);
var dataLeftTop = cartesian.pointToData([selRange[0][0], selRange[1][0]], true);
var dataRightBottom = cartesian.pointToData([selRange[0][1], selRange[1][1]], true);
return [
asc([dataLeftTop[0], dataRightBottom[0]]), // x, using asc to handle inverse
asc([dataLeftTop[1], dataRightBottom[1]]) // y, using asc to handle inverse
];
}
function scaleCartesianAxis(selDataRange, coordInfo, dimIdx, dimName) {
var dimCoordInfo = coordInfo[dimIdx];
var dataZoomModel = dimCoordInfo.dataZoomModel;
if (dataZoomModel) {
return {
dataZoomId: dataZoomModel.id,
startValue: selDataRange[dimIdx][0],
endValue: selDataRange[dimIdx][1]
};
}
}
/**
* @private
*/
proto._dispatchAction = function (snapshot, api) {
var batch = [];
each(snapshot, function (batchItem) {
batch.push(batchItem);
});
batch.length && api.dispatchAction({
type: 'dataZoom',
from: this.uid,
batch: zrUtil.clone(batch, true)
});
};
function updateBackBtnStatus(featureModel, ecModel) {
featureModel.setIconStatus(
'back',
history.count(ecModel) > 1 ? 'emphasis' : 'normal'
);
}
require('../featureManager').register('dataZoom', DataZoom);
// Create special dataZoom option for select
require('../../../echarts').registerPreprocessor(function (option) {
if (!option) {
return;
}
var dataZoomOpts = option.dataZoom || (option.dataZoom = []);
if (!zrUtil.isArray(dataZoomOpts)) {
option.dataZoom = dataZoomOpts = [dataZoomOpts];
}
var toolboxOpt = option.toolbox;
if (toolboxOpt) {
// Assume there is only one toolbox
if (zrUtil.isArray(toolboxOpt)) {
toolboxOpt = toolboxOpt[0];
}
if (toolboxOpt && toolboxOpt.feature) {
var dataZoomOpt = toolboxOpt.feature.dataZoom;
addForAxis('xAxis', dataZoomOpt);
addForAxis('yAxis', dataZoomOpt);
}
}
function addForAxis(axisName, dataZoomOpt) {
if (!dataZoomOpt) {
return;
}
var axisIndicesName = axisName + 'Index';
var givenAxisIndices = dataZoomOpt[axisIndicesName];
if (givenAxisIndices != null && !zrUtil.isArray(givenAxisIndices)) {
givenAxisIndices = givenAxisIndices === false ? [] : [givenAxisIndices];
}
forEachComponent(axisName, function (axisOpt, axisIndex) {
if (givenAxisIndices != null
&& zrUtil.indexOf(givenAxisIndices, axisIndex) === -1
) {
return;
}
var newOpt = {
type: 'select',
$fromToolbox: true,
// Id for merge mapping.
id: DATA_ZOOM_ID_BASE + axisName + axisIndex
};
// FIXME
// Only support one axis now.
newOpt[axisIndicesName] = axisIndex;
dataZoomOpts.push(newOpt);
});
}
function forEachComponent(mainType, cb) {
var opts = option[mainType];
if (!zrUtil.isArray(opts)) {
opts = opts ? [opts] : [];
}
each(opts, cb);
}
});
return DataZoom;
});

View File

@@ -0,0 +1,169 @@
define(function(require) {
'use strict';
var zrUtil = require('zrender/core/util');
function MagicType(model) {
this.model = model;
}
MagicType.defaultOption = {
show: true,
type: [],
// Icon group
icon: {
line: 'M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4',
bar: 'M6.7,22.9h10V48h-10V22.9zM24.9,13h10v35h-10V13zM43.2,2h10v46h-10V2zM3.1,58h53.7',
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', // jshint ignore:line
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: '切换为平铺'
},
option: {},
seriesIndex: {}
};
var proto = MagicType.prototype;
proto.getIcons = function () {
var model = this.model;
var availableIcons = model.get('icon');
var icons = {};
zrUtil.each(model.get('type'), function (type) {
if (availableIcons[type]) {
icons[type] = availableIcons[type];
}
});
return icons;
};
var seriesOptGenreator = {
'line': function (seriesType, seriesId, seriesModel, model) {
if (seriesType === 'bar') {
return zrUtil.merge({
id: seriesId,
type: 'line',
// Preserve data related option
data: seriesModel.get('data'),
stack: seriesModel.get('stack'),
markPoint: seriesModel.get('markPoint'),
markLine: seriesModel.get('markLine')
}, model.get('option.line') || {}, true);
}
},
'bar': function (seriesType, seriesId, seriesModel, model) {
if (seriesType === 'line') {
return zrUtil.merge({
id: seriesId,
type: 'bar',
// Preserve data related option
data: seriesModel.get('data'),
stack: seriesModel.get('stack'),
markPoint: seriesModel.get('markPoint'),
markLine: seriesModel.get('markLine')
}, model.get('option.bar') || {}, true);
}
},
'stack': function (seriesType, seriesId, seriesModel, model) {
if (seriesType === 'line' || seriesType === 'bar') {
return zrUtil.merge({
id: seriesId,
stack: '__ec_magicType_stack__'
}, model.get('option.stack') || {}, true);
}
},
'tiled': function (seriesType, seriesId, seriesModel, model) {
if (seriesType === 'line' || seriesType === 'bar') {
return zrUtil.merge({
id: seriesId,
stack: ''
}, model.get('option.tiled') || {}, true);
}
}
};
var radioTypes = [
['line', 'bar'],
['stack', 'tiled']
];
proto.onclick = function (ecModel, api, type) {
var model = this.model;
var seriesIndex = model.get('seriesIndex.' + type);
// Not supported magicType
if (!seriesOptGenreator[type]) {
return;
}
var newOption = {
series: []
};
var generateNewSeriesTypes = function (seriesModel) {
var seriesType = seriesModel.subType;
var seriesId = seriesModel.id;
var newSeriesOpt = seriesOptGenreator[type](
seriesType, seriesId, seriesModel, model
);
if (newSeriesOpt) {
// PENDING If merge original option?
zrUtil.defaults(newSeriesOpt, seriesModel.option);
newOption.series.push(newSeriesOpt);
}
// Modify boundaryGap
var coordSys = seriesModel.coordinateSystem;
if (coordSys && coordSys.type === 'cartesian2d' && (type === 'line' || type === 'bar')) {
var categoryAxis = coordSys.getAxesByScale('ordinal')[0];
if (categoryAxis) {
var axisDim = categoryAxis.dim;
var axisIndex = seriesModel.get(axisDim + 'AxisIndex');
var axisKey = axisDim + 'Axis';
newOption[axisKey] = newOption[axisKey] || [];
for (var i = 0; i <= axisIndex; i++) {
newOption[axisKey][axisIndex] = newOption[axisKey][axisIndex] || {};
}
newOption[axisKey][axisIndex].boundaryGap = type === 'bar' ? true : false;
}
}
};
zrUtil.each(radioTypes, function (radio) {
if (zrUtil.indexOf(radio, type) >= 0) {
zrUtil.each(radio, function (item) {
model.setIconStatus(item, 'normal');
});
}
});
model.setIconStatus(type, 'emphasis');
ecModel.eachComponent(
{
mainType: 'series',
query: seriesIndex == null ? null : {
seriesIndex: seriesIndex
}
}, generateNewSeriesTypes
);
api.dispatchAction({
type: 'changeMagicType',
currentType: type,
newOption: newOption
});
};
var echarts = require('../../../echarts');
echarts.registerAction({
type: 'changeMagicType',
event: 'magicTypeChanged',
update: 'prepareAndUpdate'
}, function (payload, ecModel) {
ecModel.mergeOption(payload.newOption);
});
require('../featureManager').register('magicType', MagicType);
return MagicType;
});

View File

@@ -0,0 +1,39 @@
define(function(require) {
'use strict';
var history = require('../../dataZoom/history');
function Restore(model) {
this.model = model;
}
Restore.defaultOption = {
show: true,
icon: 'M3.8,33.4 M47,18.9h9.8V8.7 M56.3,20.1 C52.1,9,40.5,0.6,26.8,2.1C12.6,3.7,1.6,16.2,2.1,30.6 M13,41.1H3.1v10.2 M3.7,39.9c4.2,11.1,15.8,19.5,29.5,18 c14.2-1.6,25.2-14.1,24.7-28.5',
title: '还原'
};
var proto = Restore.prototype;
proto.onclick = function (ecModel, api, type) {
history.clear(ecModel);
api.dispatchAction({
type: 'restore',
from: this.uid
});
};
require('../featureManager').register('restore', Restore);
require('../../../echarts').registerAction(
{type: 'restore', event: 'restore', update: 'prepareAndUpdate'},
function (payload, ecModel) {
ecModel.resetOption('recreate');
}
);
return Restore;
});

View File

@@ -0,0 +1,67 @@
define(function (require) {
var env = require('zrender/core/env');
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.6M29.2,45.1L29.2,0',
title: '保存为图片',
type: 'png',
// Default use option.backgroundColor
// backgroundColor: '#fff',
name: '',
excludeComponents: ['toolbox'],
pixelRatio: 1,
lang: ['右键另存为图片']
};
SaveAsImage.prototype.unusable = !env.canvasSupported;
var proto = SaveAsImage.prototype;
proto.onclick = function (ecModel, api) {
var model = this.model;
var title = model.get('name') || ecModel.get('title.0.text') || 'echarts';
var $a = document.createElement('a');
var type = model.get('type', true) || 'png';
$a.download = title + '.' + type;
$a.target = '_blank';
var url = api.getConnectedDataURL({
type: type,
backgroundColor: model.get('backgroundColor', true)
|| ecModel.get('backgroundColor') || '#fff',
excludeComponents: model.get('excludeComponents'),
pixelRatio: model.get('pixelRatio')
});
$a.href = url;
// Chrome and Firefox
if (typeof MouseEvent === 'function') {
var evt = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: false
});
$a.dispatchEvent(evt);
}
// IE
else {
var lang = model.get('lang');
var html = ''
+ '<body style="margin:0;">'
+ '<img src="' + url + '" style="max-width:100%;" title="' + ((lang && lang[0]) || '') + '" />'
+ '</body>';
var tab = window.open();
tab.document.write(html);
}
};
require('../featureManager').register(
'saveAsImage', SaveAsImage
);
return SaveAsImage;
});

View File

@@ -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];
}
};
});

View File

@@ -0,0 +1,36 @@
// FIXME Better way to pack data in graphic element
define(function (require) {
require('./tooltip/TooltipModel');
require('./tooltip/TooltipView');
// Show tip action
/**
* @action
* @property {string} type
* @property {number} seriesIndex
* @property {number} dataIndex
* @property {number} [x]
* @property {number} [y]
*/
require('../echarts').registerAction(
{
type: 'showTip',
event: 'showTip',
update: 'none'
},
// noop
function () {}
);
// Hide tip action
require('../echarts').registerAction(
{
type: 'hideTip',
event: 'hideTip',
update: 'none'
},
// noop
function () {}
);
});

View File

@@ -0,0 +1,265 @@
/**
* @module echarts/component/tooltip/TooltipContent
*/
define(function (require) {
var zrUtil = require('zrender/core/util');
var zrColor = require('zrender/tool/color');
var eventUtil = require('zrender/core/event');
var formatUtil = require('../../util/format');
var each = zrUtil.each;
var toCamelCase = formatUtil.toCamelCase;
var env = require('zrender/core/env');
var vendors = ['', '-webkit-', '-moz-', '-o-'];
var gCssText = 'position:absolute;display:block;border-style:solid;white-space:nowrap;z-index:9999999;';
/**
* @param {number} duration
* @return {string}
* @inner
*/
function assembleTransition(duration) {
var transitionCurve = 'cubic-bezier(0.23, 1, 0.32, 1)';
var transitionText = 'left ' + duration + 's ' + transitionCurve + ','
+ 'top ' + duration + 's ' + transitionCurve;
return zrUtil.map(vendors, function (vendorPrefix) {
return vendorPrefix + 'transition:' + transitionText;
}).join(';');
}
/**
* @param {Object} textStyle
* @return {string}
* @inner
*/
function assembleFont(textStyleModel) {
var cssText = [];
var fontSize = textStyleModel.get('fontSize');
var color = textStyleModel.getTextColor();
color && cssText.push('color:' + color);
cssText.push('font:' + textStyleModel.getFont());
fontSize &&
cssText.push('line-height:' + Math.round(fontSize * 3 / 2) + 'px');
each(['decoration', 'align'], function (name) {
var val = textStyleModel.get(name);
val && cssText.push('text-' + name + ':' + val);
});
return cssText.join(';');
}
/**
* @param {Object} tooltipModel
* @return {string}
* @inner
*/
function assembleCssText(tooltipModel) {
tooltipModel = tooltipModel;
var cssText = [];
var transitionDuration = tooltipModel.get('transitionDuration');
var backgroundColor = tooltipModel.get('backgroundColor');
var textStyleModel = tooltipModel.getModel('textStyle');
var padding = tooltipModel.get('padding');
// Animation transition
transitionDuration &&
cssText.push(assembleTransition(transitionDuration));
if (backgroundColor) {
if (env.canvasSupported) {
cssText.push('background-Color:' + backgroundColor);
}
else {
// for ie
cssText.push(
'background-Color:#' + zrColor.toHex(backgroundColor)
);
cssText.push('filter:alpha(opacity=70)');
}
}
// Border style
each(['width', 'color', 'radius'], function (name) {
var borderName = 'border-' + name;
var camelCase = toCamelCase(borderName);
var val = tooltipModel.get(camelCase);
val != null &&
cssText.push(borderName + ':' + val + (name === 'color' ? '' : 'px'));
});
// Text style
cssText.push(assembleFont(textStyleModel));
// Padding
if (padding != null) {
cssText.push('padding:' + formatUtil.normalizeCssArray(padding).join('px ') + 'px');
}
return cssText.join(';') + ';';
}
/**
* @alias module:echarts/component/tooltip/TooltipContent
* @constructor
*/
function TooltipContent(container, api) {
var el = document.createElement('div');
var zr = api.getZr();
this.el = el;
this._x = api.getWidth() / 2;
this._y = api.getHeight() / 2;
container.appendChild(el);
this._container = container;
this._show = false;
/**
* @private
*/
this._hideTimeout;
var self = this;
el.onmouseenter = function () {
// clear the timeout in hideLater and keep showing tooltip
if (self.enterable) {
clearTimeout(self._hideTimeout);
self._show = true;
}
self._inContent = true;
};
el.onmousemove = function (e) {
if (!self.enterable) {
// Try trigger zrender event to avoid mouse
// in and out shape too frequently
var handler = zr.handler;
eventUtil.normalizeEvent(container, e);
handler.dispatch('mousemove', e);
}
};
el.onmouseleave = function () {
if (self.enterable) {
if (self._show) {
self.hideLater(self._hideDelay);
}
}
self._inContent = false;
};
compromiseMobile(el, container);
}
function compromiseMobile(tooltipContentEl, container) {
// Prevent default behavior on mobile. For example,
// default pinch gesture will cause browser zoom.
// We do not preventing event on tooltip contnet el,
// because user may need customization in tooltip el.
eventUtil.addEventListener(container, 'touchstart', preventDefault);
eventUtil.addEventListener(container, 'touchmove', preventDefault);
eventUtil.addEventListener(container, 'touchend', preventDefault);
function preventDefault(e) {
if (contains(e.target)) {
e.preventDefault();
}
}
function contains(targetEl) {
while (targetEl && targetEl !== container) {
if (targetEl === tooltipContentEl) {
return true;
}
targetEl = targetEl.parentNode;
}
}
}
TooltipContent.prototype = {
constructor: TooltipContent,
enterable: true,
/**
* Update when tooltip is rendered
*/
update: function () {
var container = this._container;
var stl = container.currentStyle
|| document.defaultView.getComputedStyle(container);
var domStyle = container.style;
if (domStyle.position !== 'absolute' && stl.position !== 'absolute') {
domStyle.position = 'relative';
}
// Hide the tooltip
// PENDING
// this.hide();
},
show: function (tooltipModel) {
clearTimeout(this._hideTimeout);
this.el.style.cssText = gCssText + assembleCssText(tooltipModel)
// http://stackoverflow.com/questions/21125587/css3-transition-not-working-in-chrome-anymore
+ ';left:' + this._x + 'px;top:' + this._y + 'px;'
+ (tooltipModel.get('extraCssText') || '');
this._show = true;
},
setContent: function (content) {
var el = this.el;
el.innerHTML = content;
el.style.display = content ? 'block' : 'none';
},
moveTo: function (x, y) {
var style = this.el.style;
style.left = x + 'px';
style.top = y + 'px';
this._x = x;
this._y = y;
},
hide: function () {
this.el.style.display = 'none';
this._show = false;
},
// showLater: function ()
hideLater: function (time) {
if (this._show && !(this._inContent && this.enterable)) {
if (time) {
this._hideDelay = time;
// Set show false to avoid invoke hideLater mutiple times
this._show = false;
this._hideTimeout = setTimeout(zrUtil.bind(this.hide, this), time);
}
else {
this.hide();
}
}
},
isShow: function () {
return this._show;
}
};
return TooltipContent;
});

View File

@@ -0,0 +1,104 @@
define(function (require) {
require('../../echarts').extendComponentModel({
type: 'tooltip',
defaultOption: {
zlevel: 0,
z: 8,
show: true,
// tooltip主体内容
showContent: true,
// 触发类型,默认数据触发,见下图,可选为:'item' ¦ 'axis'
trigger: 'item',
// 触发条件,支持 'click' | 'mousemove'
triggerOn: 'mousemove',
// 是否永远显示 content
alwaysShowContent: false,
// 位置 {Array} | {Function}
// position: null
// 内容格式器:{string}Template ¦ {Function}
// formatter: null
showDelay: 0,
// 隐藏延迟单位ms
hideDelay: 100,
// 动画变换时间单位s
transitionDuration: 0.4,
enterable: false,
// 提示背景颜色默认为透明度为0.7的黑色
backgroundColor: 'rgba(50,50,50,0.7)',
// 提示边框颜色
borderColor: '#333',
// 提示边框圆角单位px默认为4
borderRadius: 4,
// 提示边框线宽单位px默认为0无边框
borderWidth: 0,
// 提示内边距单位px默认各方向内边距为5
// 接受数组分别设定上右下左边距同css
padding: 5,
// Extra css text
extraCssText: '',
// 坐标轴指示器,坐标轴触发有效
axisPointer: {
// 默认为直线
// 可选为:'line' | 'shadow' | 'cross'
type: 'line',
// type 为 line 的时候有效,指定 tooltip line 所在的轴,可选
// 可选 'x' | 'y' | 'angle' | 'radius' | 'auto'
// 默认 'auto',会选择类型为 cateogry 的轴,对于双数值轴,笛卡尔坐标系会默认选择 x 轴
// 极坐标系会默认选择 angle 轴
axis: 'auto',
animation: true,
animationDurationUpdate: 200,
animationEasingUpdate: 'exponentialOut',
// 直线指示器样式设置
lineStyle: {
color: '#555',
width: 1,
type: 'solid'
},
crossStyle: {
color: '#555',
width: 1,
type: 'dashed',
// TODO formatter
textStyle: {}
},
// 阴影指示器样式设置
shadowStyle: {
color: 'rgba(150,150,150,0.3)'
}
},
textStyle: {
color: '#fff',
fontSize: 14
}
}
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
/**
* visualMap component entry
*/
define(function (require) {
require('./visualMapContinuous');
require('./visualMapPiecewise');
});

View File

@@ -0,0 +1,171 @@
/**
* @file Data zoom model
*/
define(function(require) {
var VisualMapModel = require('./VisualMapModel');
var zrUtil = require('zrender/core/util');
var numberUtil = require('../../util/number');
// Constant
var DEFAULT_BAR_BOUND = [20, 140];
var ContinuousModel = VisualMapModel.extend({
type: 'visualMap.continuous',
/**
* @protected
*/
defaultOption: {
align: 'auto', // 'auto', 'left', 'right', 'top', 'bottom'
calculable: false, // This prop effect default component type determine,
// See echarts/component/visualMap/typeDefaulter.
range: null, // selected range. In default case `range` is [min, max]
// and can auto change along with modification of min max,
// util use specifid a range.
realtime: true, // Whether realtime update.
itemHeight: null, // The length of the range control edge.
itemWidth: null, // The length of the other side.
hoverLink: true // Enable hover highlight.
},
/**
* @override
*/
doMergeOption: function (newOption, isInit) {
ContinuousModel.superApply(this, 'doMergeOption', arguments);
this.resetTargetSeries(newOption, isInit);
this.resetExtent();
this.resetVisual(function (mappingOption) {
mappingOption.mappingMethod = 'linear';
});
this._resetRange();
},
/**
* @protected
* @override
*/
resetItemSize: function () {
VisualMapModel.prototype.resetItemSize.apply(this, arguments);
var itemSize = this.itemSize;
this._orient === 'horizontal' && itemSize.reverse();
(itemSize[0] == null || isNaN(itemSize[0])) && (itemSize[0] = DEFAULT_BAR_BOUND[0]);
(itemSize[1] == null || isNaN(itemSize[1])) && (itemSize[1] = DEFAULT_BAR_BOUND[1]);
},
/**
* @private
*/
_resetRange: function () {
var dataExtent = this.getExtent();
var range = this.option.range;
if (!range || range.auto) {
// `range` should always be array (so we dont use other
// value like 'auto') for user-friend. (consider getOption).
dataExtent.auto = 1;
this.option.range = dataExtent;
}
else if (zrUtil.isArray(range)) {
if (range[0] > range[1]) {
range.reverse();
}
range[0] = Math.max(range[0], dataExtent[0]);
range[1] = Math.min(range[1], dataExtent[1]);
}
},
/**
* @protected
* @override
*/
completeVisualOption: function () {
VisualMapModel.prototype.completeVisualOption.apply(this, arguments);
zrUtil.each(this.stateList, function (state) {
var symbolSize = this.option.controller[state].symbolSize;
if (symbolSize && symbolSize[0] !== symbolSize[1]) {
symbolSize[0] = 0; // For good looking.
}
}, this);
},
/**
* @public
* @override
*/
setSelected: function (selected) {
this.option.range = selected.slice();
this._resetRange();
},
/**
* @public
*/
getSelected: function () {
var dataExtent = this.getExtent();
var dataInterval = numberUtil.asc(
(this.get('range') || []).slice()
);
// Clamp
dataInterval[0] > dataExtent[1] && (dataInterval[0] = dataExtent[1]);
dataInterval[1] > dataExtent[1] && (dataInterval[1] = dataExtent[1]);
dataInterval[0] < dataExtent[0] && (dataInterval[0] = dataExtent[0]);
dataInterval[1] < dataExtent[0] && (dataInterval[1] = dataExtent[0]);
return dataInterval;
},
/**
* @public
* @override
*/
getValueState: function (value) {
var range = this.option.range;
var dataExtent = this.getExtent();
// When range[0] === dataExtent[0], any value larger than dataExtent[0] maps to 'inRange'.
// range[1] is processed likewise.
return (
(range[0] <= dataExtent[0] || range[0] <= value)
&& (range[1] >= dataExtent[1] || value <= range[1])
) ? 'inRange' : 'outOfRange';
},
/**
* @public
* @params {Array.<number>} range target value: range[0] <= value && value <= range[1]
* @return {Array.<Object>} [{seriesId, dataIndices: <Array.<number>>}, ...]
*/
findTargetDataIndices: function (range) {
var result = [];
this.eachTargetSeries(function (seriesModel) {
var dataIndices = [];
var data = seriesModel.getData();
data.each(this.getDataDimension(data), function (value, dataIndex) {
range[0] <= value && value <= range[1] && dataIndices.push(dataIndex);
}, true, this);
result.push({seriesId: seriesModel.id, dataIndices: dataIndices});
}, this);
return result;
}
});
return ContinuousModel;
});

View File

@@ -0,0 +1,718 @@
define(function(require) {
var VisualMapView = require('./VisualMapView');
var graphic = require('../../util/graphic');
var zrUtil = require('zrender/core/util');
var numberUtil = require('../../util/number');
var sliderMove = require('../helper/sliderMove');
var LinearGradient = require('zrender/graphic/LinearGradient');
var helper = require('./helper');
var linearMap = numberUtil.linearMap;
var convertDataIndicesToBatch = helper.convertDataIndicesToBatch;
var each = zrUtil.each;
var mathMin = Math.min;
var mathMax = Math.max;
// Arbitrary value
var HOVER_LINK_RANGE = 6;
var HOVER_LINK_OUT = 6;
// Notice:
// Any "interval" should be by the order of [low, high].
// "handle0" (handleIndex === 0) maps to
// low data value: this._dataInterval[0] and has low coord.
// "handle1" (handleIndex === 1) maps to
// high data value: this._dataInterval[1] and has high coord.
// The logic of transform is implemented in this._createBarGroup.
var ContinuousVisualMapView = VisualMapView.extend({
type: 'visualMap.continuous',
/**
* @override
*/
init: function () {
VisualMapView.prototype.init.apply(this, arguments);
/**
* @private
*/
this._shapes = {};
/**
* @private
*/
this._dataInterval = [];
/**
* @private
*/
this._handleEnds = [];
/**
* @private
*/
this._orient;
/**
* @private
*/
this._useHandle;
/**
* @private
*/
this._hoverLinkDataIndices = [];
},
/**
* @protected
* @override
*/
doRender: function (visualMapModel, ecModel, api, payload) {
if (!payload || payload.type !== 'selectDataRange' || payload.from !== this.uid) {
this._buildView();
}
else {
this._updateView();
}
},
/**
* @private
*/
_buildView: function () {
this.group.removeAll();
var visualMapModel = this.visualMapModel;
var thisGroup = this.group;
this._orient = visualMapModel.get('orient');
this._useHandle = visualMapModel.get('calculable');
this._resetInterval();
this._renderBar(thisGroup);
var dataRangeText = visualMapModel.get('text');
this._renderEndsText(thisGroup, dataRangeText, 0);
this._renderEndsText(thisGroup, dataRangeText, 1);
// Do this for background size calculation.
this._updateView(true);
// After updating view, inner shapes is built completely,
// and then background can be rendered.
this.renderBackground(thisGroup);
// Real update view
this._updateView();
this._enableHoverLinkToSeries();
this._enableHoverLinkFromSeries();
this.positionGroup(thisGroup);
},
/**
* @private
*/
_renderEndsText: function (group, dataRangeText, endsIndex) {
if (!dataRangeText) {
return;
}
// Compatible with ec2, text[0] map to high value, text[1] map low value.
var text = dataRangeText[1 - endsIndex];
text = text != null ? text + '' : '';
var visualMapModel = this.visualMapModel;
var textGap = visualMapModel.get('textGap');
var itemSize = visualMapModel.itemSize;
var barGroup = this._shapes.barGroup;
var position = this._applyTransform(
[
itemSize[0] / 2,
endsIndex === 0 ? -textGap : itemSize[1] + textGap
],
barGroup
);
var align = this._applyTransform(
endsIndex === 0 ? 'bottom' : 'top',
barGroup
);
var orient = this._orient;
var textStyleModel = this.visualMapModel.textStyleModel;
this.group.add(new graphic.Text({
style: {
x: position[0],
y: position[1],
textVerticalAlign: orient === 'horizontal' ? 'middle' : align,
textAlign: orient === 'horizontal' ? align : 'center',
text: text,
textFont: textStyleModel.getFont(),
fill: textStyleModel.getTextColor()
}
}));
},
/**
* @private
*/
_renderBar: function (targetGroup) {
var visualMapModel = this.visualMapModel;
var shapes = this._shapes;
var itemSize = visualMapModel.itemSize;
var orient = this._orient;
var useHandle = this._useHandle;
var itemAlign = helper.getItemAlign(visualMapModel, this.api, itemSize);
var barGroup = shapes.barGroup = this._createBarGroup(itemAlign);
// Bar
barGroup.add(shapes.outOfRange = createPolygon());
barGroup.add(shapes.inRange = createPolygon(
null,
zrUtil.bind(this._modifyHandle, this, 'all'),
useHandle ? 'move' : null
));
var textRect = visualMapModel.textStyleModel.getTextRect('国');
var textSize = Math.max(textRect.width, textRect.height);
// Handle
if (useHandle) {
shapes.handleThumbs = [];
shapes.handleLabels = [];
shapes.handleLabelPoints = [];
this._createHandle(barGroup, 0, itemSize, textSize, orient, itemAlign);
this._createHandle(barGroup, 1, itemSize, textSize, orient, itemAlign);
}
this._createIndicator(barGroup, itemSize, textSize, orient);
targetGroup.add(barGroup);
},
/**
* @private
*/
_createHandle: function (barGroup, handleIndex, itemSize, textSize, orient) {
var modifyHandle = zrUtil.bind(this._modifyHandle, this, handleIndex);
var handleThumb = createPolygon(
createHandlePoints(handleIndex, textSize),
modifyHandle,
'move'
);
handleThumb.position[0] = itemSize[0];
barGroup.add(handleThumb);
// Text is always horizontal layout but should not be effected by
// transform (orient/inverse). So label is built separately but not
// use zrender/graphic/helper/RectText, and is located based on view
// group (according to handleLabelPoint) but not barGroup.
var textStyleModel = this.visualMapModel.textStyleModel;
var handleLabel = new graphic.Text({
draggable: true,
drift: modifyHandle,
style: {
x: 0, y: 0, text: '',
textFont: textStyleModel.getFont(),
fill: textStyleModel.getTextColor()
}
});
this.group.add(handleLabel);
var handleLabelPoint = [
orient === 'horizontal'
? textSize / 2
: textSize * 1.5,
orient === 'horizontal'
? (handleIndex === 0 ? -(textSize * 1.5) : (textSize * 1.5))
: (handleIndex === 0 ? -textSize / 2 : textSize / 2)
];
var shapes = this._shapes;
shapes.handleThumbs[handleIndex] = handleThumb;
shapes.handleLabelPoints[handleIndex] = handleLabelPoint;
shapes.handleLabels[handleIndex] = handleLabel;
},
/**
* @private
*/
_createIndicator: function (barGroup, itemSize, textSize, orient) {
var indicator = createPolygon([[0, 0]], null, 'move');
indicator.position[0] = itemSize[0];
indicator.attr({invisible: true, silent: true});
barGroup.add(indicator);
var textStyleModel = this.visualMapModel.textStyleModel;
var indicatorLabel = new graphic.Text({
silent: true,
invisible: true,
style: {
x: 0, y: 0, text: '',
textFont: textStyleModel.getFont(),
fill: textStyleModel.getTextColor()
}
});
this.group.add(indicatorLabel);
var indicatorLabelPoint = [
orient === 'horizontal' ? textSize / 2 : HOVER_LINK_OUT + 3,
0
];
var shapes = this._shapes;
shapes.indicator = indicator;
shapes.indicatorLabel = indicatorLabel;
shapes.indicatorLabelPoint = indicatorLabelPoint;
},
/**
* @private
*/
_modifyHandle: function (handleIndex, dx, dy) {
if (!this._useHandle) {
return;
}
// Transform dx, dy to bar coordination.
var vertex = this._applyTransform([dx, dy], this._shapes.barGroup, true);
this._updateInterval(handleIndex, vertex[1]);
this.api.dispatchAction({
type: 'selectDataRange',
from: this.uid,
visualMapId: this.visualMapModel.id,
selected: this._dataInterval.slice()
});
},
/**
* @private
*/
_resetInterval: function () {
var visualMapModel = this.visualMapModel;
var dataInterval = this._dataInterval = visualMapModel.getSelected();
var dataExtent = visualMapModel.getExtent();
var sizeExtent = [0, visualMapModel.itemSize[1]];
this._handleEnds = [
linearMap(dataInterval[0], dataExtent, sizeExtent, true),
linearMap(dataInterval[1], dataExtent, sizeExtent, true)
];
},
/**
* @private
* @param {(number|string)} handleIndex 0 or 1 or 'all'
* @param {number} dx
* @param {number} dy
*/
_updateInterval: function (handleIndex, delta) {
delta = delta || 0;
var visualMapModel = this.visualMapModel;
var handleEnds = this._handleEnds;
sliderMove(
delta,
handleEnds,
[0, visualMapModel.itemSize[1]],
handleIndex === 'all' ? 'rigid' : 'push',
handleIndex
);
var dataExtent = visualMapModel.getExtent();
var sizeExtent = [0, visualMapModel.itemSize[1]];
// Update data interval.
this._dataInterval = [
linearMap(handleEnds[0], sizeExtent, dataExtent, true),
linearMap(handleEnds[1], sizeExtent, dataExtent, true)
];
},
/**
* @private
*/
_updateView: function (forSketch) {
var visualMapModel = this.visualMapModel;
var dataExtent = visualMapModel.getExtent();
var shapes = this._shapes;
var outOfRangeHandleEnds = [0, visualMapModel.itemSize[1]];
var inRangeHandleEnds = forSketch ? outOfRangeHandleEnds : this._handleEnds;
var visualInRange = this._createBarVisual(
this._dataInterval, dataExtent, inRangeHandleEnds, 'inRange'
);
var visualOutOfRange = this._createBarVisual(
dataExtent, dataExtent, outOfRangeHandleEnds, 'outOfRange'
);
shapes.inRange
.setStyle({
fill: visualInRange.barColor,
opacity: visualInRange.opacity
})
.setShape('points', visualInRange.barPoints);
shapes.outOfRange
.setStyle({
fill: visualOutOfRange.barColor,
opacity: visualOutOfRange.opacity
})
.setShape('points', visualOutOfRange.barPoints);
this._updateHandle(inRangeHandleEnds, visualInRange);
},
/**
* @private
*/
_createBarVisual: function (dataInterval, dataExtent, handleEnds, forceState) {
var opts = {
forceState: forceState,
convertOpacityToAlpha: true
};
var colorStops = this._makeColorGradient(dataInterval, opts);
var symbolSizes = [
this.getControllerVisual(dataInterval[0], 'symbolSize', opts),
this.getControllerVisual(dataInterval[1], 'symbolSize', opts)
];
var barPoints = this._createBarPoints(handleEnds, symbolSizes);
return {
barColor: new LinearGradient(0, 0, 1, 1, colorStops),
barPoints: barPoints,
handlesColor: [
colorStops[0].color,
colorStops[colorStops.length - 1].color
]
};
},
/**
* @private
*/
_makeColorGradient: function (dataInterval, opts) {
// Considering colorHue, which is not linear, so we have to sample
// to calculate gradient color stops, but not only caculate head
// and tail.
var sampleNumber = 100; // Arbitrary value.
var colorStops = [];
var step = (dataInterval[1] - dataInterval[0]) / sampleNumber;
colorStops.push({
color: this.getControllerVisual(dataInterval[0], 'color', opts),
offset: 0
});
for (var i = 1; i < sampleNumber; i++) {
var currValue = dataInterval[0] + step * i;
if (currValue > dataInterval[1]) {
break;
}
colorStops.push({
color: this.getControllerVisual(currValue, 'color', opts),
offset: i / sampleNumber
});
}
colorStops.push({
color: this.getControllerVisual(dataInterval[1], 'color', opts),
offset: 1
});
return colorStops;
},
/**
* @private
*/
_createBarPoints: function (handleEnds, symbolSizes) {
var itemSize = this.visualMapModel.itemSize;
return [
[itemSize[0] - symbolSizes[0], handleEnds[0]],
[itemSize[0], handleEnds[0]],
[itemSize[0], handleEnds[1]],
[itemSize[0] - symbolSizes[1], handleEnds[1]]
];
},
/**
* @private
*/
_createBarGroup: function (itemAlign) {
var orient = this._orient;
var inverse = this.visualMapModel.get('inverse');
return new graphic.Group(
(orient === 'horizontal' && !inverse)
? {scale: itemAlign === 'bottom' ? [1, 1] : [-1, 1], rotation: Math.PI / 2}
: (orient === 'horizontal' && inverse)
? {scale: itemAlign === 'bottom' ? [-1, 1] : [1, 1], rotation: -Math.PI / 2}
: (orient === 'vertical' && !inverse)
? {scale: itemAlign === 'left' ? [1, -1] : [-1, -1]}
: {scale: itemAlign === 'left' ? [1, 1] : [-1, 1]}
);
},
/**
* @private
*/
_updateHandle: function (handleEnds, visualInRange) {
if (!this._useHandle) {
return;
}
var shapes = this._shapes;
var visualMapModel = this.visualMapModel;
var handleThumbs = shapes.handleThumbs;
var handleLabels = shapes.handleLabels;
each([0, 1], function (handleIndex) {
var handleThumb = handleThumbs[handleIndex];
handleThumb.setStyle('fill', visualInRange.handlesColor[handleIndex]);
handleThumb.position[1] = handleEnds[handleIndex];
// Update handle label position.
var textPoint = graphic.applyTransform(
shapes.handleLabelPoints[handleIndex],
graphic.getTransform(handleThumb, this.group)
);
handleLabels[handleIndex].setStyle({
x: textPoint[0],
y: textPoint[1],
text: visualMapModel.formatValueText(this._dataInterval[handleIndex]),
textVerticalAlign: 'middle',
textAlign: this._applyTransform(
this._orient === 'horizontal'
? (handleIndex === 0 ? 'bottom' : 'top')
: 'left',
shapes.barGroup
)
});
}, this);
},
/**
* @private
*/
_showIndicator: function (value, isRange) {
var visualMapModel = this.visualMapModel;
var dataExtent = visualMapModel.getExtent();
var itemSize = visualMapModel.itemSize;
var sizeExtent = [0, itemSize[1]];
var pos = linearMap(value, dataExtent, sizeExtent, true);
var shapes = this._shapes;
var indicator = shapes.indicator;
if (!indicator) {
return;
}
indicator.position[1] = pos;
indicator.attr('invisible', false);
indicator.setShape('points', createIndicatorPoints(isRange, pos, itemSize[1]));
var opts = {convertOpacityToAlpha: true};
var color = this.getControllerVisual(value, 'color', opts);
indicator.setStyle('fill', color);
// Update handle label position.
var textPoint = graphic.applyTransform(
shapes.indicatorLabelPoint,
graphic.getTransform(indicator, this.group)
);
var indicatorLabel = shapes.indicatorLabel;
indicatorLabel.attr('invisible', false);
var align = this._applyTransform('left', shapes.barGroup);
var orient = this._orient;
indicatorLabel.setStyle({
text: (isRange ? '≈' : '') + visualMapModel.formatValueText(value),
textVerticalAlign: orient === 'horizontal' ? align : 'middle',
textAlign: orient === 'horizontal' ? 'center' : align,
x: textPoint[0],
y: textPoint[1]
});
},
/**
* @private
*/
_enableHoverLinkToSeries: function () {
this._shapes.barGroup
.on('mousemove', zrUtil.bind(onMouseOver, this))
.on('mouseout', zrUtil.bind(this._clearHoverLinkToSeries, this));
function onMouseOver(e) {
var visualMapModel = this.visualMapModel;
var itemSize = visualMapModel.itemSize;
if (!visualMapModel.option.hoverLink) {
return;
}
var pos = this._applyTransform(
[e.offsetX, e.offsetY], this._shapes.barGroup, true, true
);
var hoverRange = [pos[1] - HOVER_LINK_RANGE / 2, pos[1] + HOVER_LINK_RANGE / 2];
var sizeExtent = [0, itemSize[1]];
var dataExtent = visualMapModel.getExtent();
var valueRange = [
linearMap(hoverRange[0], sizeExtent, dataExtent, true),
linearMap(hoverRange[1], sizeExtent, dataExtent, true)
];
// Do not show indicator when mouse is over handle,
// otherwise labels overlap, especially when dragging.
if (0 <= pos[0] && pos[0] <= itemSize[0]) {
this._showIndicator((valueRange[0] + valueRange[1]) / 2, true);
}
var oldBatch = convertDataIndicesToBatch(this._hoverLinkDataIndices);
this._hoverLinkDataIndices = visualMapModel.findTargetDataIndices(valueRange);
var newBatch = convertDataIndicesToBatch(this._hoverLinkDataIndices);
var resultBatches = helper.removeDuplicateBatch(oldBatch, newBatch);
this.api.dispatchAction({type: 'downplay', batch: resultBatches[0]});
this.api.dispatchAction({type: 'highlight', batch: resultBatches[1]});
}
},
/**
* @private
*/
_enableHoverLinkFromSeries: function () {
var zr = this.api.getZr();
if (this.visualMapModel.option.hoverLink) {
zr.on('mouseover', this._hoverLinkFromSeriesMouseOver, this);
zr.on('mouseout', this._hideIndicator, this);
}
else {
this._clearHoverLinkFromSeries();
}
},
/**
* @private
*/
_hoverLinkFromSeriesMouseOver: function (e) {
var el = e.target;
if (!el || el.dataIndex == null) {
return;
}
var dataModel = el.dataModel || this.ecModel.getSeriesByIndex(el.seriesIndex);
var data = dataModel.getData(el.dataType);
var dim = data.getDimension(this.visualMapModel.getDataDimension(data));
var value = data.get(dim, el.dataIndex, true);
this._showIndicator(value);
},
/**
* @private
*/
_hideIndicator: function () {
var shapes = this._shapes;
shapes.indicator && shapes.indicator.attr('invisible', true);
shapes.indicatorLabel && shapes.indicatorLabel.attr('invisible', true);
},
/**
* @private
*/
_clearHoverLinkToSeries: function () {
this._hideIndicator();
var indices = this._hoverLinkDataIndices;
this.api.dispatchAction({
type: 'downplay',
batch: convertDataIndicesToBatch(indices)
});
indices.length = 0;
},
/**
* @private
*/
_clearHoverLinkFromSeries: function () {
this._hideIndicator();
var zr = this.api.getZr();
zr.off('mouseover', this._hoverLinkFromSeriesMouseOver);
zr.off('mouseout', this._hideIndicator);
},
/**
* @private
*/
_applyTransform: function (vertex, element, inverse, global) {
var transform = graphic.getTransform(element, global ? null : this.group);
return graphic[
zrUtil.isArray(vertex) ? 'applyTransform' : 'transformDirection'
](vertex, transform, inverse);
},
/**
* @override
*/
dispose: function () {
this._clearHoverLinkFromSeries();
this._clearHoverLinkToSeries();
},
/**
* @override
*/
remove: function () {
this._clearHoverLinkFromSeries();
this._clearHoverLinkToSeries();
}
});
function createPolygon(points, onDrift, cursor) {
return new graphic.Polygon({
shape: {points: points},
draggable: !!onDrift,
cursor: cursor,
drift: onDrift
});
}
function createHandlePoints(handleIndex, textSize) {
return handleIndex === 0
? [[0, 0], [textSize, 0], [textSize, -textSize]]
: [[0, 0], [textSize, 0], [textSize, textSize]];
}
function createIndicatorPoints(isRange, pos, extentMax) {
return isRange
? [ // indicate range
[0, -mathMin(HOVER_LINK_RANGE, mathMax(pos, 0))],
[HOVER_LINK_OUT, 0],
[0, mathMin(HOVER_LINK_RANGE, mathMax(extentMax - pos, 0))]
]
: [ // indicate single value
[0, 0], [5, -5], [5, 5]
];
}
return ContinuousVisualMapView;
});

View File

@@ -0,0 +1,323 @@
define(function(require) {
var VisualMapModel = require('./VisualMapModel');
var zrUtil = require('zrender/core/util');
var VisualMapping = require('../../visual/VisualMapping');
var PiecewiseModel = VisualMapModel.extend({
type: 'visualMap.piecewise',
/**
* Order Rule:
*
* option.categories / option.pieces / option.text / option.selected:
* If !option.inverse,
* Order when vertical: ['top', ..., 'bottom'].
* Order when horizontal: ['left', ..., 'right'].
* If option.inverse, the meaning of
* the order should be reversed.
*
* this._pieceList:
* The order is always [low, ..., high].
*
* Mapping from location to low-high:
* If !option.inverse
* When vertical, top is high.
* When horizontal, right is high.
* If option.inverse, reverse.
*/
/**
* @protected
*/
defaultOption: {
selected: null, // Object. If not specified, means selected.
// When pieces and splitNumber: {'0': true, '5': true}
// When categories: {'cate1': false, 'cate3': true}
// When selected === false, means all unselected.
align: 'auto', // 'auto', 'left', 'right'
itemWidth: 20, // When put the controller vertically, it is the length of
// horizontal side of each item. Otherwise, vertical side.
itemHeight: 14, // When put the controller vertically, it is the length of
// vertical side of each item. Otherwise, horizontal side.
itemSymbol: 'roundRect',
pieceList: null, // Each item is Object, with some of those attrs:
// {min, max, value, color, colorSaturation, colorAlpha, opacity,
// symbol, symbolSize}, which customize the range or visual
// coding of the certain piece. Besides, see "Order Rule".
categories: null, // category names, like: ['some1', 'some2', 'some3'].
// Attr min/max are ignored when categories set. See "Order Rule"
splitNumber: 5, // If set to 5, auto split five pieces equally.
// If set to 0 and component type not set, component type will be
// determined as "continuous". (It is less reasonable but for ec2
// compatibility, see echarts/component/visualMap/typeDefaulter)
selectedMode: 'multiple', // Can be 'multiple' or 'single'.
itemGap: 10, // The gap between two items, in px.
hoverLink: true // Enable hover highlight.
},
/**
* @override
*/
doMergeOption: function (newOption, isInit) {
PiecewiseModel.superApply(this, 'doMergeOption', arguments);
/**
* The order is always [low, ..., high].
* [{text: string, interval: Array.<number>}, ...]
* @private
* @type {Array.<Object>}
*/
this._pieceList = [];
this.resetTargetSeries(newOption, isInit);
this.resetExtent();
/**
* 'pieces', 'categories', 'splitNumber'
* @type {string}
*/
var mode = this._mode = this._decideMode();
resetMethods[this._mode].call(this);
this._resetSelected(newOption, isInit);
var categories = this.option.categories;
this.resetVisual(function (mappingOption, state) {
if (mode === 'categories') {
mappingOption.mappingMethod = 'category';
mappingOption.categories = zrUtil.clone(categories);
}
else {
mappingOption.mappingMethod = 'piecewise';
mappingOption.pieceList = zrUtil.map(this._pieceList, function (piece) {
var piece = zrUtil.clone(piece);
if (state !== 'inRange') {
piece.visual = null;
}
return piece;
});
}
});
},
_resetSelected: function (newOption, isInit) {
var thisOption = this.option;
var pieceList = this._pieceList;
// Selected do not merge but all override.
var selected = (isInit ? thisOption : newOption).selected || {};
thisOption.selected = selected;
// Consider 'not specified' means true.
zrUtil.each(pieceList, function (piece, index) {
var key = this.getSelectedMapKey(piece);
if (!(key in selected)) {
selected[key] = true;
}
}, this);
if (thisOption.selectedMode === 'single') {
// Ensure there is only one selected.
var hasSel = false;
zrUtil.each(pieceList, function (piece, index) {
var key = this.getSelectedMapKey(piece);
if (selected[key]) {
hasSel
? (selected[key] = false)
: (hasSel = true);
}
}, this);
}
// thisOption.selectedMode === 'multiple', default: all selected.
},
/**
* @public
*/
getSelectedMapKey: function (piece) {
return this._mode === 'categories'
? piece.value + '' : piece.index + '';
},
/**
* @public
*/
getPieceList: function () {
return this._pieceList;
},
/**
* @private
* @return {string}
*/
_decideMode: function () {
var option = this.option;
return option.pieces && option.pieces.length > 0
? 'pieces'
: this.option.categories
? 'categories'
: 'splitNumber';
},
/**
* @public
* @override
*/
setSelected: function (selected) {
this.option.selected = zrUtil.clone(selected);
},
/**
* @public
* @override
*/
getValueState: function (value) {
var index = VisualMapping.findPieceIndex(value, this._pieceList);
return index != null
? (this.option.selected[this.getSelectedMapKey(this._pieceList[index])]
? 'inRange' : 'outOfRange'
)
: 'outOfRange';
},
/**
* @public
* @params {number} pieceIndex piece index in visualMapModel.getPieceList()
* @return {Array.<Object>} [{seriesId, dataIndices: <Array.<number>>}, ...]
*/
findTargetDataIndices: function (pieceIndex) {
var result = [];
this.eachTargetSeries(function (seriesModel) {
var dataIndices = [];
var data = seriesModel.getData();
data.each(this.getDataDimension(data), function (value, dataIndex) {
// Should always base on model pieceList, because it is order sensitive.
var pIdx = VisualMapping.findPieceIndex(value, this._pieceList);
pIdx === pieceIndex && dataIndices.push(dataIndex);
}, true, this);
result.push({seriesId: seriesModel.id, dataIndices: dataIndices});
}, this);
return result;
}
});
/**
* Key is this._mode
* @type {Object}
* @this {module:echarts/component/viusalMap/PiecewiseMode}
*/
var resetMethods = {
splitNumber: function () {
var thisOption = this.option;
var precision = thisOption.precision;
var dataExtent = this.getExtent();
var splitNumber = thisOption.splitNumber;
splitNumber = Math.max(parseInt(splitNumber, 10), 1);
thisOption.splitNumber = splitNumber;
var splitStep = (dataExtent[1] - dataExtent[0]) / splitNumber;
// Precision auto-adaption
while (+splitStep.toFixed(precision) !== splitStep && precision < 5) {
precision++;
}
thisOption.precision = precision;
splitStep = +splitStep.toFixed(precision);
for (var i = 0, curr = dataExtent[0]; i < splitNumber; i++, curr += splitStep) {
var max = i === splitNumber - 1 ? dataExtent[1] : (curr + splitStep);
this._pieceList.push({
text: this.formatValueText([curr, max]),
index: i,
interval: [curr, max]
});
}
},
categories: function () {
var thisOption = this.option;
zrUtil.each(thisOption.categories, function (cate) {
// FIXME category模式也使用pieceList但在visualMapping中不是使用pieceList。
// 是否改一致。
this._pieceList.push({
text: this.formatValueText(cate, true),
value: cate
});
}, this);
// See "Order Rule".
normalizeReverse(thisOption, this._pieceList);
},
pieces: function () {
var thisOption = this.option;
zrUtil.each(thisOption.pieces, function (pieceListItem, index) {
if (!zrUtil.isObject(pieceListItem)) {
pieceListItem = {value: pieceListItem};
}
var item = {text: '', index: index};
var hasLabel;
if (pieceListItem.label != null) {
item.text = pieceListItem.label;
hasLabel = true;
}
if (pieceListItem.hasOwnProperty('value')) {
item.value = pieceListItem.value;
if (!hasLabel) {
item.text = this.formatValueText(item.value);
}
}
else {
var min = pieceListItem.min;
var max = pieceListItem.max;
min == null && (min = -Infinity);
max == null && (max = Infinity);
if (min === max) {
// Consider: [{min: 5, max: 5, visual: {...}}, {min: 0, max: 5}],
// we use value to lift the priority when min === max
item.value = min;
}
item.interval = [min, max];
if (!hasLabel) {
item.text = this.formatValueText([min, max]);
}
}
item.visual = VisualMapping.retrieveVisuals(pieceListItem);
this._pieceList.push(item);
}, this);
// See "Order Rule".
normalizeReverse(thisOption, this._pieceList);
}
};
function normalizeReverse(thisOption, arr) {
var inverse = thisOption.inverse;
if (thisOption.orient === 'vertical' ? !inverse : inverse) {
arr.reverse();
}
}
return PiecewiseModel;
});

View File

@@ -0,0 +1,239 @@
define(function(require) {
var VisualMapView = require('./VisualMapView');
var zrUtil = require('zrender/core/util');
var graphic = require('../../util/graphic');
var symbolCreators = require('../../util/symbol');
var layout = require('../../util/layout');
var helper = require('./helper');
var PiecewiseVisualMapView = VisualMapView.extend({
type: 'visualMap.piecewise',
/**
* @protected
* @override
*/
doRender: function () {
var thisGroup = this.group;
thisGroup.removeAll();
var visualMapModel = this.visualMapModel;
var textGap = visualMapModel.get('textGap');
var textStyleModel = visualMapModel.textStyleModel;
var textFont = textStyleModel.getFont();
var textFill = textStyleModel.getTextColor();
var itemAlign = this._getItemAlign();
var itemSize = visualMapModel.itemSize;
var viewData = this._getViewData();
var showLabel = !viewData.endsText;
var showEndsText = !showLabel;
showEndsText && this._renderEndsText(thisGroup, viewData.endsText[0], itemSize);
zrUtil.each(viewData.viewPieceList, renderItem, this);
showEndsText && this._renderEndsText(thisGroup, viewData.endsText[1], itemSize);
layout.box(
visualMapModel.get('orient'), thisGroup, visualMapModel.get('itemGap')
);
this.renderBackground(thisGroup);
this.positionGroup(thisGroup);
function renderItem(item) {
var piece = item.piece;
var itemGroup = new graphic.Group();
itemGroup.onclick = zrUtil.bind(this._onItemClick, this, piece);
this._enableHoverLink(itemGroup, item.indexInModelPieceList);
var representValue = this._getRepresentValue(piece);
this._createItemSymbol(
itemGroup, representValue, [0, 0, itemSize[0], itemSize[1]]
);
if (showLabel) {
var visualState = this.visualMapModel.getValueState(representValue);
itemGroup.add(new graphic.Text({
style: {
x: itemAlign === 'right' ? -textGap : itemSize[0] + textGap,
y: itemSize[1] / 2,
text: piece.text,
textVerticalAlign: 'middle',
textAlign: itemAlign,
textFont: textFont,
fill: textFill,
opacity: visualState === 'outOfRange' ? 0.5 : 1
}
}));
}
thisGroup.add(itemGroup);
}
},
/**
* @private
*/
_enableHoverLink: function (itemGroup, pieceIndex) {
itemGroup
.on('mouseover', zrUtil.bind(onHoverLink, this, 'highlight'))
.on('mouseout', zrUtil.bind(onHoverLink, this, 'downplay'));
function onHoverLink(method) {
var visualMapModel = this.visualMapModel;
visualMapModel.option.hoverLink && this.api.dispatchAction({
type: method,
batch: helper.convertDataIndicesToBatch(
visualMapModel.findTargetDataIndices(pieceIndex)
)
});
}
},
/**
* @private
*/
_getItemAlign: function () {
var visualMapModel = this.visualMapModel;
var modelOption = visualMapModel.option;
if (modelOption.orient === 'vertical') {
return helper.getItemAlign(
visualMapModel, this.api, visualMapModel.itemSize
);
}
else { // horizontal, most case left unless specifying right.
var align = modelOption.align;
if (!align || align === 'auto') {
align = 'left';
}
return align;
}
},
/**
* @private
*/
_renderEndsText: function (group, text, itemSize) {
if (!text) {
return;
}
var itemGroup = new graphic.Group();
var textStyleModel = this.visualMapModel.textStyleModel;
itemGroup.add(new graphic.Text({
style: {
x: itemSize[0] / 2,
y: itemSize[1] / 2,
textVerticalAlign: 'middle',
textAlign: 'center',
text: text,
textFont: textStyleModel.getFont(),
fill: textStyleModel.getTextColor()
}
}));
group.add(itemGroup);
},
/**
* @private
* @return {Object} {peiceList, endsText} The order is the same as screen pixel order.
*/
_getViewData: function () {
var visualMapModel = this.visualMapModel;
var viewPieceList = zrUtil.map(visualMapModel.getPieceList(), function (piece, index) {
return {piece: piece, indexInModelPieceList: index};
});
var endsText = visualMapModel.get('text');
// Consider orient and inverse.
var orient = visualMapModel.get('orient');
var inverse = visualMapModel.get('inverse');
// Order of model pieceList is always [low, ..., high]
if (orient === 'horizontal' ? inverse : !inverse) {
viewPieceList.reverse();
}
// Origin order of endsText is [high, low]
else if (endsText) {
endsText = endsText.slice().reverse();
}
return {viewPieceList: viewPieceList, endsText: endsText};
},
/**
* @private
*/
_getRepresentValue: function (piece) {
var representValue;
if (this.visualMapModel.isCategory()) {
representValue = piece.value;
}
else {
if (piece.value != null) {
representValue = piece.value;
}
else {
var pieceInterval = piece.interval || [];
representValue = (pieceInterval[0] + pieceInterval[1]) / 2;
}
}
return representValue;
},
/**
* @private
*/
_createItemSymbol: function (group, representValue, shapeParam) {
group.add(symbolCreators.createSymbol(
this.getControllerVisual(representValue, 'symbol'),
shapeParam[0], shapeParam[1], shapeParam[2], shapeParam[3],
this.getControllerVisual(representValue, 'color')
));
},
/**
* @private
*/
_onItemClick: function (piece) {
var visualMapModel = this.visualMapModel;
var option = visualMapModel.option;
var selected = zrUtil.clone(option.selected);
var newKey = visualMapModel.getSelectedMapKey(piece);
if (option.selectedMode === 'single') {
selected[newKey] = true;
zrUtil.each(selected, function (o, key) {
selected[key] = key === newKey;
});
}
else {
selected[newKey] = !selected[newKey];
}
this.api.dispatchAction({
type: 'selectDataRange',
from: this.uid,
visualMapId: this.visualMapModel.id,
selected: selected
});
}
});
return PiecewiseVisualMapView;
});

View File

@@ -0,0 +1,539 @@
/**
* @file Data zoom model
*/
define(function(require) {
var zrUtil = require('zrender/core/util');
var env = require('zrender/core/env');
var echarts = require('../../echarts');
var modelUtil = require('../../util/model');
var visualDefault = require('../../visual/visualDefault');
var VisualMapping = require('../../visual/VisualMapping');
var mapVisual = VisualMapping.mapVisual;
var eachVisual = VisualMapping.eachVisual;
var numberUtil = require('../../util/number');
var isArray = zrUtil.isArray;
var each = zrUtil.each;
var asc = numberUtil.asc;
var linearMap = numberUtil.linearMap;
var VisualMapModel = echarts.extendComponentModel({
type: 'visualMap',
dependencies: ['series'],
/**
* [lowerBound, upperBound]
*
* @readOnly
* @type {Array.<number>}
*/
dataBound: [-Infinity, Infinity],
/**
* @readOnly
* @type {Array.<string>}
*/
stateList: ['inRange', 'outOfRange'],
/**
* @readOnly
* @type {string|Object}
*/
layoutMode: {type: 'box', ignoreSize: true},
/**
* @protected
*/
defaultOption: {
show: true,
zlevel: 0,
z: 4,
// set min: 0, max: 200, only for campatible with ec2.
// In fact min max should not have default value.
min: 0, // min value, must specified if pieces is not specified.
max: 200, // max value, must specified if pieces is not specified.
dimension: null,
inRange: null, // 'color', 'colorHue', 'colorSaturation', 'colorLightness', 'colorAlpha',
// 'symbol', 'symbolSize'
outOfRange: null, // 'color', 'colorHue', 'colorSaturation',
// 'colorLightness', 'colorAlpha',
// 'symbol', 'symbolSize'
left: 0, // 'center' ¦ 'left' ¦ 'right' ¦ {number} (px)
right: null, // The same as left.
top: null, // 'top' ¦ 'bottom' ¦ 'center' ¦ {number} (px)
bottom: 0, // The same as top.
itemWidth: null,
itemHeight: null,
inverse: false,
orient: 'vertical', // 'horizontal' ¦ 'vertical'
seriesIndex: null, // 所控制的series indices默认所有有value的series.
backgroundColor: 'rgba(0,0,0,0)',
borderColor: '#ccc', // 值域边框颜色
contentColor: '#5793f3',
inactiveColor: '#aaa',
borderWidth: 0, // 值域边框线宽单位px默认为0无边框
padding: 5, // 值域内边距单位px默认各方向内边距为5
// 接受数组分别设定上右下左边距同css
textGap: 10, //
precision: 0, // 小数精度默认为0无小数点
color: ['#bf444c', '#d88273', '#f6efa6'], //颜色deprecated兼容ec2顺序同pieces不同于inRange/outOfRange
formatter: null,
text: null, // 文本,如['高', '低']兼容ec2text[0]对应高值text[1]对应低值
textStyle: {
color: '#333' // 值域文字颜色
}
},
/**
* @protected
*/
init: function (option, parentModel, ecModel) {
/**
* @private
* @type {Array.<number>}
*/
this._dataExtent;
/**
* @readOnly
*/
this.controllerVisuals = {};
/**
* @readOnly
*/
this.targetVisuals = {};
/**
* @readOnly
*/
this.textStyleModel;
/**
* [width, height]
* @readOnly
* @type {Array.<number>}
*/
this.itemSize;
this.mergeDefaultAndTheme(option, ecModel);
this.doMergeOption({}, true);
},
/**
* @public
*/
mergeOption: function (option) {
VisualMapModel.superApply(this, 'mergeOption', arguments);
this.doMergeOption(option, false);
},
/**
* @protected
*/
doMergeOption: function (newOption, isInit) {
var thisOption = this.option;
!isInit && replaceVisualOption(thisOption, newOption);
// FIXME
// necessary?
// Disable realtime view update if canvas is not supported.
if (!env.canvasSupported) {
thisOption.realtime = false;
}
this.textStyleModel = this.getModel('textStyle');
this.resetItemSize();
this.completeVisualOption();
},
/**
* @example
* this.formatValueText(someVal); // format single numeric value to text.
* this.formatValueText(someVal, true); // format single category value to text.
* this.formatValueText([min, max]); // format numeric min-max to text.
* this.formatValueText([this.dataBound[0], max]); // using data lower bound.
* this.formatValueText([min, this.dataBound[1]]); // using data upper bound.
*
* @param {number|Array.<number>} value Real value, or this.dataBound[0 or 1].
* @param {boolean} [isCategory=false] Only available when value is number.
* @return {string}
* @protected
*/
formatValueText: function(value, isCategory) {
var option = this.option;
var precision = option.precision;
var dataBound = this.dataBound;
var formatter = option.formatter;
var isMinMax;
var textValue;
if (zrUtil.isArray(value)) {
value = value.slice();
isMinMax = true;
}
textValue = isCategory
? value
: (isMinMax
? [toFixed(value[0]), toFixed(value[1])]
: toFixed(value)
);
if (zrUtil.isString(formatter)) {
return formatter
.replace('{value}', isMinMax ? textValue[0] : textValue)
.replace('{value2}', isMinMax ? textValue[1] : textValue);
}
else if (zrUtil.isFunction(formatter)) {
return isMinMax
? formatter(value[0], value[1])
: formatter(value);
}
if (isMinMax) {
if (value[0] === dataBound[0]) {
return '< ' + textValue[1];
}
else if (value[1] === dataBound[1]) {
return '> ' + textValue[0];
}
else {
return textValue[0] + ' - ' + textValue[1];
}
}
else { // Format single value (includes category case).
return textValue;
}
function toFixed(val) {
return val === dataBound[0]
? 'min'
: val === dataBound[1]
? 'max'
: (+val).toFixed(precision);
}
},
/**
* @protected
*/
resetTargetSeries: function (newOption, isInit) {
var thisOption = this.option;
var allSeriesIndex = thisOption.seriesIndex == null;
thisOption.seriesIndex = allSeriesIndex
? [] : modelUtil.normalizeToArray(thisOption.seriesIndex);
allSeriesIndex && this.ecModel.eachSeries(function (seriesModel, index) {
var data = seriesModel.getData();
// FIXME
// 只考虑了list还没有考虑map等。
// FIXME
// 这里可能应该这么判断data.dimensions中有超出其所属coordSystem的量。
if (data.type === 'list') {
thisOption.seriesIndex.push(index);
}
});
},
/**
* @protected
*/
resetExtent: function () {
var thisOption = this.option;
// Can not calculate data extent by data here.
// Because series and data may be modified in processing stage.
// So we do not support the feature "auto min/max".
var extent = asc([thisOption.min, thisOption.max]);
this._dataExtent = extent;
},
/**
* @protected
*/
getDataDimension: function (list) {
var optDim = this.option.dimension;
return optDim != null
? optDim : list.dimensions.length - 1;
},
/**
* @public
* @override
*/
getExtent: function () {
return this._dataExtent.slice();
},
/**
* @protected
*/
resetVisual: function (fillVisualOption) {
var dataExtent = this.getExtent();
doReset.call(this, 'controller', this.controllerVisuals);
doReset.call(this, 'target', this.targetVisuals);
function doReset(baseAttr, visualMappings) {
each(this.stateList, function (state) {
var mappings = visualMappings[state] || (
visualMappings[state] = createMappings()
);
var visaulOption = this.option[baseAttr][state] || {};
each(visaulOption, function (visualData, visualType) {
if (!VisualMapping.isValidType(visualType)) {
return;
}
var mappingOption = {
type: visualType,
dataExtent: dataExtent,
visual: visualData
};
fillVisualOption && fillVisualOption.call(this, mappingOption, state);
mappings[visualType] = new VisualMapping(mappingOption);
// Prepare a alpha for opacity, for some case that opacity
// is not supported, such as rendering using gradient color.
if (baseAttr === 'controller' && visualType === 'opacity') {
mappingOption = zrUtil.clone(mappingOption);
mappingOption.type = 'colorAlpha';
mappings.__hidden.__alphaForOpacity = new VisualMapping(mappingOption);
}
}, this);
}, this);
}
function createMappings() {
var Creater = function () {};
// Make sure hidden fields will not be visited by
// object iteration (with hasOwnProperty checking).
Creater.prototype.__hidden = Creater.prototype;
var obj = new Creater();
return obj;
}
},
/**
* @protected
*/
completeVisualOption: function () {
var thisOption = this.option;
var base = {inRange: thisOption.inRange, outOfRange: thisOption.outOfRange};
var target = thisOption.target || (thisOption.target = {});
var controller = thisOption.controller || (thisOption.controller = {});
zrUtil.merge(target, base); // Do not override
zrUtil.merge(controller, base); // Do not override
var isCategory = this.isCategory();
completeSingle.call(this, target);
completeSingle.call(this, controller);
completeInactive.call(this, target, 'inRange', 'outOfRange');
completeInactive.call(this, target, 'outOfRange', 'inRange');
completeController.call(this, controller);
function completeSingle(base) {
// Compatible with ec2 dataRange.color.
// The mapping order of dataRange.color is: [high value, ..., low value]
// whereas inRange.color and outOfRange.color is [low value, ..., high value]
// Notice: ec2 has no inverse.
if (isArray(thisOption.color)
// If there has been inRange: {symbol: ...}, adding color is a mistake.
// So adding color only when no inRange defined.
&& !base.inRange
) {
base.inRange = {color: thisOption.color.slice().reverse()};
}
// If using shortcut like: {inRange: 'symbol'}, complete default value.
each(this.stateList, function (state) {
var visualType = base[state];
if (zrUtil.isString(visualType)) {
var defa = visualDefault.get(visualType, 'active', isCategory);
if (defa) {
base[state] = {};
base[state][visualType] = defa;
}
else {
// Mark as not specified.
delete base[state];
}
}
}, this);
}
function completeInactive(base, stateExist, stateAbsent) {
var optExist = base[stateExist];
var optAbsent = base[stateAbsent];
if (optExist && !optAbsent) {
optAbsent = base[stateAbsent] = {};
each(optExist, function (visualData, visualType) {
if (!VisualMapping.isValidType(visualType)) {
return;
}
var defa = visualDefault.get(visualType, 'inactive', isCategory);
if (defa != null) {
optAbsent[visualType] = defa;
// Compatibable with ec2:
// Only inactive color to rgba(0,0,0,0) can not
// make label transparent, so use opacity also.
if (visualType === 'color'
&& !optAbsent.hasOwnProperty('opacity')
&& !optAbsent.hasOwnProperty('colorAlpha')
) {
optAbsent.opacity = [0, 0];
}
}
});
}
}
function completeController(controller) {
var symbolExists = (controller.inRange || {}).symbol
|| (controller.outOfRange || {}).symbol;
var symbolSizeExists = (controller.inRange || {}).symbolSize
|| (controller.outOfRange || {}).symbolSize;
var inactiveColor = this.get('inactiveColor');
each(this.stateList, function (state) {
var itemSize = this.itemSize;
var visuals = controller[state];
// Set inactive color for controller if no other color
// attr (like colorAlpha) specified.
if (!visuals) {
visuals = controller[state] = {
color: isCategory ? inactiveColor : [inactiveColor]
};
}
// Consistent symbol and symbolSize if not specified.
if (visuals.symbol == null) {
visuals.symbol = symbolExists
&& zrUtil.clone(symbolExists)
|| (isCategory ? 'roundRect' : ['roundRect']);
}
if (visuals.symbolSize == null) {
visuals.symbolSize = symbolSizeExists
&& zrUtil.clone(symbolSizeExists)
|| (isCategory ? itemSize[0] : [itemSize[0], itemSize[0]]);
}
// Filter square and none.
visuals.symbol = mapVisual(visuals.symbol, function (symbol) {
return (symbol === 'none' || symbol === 'square') ? 'roundRect' : symbol;
});
// Normalize symbolSize
var symbolSize = visuals.symbolSize;
if (symbolSize != null) {
var max = -Infinity;
// symbolSize can be object when categories defined.
eachVisual(symbolSize, function (value) {
value > max && (max = value);
});
visuals.symbolSize = mapVisual(symbolSize, function (value) {
return linearMap(value, [0, max], [0, itemSize[0]], true);
});
}
}, this);
}
},
/**
* @public
*/
eachTargetSeries: function (callback, context) {
zrUtil.each(this.option.seriesIndex, function (seriesIndex) {
callback.call(context, this.ecModel.getSeriesByIndex(seriesIndex));
}, this);
},
/**
* @public
*/
isCategory: function () {
return !!this.option.categories;
},
/**
* @protected
*/
resetItemSize: function () {
this.itemSize = [
parseFloat(this.get('itemWidth')),
parseFloat(this.get('itemHeight'))
];
},
/**
* @public
* @abstract
*/
setSelected: zrUtil.noop,
/**
* @public
* @abstract
*/
getValueState: zrUtil.noop
});
function replaceVisualOption(thisOption, newOption) {
// Visual attributes merge is not supported, otherwise it
// brings overcomplicated merge logic. See #2853. So if
// newOption has anyone of these keys, all of these keys
// will be reset. Otherwise, all keys remain.
var visualKeys = [
'inRange', 'outOfRange', 'target', 'controller', 'color'
];
var has;
zrUtil.each(visualKeys, function (key) {
if (newOption.hasOwnProperty(key)) {
has = true;
}
});
has && zrUtil.each(visualKeys, function (key) {
if (newOption.hasOwnProperty(key)) {
thisOption[key] = zrUtil.clone(newOption[key]);
}
else {
delete thisOption[key];
}
});
}
return VisualMapModel;
});

View File

@@ -0,0 +1,161 @@
define(function (require) {
var echarts = require('../../echarts');
var zrUtil = require('zrender/core/util');
var graphic = require('../../util/graphic');
var formatUtil = require('../../util/format');
var layout = require('../../util/layout');
var VisualMapping = require('../../visual/VisualMapping');
return echarts.extendComponentView({
type: 'visualMap',
/**
* @readOnly
* @type {Object}
*/
autoPositionValues: {left: 1, right: 1, top: 1, bottom: 1},
init: function (ecModel, api) {
/**
* @readOnly
* @type {module:echarts/model/Global}
*/
this.ecModel = ecModel;
/**
* @readOnly
* @type {module:echarts/ExtensionAPI}
*/
this.api = api;
/**
* @readOnly
* @type {module:echarts/component/visualMap/visualMapModel}
*/
this.visualMapModel;
/**
* @private
* @type {Object}
*/
this._updatableShapes = {};
},
/**
* @protected
*/
render: function (visualMapModel, ecModel, api, payload) {
this.visualMapModel = visualMapModel;
if (visualMapModel.get('show') === false) {
this.group.removeAll();
return;
}
this.doRender.apply(this, arguments);
},
/**
* @protected
*/
renderBackground: function (group) {
var visualMapModel = this.visualMapModel;
var padding = formatUtil.normalizeCssArray(visualMapModel.get('padding') || 0);
var rect = group.getBoundingRect();
group.add(new graphic.Rect({
z2: -1, // Lay background rect on the lowest layer.
silent: true,
shape: {
x: rect.x - padding[3],
y: rect.y - padding[0],
width: rect.width + padding[3] + padding[1],
height: rect.height + padding[0] + padding[2]
},
style: {
fill: visualMapModel.get('backgroundColor'),
stroke: visualMapModel.get('borderColor'),
lineWidth: visualMapModel.get('borderWidth')
}
}));
},
/**
* @protected
* @param {number} targetValue
* @param {string=} visualCluster Only can be 'color' 'opacity' 'symbol' 'symbolSize'
* @param {Object} [opts]
* @param {string=} [opts.forceState] Specify state, instead of using getValueState method.
* @param {string=} [opts.convertOpacityToAlpha=false] For color gradient in controller widget.
* @return {*} Visual value.
*/
getControllerVisual: function (targetValue, visualCluster, opts) {
opts = opts || {};
var forceState = opts.forceState;
var visualMapModel = this.visualMapModel;
var visualObj = {};
// Default values.
if (visualCluster === 'symbol') {
visualObj.symbol = visualMapModel.get('itemSymbol');
}
if (visualCluster === 'color') {
var defaultColor = visualMapModel.get('contentColor');
visualObj.color = defaultColor;
}
function getter(key) {
return visualObj[key];
}
function setter(key, value) {
visualObj[key] = value;
}
var mappings = visualMapModel.controllerVisuals[
forceState || visualMapModel.getValueState(targetValue)
];
var visualTypes = VisualMapping.prepareVisualTypes(mappings);
zrUtil.each(visualTypes, function (type) {
var visualMapping = mappings[type];
if (opts.convertOpacityToAlpha && type === 'opacity') {
type = 'colorAlpha';
visualMapping = mappings.__alphaForOpacity;
}
if (VisualMapping.dependsOn(type, visualCluster)) {
visualMapping && visualMapping.applyVisual(
targetValue, getter, setter
);
}
});
return visualObj[visualCluster];
},
/**
* @protected
*/
positionGroup: function (group) {
var model = this.visualMapModel;
var api = this.api;
layout.positionGroup(
group,
model.getBoxLayoutParams(),
{width: api.getWidth(), height: api.getHeight()}
);
},
/**
* @protected
* @abstract
*/
doRender: zrUtil.noop
});
});

View File

@@ -0,0 +1,86 @@
define(function(require) {
var layout = require('../../util/layout');
var zrUtil = require('zrender/core/util');
var DataDiffer = require('../../data/DataDiffer');
var helper = {
/**
* @param {module:echarts/component/visualMap/VisualMapModel} visualMapModel\
* @param {module:echarts/ExtensionAPI} api
* @param {Array.<number>} itemSize always [short, long]
* @return {string} 'left' or 'right' or 'top' or 'bottom'
*/
getItemAlign: function (visualMapModel, api, itemSize) {
var modelOption = visualMapModel.option;
var itemAlign = modelOption.align;
if (itemAlign != null && itemAlign !== 'auto') {
return itemAlign;
}
// Auto decision align.
var ecSize = {width: api.getWidth(), height: api.getHeight()};
var realIndex = modelOption.orient === 'horizontal' ? 1 : 0;
var paramsSet = [
['left', 'right', 'width'],
['top', 'bottom', 'height']
];
var reals = paramsSet[realIndex];
var fakeValue = [0, null, 10];
var layoutInput = {};
for (var i = 0; i < 3; i++) {
layoutInput[paramsSet[1 - realIndex][i]] = fakeValue[i];
layoutInput[reals[i]] = i === 2 ? itemSize[0] : modelOption[reals[i]];
}
var rParam = [['x', 'width', 3], ['y', 'height', 0]][realIndex];
var rect = layout.getLayoutRect(layoutInput, ecSize, modelOption.padding);
return reals[
(rect.margin[rParam[2]] || 0) + rect[rParam[0]] + rect[rParam[1]] * 0.5
< ecSize[rParam[1]] * 0.5 ? 0 : 1
];
},
convertDataIndicesToBatch: function (dataIndicesBySeries) {
var batch = [];
zrUtil.each(dataIndicesBySeries, function (item) {
zrUtil.each(item.dataIndices, function (dataIndex) {
batch.push({seriesId: item.seriesId, dataIndex: dataIndex});
});
});
return batch;
},
removeDuplicateBatch: function (batchA, batchB) {
var result = [[], []];
(new DataDiffer(batchA, batchB, getKey, getKey))
.add(add)
.update(zrUtil.noop)
.remove(remove)
.execute();
function getKey(item) {
return item.seriesId + '-' + item.dataIndex;
}
function add(index) {
result[1].push(batchB[index]);
}
function remove(index) {
result[0].push(batchA[index]);
}
return result;
}
};
return helper;
});

View File

@@ -0,0 +1,47 @@
/**
* @file VisualMap preprocessor
*/
define(function(require) {
var zrUtil = require('zrender/core/util');
var each = zrUtil.each;
return function (option) {
var visualMap = option && option.visualMap;
if (!zrUtil.isArray(visualMap)) {
visualMap = visualMap ? [visualMap] : [];
}
each(visualMap, function (opt) {
if (!opt) {
return;
}
// rename splitList to pieces
if (has(opt, 'splitList') && !has(opt, 'pieces')) {
opt.pieces = opt.splitList;
delete opt.splitList;
}
var pieces = opt.pieces;
if (pieces && zrUtil.isArray(pieces)) {
each(pieces, function (piece) {
if (zrUtil.isObject(piece)) {
if (has(piece, 'start') && !has(piece, 'min')) {
piece.min = piece.start;
}
if (has(piece, 'end') && !has(piece, 'max')) {
piece.max = piece.end;
}
}
});
}
});
};
function has(obj, name) {
return obj && obj.hasOwnProperty && obj.hasOwnProperty(name);
}
});

View File

@@ -0,0 +1,19 @@
define(function (require) {
require('../../model/Component').registerSubTypeDefaulter('visualMap', function (option) {
// Compatible with ec2, when splitNumber === 0, continuous visualMap will be used.
return (
!option.categories
&& (
!(
option.pieces
? option.pieces.length > 0
: option.splitNumber > 0
)
|| option.calculable
)
)
? 'continuous' : 'piecewise';
});
});

View File

@@ -0,0 +1,51 @@
/**
* @file Data range visual coding.
*/
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('visualMap', function (visualMapModel) {
processSingleVisualMap(visualMapModel, ecModel);
});
});
function processSingleVisualMap(visualMapModel, ecModel) {
var visualMappings = visualMapModel.targetVisuals;
var visualTypesMap = {};
zrUtil.each(['inRange', 'outOfRange'], function (state) {
var visualTypes = VisualMapping.prepareVisualTypes(visualMappings[state]);
visualTypesMap[state] = visualTypes;
});
visualMapModel.eachTargetSeries(function (seriesModel) {
var data = seriesModel.getData();
var dimension = visualMapModel.getDataDimension(data);
var dataIndex;
function getVisual(key) {
return data.getItemVisual(dataIndex, key);
}
function setVisual(key, value) {
data.setItemVisual(dataIndex, key, value);
}
data.each([dimension], function (value, index) {
// For performance consideration, do not use curry.
dataIndex = index;
var valueState = visualMapModel.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);
}
}, true);
});
}
});

View File

@@ -0,0 +1,23 @@
/**
* @file Data range action
*/
define(function(require) {
var echarts = require('../../echarts');
var actionInfo = {
type: 'selectDataRange',
event: 'dataRangeSelected',
// FIXME use updateView appears wrong
update: 'update'
};
echarts.registerAction(actionInfo, function (payload, ecModel) {
ecModel.eachComponent({mainType: 'visualMap', query: payload}, function (model) {
model.setSelected(payload.selected);
});
});
});

View File

@@ -0,0 +1,16 @@
/**
* DataZoom component entry
*/
define(function (require) {
require('../echarts').registerPreprocessor(
require('./visualMap/preprocessor')
);
require('./visualMap/typeDefaulter');
require('./visualMap/visualCoding');
require('./visualMap/ContinuousModel');
require('./visualMap/ContinuousView');
require('./visualMap/visualMapAction');
});

View File

@@ -0,0 +1,16 @@
/**
* DataZoom component entry
*/
define(function (require) {
require('../echarts').registerPreprocessor(
require('./visualMap/preprocessor')
);
require('./visualMap/typeDefaulter');
require('./visualMap/visualCoding');
require('./visualMap/PiecewiseModel');
require('./visualMap/PiecewiseView');
require('./visualMap/visualMapAction');
});