mirror of https://github.com/masayuki0812/c3.git
Quite good looking graph derived from d3.js
http://c3js.org
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
395 lines
16 KiB
395 lines
16 KiB
import CLASS from './class'; |
|
import { ChartInternal } from './core'; |
|
import { isValue, isFunction, isUndefined, isDefined } from './util'; |
|
|
|
ChartInternal.prototype.initLine = function () { |
|
var $$ = this; |
|
$$.main.select('.' + CLASS.chart).append("g") |
|
.attr("class", CLASS.chartLines); |
|
}; |
|
ChartInternal.prototype.updateTargetsForLine = function (targets) { |
|
var $$ = this, config = $$.config, |
|
mainLines, mainLineEnter, |
|
classChartLine = $$.classChartLine.bind($$), |
|
classLines = $$.classLines.bind($$), |
|
classAreas = $$.classAreas.bind($$), |
|
classCircles = $$.classCircles.bind($$), |
|
classFocus = $$.classFocus.bind($$); |
|
mainLines = $$.main.select('.' + CLASS.chartLines).selectAll('.' + CLASS.chartLine) |
|
.data(targets) |
|
.attr('class', function (d) { return classChartLine(d) + classFocus(d); }); |
|
mainLineEnter = mainLines.enter().append('g') |
|
.attr('class', classChartLine) |
|
.style('opacity', 0) |
|
.style("pointer-events", "none"); |
|
// Lines for each data |
|
mainLineEnter.append('g') |
|
.attr("class", classLines); |
|
// Areas |
|
mainLineEnter.append('g') |
|
.attr('class', classAreas); |
|
// Circles for each data point on lines |
|
mainLineEnter.append('g') |
|
.attr("class", function (d) { return $$.generateClass(CLASS.selectedCircles, d.id); }); |
|
mainLineEnter.append('g') |
|
.attr("class", classCircles) |
|
.style("cursor", function (d) { return config.data_selection_isselectable(d) ? "pointer" : null; }); |
|
// Update date for selected circles |
|
targets.forEach(function (t) { |
|
$$.main.selectAll('.' + CLASS.selectedCircles + $$.getTargetSelectorSuffix(t.id)).selectAll('.' + CLASS.selectedCircle).each(function (d) { |
|
d.value = t.values[d.index].value; |
|
}); |
|
}); |
|
// MEMO: can not keep same color... |
|
//mainLineUpdate.exit().remove(); |
|
}; |
|
ChartInternal.prototype.updateLine = function (durationForExit) { |
|
var $$ = this; |
|
var mainLine = $$.main.selectAll('.' + CLASS.lines).selectAll('.' + CLASS.line) |
|
.data($$.lineData.bind($$)); |
|
var mainLineEnter = mainLine.enter().append('path') |
|
.attr('class', $$.classLine.bind($$)) |
|
.style("stroke", $$.color); |
|
$$.mainLine = mainLineEnter.merge(mainLine) |
|
.style("opacity", $$.initialOpacity.bind($$)) |
|
.style('shape-rendering', function (d) { return $$.isStepType(d) ? 'crispEdges' : ''; }) |
|
.attr('transform', null); |
|
mainLine.exit().transition().duration(durationForExit) |
|
.style('opacity', 0); |
|
}; |
|
ChartInternal.prototype.redrawLine = function (drawLine, withTransition, transition) { |
|
return [ |
|
(withTransition ? this.mainLine.transition(transition) : this.mainLine) |
|
.attr("d", drawLine) |
|
.style("stroke", this.color) |
|
.style("opacity", 1) |
|
]; |
|
}; |
|
ChartInternal.prototype.generateDrawLine = function (lineIndices, isSub) { |
|
var $$ = this, config = $$.config, |
|
line = $$.d3.line(), |
|
getPoints = $$.generateGetLinePoints(lineIndices, isSub), |
|
yScaleGetter = isSub ? $$.getSubYScale : $$.getYScale, |
|
xValue = function (d) { return (isSub ? $$.subxx : $$.xx).call($$, d); }, |
|
yValue = function (d, i) { |
|
return config.data_groups.length > 0 ? getPoints(d, i)[0][1] : yScaleGetter.call($$, d.id)(d.value); |
|
}; |
|
|
|
line = config.axis_rotated ? line.x(yValue).y(xValue) : line.x(xValue).y(yValue); |
|
if (!config.line_connectNull) { line = line.defined(function (d) { return d.value != null; }); } |
|
return function (d) { |
|
var values = config.line_connectNull ? $$.filterRemoveNull(d.values) : d.values, |
|
x = isSub ? $$.subX : $$.x, y = yScaleGetter.call($$, d.id), x0 = 0, y0 = 0, path; |
|
if ($$.isLineType(d)) { |
|
if (config.data_regions[d.id]) { |
|
path = $$.lineWithRegions(values, x, y, config.data_regions[d.id]); |
|
} else { |
|
if ($$.isStepType(d)) { values = $$.convertValuesToStep(values); } |
|
path = line.curve($$.getInterpolate(d))(values); |
|
} |
|
} else { |
|
if (values[0]) { |
|
x0 = x(values[0].x); |
|
y0 = y(values[0].value); |
|
} |
|
path = config.axis_rotated ? "M " + y0 + " " + x0 : "M " + x0 + " " + y0; |
|
} |
|
return path ? path : "M 0 0"; |
|
}; |
|
}; |
|
ChartInternal.prototype.generateGetLinePoints = function (lineIndices, isSub) { // partial duplication of generateGetBarPoints |
|
var $$ = this, config = $$.config, |
|
lineTargetsNum = lineIndices.__max__ + 1, |
|
x = $$.getShapeX(0, lineTargetsNum, lineIndices, !!isSub), |
|
y = $$.getShapeY(!!isSub), |
|
lineOffset = $$.getShapeOffset($$.isLineType, lineIndices, !!isSub), |
|
yScale = isSub ? $$.getSubYScale : $$.getYScale; |
|
return function (d, i) { |
|
var y0 = yScale.call($$, d.id)(0), |
|
offset = lineOffset(d, i) || y0, // offset is for stacked area chart |
|
posX = x(d), posY = y(d); |
|
// fix posY not to overflow opposite quadrant |
|
if (config.axis_rotated) { |
|
if ((0 < d.value && posY < y0) || (d.value < 0 && y0 < posY)) { posY = y0; } |
|
} |
|
// 1 point that marks the line position |
|
return [ |
|
[posX, posY - (y0 - offset)], |
|
[posX, posY - (y0 - offset)], // needed for compatibility |
|
[posX, posY - (y0 - offset)], // needed for compatibility |
|
[posX, posY - (y0 - offset)] // needed for compatibility |
|
]; |
|
}; |
|
}; |
|
|
|
|
|
ChartInternal.prototype.lineWithRegions = function (d, x, y, _regions) { |
|
var $$ = this, config = $$.config, |
|
prev = -1, i, j, |
|
s = "M", sWithRegion, |
|
xp, yp, dx, dy, dd, diff, diffx2, |
|
xOffset = $$.isCategorized() ? 0.5 : 0, |
|
xValue, yValue, |
|
regions = []; |
|
|
|
function isWithinRegions(x, regions) { |
|
var i; |
|
for (i = 0; i < regions.length; i++) { |
|
if (regions[i].start < x && x <= regions[i].end) { return true; } |
|
} |
|
return false; |
|
} |
|
|
|
// Check start/end of regions |
|
if (isDefined(_regions)) { |
|
for (i = 0; i < _regions.length; i++) { |
|
regions[i] = {}; |
|
if (isUndefined(_regions[i].start)) { |
|
regions[i].start = d[0].x; |
|
} else { |
|
regions[i].start = $$.isTimeSeries() ? $$.parseDate(_regions[i].start) : _regions[i].start; |
|
} |
|
if (isUndefined(_regions[i].end)) { |
|
regions[i].end = d[d.length - 1].x; |
|
} else { |
|
regions[i].end = $$.isTimeSeries() ? $$.parseDate(_regions[i].end) : _regions[i].end; |
|
} |
|
} |
|
} |
|
|
|
// Set scales |
|
xValue = config.axis_rotated ? function (d) { return y(d.value); } : function (d) { return x(d.x); }; |
|
yValue = config.axis_rotated ? function (d) { return x(d.x); } : function (d) { return y(d.value); }; |
|
|
|
// Define svg generator function for region |
|
function generateM(points) { |
|
return 'M' + points[0][0] + ' ' + points[0][1] + ' ' + points[1][0] + ' ' + points[1][1]; |
|
} |
|
if ($$.isTimeSeries()) { |
|
sWithRegion = function (d0, d1, j, diff) { |
|
var x0 = d0.x.getTime(), x_diff = d1.x - d0.x, |
|
xv0 = new Date(x0 + x_diff * j), |
|
xv1 = new Date(x0 + x_diff * (j + diff)), |
|
points; |
|
if (config.axis_rotated) { |
|
points = [[y(yp(j)), x(xv0)], [y(yp(j + diff)), x(xv1)]]; |
|
} else { |
|
points = [[x(xv0), y(yp(j))], [x(xv1), y(yp(j + diff))]]; |
|
} |
|
return generateM(points); |
|
}; |
|
} else { |
|
sWithRegion = function (d0, d1, j, diff) { |
|
var points; |
|
if (config.axis_rotated) { |
|
points = [[y(yp(j), true), x(xp(j))], [y(yp(j + diff), true), x(xp(j + diff))]]; |
|
} else { |
|
points = [[x(xp(j), true), y(yp(j))], [x(xp(j + diff), true), y(yp(j + diff))]]; |
|
} |
|
return generateM(points); |
|
}; |
|
} |
|
|
|
// Generate |
|
for (i = 0; i < d.length; i++) { |
|
|
|
// Draw as normal |
|
if (isUndefined(regions) || ! isWithinRegions(d[i].x, regions)) { |
|
s += " " + xValue(d[i]) + " " + yValue(d[i]); |
|
} |
|
// Draw with region // TODO: Fix for horizotal charts |
|
else { |
|
xp = $$.getScale(d[i - 1].x + xOffset, d[i].x + xOffset, $$.isTimeSeries()); |
|
yp = $$.getScale(d[i - 1].value, d[i].value); |
|
|
|
dx = x(d[i].x) - x(d[i - 1].x); |
|
dy = y(d[i].value) - y(d[i - 1].value); |
|
dd = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); |
|
diff = 2 / dd; |
|
diffx2 = diff * 2; |
|
|
|
for (j = diff; j <= 1; j += diffx2) { |
|
s += sWithRegion(d[i - 1], d[i], j, diff); |
|
} |
|
} |
|
prev = d[i].x; |
|
} |
|
|
|
return s; |
|
}; |
|
|
|
|
|
ChartInternal.prototype.updateArea = function (durationForExit) { |
|
var $$ = this, d3 = $$.d3; |
|
var mainArea = $$.main.selectAll('.' + CLASS.areas).selectAll('.' + CLASS.area) |
|
.data($$.lineData.bind($$)); |
|
var mainAreaEnter = mainArea.enter().append('path') |
|
.attr("class", $$.classArea.bind($$)) |
|
.style("fill", $$.color) |
|
.style("opacity", function () { $$.orgAreaOpacity = +d3.select(this).style('opacity'); return 0; }); |
|
$$.mainArea = mainAreaEnter.merge(mainArea) |
|
.style("opacity", $$.orgAreaOpacity); |
|
mainArea.exit().transition().duration(durationForExit) |
|
.style('opacity', 0); |
|
}; |
|
ChartInternal.prototype.redrawArea = function (drawArea, withTransition, transition) { |
|
return [ |
|
(withTransition ? this.mainArea.transition(transition) : this.mainArea) |
|
.attr("d", drawArea) |
|
.style("fill", this.color) |
|
.style("opacity", this.orgAreaOpacity) |
|
]; |
|
}; |
|
ChartInternal.prototype.generateDrawArea = function (areaIndices, isSub) { |
|
var $$ = this, config = $$.config, area = $$.d3.area(), |
|
getPoints = $$.generateGetAreaPoints(areaIndices, isSub), |
|
yScaleGetter = isSub ? $$.getSubYScale : $$.getYScale, |
|
xValue = function (d) { return (isSub ? $$.subxx : $$.xx).call($$, d); }, |
|
value0 = function (d, i) { |
|
return config.data_groups.length > 0 ? getPoints(d, i)[0][1] : yScaleGetter.call($$, d.id)($$.getAreaBaseValue(d.id)); |
|
}, |
|
value1 = function (d, i) { |
|
return config.data_groups.length > 0 ? getPoints(d, i)[1][1] : yScaleGetter.call($$, d.id)(d.value); |
|
}; |
|
|
|
area = config.axis_rotated ? area.x0(value0).x1(value1).y(xValue) : area.x(xValue).y0(config.area_above ? 0 : value0).y1(value1); |
|
if (!config.line_connectNull) { |
|
area = area.defined(function (d) { return d.value !== null; }); |
|
} |
|
|
|
return function (d) { |
|
var values = config.line_connectNull ? $$.filterRemoveNull(d.values) : d.values, |
|
x0 = 0, y0 = 0, path; |
|
if ($$.isAreaType(d)) { |
|
if ($$.isStepType(d)) { values = $$.convertValuesToStep(values); } |
|
path = area.curve($$.getInterpolate(d))(values); |
|
} else { |
|
if (values[0]) { |
|
x0 = $$.x(values[0].x); |
|
y0 = $$.getYScale(d.id)(values[0].value); |
|
} |
|
path = config.axis_rotated ? "M " + y0 + " " + x0 : "M " + x0 + " " + y0; |
|
} |
|
return path ? path : "M 0 0"; |
|
}; |
|
}; |
|
ChartInternal.prototype.getAreaBaseValue = function () { |
|
return 0; |
|
}; |
|
ChartInternal.prototype.generateGetAreaPoints = function (areaIndices, isSub) { // partial duplication of generateGetBarPoints |
|
var $$ = this, config = $$.config, |
|
areaTargetsNum = areaIndices.__max__ + 1, |
|
x = $$.getShapeX(0, areaTargetsNum, areaIndices, !!isSub), |
|
y = $$.getShapeY(!!isSub), |
|
areaOffset = $$.getShapeOffset($$.isAreaType, areaIndices, !!isSub), |
|
yScale = isSub ? $$.getSubYScale : $$.getYScale; |
|
return function (d, i) { |
|
var y0 = yScale.call($$, d.id)(0), |
|
offset = areaOffset(d, i) || y0, // offset is for stacked area chart |
|
posX = x(d), posY = y(d); |
|
// fix posY not to overflow opposite quadrant |
|
if (config.axis_rotated) { |
|
if ((0 < d.value && posY < y0) || (d.value < 0 && y0 < posY)) { posY = y0; } |
|
} |
|
// 1 point that marks the area position |
|
return [ |
|
[posX, offset], |
|
[posX, posY - (y0 - offset)], |
|
[posX, posY - (y0 - offset)], // needed for compatibility |
|
[posX, offset] // needed for compatibility |
|
]; |
|
}; |
|
}; |
|
|
|
|
|
ChartInternal.prototype.updateCircle = function (cx, cy) { |
|
var $$ = this; |
|
var mainCircle = $$.main.selectAll('.' + CLASS.circles).selectAll('.' + CLASS.circle) |
|
.data($$.lineOrScatterData.bind($$)); |
|
var mainCircleEnter = mainCircle.enter().append("circle") |
|
.attr("class", $$.classCircle.bind($$)) |
|
.attr("cx", cx) |
|
.attr("cy", cy) |
|
.attr("r", $$.pointR.bind($$)) |
|
.style("fill", $$.color); |
|
$$.mainCircle = mainCircleEnter.merge(mainCircle) |
|
.style("opacity", $$.initialOpacityForCircle.bind($$)); |
|
mainCircle.exit() |
|
.style("opacity", 0); |
|
}; |
|
ChartInternal.prototype.redrawCircle = function (cx, cy, withTransition, transition) { |
|
var $$ = this, |
|
selectedCircles = $$.main.selectAll('.' + CLASS.selectedCircle); |
|
return [ |
|
(withTransition ? $$.mainCircle.transition(transition) : $$.mainCircle) |
|
.style('opacity', this.opacityForCircle.bind($$)) |
|
.style("fill", $$.color) |
|
.attr("cx", cx) |
|
.attr("cy", cy), |
|
(withTransition ? selectedCircles.transition(transition) : selectedCircles) |
|
.attr("cx", cx) |
|
.attr("cy", cy) |
|
]; |
|
}; |
|
ChartInternal.prototype.circleX = function (d) { |
|
return d.x || d.x === 0 ? this.x(d.x) : null; |
|
}; |
|
ChartInternal.prototype.updateCircleY = function () { |
|
var $$ = this, lineIndices, getPoints; |
|
if ($$.config.data_groups.length > 0) { |
|
lineIndices = $$.getShapeIndices($$.isLineType), |
|
getPoints = $$.generateGetLinePoints(lineIndices); |
|
$$.circleY = function (d, i) { |
|
return getPoints(d, i)[0][1]; |
|
}; |
|
} else { |
|
$$.circleY = function (d) { |
|
return $$.getYScale(d.id)(d.value); |
|
}; |
|
} |
|
}; |
|
ChartInternal.prototype.getCircles = function (i, id) { |
|
var $$ = this; |
|
return (id ? $$.main.selectAll('.' + CLASS.circles + $$.getTargetSelectorSuffix(id)) : $$.main).selectAll('.' + CLASS.circle + (isValue(i) ? '-' + i : '')); |
|
}; |
|
ChartInternal.prototype.expandCircles = function (i, id, reset) { |
|
var $$ = this, |
|
r = $$.pointExpandedR.bind($$); |
|
if (reset) { $$.unexpandCircles(); } |
|
$$.getCircles(i, id) |
|
.classed(CLASS.EXPANDED, true) |
|
.attr('r', r); |
|
}; |
|
ChartInternal.prototype.unexpandCircles = function (i) { |
|
var $$ = this, |
|
r = $$.pointR.bind($$); |
|
$$.getCircles(i) |
|
.filter(function () { return $$.d3.select(this).classed(CLASS.EXPANDED); }) |
|
.classed(CLASS.EXPANDED, false) |
|
.attr('r', r); |
|
}; |
|
ChartInternal.prototype.pointR = function (d) { |
|
var $$ = this, config = $$.config; |
|
return $$.isStepType(d) ? 0 : (isFunction(config.point_r) ? config.point_r(d) : config.point_r); |
|
}; |
|
ChartInternal.prototype.pointExpandedR = function (d) { |
|
var $$ = this, config = $$.config; |
|
if (config.point_focus_expand_enabled) { |
|
return isFunction(config.point_focus_expand_r) ? config.point_focus_expand_r(d) : ((config.point_focus_expand_r) ? config.point_focus_expand_r : $$.pointR(d) * 1.75); |
|
} else { |
|
return $$.pointR(d); |
|
} |
|
}; |
|
ChartInternal.prototype.pointSelectR = function (d) { |
|
var $$ = this, config = $$.config; |
|
return isFunction(config.point_select_r) ? config.point_select_r(d) : ((config.point_select_r) ? config.point_select_r : $$.pointR(d) * 4); |
|
}; |
|
ChartInternal.prototype.isWithinCircle = function (that, r) { |
|
var d3 = this.d3, |
|
mouse = d3.mouse(that), d3_this = d3.select(that), |
|
cx = +d3_this.attr("cx"), cy = +d3_this.attr("cy"); |
|
return Math.sqrt(Math.pow(cx - mouse[0], 2) + Math.pow(cy - mouse[1], 2)) < r; |
|
}; |
|
ChartInternal.prototype.isWithinStep = function (that, y) { |
|
return Math.abs(y - this.d3.mouse(that)[1]) < 30; |
|
};
|
|
|