c3
Version:
D3-based reusable chart library
383 lines (367 loc) • 10.5 kB
text/typescript
import CLASS from './class'
import { ChartInternal } from './core'
import { isFunction } from './util'
ChartInternal.prototype.initBrush = function(scale) {
var $$ = this,
d3 = $$.d3
// TODO: dynamically change brushY/brushX according to axis_rotated.
$$.brush = ($$.config.axis_rotated ? d3.brushY() : d3.brushX())
.on('brush', function() {
var event = d3.event.sourceEvent
if (event && event.type === 'zoom') {
return
}
$$.redrawForBrush()
})
.on('end', function() {
var event = d3.event.sourceEvent
if (event && event.type === 'zoom') {
return
}
if ($$.brush.empty() && event && event.type !== 'end') {
$$.brush.clear()
}
})
$$.brush.updateExtent = function() {
var range = this.scale.range(),
extent
if ($$.config.axis_rotated) {
extent = [
[0, range[0]],
[$$.width2, range[1]]
]
} else {
extent = [
[range[0], 0],
[range[1], $$.height2]
]
}
this.extent(extent)
return this
}
$$.brush.updateScale = function(scale) {
this.scale = scale
return this
}
$$.brush.update = function(scale) {
this.updateScale(scale || $$.subX).updateExtent()
$$.context.select('.' + CLASS.brush).call(this)
}
$$.brush.clear = function() {
$$.context.select('.' + CLASS.brush).call($$.brush.move, null)
}
$$.brush.selection = function() {
return d3.brushSelection($$.context.select('.' + CLASS.brush).node())
}
$$.brush.selectionAsValue = function(selectionAsValue, withTransition) {
var selection, brush
if (selectionAsValue) {
if ($$.context) {
selection = [
this.scale(selectionAsValue[0]),
this.scale(selectionAsValue[1])
]
brush = $$.context.select('.' + CLASS.brush)
if (withTransition) {
brush = brush.transition()
}
$$.brush.move(brush, selection)
}
return []
}
selection = $$.brush.selection() || [0, 0]
return [this.scale.invert(selection[0]), this.scale.invert(selection[1])]
}
$$.brush.empty = function() {
var selection = $$.brush.selection()
return !selection || selection[0] === selection[1]
}
return $$.brush.updateScale(scale)
}
ChartInternal.prototype.initSubchart = function() {
var $$ = this,
config = $$.config,
context = ($$.context = $$.svg
.append('g')
.attr('transform', $$.getTranslate('context')))
// set style
context.style('visibility', 'visible')
// Define g for chart area
context
.append('g')
.attr('clip-path', $$.clipPathForSubchart)
.attr('class', CLASS.chart)
// Define g for bar chart area
context
.select('.' + CLASS.chart)
.append('g')
.attr('class', CLASS.chartBars)
// Define g for line chart area
context
.select('.' + CLASS.chart)
.append('g')
.attr('class', CLASS.chartLines)
// Add extent rect for Brush
context
.append('g')
.attr('clip-path', $$.clipPath)
.attr('class', CLASS.brush)
// ATTENTION: This must be called AFTER chart added
// Add Axis
$$.axes.subx = context
.append('g')
.attr('class', CLASS.axisX)
.attr('transform', $$.getTranslate('subx'))
.attr('clip-path', config.axis_rotated ? '' : $$.clipPathForXAxis)
.style('visibility', config.subchart_axis_x_show ? 'visible' : 'hidden');
}
ChartInternal.prototype.initSubchartBrush = function() {
var $$ = this
// Add extent rect for Brush
$$.initBrush($$.subX).updateExtent()
$$.context.select('.' + CLASS.brush).call($$.brush)
}
ChartInternal.prototype.updateTargetsForSubchart = function(targets) {
var $$ = this,
context = $$.context,
config = $$.config,
contextLineEnter,
contextLine,
contextBarEnter,
contextBar,
classChartBar = $$.classChartBar.bind($$),
classBars = $$.classBars.bind($$),
classChartLine = $$.classChartLine.bind($$),
classLines = $$.classLines.bind($$),
classAreas = $$.classAreas.bind($$)
//-- Bar --//
contextBar = context
.select('.' + CLASS.chartBars)
.selectAll('.' + CLASS.chartBar)
.data(targets)
contextBarEnter = contextBar
.enter()
.append('g')
.style('opacity', 0)
contextBarEnter.merge(contextBar).attr('class', classChartBar)
// Bars for each data
contextBarEnter.append('g').attr('class', classBars)
//-- Line --//
contextLine = context
.select('.' + CLASS.chartLines)
.selectAll('.' + CLASS.chartLine)
.data(targets)
contextLineEnter = contextLine
.enter()
.append('g')
.style('opacity', 0)
contextLineEnter.merge(contextLine).attr('class', classChartLine)
// Lines for each data
contextLineEnter.append('g').attr('class', classLines)
// Area
contextLineEnter.append('g').attr('class', classAreas)
//-- Brush --//
context
.selectAll('.' + CLASS.brush + ' rect')
.attr(
config.axis_rotated ? 'width' : 'height',
config.axis_rotated ? $$.width2 : $$.height2
)
}
ChartInternal.prototype.updateBarForSubchart = function(durationForExit) {
var $$ = this
var contextBar = $$.context
.selectAll('.' + CLASS.bars)
.selectAll('.' + CLASS.bar)
.data($$.barData.bind($$))
var contextBarEnter = contextBar
.enter()
.append('path')
.attr('class', $$.classBar.bind($$))
.style('stroke', 'none')
.style('fill', $$.color)
contextBar
.exit()
.transition()
.duration(durationForExit)
.style('opacity', 0)
.remove()
$$.contextBar = contextBarEnter
.merge(contextBar)
.style('opacity', $$.initialOpacity.bind($$))
}
ChartInternal.prototype.redrawBarForSubchart = function(
drawBarOnSub,
withTransition,
duration
) {
;(withTransition
? this.contextBar.transition(Math.random().toString()).duration(duration)
: this.contextBar
)
.attr('d', drawBarOnSub)
.style('opacity', 1)
}
ChartInternal.prototype.updateLineForSubchart = function(durationForExit) {
var $$ = this
var contextLine = $$.context
.selectAll('.' + CLASS.lines)
.selectAll('.' + CLASS.line)
.data($$.lineData.bind($$))
var contextLineEnter = contextLine
.enter()
.append('path')
.attr('class', $$.classLine.bind($$))
.style('stroke', $$.color)
contextLine
.exit()
.transition()
.duration(durationForExit)
.style('opacity', 0)
.remove()
$$.contextLine = contextLineEnter
.merge(contextLine)
.style('opacity', $$.initialOpacity.bind($$))
}
ChartInternal.prototype.redrawLineForSubchart = function(
drawLineOnSub,
withTransition,
duration
) {
;(withTransition
? this.contextLine.transition(Math.random().toString()).duration(duration)
: this.contextLine
)
.attr('d', drawLineOnSub)
.style('opacity', 1)
}
ChartInternal.prototype.updateAreaForSubchart = function(durationForExit) {
var $$ = this,
d3 = $$.d3
var contextArea = $$.context
.selectAll('.' + CLASS.areas)
.selectAll('.' + CLASS.area)
.data($$.lineData.bind($$))
var contextAreaEnter = contextArea
.enter()
.append('path')
.attr('class', $$.classArea.bind($$))
.style('fill', $$.color)
.style('opacity', function() {
$$.orgAreaOpacity = +d3.select(this).style('opacity')
return 0
})
contextArea
.exit()
.transition()
.duration(durationForExit)
.style('opacity', 0)
.remove()
$$.contextArea = contextAreaEnter.merge(contextArea).style('opacity', 0)
}
ChartInternal.prototype.redrawAreaForSubchart = function(
drawAreaOnSub,
withTransition,
duration
) {
;(withTransition
? this.contextArea.transition(Math.random().toString()).duration(duration)
: this.contextArea
)
.attr('d', drawAreaOnSub)
.style('fill', this.color)
.style('opacity', this.orgAreaOpacity)
}
ChartInternal.prototype.redrawSubchart = function(
withSubchart,
transitions,
duration,
durationForExit,
areaIndices,
barIndices,
lineIndices
) {
var $$ = this,
d3 = $$.d3,
drawAreaOnSub,
drawBarOnSub,
drawLineOnSub
// reflect main chart to extent on subchart if zoomed
if (d3.event && d3.event.type === 'zoom') {
$$.brush.selectionAsValue($$.x.orgDomain())
}
// update subchart elements if needed
if (withSubchart) {
// extent rect
if (!$$.brush.empty()) {
$$.brush.selectionAsValue($$.x.orgDomain())
}
// setup drawer - MEMO: this must be called after axis updated
drawAreaOnSub = $$.generateDrawArea(areaIndices, true)
drawBarOnSub = $$.generateDrawBar(barIndices, true)
drawLineOnSub = $$.generateDrawLine(lineIndices, true)
$$.updateBarForSubchart(duration)
$$.updateLineForSubchart(duration)
$$.updateAreaForSubchart(duration)
$$.redrawBarForSubchart(drawBarOnSub, duration, duration)
$$.redrawLineForSubchart(drawLineOnSub, duration, duration)
$$.redrawAreaForSubchart(drawAreaOnSub, duration, duration)
}
}
ChartInternal.prototype.redrawForBrush = function() {
var $$ = this,
x = $$.x,
d3 = $$.d3,
s
$$.redraw({
withTransition: false,
withY: $$.config.zoom_rescale,
withSubchart: false,
withUpdateXDomain: true,
withEventRect: false,
withDimension: false
})
// update zoom transation binded to event rect
s = d3.event.selection || $$.brush.scale.range()
$$.main
.select('.' + CLASS.eventRect)
.call(
$$.zoom.transform,
d3.zoomIdentity.scale($$.width / (s[1] - s[0])).translate(-s[0], 0)
)
$$.config.subchart_onbrush.call($$.api, x.orgDomain())
}
ChartInternal.prototype.transformContext = function(
withTransition,
transitions
) {
var $$ = this,
subXAxis
if (transitions && transitions.axisSubX) {
subXAxis = transitions.axisSubX
} else {
subXAxis = $$.context.select('.' + CLASS.axisX)
if (withTransition) {
subXAxis = subXAxis.transition()
}
}
$$.context.attr('transform', $$.getTranslate('context'))
subXAxis.attr('transform', $$.getTranslate('subx'))
}
ChartInternal.prototype.getDefaultSelection = function() {
var $$ = this,
config = $$.config,
selection = isFunction(config.axis_x_selection)
? config.axis_x_selection($$.getXDomain($$.data.targets))
: config.axis_x_selection
if ($$.isTimeSeries()) {
selection = [$$.parseDate(selection[0]), $$.parseDate(selection[1])]
}
return selection
}
ChartInternal.prototype.removeSubchart = function() {
const $$ = this
$$.brush = null
$$.context.remove()
$$.context = null
}