|
|
|
import CLASS from './class';
|
|
|
|
import { c3_chart_fn, c3_chart_internal_fn } from './core';
|
|
|
|
import { isValue, isDefined, diffDomain } from './util';
|
|
|
|
|
|
|
|
c3_chart_fn.flow = function (args) {
|
|
|
|
var $$ = this.internal,
|
|
|
|
targets, data, notfoundIds = [], orgDataCount = $$.getMaxDataCount(),
|
|
|
|
dataCount, domain, baseTarget, baseValue, length = 0, tail = 0, diff, to;
|
|
|
|
|
|
|
|
if (args.json) {
|
|
|
|
data = $$.convertJsonToData(args.json, args.keys);
|
|
|
|
}
|
|
|
|
else if (args.rows) {
|
|
|
|
data = $$.convertRowsToData(args.rows);
|
|
|
|
}
|
|
|
|
else if (args.columns) {
|
|
|
|
data = $$.convertColumnsToData(args.columns);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
targets = $$.convertDataToTargets(data, true);
|
|
|
|
|
|
|
|
// Update/Add data
|
|
|
|
$$.data.targets.forEach(function (t) {
|
|
|
|
var found = false, i, j;
|
|
|
|
for (i = 0; i < targets.length; i++) {
|
|
|
|
if (t.id === targets[i].id) {
|
|
|
|
found = true;
|
|
|
|
|
|
|
|
if (t.values[t.values.length - 1]) {
|
|
|
|
tail = t.values[t.values.length - 1].index + 1;
|
|
|
|
}
|
|
|
|
length = targets[i].values.length;
|
|
|
|
|
|
|
|
for (j = 0; j < length; j++) {
|
|
|
|
targets[i].values[j].index = tail + j;
|
|
|
|
if (!$$.isTimeSeries()) {
|
|
|
|
targets[i].values[j].x = tail + j;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
t.values = t.values.concat(targets[i].values);
|
|
|
|
|
|
|
|
targets.splice(i, 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!found) { notfoundIds.push(t.id); }
|
|
|
|
});
|
|
|
|
|
|
|
|
// Append null for not found targets
|
|
|
|
$$.data.targets.forEach(function (t) {
|
|
|
|
var i, j;
|
|
|
|
for (i = 0; i < notfoundIds.length; i++) {
|
|
|
|
if (t.id === notfoundIds[i]) {
|
|
|
|
tail = t.values[t.values.length - 1].index + 1;
|
|
|
|
for (j = 0; j < length; j++) {
|
|
|
|
t.values.push({
|
|
|
|
id: t.id,
|
|
|
|
index: tail + j,
|
|
|
|
x: $$.isTimeSeries() ? $$.getOtherTargetX(tail + j) : tail + j,
|
|
|
|
value: null
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Generate null values for new target
|
|
|
|
if ($$.data.targets.length) {
|
|
|
|
targets.forEach(function (t) {
|
|
|
|
var i, missing = [];
|
|
|
|
for (i = $$.data.targets[0].values[0].index; i < tail; i++) {
|
|
|
|
missing.push({
|
|
|
|
id: t.id,
|
|
|
|
index: i,
|
|
|
|
x: $$.isTimeSeries() ? $$.getOtherTargetX(i) : i,
|
|
|
|
value: null
|
|
|
|
});
|
|
|
|
}
|
|
|
|
t.values.forEach(function (v) {
|
|
|
|
v.index += tail;
|
|
|
|
if (!$$.isTimeSeries()) {
|
|
|
|
v.x += tail;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
t.values = missing.concat(t.values);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
$$.data.targets = $$.data.targets.concat(targets); // add remained
|
|
|
|
|
|
|
|
// check data count because behavior needs to change when it's only one
|
|
|
|
dataCount = $$.getMaxDataCount();
|
|
|
|
baseTarget = $$.data.targets[0];
|
|
|
|
baseValue = baseTarget.values[0];
|
|
|
|
|
|
|
|
// Update length to flow if needed
|
|
|
|
if (isDefined(args.to)) {
|
|
|
|
length = 0;
|
|
|
|
to = $$.isTimeSeries() ? $$.parseDate(args.to) : args.to;
|
|
|
|
baseTarget.values.forEach(function (v) {
|
|
|
|
if (v.x < to) { length++; }
|
|
|
|
});
|
|
|
|
} else if (isDefined(args.length)) {
|
|
|
|
length = args.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If only one data, update the domain to flow from left edge of the chart
|
|
|
|
if (!orgDataCount) {
|
|
|
|
if ($$.isTimeSeries()) {
|
|
|
|
if (baseTarget.values.length > 1) {
|
|
|
|
diff = baseTarget.values[baseTarget.values.length - 1].x - baseValue.x;
|
|
|
|
} else {
|
|
|
|
diff = baseValue.x - $$.getXDomain($$.data.targets)[0];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
diff = 1;
|
|
|
|
}
|
|
|
|
domain = [baseValue.x - diff, baseValue.x];
|
|
|
|
$$.updateXDomain(null, true, true, false, domain);
|
|
|
|
} else if (orgDataCount === 1) {
|
|
|
|
if ($$.isTimeSeries()) {
|
|
|
|
diff = (baseTarget.values[baseTarget.values.length - 1].x - baseValue.x) / 2;
|
|
|
|
domain = [new Date(+baseValue.x - diff), new Date(+baseValue.x + diff)];
|
|
|
|
$$.updateXDomain(null, true, true, false, domain);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set targets
|
|
|
|
$$.updateTargets($$.data.targets);
|
|
|
|
|
|
|
|
// Redraw with new targets
|
|
|
|
$$.redraw({
|
|
|
|
flow: {
|
|
|
|
index: baseValue.index,
|
|
|
|
length: length,
|
|
|
|
duration: isValue(args.duration) ? args.duration : $$.config.transition_duration,
|
|
|
|
done: args.done,
|
|
|
|
orgDataCount: orgDataCount,
|
|
|
|
},
|
|
|
|
withLegend: true,
|
|
|
|
withTransition: orgDataCount > 1,
|
|
|
|
withTrimXDomain: false,
|
|
|
|
withUpdateXAxis: true,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
c3_chart_internal_fn.generateFlow = function (args) {
|
|
|
|
var $$ = this, config = $$.config, d3 = $$.d3;
|
|
|
|
|
|
|
|
return function () {
|
|
|
|
var targets = args.targets,
|
|
|
|
flow = args.flow,
|
|
|
|
drawBar = args.drawBar,
|
|
|
|
drawLine = args.drawLine,
|
|
|
|
drawArea = args.drawArea,
|
|
|
|
cx = args.cx,
|
|
|
|
cy = args.cy,
|
|
|
|
xv = args.xv,
|
|
|
|
xForText = args.xForText,
|
|
|
|
yForText = args.yForText,
|
|
|
|
duration = args.duration;
|
|
|
|
|
|
|
|
var translateX, scaleX = 1, transform,
|
|
|
|
flowIndex = flow.index,
|
|
|
|
flowLength = flow.length,
|
|
|
|
flowStart = $$.getValueOnIndex($$.data.targets[0].values, flowIndex),
|
|
|
|
flowEnd = $$.getValueOnIndex($$.data.targets[0].values, flowIndex + flowLength),
|
|
|
|
orgDomain = $$.x.domain(), domain,
|
|
|
|
durationForFlow = flow.duration || duration,
|
|
|
|
done = flow.done || function () {},
|
|
|
|
wait = $$.generateWait();
|
|
|
|
|
|
|
|
var xgrid,
|
|
|
|
xgridLines,
|
|
|
|
mainRegion,
|
|
|
|
mainText,
|
|
|
|
mainBar,
|
|
|
|
mainLine,
|
|
|
|
mainArea,
|
|
|
|
mainCircle;
|
|
|
|
|
|
|
|
// set flag
|
|
|
|
$$.flowing = true;
|
|
|
|
|
|
|
|
// remove head data after rendered
|
|
|
|
$$.data.targets.forEach(function (d) {
|
|
|
|
d.values.splice(0, flowLength);
|
|
|
|
});
|
|
|
|
|
|
|
|
// update x domain to generate axis elements for flow
|
|
|
|
domain = $$.updateXDomain(targets, true, true);
|
|
|
|
// update elements related to x scale
|
|
|
|
if ($$.updateXGrid) { $$.updateXGrid(true); }
|
|
|
|
|
|
|
|
xgrid = $$.xgrid || d3.selectAll([]); // xgrid needs to be obtained after updateXGrid
|
|
|
|
xgridLines = $$.xgridLines || d3.selectAll([]);
|
|
|
|
mainRegion = $$.mainRegion || d3.selectAll([]);
|
|
|
|
mainText = $$.mainText || d3.selectAll([]);
|
|
|
|
mainBar = $$.mainBar || d3.selectAll([]);
|
|
|
|
mainLine = $$.mainLine || d3.selectAll([]);
|
|
|
|
mainArea = $$.mainArea || d3.selectAll([]);
|
|
|
|
mainCircle = $$.mainCircle || d3.selectAll([]);
|
|
|
|
|
|
|
|
// generate transform to flow
|
|
|
|
if (!flow.orgDataCount) { // if empty
|
|
|
|
if ($$.data.targets[0].values.length !== 1) {
|
|
|
|
translateX = $$.x(orgDomain[0]) - $$.x(domain[0]);
|
|
|
|
} else {
|
|
|
|
if ($$.isTimeSeries()) {
|
|
|
|
flowStart = $$.getValueOnIndex($$.data.targets[0].values, 0);
|
|
|
|
flowEnd = $$.getValueOnIndex($$.data.targets[0].values, $$.data.targets[0].values.length - 1);
|
|
|
|
translateX = $$.x(flowStart.x) - $$.x(flowEnd.x);
|
|
|
|
} else {
|
|
|
|
translateX = diffDomain(domain) / 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (flow.orgDataCount === 1 || (flowStart && flowStart.x) === (flowEnd && flowEnd.x)) {
|
|
|
|
translateX = $$.x(orgDomain[0]) - $$.x(domain[0]);
|
|
|
|
} else {
|
|
|
|
if ($$.isTimeSeries()) {
|
|
|
|
translateX = ($$.x(orgDomain[0]) - $$.x(domain[0]));
|
|
|
|
} else {
|
|
|
|
translateX = ($$.x(flowStart.x) - $$.x(flowEnd.x));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
scaleX = (diffDomain(orgDomain) / diffDomain(domain));
|
|
|
|
transform = 'translate(' + translateX + ',0) scale(' + scaleX + ',1)';
|
|
|
|
|
|
|
|
$$.hideXGridFocus();
|
|
|
|
|
|
|
|
var flowTransition = d3.transition().ease(d3.easeLinear).duration(durationForFlow);
|
|
|
|
wait.add($$.xAxis($$.axes.x, flowTransition));
|
|
|
|
wait.add(mainBar.transition(flowTransition).attr('transform', transform));
|
|
|
|
wait.add(mainLine.transition(flowTransition).attr('transform', transform));
|
|
|
|
wait.add(mainArea.transition(flowTransition).attr('transform', transform));
|
|
|
|
wait.add(mainCircle.transition(flowTransition).attr('transform', transform));
|
|
|
|
wait.add(mainText.transition(flowTransition).attr('transform', transform));
|
|
|
|
wait.add(mainRegion.filter($$.isRegionOnX).transition(flowTransition).attr('transform', transform));
|
|
|
|
wait.add(xgrid.transition(flowTransition).attr('transform', transform));
|
|
|
|
wait.add(xgridLines.transition(flowTransition).attr('transform', transform));
|
|
|
|
wait(function () {
|
|
|
|
var i, shapes = [], texts = [];
|
|
|
|
|
|
|
|
// remove flowed elements
|
|
|
|
if (flowLength) {
|
|
|
|
for (i = 0; i < flowLength; i++) {
|
|
|
|
shapes.push('.' + CLASS.shape + '-' + (flowIndex + i));
|
|
|
|
texts.push('.' + CLASS.text + '-' + (flowIndex + i));
|
|
|
|
}
|
|
|
|
$$.svg.selectAll('.' + CLASS.shapes).selectAll(shapes).remove();
|
|
|
|
$$.svg.selectAll('.' + CLASS.texts).selectAll(texts).remove();
|
|
|
|
$$.svg.select('.' + CLASS.xgrid).remove();
|
|
|
|
}
|
|
|
|
|
|
|
|
// draw again for removing flowed elements and reverting attr
|
|
|
|
xgrid
|
|
|
|
.attr('transform', null)
|
|
|
|
.attr('x1', $$.xgridAttr.x1)
|
|
|
|
.attr('x2', $$.xgridAttr.x2)
|
|
|
|
.attr('y1', $$.xgridAttr.y1)
|
|
|
|
.attr('y2', $$.xgridAttr.y2)
|
|
|
|
.style("opacity", $$.xgridAttr.opacity);
|
|
|
|
xgridLines
|
|
|
|
.attr('transform', null);
|
|
|
|
xgridLines.select('line')
|
|
|
|
.attr("x1", config.axis_rotated ? 0 : xv)
|
|
|
|
.attr("x2", config.axis_rotated ? $$.width : xv);
|
|
|
|
xgridLines.select('text')
|
|
|
|
.attr("x", config.axis_rotated ? $$.width : 0)
|
|
|
|
.attr("y", xv);
|
|
|
|
mainBar
|
|
|
|
.attr('transform', null)
|
|
|
|
.attr("d", drawBar);
|
|
|
|
mainLine
|
|
|
|
.attr('transform', null)
|
|
|
|
.attr("d", drawLine);
|
|
|
|
mainArea
|
|
|
|
.attr('transform', null)
|
|
|
|
.attr("d", drawArea);
|
|
|
|
mainCircle
|
|
|
|
.attr('transform', null)
|
|
|
|
.attr("cx", cx)
|
|
|
|
.attr("cy", cy);
|
|
|
|
mainText
|
|
|
|
.attr('transform', null)
|
|
|
|
.attr('x', xForText)
|
|
|
|
.attr('y', yForText)
|
|
|
|
.style('fill-opacity', $$.opacityForText.bind($$));
|
|
|
|
mainRegion
|
|
|
|
.attr('transform', null);
|
|
|
|
mainRegion.filter($$.isRegionOnX)
|
|
|
|
.attr("x", $$.regionX.bind($$))
|
|
|
|
.attr("width", $$.regionWidth.bind($$));
|
|
|
|
|
|
|
|
// callback for end of flow
|
|
|
|
done();
|
|
|
|
|
|
|
|
$$.flowing = false;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|