c3
Version:
D3-based reusable chart library
608 lines (593 loc) • 17.9 kB
text/typescript
import CLASS from './class'
import { isValue, isFunction, isString, isEmpty, getBBox } from './util'
import { AxisInternal } from './axis-internal'
export default class AxisClass {
owner: any
d3: any
internal: typeof AxisInternal
constructor(owner) {
this.owner = owner
this.d3 = owner.d3
this.internal = AxisInternal
}
}
const Axis = AxisClass as any
Axis.prototype.init = function init() {
var $$ = this.owner,
config = $$.config,
main = $$.main
$$.axes.x = main
.append('g')
.attr('class', CLASS.axis + ' ' + CLASS.axisX)
.attr('clip-path', config.axis_x_inner ? '' : $$.clipPathForXAxis)
.attr('transform', $$.getTranslate('x'))
.style('visibility', config.axis_x_show ? 'visible' : 'hidden')
$$.axes.x
.append('text')
.attr('class', CLASS.axisXLabel)
.attr('transform', config.axis_rotated ? 'rotate(-90)' : '')
.style('text-anchor', this.textAnchorForXAxisLabel.bind(this))
$$.axes.y = main
.append('g')
.attr('class', CLASS.axis + ' ' + CLASS.axisY)
.attr('clip-path', config.axis_y_inner ? '' : $$.clipPathForYAxis)
.attr('transform', $$.getTranslate('y'))
.style('visibility', config.axis_y_show ? 'visible' : 'hidden')
$$.axes.y
.append('text')
.attr('class', CLASS.axisYLabel)
.attr('transform', config.axis_rotated ? '' : 'rotate(-90)')
.style('text-anchor', this.textAnchorForYAxisLabel.bind(this))
$$.axes.y2 = main
.append('g')
.attr('class', CLASS.axis + ' ' + CLASS.axisY2)
// clip-path?
.attr('transform', $$.getTranslate('y2'))
.style('visibility', config.axis_y2_show ? 'visible' : 'hidden')
$$.axes.y2
.append('text')
.attr('class', CLASS.axisY2Label)
.attr('transform', config.axis_rotated ? '' : 'rotate(-90)')
.style('text-anchor', this.textAnchorForY2AxisLabel.bind(this))
}
Axis.prototype.getXAxis = function getXAxis(
scale,
orient,
tickFormat,
tickValues,
withOuterTick,
withoutTransition,
withoutRotateTickText
) {
var $$ = this.owner,
config = $$.config,
axisParams = {
isCategory: $$.isCategorized(),
withOuterTick: withOuterTick,
tickMultiline: config.axis_x_tick_multiline,
tickMultilineMax: config.axis_x_tick_multiline
? Number(config.axis_x_tick_multilineMax)
: 0,
tickWidth: config.axis_x_tick_width,
tickTextRotate: withoutRotateTickText ? 0 : config.axis_x_tick_rotate,
withoutTransition: withoutTransition
},
axis = new this.internal(this, axisParams).axis.scale(scale).orient(orient)
if ($$.isTimeSeries() && tickValues && typeof tickValues !== 'function') {
tickValues = tickValues.map(function(v) {
return $$.parseDate(v)
})
}
// Set tick
axis.tickFormat(tickFormat).tickValues(tickValues)
if ($$.isCategorized()) {
axis.tickCentered(config.axis_x_tick_centered)
if (isEmpty(config.axis_x_tick_culling)) {
config.axis_x_tick_culling = false
}
}
return axis
}
Axis.prototype.updateXAxisTickValues = function updateXAxisTickValues(
targets,
axis
) {
var $$ = this.owner,
config = $$.config,
tickValues
if (config.axis_x_tick_fit || config.axis_x_tick_count) {
tickValues = this.generateTickValues(
$$.mapTargetsToUniqueXs(targets),
config.axis_x_tick_count,
$$.isTimeSeries()
)
}
if (axis) {
axis.tickValues(tickValues)
} else {
$$.xAxis.tickValues(tickValues)
$$.subXAxis.tickValues(tickValues)
}
return tickValues
}
Axis.prototype.getYAxis = function getYAxis(
axisId,
scale,
orient,
tickValues,
withOuterTick,
withoutTransition,
withoutRotateTickText
) {
const $$ = this.owner
const config = $$.config
let tickFormat = config[`axis_${axisId}_tick_format`]
if (!tickFormat && $$.isAxisNormalized(axisId)) {
tickFormat = x => `${x}%`
}
const axis = new this.internal(this, {
withOuterTick: withOuterTick,
withoutTransition: withoutTransition,
tickTextRotate: withoutRotateTickText ? 0 : config.axis_y_tick_rotate
}).axis
.scale(scale)
.orient(orient)
if (tickFormat) {
axis.tickFormat(tickFormat)
}
if ($$.isTimeSeriesY()) {
axis.ticks(config.axis_y_tick_time_type, config.axis_y_tick_time_interval)
} else {
axis.tickValues(tickValues)
}
return axis
}
Axis.prototype.getId = function getId(id) {
var config = this.owner.config
return id in config.data_axes ? config.data_axes[id] : 'y'
}
Axis.prototype.getXAxisTickFormat = function getXAxisTickFormat() {
// #2251 previously set any negative values to a whole number,
// however both should be truncated according to the users format specification
var $$ = this.owner,
config = $$.config
let format = $$.isTimeSeries()
? $$.defaultAxisTimeFormat
: $$.isCategorized()
? $$.categoryName
: function(v) {
return v
}
if (config.axis_x_tick_format) {
if (isFunction(config.axis_x_tick_format)) {
format = config.axis_x_tick_format
} else if ($$.isTimeSeries()) {
format = function(date) {
return date ? $$.axisTimeFormat(config.axis_x_tick_format)(date) : ''
}
}
}
return isFunction(format)
? function(v) {
return format.call($$, v)
}
: format
}
Axis.prototype.getTickValues = function getTickValues(tickValues, axis) {
return tickValues ? tickValues : axis ? axis.tickValues() : undefined
}
Axis.prototype.getXAxisTickValues = function getXAxisTickValues() {
return this.getTickValues(
this.owner.config.axis_x_tick_values,
this.owner.xAxis
)
}
Axis.prototype.getYAxisTickValues = function getYAxisTickValues() {
return this.getTickValues(
this.owner.config.axis_y_tick_values,
this.owner.yAxis
)
}
Axis.prototype.getY2AxisTickValues = function getY2AxisTickValues() {
return this.getTickValues(
this.owner.config.axis_y2_tick_values,
this.owner.y2Axis
)
}
Axis.prototype.getLabelOptionByAxisId = function getLabelOptionByAxisId(
axisId
) {
var $$ = this.owner,
config = $$.config,
option
if (axisId === 'y') {
option = config.axis_y_label
} else if (axisId === 'y2') {
option = config.axis_y2_label
} else if (axisId === 'x') {
option = config.axis_x_label
}
return option
}
Axis.prototype.getLabelText = function getLabelText(axisId) {
var option = this.getLabelOptionByAxisId(axisId)
return isString(option) ? option : option ? option.text : null
}
Axis.prototype.setLabelText = function setLabelText(axisId, text) {
var $$ = this.owner,
config = $$.config,
option = this.getLabelOptionByAxisId(axisId)
if (isString(option)) {
if (axisId === 'y') {
config.axis_y_label = text
} else if (axisId === 'y2') {
config.axis_y2_label = text
} else if (axisId === 'x') {
config.axis_x_label = text
}
} else if (option) {
option.text = text
}
}
Axis.prototype.getLabelPosition = function getLabelPosition(
axisId,
defaultPosition
) {
var option = this.getLabelOptionByAxisId(axisId),
position =
option && typeof option === 'object' && option.position
? option.position
: defaultPosition
return {
isInner: position.indexOf('inner') >= 0,
isOuter: position.indexOf('outer') >= 0,
isLeft: position.indexOf('left') >= 0,
isCenter: position.indexOf('center') >= 0,
isRight: position.indexOf('right') >= 0,
isTop: position.indexOf('top') >= 0,
isMiddle: position.indexOf('middle') >= 0,
isBottom: position.indexOf('bottom') >= 0
}
}
Axis.prototype.getXAxisLabelPosition = function getXAxisLabelPosition() {
return this.getLabelPosition(
'x',
this.owner.config.axis_rotated ? 'inner-top' : 'inner-right'
)
}
Axis.prototype.getYAxisLabelPosition = function getYAxisLabelPosition() {
return this.getLabelPosition(
'y',
this.owner.config.axis_rotated ? 'inner-right' : 'inner-top'
)
}
Axis.prototype.getY2AxisLabelPosition = function getY2AxisLabelPosition() {
return this.getLabelPosition(
'y2',
this.owner.config.axis_rotated ? 'inner-right' : 'inner-top'
)
}
Axis.prototype.getLabelPositionById = function getLabelPositionById(id) {
return id === 'y2'
? this.getY2AxisLabelPosition()
: id === 'y'
? this.getYAxisLabelPosition()
: this.getXAxisLabelPosition()
}
Axis.prototype.textForXAxisLabel = function textForXAxisLabel() {
return this.getLabelText('x')
}
Axis.prototype.textForYAxisLabel = function textForYAxisLabel() {
return this.getLabelText('y')
}
Axis.prototype.textForY2AxisLabel = function textForY2AxisLabel() {
return this.getLabelText('y2')
}
Axis.prototype.xForAxisLabel = function xForAxisLabel(forHorizontal, position) {
var $$ = this.owner
if (forHorizontal) {
return position.isLeft ? 0 : position.isCenter ? $$.width / 2 : $$.width
} else {
return position.isBottom
? -$$.height
: position.isMiddle
? -$$.height / 2
: 0
}
}
Axis.prototype.dxForAxisLabel = function dxForAxisLabel(
forHorizontal,
position
) {
if (forHorizontal) {
return position.isLeft ? '0.5em' : position.isRight ? '-0.5em' : '0'
} else {
return position.isTop ? '-0.5em' : position.isBottom ? '0.5em' : '0'
}
}
Axis.prototype.textAnchorForAxisLabel = function textAnchorForAxisLabel(
forHorizontal,
position
) {
if (forHorizontal) {
return position.isLeft ? 'start' : position.isCenter ? 'middle' : 'end'
} else {
return position.isBottom ? 'start' : position.isMiddle ? 'middle' : 'end'
}
}
Axis.prototype.xForXAxisLabel = function xForXAxisLabel() {
return this.xForAxisLabel(
!this.owner.config.axis_rotated,
this.getXAxisLabelPosition()
)
}
Axis.prototype.xForYAxisLabel = function xForYAxisLabel() {
return this.xForAxisLabel(
this.owner.config.axis_rotated,
this.getYAxisLabelPosition()
)
}
Axis.prototype.xForY2AxisLabel = function xForY2AxisLabel() {
return this.xForAxisLabel(
this.owner.config.axis_rotated,
this.getY2AxisLabelPosition()
)
}
Axis.prototype.dxForXAxisLabel = function dxForXAxisLabel() {
return this.dxForAxisLabel(
!this.owner.config.axis_rotated,
this.getXAxisLabelPosition()
)
}
Axis.prototype.dxForYAxisLabel = function dxForYAxisLabel() {
return this.dxForAxisLabel(
this.owner.config.axis_rotated,
this.getYAxisLabelPosition()
)
}
Axis.prototype.dxForY2AxisLabel = function dxForY2AxisLabel() {
return this.dxForAxisLabel(
this.owner.config.axis_rotated,
this.getY2AxisLabelPosition()
)
}
Axis.prototype.dyForXAxisLabel = function dyForXAxisLabel() {
var $$ = this.owner,
config = $$.config,
position = this.getXAxisLabelPosition()
if (config.axis_rotated) {
return position.isInner
? '1.2em'
: -25 - ($$.config.axis_x_inner ? 0 : this.getMaxTickWidth('x'))
} else {
return position.isInner ? '-0.5em' : $$.getHorizontalAxisHeight('x') - 10
}
}
Axis.prototype.dyForYAxisLabel = function dyForYAxisLabel() {
var $$ = this.owner,
position = this.getYAxisLabelPosition()
if ($$.config.axis_rotated) {
return position.isInner ? '-0.5em' : '3em'
} else {
return position.isInner
? '1.2em'
: -10 - ($$.config.axis_y_inner ? 0 : this.getMaxTickWidth('y') + 10)
}
}
Axis.prototype.dyForY2AxisLabel = function dyForY2AxisLabel() {
var $$ = this.owner,
position = this.getY2AxisLabelPosition()
if ($$.config.axis_rotated) {
return position.isInner ? '1.2em' : '-2.2em'
} else {
return position.isInner
? '-0.5em'
: 15 + ($$.config.axis_y2_inner ? 0 : this.getMaxTickWidth('y2') + 15)
}
}
Axis.prototype.textAnchorForXAxisLabel = function textAnchorForXAxisLabel() {
var $$ = this.owner
return this.textAnchorForAxisLabel(
!$$.config.axis_rotated,
this.getXAxisLabelPosition()
)
}
Axis.prototype.textAnchorForYAxisLabel = function textAnchorForYAxisLabel() {
var $$ = this.owner
return this.textAnchorForAxisLabel(
$$.config.axis_rotated,
this.getYAxisLabelPosition()
)
}
Axis.prototype.textAnchorForY2AxisLabel = function textAnchorForY2AxisLabel() {
var $$ = this.owner
return this.textAnchorForAxisLabel(
$$.config.axis_rotated,
this.getY2AxisLabelPosition()
)
}
Axis.prototype.getMaxTickWidth = function getMaxTickWidth(
id,
withoutRecompute
) {
var $$ = this.owner,
maxWidth = 0,
targetsToShow,
scale,
axis,
dummy,
svg
if (withoutRecompute && $$.currentMaxTickWidths[id]) {
return $$.currentMaxTickWidths[id]
}
if ($$.svg) {
targetsToShow = $$.filterTargetsToShow($$.data.targets)
if (id === 'y') {
scale = $$.y.copy().domain($$.getYDomain(targetsToShow, 'y'))
axis = this.getYAxis(
id,
scale,
$$.yOrient,
$$.yAxisTickValues,
false,
true,
true
)
} else if (id === 'y2') {
scale = $$.y2.copy().domain($$.getYDomain(targetsToShow, 'y2'))
axis = this.getYAxis(
id,
scale,
$$.y2Orient,
$$.y2AxisTickValues,
false,
true,
true
)
} else {
scale = $$.x.copy().domain($$.getXDomain(targetsToShow))
axis = this.getXAxis(
scale,
$$.xOrient,
$$.xAxisTickFormat,
$$.xAxisTickValues,
false,
true,
true
)
this.updateXAxisTickValues(targetsToShow, axis)
}
dummy = $$.d3
.select('body')
.append('div')
.classed('c3', true)
;(svg = dummy
.append('svg')
.style('visibility', 'hidden')
.style('position', 'fixed')
.style('top', 0)
.style('left', 0)),
svg
.append('g')
.call(axis)
.each(function() {
$$.d3
.select(this)
.selectAll('text')
.each(function() {
var box = getBBox(this)
if (maxWidth < box.width) {
maxWidth = box.width
}
})
dummy.remove()
})
}
$$.currentMaxTickWidths[id] =
maxWidth <= 0 ? $$.currentMaxTickWidths[id] : maxWidth
return $$.currentMaxTickWidths[id]
}
Axis.prototype.updateLabels = function updateLabels(withTransition) {
var $$ = this.owner
var axisXLabel = $$.main.select('.' + CLASS.axisX + ' .' + CLASS.axisXLabel),
axisYLabel = $$.main.select('.' + CLASS.axisY + ' .' + CLASS.axisYLabel),
axisY2Label = $$.main.select('.' + CLASS.axisY2 + ' .' + CLASS.axisY2Label)
;(withTransition ? axisXLabel.transition() : axisXLabel)
.attr('x', this.xForXAxisLabel.bind(this))
.attr('dx', this.dxForXAxisLabel.bind(this))
.attr('dy', this.dyForXAxisLabel.bind(this))
.text(this.textForXAxisLabel.bind(this))
;(withTransition ? axisYLabel.transition() : axisYLabel)
.attr('x', this.xForYAxisLabel.bind(this))
.attr('dx', this.dxForYAxisLabel.bind(this))
.attr('dy', this.dyForYAxisLabel.bind(this))
.text(this.textForYAxisLabel.bind(this))
;(withTransition ? axisY2Label.transition() : axisY2Label)
.attr('x', this.xForY2AxisLabel.bind(this))
.attr('dx', this.dxForY2AxisLabel.bind(this))
.attr('dy', this.dyForY2AxisLabel.bind(this))
.text(this.textForY2AxisLabel.bind(this))
}
Axis.prototype.getPadding = function getPadding(
padding,
key,
defaultValue,
domainLength
) {
var p = typeof padding === 'number' ? padding : padding[key]
if (!isValue(p)) {
return defaultValue
}
if (padding.unit === 'ratio') {
return padding[key] * domainLength
}
// assume padding is pixels if unit is not specified
return this.convertPixelsToAxisPadding(p, domainLength)
}
Axis.prototype.convertPixelsToAxisPadding = function convertPixelsToAxisPadding(
pixels,
domainLength
) {
var $$ = this.owner,
length = $$.config.axis_rotated ? $$.width : $$.height
return domainLength * (pixels / length)
}
Axis.prototype.generateTickValues = function generateTickValues(
values,
tickCount,
forTimeSeries
) {
var tickValues = values,
targetCount,
start,
end,
count,
interval,
i,
tickValue
if (tickCount) {
targetCount = isFunction(tickCount) ? tickCount() : tickCount
// compute ticks according to tickCount
if (targetCount === 1) {
tickValues = [values[0]]
} else if (targetCount === 2) {
tickValues = [values[0], values[values.length - 1]]
} else if (targetCount > 2) {
count = targetCount - 2
start = values[0]
end = values[values.length - 1]
interval = (end - start) / (count + 1)
// re-construct unique values
tickValues = [start]
for (i = 0; i < count; i++) {
tickValue = +start + interval * (i + 1)
tickValues.push(forTimeSeries ? new Date(tickValue) : tickValue)
}
tickValues.push(end)
}
}
if (!forTimeSeries) {
tickValues = tickValues.sort(function(a, b) {
return a - b
})
}
return tickValues
}
Axis.prototype.generateTransitions = function generateTransitions(duration) {
var $$ = this.owner,
axes = $$.axes
return {
axisX: duration ? axes.x.transition().duration(duration) : axes.x,
axisY: duration ? axes.y.transition().duration(duration) : axes.y,
axisY2: duration ? axes.y2.transition().duration(duration) : axes.y2,
axisSubX: duration ? axes.subx.transition().duration(duration) : axes.subx
}
}
Axis.prototype.redraw = function redraw(duration, isHidden) {
var $$ = this.owner,
transition = duration ? $$.d3.transition().duration(duration) : null
$$.axes.x.style('opacity', isHidden ? 0 : 1).call($$.xAxis, transition)
$$.axes.y.style('opacity', isHidden ? 0 : 1).call($$.yAxis, transition)
$$.axes.y2.style('opacity', isHidden ? 0 : 1).call($$.y2Axis, transition)
$$.axes.subx.style('opacity', isHidden ? 0 : 1).call($$.subXAxis, transition)
}