import CLASS from './class' import { Chart, ChartInternal } from './core' import { isValue, isDefined, diffDomain } from './util' Chart.prototype.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 }) } ChartInternal.prototype.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 }) } }