c3
Version:
D3-based reusable chart library
348 lines (319 loc) • 9.95 kB
text/typescript
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
})
}
}