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, 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, 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, }); }; c3_chart_internal_fn.generateFlow = function (args) { var $$ = this, config = $$.config, d3 = $$.d3, CLASS = $$.CLASS; 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 = $$.xgrid || d3.selectAll([]), 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([]); // 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); } // 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.x === 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)'; d3.transition().ease('linear').duration(durationForFlow).each(function () { wait.add($$.axes.x.transition().call($$.xAxis)); wait.add(mainBar.transition().attr('transform', transform)); wait.add(mainLine.transition().attr('transform', transform)); wait.add(mainArea.transition().attr('transform', transform)); wait.add(mainCircle.transition().attr('transform', transform)); wait.add(mainText.transition().attr('transform', transform)); wait.add(mainRegion.filter($$.isRegionOnX).transition().attr('transform', transform)); wait.add(xgrid.transition().attr('transform', transform)); wait.add(xgridLines.transition().attr('transform', transform)); }) .call(wait, function () { var i, shapes = [], texts = [], eventRects = []; // remove flowed elements if (flowLength) { for (i = 0; i < flowLength; i++) { shapes.push('.' + CLASS[_shape] + '-' + (flowIndex + i)); texts.push('.' + CLASS[_text] + '-' + (flowIndex + i)); eventRects.push('.' + CLASS[_eventRect] + '-' + (flowIndex + i)); } $$.svg.selectAll('.' + CLASS[_shapes]).selectAll(shapes).remove(); $$.svg.selectAll('.' + CLASS[_texts]).selectAll(texts).remove(); $$.svg.selectAll('.' + CLASS[_eventRects]).selectAll(eventRects).remove(); $$.svg.select('.' + CLASS[_xgrid]).remove(); } // draw again for removing flowed elements and reverting attr xgrid .attr('transform', null) .attr($$.xgridAttr); 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.select('rect').filter($$.isRegionOnX) .attr("x", $$.regionX.bind($$)) .attr("width", $$.regionWidth.bind($$)); $$.updateEventRect(); // callback for end of flow done(); }); }; };