You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1324 lines
34 KiB
1324 lines
34 KiB
import { ChartInternal } from './chart-internal' |
|
import { Chart } from './chart' |
|
import { AxisInternal } from './axis-internal' |
|
import Axis from './axis' |
|
import CLASS from './class' |
|
|
|
import { |
|
asHalfPixel, |
|
getOption, |
|
getPathBox, |
|
isFunction, |
|
isValue, |
|
notEmpty |
|
} from './util' |
|
|
|
var c3 = { |
|
version: '0.7.20', |
|
chart: { |
|
fn: Chart.prototype, |
|
internal: { |
|
fn: ChartInternal.prototype, |
|
axis: { |
|
fn: Axis.prototype, |
|
internal: { |
|
fn: AxisInternal.prototype |
|
} |
|
} |
|
} |
|
}, |
|
generate: function(config) { |
|
return new Chart(config) |
|
} |
|
} |
|
|
|
export { c3 } |
|
|
|
ChartInternal.prototype.beforeInit = function() { |
|
// can do something |
|
} |
|
ChartInternal.prototype.afterInit = function() { |
|
// can do something |
|
} |
|
ChartInternal.prototype.init = function() { |
|
var $$ = this, |
|
config = $$.config |
|
|
|
$$.initParams() |
|
|
|
if (config.data_url) { |
|
$$.convertUrlToData( |
|
config.data_url, |
|
config.data_mimeType, |
|
config.data_headers, |
|
config.data_keys, |
|
$$.initWithData |
|
) |
|
} else if (config.data_json) { |
|
$$.initWithData($$.convertJsonToData(config.data_json, config.data_keys)) |
|
} else if (config.data_rows) { |
|
$$.initWithData($$.convertRowsToData(config.data_rows)) |
|
} else if (config.data_columns) { |
|
$$.initWithData($$.convertColumnsToData(config.data_columns)) |
|
} else { |
|
throw Error('url or json or rows or columns is required.') |
|
} |
|
} |
|
|
|
ChartInternal.prototype.initParams = function() { |
|
var $$ = this, |
|
d3 = $$.d3, |
|
config = $$.config |
|
|
|
// MEMO: clipId needs to be unique because it conflicts when multiple charts exist |
|
$$.clipId = 'c3-' + new Date().valueOf() + '-clip' |
|
$$.clipIdForXAxis = $$.clipId + '-xaxis' |
|
$$.clipIdForYAxis = $$.clipId + '-yaxis' |
|
$$.clipIdForGrid = $$.clipId + '-grid' |
|
$$.clipIdForSubchart = $$.clipId + '-subchart' |
|
$$.clipPath = $$.getClipPath($$.clipId) |
|
$$.clipPathForXAxis = $$.getClipPath($$.clipIdForXAxis) |
|
$$.clipPathForYAxis = $$.getClipPath($$.clipIdForYAxis) |
|
$$.clipPathForGrid = $$.getClipPath($$.clipIdForGrid) |
|
$$.clipPathForSubchart = $$.getClipPath($$.clipIdForSubchart) |
|
|
|
$$.dragStart = null |
|
$$.dragging = false |
|
$$.flowing = false |
|
$$.cancelClick = false |
|
$$.mouseover = undefined |
|
$$.transiting = false |
|
|
|
$$.color = $$.generateColor() |
|
$$.levelColor = $$.generateLevelColor() |
|
|
|
$$.dataTimeParse = (config.data_xLocaltime ? d3.timeParse : d3.utcParse)( |
|
$$.config.data_xFormat |
|
) |
|
$$.axisTimeFormat = config.axis_x_localtime ? d3.timeFormat : d3.utcFormat |
|
$$.defaultAxisTimeFormat = function(date) { |
|
if (date.getMilliseconds()) { |
|
return d3.timeFormat('.%L')(date) |
|
} |
|
if (date.getSeconds()) { |
|
return d3.timeFormat(':%S')(date) |
|
} |
|
if (date.getMinutes()) { |
|
return d3.timeFormat('%I:%M')(date) |
|
} |
|
if (date.getHours()) { |
|
return d3.timeFormat('%I %p')(date) |
|
} |
|
if (date.getDay() && date.getDate() !== 1) { |
|
return d3.timeFormat('%-m/%-d')(date) |
|
} |
|
if (date.getDate() !== 1) { |
|
return d3.timeFormat('%-m/%-d')(date) |
|
} |
|
if (date.getMonth()) { |
|
return d3.timeFormat('%-m/%-d')(date) |
|
} |
|
return d3.timeFormat('%Y/%-m/%-d')(date) |
|
} |
|
$$.hiddenTargetIds = [] |
|
$$.hiddenLegendIds = [] |
|
$$.focusedTargetIds = [] |
|
$$.defocusedTargetIds = [] |
|
|
|
$$.xOrient = config.axis_rotated |
|
? config.axis_x_inner |
|
? 'right' |
|
: 'left' |
|
: config.axis_x_inner |
|
? 'top' |
|
: 'bottom' |
|
$$.yOrient = config.axis_rotated |
|
? config.axis_y_inner |
|
? 'top' |
|
: 'bottom' |
|
: config.axis_y_inner |
|
? 'right' |
|
: 'left' |
|
$$.y2Orient = config.axis_rotated |
|
? config.axis_y2_inner |
|
? 'bottom' |
|
: 'top' |
|
: config.axis_y2_inner |
|
? 'left' |
|
: 'right' |
|
$$.subXOrient = config.axis_rotated ? 'left' : 'bottom' |
|
|
|
$$.isLegendRight = config.legend_position === 'right' |
|
$$.isLegendInset = config.legend_position === 'inset' |
|
$$.isLegendTop = |
|
config.legend_inset_anchor === 'top-left' || |
|
config.legend_inset_anchor === 'top-right' |
|
$$.isLegendLeft = |
|
config.legend_inset_anchor === 'top-left' || |
|
config.legend_inset_anchor === 'bottom-left' |
|
$$.legendStep = 0 |
|
$$.legendItemWidth = 0 |
|
$$.legendItemHeight = 0 |
|
|
|
$$.currentMaxTickWidths = { |
|
x: 0, |
|
y: 0, |
|
y2: 0 |
|
} |
|
|
|
$$.rotated_padding_left = 30 |
|
$$.rotated_padding_right = config.axis_rotated && !config.axis_x_show ? 0 : 30 |
|
$$.rotated_padding_top = 5 |
|
|
|
$$.withoutFadeIn = {} |
|
|
|
$$.intervalForObserveInserted = undefined |
|
|
|
$$.axes.subx = d3.selectAll([]) // needs when excluding subchart.js |
|
} |
|
|
|
ChartInternal.prototype.initChartElements = function() { |
|
if (this.initBar) { |
|
this.initBar() |
|
} |
|
if (this.initLine) { |
|
this.initLine() |
|
} |
|
if (this.initArc) { |
|
this.initArc() |
|
} |
|
if (this.initGauge) { |
|
this.initGauge() |
|
} |
|
if (this.initText) { |
|
this.initText() |
|
} |
|
} |
|
|
|
ChartInternal.prototype.initWithData = function(data) { |
|
var $$ = this, |
|
d3 = $$.d3, |
|
config = $$.config |
|
var defs, |
|
main, |
|
binding = true |
|
|
|
$$.axis = new Axis($$) |
|
|
|
if (!config.bindto) { |
|
$$.selectChart = d3.selectAll([]) |
|
} else if (typeof config.bindto.node === 'function') { |
|
$$.selectChart = config.bindto |
|
} else { |
|
$$.selectChart = d3.select(config.bindto) |
|
} |
|
if ($$.selectChart.empty()) { |
|
$$.selectChart = d3 |
|
.select(document.createElement('div')) |
|
.style('opacity', 0) |
|
$$.observeInserted($$.selectChart) |
|
binding = false |
|
} |
|
$$.selectChart.html('').classed('c3', true) |
|
|
|
// Init data as targets |
|
$$.data.xs = {} |
|
$$.data.targets = $$.convertDataToTargets(data) |
|
|
|
if (config.data_filter) { |
|
$$.data.targets = $$.data.targets.filter(config.data_filter) |
|
} |
|
|
|
// Set targets to hide if needed |
|
if (config.data_hide) { |
|
$$.addHiddenTargetIds( |
|
config.data_hide === true |
|
? $$.mapToIds($$.data.targets) |
|
: config.data_hide |
|
) |
|
} |
|
if (config.legend_hide) { |
|
$$.addHiddenLegendIds( |
|
config.legend_hide === true |
|
? $$.mapToIds($$.data.targets) |
|
: config.legend_hide |
|
) |
|
} |
|
|
|
if ($$.isStanfordGraphType()) { |
|
$$.initStanfordData() |
|
} |
|
|
|
// Init sizes and scales |
|
$$.updateSizes() |
|
$$.updateScales() |
|
|
|
// Set domains for each scale |
|
$$.x.domain(d3.extent($$.getXDomain($$.data.targets))) |
|
$$.y.domain($$.getYDomain($$.data.targets, 'y')) |
|
$$.y2.domain($$.getYDomain($$.data.targets, 'y2')) |
|
$$.subX.domain($$.x.domain()) |
|
$$.subY.domain($$.y.domain()) |
|
$$.subY2.domain($$.y2.domain()) |
|
|
|
// Save original x domain for zoom update |
|
$$.orgXDomain = $$.x.domain() |
|
|
|
/*-- Basic Elements --*/ |
|
|
|
// Define svgs |
|
$$.svg = $$.selectChart |
|
.append('svg') |
|
.style('overflow', 'hidden') |
|
.on('mouseenter', function() { |
|
return config.onmouseover.call($$) |
|
}) |
|
.on('mouseleave', function() { |
|
return config.onmouseout.call($$) |
|
}) |
|
|
|
if ($$.config.svg_classname) { |
|
$$.svg.attr('class', $$.config.svg_classname) |
|
} |
|
|
|
// Define defs |
|
defs = $$.svg.append('defs') |
|
$$.clipChart = $$.appendClip(defs, $$.clipId) |
|
$$.clipXAxis = $$.appendClip(defs, $$.clipIdForXAxis) |
|
$$.clipYAxis = $$.appendClip(defs, $$.clipIdForYAxis) |
|
$$.clipGrid = $$.appendClip(defs, $$.clipIdForGrid) |
|
$$.clipSubchart = $$.appendClip(defs, $$.clipIdForSubchart) |
|
$$.updateSvgSize() |
|
|
|
// Define regions |
|
main = $$.main = $$.svg.append('g').attr('transform', $$.getTranslate('main')) |
|
|
|
if ($$.initPie) { |
|
$$.initPie() |
|
} |
|
if ($$.initDragZoom) { |
|
$$.initDragZoom() |
|
} |
|
if (config.subchart_show && $$.initSubchart) { |
|
$$.initSubchart() |
|
} |
|
if ($$.initTooltip) { |
|
$$.initTooltip() |
|
} |
|
if ($$.initLegend) { |
|
$$.initLegend() |
|
} |
|
if ($$.initTitle) { |
|
$$.initTitle() |
|
} |
|
if ($$.initZoom) { |
|
$$.initZoom() |
|
} |
|
if ($$.isStanfordGraphType()) { |
|
$$.drawColorScale() |
|
} |
|
|
|
// Update selection based on size and scale |
|
// TODO: currently this must be called after initLegend because of update of sizes, but it should be done in initSubchart. |
|
if (config.subchart_show && $$.initSubchartBrush) { |
|
$$.initSubchartBrush() |
|
} |
|
|
|
/*-- Main Region --*/ |
|
|
|
// text when empty |
|
main |
|
.append('text') |
|
.attr('class', CLASS.text + ' ' + CLASS.empty) |
|
.attr('text-anchor', 'middle') // horizontal centering of text at x position in all browsers. |
|
.attr('dominant-baseline', 'middle') // vertical centering of text at y position in all browsers, except IE. |
|
|
|
// Regions |
|
$$.initRegion() |
|
|
|
// Grids |
|
$$.initGrid() |
|
|
|
// Define g for chart area |
|
main |
|
.append('g') |
|
.attr('clip-path', $$.clipPath) |
|
.attr('class', CLASS.chart) |
|
|
|
// Grid lines |
|
if (config.grid_lines_front) { |
|
$$.initGridLines() |
|
} |
|
|
|
$$.initStanfordElements() |
|
|
|
// Cover whole with rects for events |
|
$$.initEventRect() |
|
|
|
// Define g for chart |
|
$$.initChartElements() |
|
|
|
// Add Axis |
|
$$.axis.init() |
|
|
|
// Set targets |
|
$$.updateTargets($$.data.targets) |
|
|
|
// Set default extent if defined |
|
if (config.axis_x_selection) { |
|
$$.brush.selectionAsValue($$.getDefaultSelection()) |
|
} |
|
|
|
// Draw with targets |
|
if (binding) { |
|
$$.updateDimension() |
|
$$.config.oninit.call($$) |
|
$$.redraw({ |
|
withTransition: false, |
|
withTransform: true, |
|
withUpdateXDomain: true, |
|
withUpdateOrgXDomain: true, |
|
withTransitionForAxis: false |
|
}) |
|
} |
|
|
|
// Bind to resize event |
|
$$.bindResize() |
|
|
|
// Bind to window focus event |
|
$$.bindWindowFocus() |
|
|
|
// export element of the chart |
|
$$.api.element = $$.selectChart.node() |
|
} |
|
|
|
ChartInternal.prototype.smoothLines = function(el, type) { |
|
var $$ = this |
|
if (type === 'grid') { |
|
el.each(function() { |
|
var g = $$.d3.select(this), |
|
x1 = g.attr('x1'), |
|
x2 = g.attr('x2'), |
|
y1 = g.attr('y1'), |
|
y2 = g.attr('y2') |
|
g.attr({ |
|
x1: Math.ceil(x1), |
|
x2: Math.ceil(x2), |
|
y1: Math.ceil(y1), |
|
y2: Math.ceil(y2) |
|
}) |
|
}) |
|
} |
|
} |
|
|
|
ChartInternal.prototype.updateSizes = function() { |
|
var $$ = this, |
|
config = $$.config |
|
var legendHeight = $$.legend ? $$.getLegendHeight() : 0, |
|
legendWidth = $$.legend ? $$.getLegendWidth() : 0, |
|
legendHeightForBottom = |
|
$$.isLegendRight || $$.isLegendInset ? 0 : legendHeight, |
|
hasArc = $$.hasArcType(), |
|
xAxisHeight = |
|
config.axis_rotated || hasArc ? 0 : $$.getHorizontalAxisHeight('x'), |
|
subchartXAxisHeight = config.axis_rotated || hasArc ? 0 : $$.getHorizontalAxisHeight('x',true), |
|
subchartHeight = |
|
config.subchart_show && !hasArc |
|
? config.subchart_size_height + subchartXAxisHeight |
|
: 0 |
|
|
|
$$.currentWidth = $$.getCurrentWidth() |
|
$$.currentHeight = $$.getCurrentHeight() |
|
|
|
// for main |
|
$$.margin = config.axis_rotated |
|
? { |
|
top: $$.getHorizontalAxisHeight('y2') + $$.getCurrentPaddingTop(), |
|
right: hasArc ? 0 : $$.getCurrentPaddingRight(), |
|
bottom: |
|
$$.getHorizontalAxisHeight('y') + |
|
legendHeightForBottom + |
|
$$.getCurrentPaddingBottom(), |
|
left: subchartHeight + (hasArc ? 0 : $$.getCurrentPaddingLeft()) |
|
} |
|
: { |
|
top: 4 + $$.getCurrentPaddingTop(), // for top tick text |
|
right: hasArc ? 0 : $$.getCurrentPaddingRight(), |
|
bottom: |
|
xAxisHeight + |
|
subchartHeight + |
|
legendHeightForBottom + |
|
$$.getCurrentPaddingBottom(), |
|
left: hasArc ? 0 : $$.getCurrentPaddingLeft() |
|
} |
|
|
|
// for subchart |
|
$$.margin2 = config.axis_rotated |
|
? { |
|
top: $$.margin.top, |
|
right: NaN, |
|
bottom: 20 + legendHeightForBottom, |
|
left: $$.rotated_padding_left |
|
} |
|
: { |
|
top: $$.currentHeight - subchartHeight - legendHeightForBottom, |
|
right: NaN, |
|
bottom: subchartXAxisHeight + legendHeightForBottom, |
|
left: $$.margin.left |
|
} |
|
|
|
// for legend |
|
$$.margin3 = { |
|
top: 0, |
|
right: NaN, |
|
bottom: 0, |
|
left: 0 |
|
} |
|
if ($$.updateSizeForLegend) { |
|
$$.updateSizeForLegend(legendHeight, legendWidth) |
|
} |
|
|
|
$$.width = $$.currentWidth - $$.margin.left - $$.margin.right |
|
$$.height = $$.currentHeight - $$.margin.top - $$.margin.bottom |
|
if ($$.width < 0) { |
|
$$.width = 0 |
|
} |
|
if ($$.height < 0) { |
|
$$.height = 0 |
|
} |
|
|
|
$$.width2 = config.axis_rotated |
|
? $$.margin.left - $$.rotated_padding_left - $$.rotated_padding_right |
|
: $$.width |
|
$$.height2 = config.axis_rotated |
|
? $$.height |
|
: $$.currentHeight - $$.margin2.top - $$.margin2.bottom |
|
if ($$.width2 < 0) { |
|
$$.width2 = 0 |
|
} |
|
if ($$.height2 < 0) { |
|
$$.height2 = 0 |
|
} |
|
|
|
// for arc |
|
$$.arcWidth = $$.width - ($$.isLegendRight ? legendWidth + 10 : 0) |
|
$$.arcHeight = $$.height - ($$.isLegendRight ? 0 : 10) |
|
if ($$.hasType('gauge') && !config.gauge_fullCircle) { |
|
$$.arcHeight += $$.height - $$.getGaugeLabelHeight() |
|
} |
|
if ($$.updateRadius) { |
|
$$.updateRadius() |
|
} |
|
|
|
if ($$.isLegendRight && hasArc) { |
|
$$.margin3.left = $$.arcWidth / 2 + $$.radiusExpanded * 1.1 |
|
} |
|
} |
|
|
|
ChartInternal.prototype.updateTargets = function(targets) { |
|
var $$ = this, |
|
config = $$.config |
|
|
|
/*-- Main --*/ |
|
|
|
//-- Text --// |
|
$$.updateTargetsForText(targets) |
|
|
|
//-- Bar --// |
|
$$.updateTargetsForBar(targets) |
|
|
|
//-- Line --// |
|
$$.updateTargetsForLine(targets) |
|
|
|
//-- Arc --// |
|
if ($$.hasArcType() && $$.updateTargetsForArc) { |
|
$$.updateTargetsForArc(targets) |
|
} |
|
|
|
/*-- Sub --*/ |
|
|
|
if (config.subchart_show && $$.updateTargetsForSubchart) { |
|
$$.updateTargetsForSubchart(targets) |
|
} |
|
|
|
// Fade-in each chart |
|
$$.showTargets() |
|
} |
|
ChartInternal.prototype.showTargets = function() { |
|
var $$ = this |
|
$$.svg |
|
.selectAll('.' + CLASS.target) |
|
.filter(function(d) { |
|
return $$.isTargetToShow(d.id) |
|
}) |
|
.transition() |
|
.duration($$.config.transition_duration) |
|
.style('opacity', 1) |
|
} |
|
|
|
ChartInternal.prototype.redraw = function(options, transitions) { |
|
var $$ = this, |
|
main = $$.main, |
|
d3 = $$.d3, |
|
config = $$.config |
|
var areaIndices = $$.getShapeIndices($$.isAreaType), |
|
barIndices = $$.getShapeIndices($$.isBarType), |
|
lineIndices = $$.getShapeIndices($$.isLineType) |
|
var withY, |
|
withSubchart, |
|
withTransition, |
|
withTransitionForExit, |
|
withTransitionForAxis, |
|
withTransform, |
|
withUpdateXDomain, |
|
withUpdateOrgXDomain, |
|
withTrimXDomain, |
|
withLegend, |
|
withEventRect, |
|
withDimension, |
|
withUpdateXAxis |
|
var hideAxis = $$.hasArcType() |
|
var drawArea, drawBar, drawLine, xForText, yForText |
|
var duration, durationForExit, durationForAxis |
|
var transitionsToWait, waitForDraw, flow, transition |
|
var targetsToShow = $$.filterTargetsToShow($$.data.targets), |
|
tickValues, |
|
i, |
|
intervalForCulling, |
|
xDomainForZoom |
|
var xv = $$.xv.bind($$), |
|
cx, |
|
cy |
|
|
|
options = options || {} |
|
withY = getOption(options, 'withY', true) |
|
withSubchart = getOption(options, 'withSubchart', true) |
|
withTransition = getOption(options, 'withTransition', true) |
|
withTransform = getOption(options, 'withTransform', false) |
|
withUpdateXDomain = getOption(options, 'withUpdateXDomain', false) |
|
withUpdateOrgXDomain = getOption(options, 'withUpdateOrgXDomain', false) |
|
withTrimXDomain = getOption(options, 'withTrimXDomain', true) |
|
withUpdateXAxis = getOption(options, 'withUpdateXAxis', withUpdateXDomain) |
|
withLegend = getOption(options, 'withLegend', false) |
|
withEventRect = getOption(options, 'withEventRect', true) |
|
withDimension = getOption(options, 'withDimension', true) |
|
withTransitionForExit = getOption( |
|
options, |
|
'withTransitionForExit', |
|
withTransition |
|
) |
|
withTransitionForAxis = getOption( |
|
options, |
|
'withTransitionForAxis', |
|
withTransition |
|
) |
|
|
|
duration = withTransition ? config.transition_duration : 0 |
|
durationForExit = withTransitionForExit ? duration : 0 |
|
durationForAxis = withTransitionForAxis ? duration : 0 |
|
|
|
transitions = transitions || $$.axis.generateTransitions(durationForAxis) |
|
|
|
// update legend and transform each g |
|
if (withLegend && config.legend_show) { |
|
$$.updateLegend($$.mapToIds($$.data.targets), options, transitions) |
|
} else if (withDimension) { |
|
// need to update dimension (e.g. axis.y.tick.values) because y tick values should change |
|
// no need to update axis in it because they will be updated in redraw() |
|
$$.updateDimension(true) |
|
} |
|
|
|
// MEMO: needed for grids calculation |
|
if ($$.isCategorized() && targetsToShow.length === 0) { |
|
$$.x.domain([0, $$.axes.x.selectAll('.tick').size()]) |
|
} |
|
|
|
if (targetsToShow.length) { |
|
$$.updateXDomain( |
|
targetsToShow, |
|
withUpdateXDomain, |
|
withUpdateOrgXDomain, |
|
withTrimXDomain |
|
) |
|
if (!config.axis_x_tick_values) { |
|
tickValues = $$.axis.updateXAxisTickValues(targetsToShow) |
|
} |
|
} else { |
|
$$.xAxis.tickValues([]) |
|
$$.subXAxis.tickValues([]) |
|
} |
|
|
|
if (config.zoom_rescale && !options.flow) { |
|
xDomainForZoom = $$.x.orgDomain() |
|
} |
|
|
|
$$.y.domain($$.getYDomain(targetsToShow, 'y', xDomainForZoom)) |
|
$$.y2.domain($$.getYDomain(targetsToShow, 'y2', xDomainForZoom)) |
|
|
|
if (!config.axis_y_tick_values && config.axis_y_tick_count) { |
|
$$.yAxis.tickValues( |
|
$$.axis.generateTickValues($$.y.domain(), config.axis_y_tick_count) |
|
) |
|
} |
|
if (!config.axis_y2_tick_values && config.axis_y2_tick_count) { |
|
$$.y2Axis.tickValues( |
|
$$.axis.generateTickValues($$.y2.domain(), config.axis_y2_tick_count) |
|
) |
|
} |
|
|
|
// axes |
|
$$.axis.redraw(durationForAxis, hideAxis) |
|
|
|
// Update axis label |
|
$$.axis.updateLabels(withTransition) |
|
|
|
// show/hide if manual culling needed |
|
if ((withUpdateXDomain || withUpdateXAxis) && targetsToShow.length) { |
|
if (config.axis_x_tick_culling && tickValues) { |
|
for (i = 1; i < tickValues.length; i++) { |
|
if (tickValues.length / i < config.axis_x_tick_culling_max) { |
|
intervalForCulling = i |
|
break |
|
} |
|
} |
|
$$.svg.selectAll('.' + CLASS.axisX + ' .tick text').each(function(e) { |
|
var index = tickValues.indexOf(e) |
|
if (index >= 0) { |
|
d3.select(this).style( |
|
'display', |
|
index % intervalForCulling ? 'none' : 'block' |
|
) |
|
} |
|
}) |
|
} else { |
|
$$.svg |
|
.selectAll('.' + CLASS.axisX + ' .tick text') |
|
.style('display', 'block') |
|
} |
|
} |
|
|
|
// setup drawer - MEMO: these must be called after axis updated |
|
drawArea = $$.generateDrawArea |
|
? $$.generateDrawArea(areaIndices, false) |
|
: undefined |
|
drawBar = $$.generateDrawBar ? $$.generateDrawBar(barIndices) : undefined |
|
drawLine = $$.generateDrawLine |
|
? $$.generateDrawLine(lineIndices, false) |
|
: undefined |
|
xForText = $$.generateXYForText(areaIndices, barIndices, lineIndices, true) |
|
yForText = $$.generateXYForText(areaIndices, barIndices, lineIndices, false) |
|
|
|
// update circleY based on updated parameters |
|
$$.updateCircleY() |
|
// generate circle x/y functions depending on updated params |
|
cx = ($$.config.axis_rotated ? $$.circleY : $$.circleX).bind($$) |
|
cy = ($$.config.axis_rotated ? $$.circleX : $$.circleY).bind($$) |
|
|
|
// Update sub domain |
|
if (withY) { |
|
$$.subY.domain($$.getYDomain(targetsToShow, 'y')) |
|
$$.subY2.domain($$.getYDomain(targetsToShow, 'y2')) |
|
} |
|
|
|
// xgrid focus |
|
$$.updateXgridFocus() |
|
|
|
// Data empty label positioning and text. |
|
main |
|
.select('text.' + CLASS.text + '.' + CLASS.empty) |
|
.attr('x', $$.width / 2) |
|
.attr('y', $$.height / 2) |
|
.text(config.data_empty_label_text) |
|
.transition() |
|
.style('opacity', targetsToShow.length ? 0 : 1) |
|
|
|
// event rect |
|
if (withEventRect) { |
|
$$.redrawEventRect() |
|
} |
|
|
|
// grid |
|
$$.updateGrid(duration) |
|
|
|
$$.updateStanfordElements(duration) |
|
|
|
// rect for regions |
|
$$.updateRegion(duration) |
|
|
|
// bars |
|
$$.updateBar(durationForExit) |
|
|
|
// lines, areas and circles |
|
$$.updateLine(durationForExit) |
|
$$.updateArea(durationForExit) |
|
$$.updateCircle(cx, cy) |
|
|
|
// text |
|
if ($$.hasDataLabel()) { |
|
$$.updateText(xForText, yForText, durationForExit) |
|
} |
|
|
|
// title |
|
if ($$.redrawTitle) { |
|
$$.redrawTitle() |
|
} |
|
|
|
// arc |
|
if ($$.redrawArc) { |
|
$$.redrawArc(duration, durationForExit, withTransform) |
|
} |
|
|
|
// subchart |
|
if (config.subchart_show && $$.redrawSubchart) { |
|
$$.redrawSubchart( |
|
withSubchart, |
|
transitions, |
|
duration, |
|
durationForExit, |
|
areaIndices, |
|
barIndices, |
|
lineIndices |
|
) |
|
} |
|
|
|
if ($$.isStanfordGraphType()) { |
|
$$.drawColorScale() |
|
} |
|
|
|
// circles for select |
|
main |
|
.selectAll('.' + CLASS.selectedCircles) |
|
.filter($$.isBarType.bind($$)) |
|
.selectAll('circle') |
|
.remove() |
|
|
|
if (options.flow) { |
|
flow = $$.generateFlow({ |
|
targets: targetsToShow, |
|
flow: options.flow, |
|
duration: options.flow.duration, |
|
drawBar: drawBar, |
|
drawLine: drawLine, |
|
drawArea: drawArea, |
|
cx: cx, |
|
cy: cy, |
|
xv: xv, |
|
xForText: xForText, |
|
yForText: yForText |
|
}) |
|
} |
|
|
|
if (duration && $$.isTabVisible()) { |
|
// Only use transition if tab visible. See #938. |
|
// transition should be derived from one transition |
|
transition = d3.transition().duration(duration) |
|
transitionsToWait = [] |
|
;[ |
|
$$.redrawBar(drawBar, true, transition), |
|
$$.redrawLine(drawLine, true, transition), |
|
$$.redrawArea(drawArea, true, transition), |
|
$$.redrawCircle(cx, cy, true, transition), |
|
$$.redrawText(xForText, yForText, options.flow, true, transition), |
|
$$.redrawRegion(true, transition), |
|
$$.redrawGrid(true, transition) |
|
].forEach(function(transitions) { |
|
transitions.forEach(function(transition) { |
|
transitionsToWait.push(transition) |
|
}) |
|
}) |
|
// Wait for end of transitions to call flow and onrendered callback |
|
waitForDraw = $$.generateWait() |
|
transitionsToWait.forEach(function(t) { |
|
waitForDraw.add(t) |
|
}) |
|
waitForDraw(function() { |
|
if (flow) { |
|
flow() |
|
} |
|
if (config.onrendered) { |
|
config.onrendered.call($$) |
|
} |
|
}) |
|
} else { |
|
$$.redrawBar(drawBar) |
|
$$.redrawLine(drawLine) |
|
$$.redrawArea(drawArea) |
|
$$.redrawCircle(cx, cy) |
|
$$.redrawText(xForText, yForText, options.flow) |
|
$$.redrawRegion() |
|
$$.redrawGrid() |
|
if (flow) { |
|
flow() |
|
} |
|
if (config.onrendered) { |
|
config.onrendered.call($$) |
|
} |
|
} |
|
|
|
// update fadein condition |
|
$$.mapToIds($$.data.targets).forEach(function(id) { |
|
$$.withoutFadeIn[id] = true |
|
}) |
|
} |
|
|
|
ChartInternal.prototype.updateAndRedraw = function(options) { |
|
var $$ = this, |
|
config = $$.config, |
|
transitions |
|
options = options || {} |
|
// same with redraw |
|
options.withTransition = getOption(options, 'withTransition', true) |
|
options.withTransform = getOption(options, 'withTransform', false) |
|
options.withLegend = getOption(options, 'withLegend', false) |
|
// NOT same with redraw |
|
options.withUpdateXDomain = getOption(options, 'withUpdateXDomain', true) |
|
options.withUpdateOrgXDomain = getOption( |
|
options, |
|
'withUpdateOrgXDomain', |
|
true |
|
) |
|
options.withTransitionForExit = false |
|
options.withTransitionForTransform = getOption( |
|
options, |
|
'withTransitionForTransform', |
|
options.withTransition |
|
) |
|
// MEMO: this needs to be called before updateLegend and it means this ALWAYS needs to be called) |
|
$$.updateSizes() |
|
// MEMO: called in updateLegend in redraw if withLegend |
|
if (!(options.withLegend && config.legend_show)) { |
|
transitions = $$.axis.generateTransitions( |
|
options.withTransitionForAxis ? config.transition_duration : 0 |
|
) |
|
// Update scales |
|
$$.updateScales() |
|
$$.updateSvgSize() |
|
// Update g positions |
|
$$.transformAll(options.withTransitionForTransform, transitions) |
|
} |
|
// Draw with new sizes & scales |
|
$$.redraw(options, transitions) |
|
} |
|
ChartInternal.prototype.redrawWithoutRescale = function() { |
|
this.redraw({ |
|
withY: false, |
|
withSubchart: false, |
|
withEventRect: false, |
|
withTransitionForAxis: false |
|
}) |
|
} |
|
|
|
ChartInternal.prototype.isTimeSeries = function() { |
|
return this.config.axis_x_type === 'timeseries' |
|
} |
|
ChartInternal.prototype.isCategorized = function() { |
|
return this.config.axis_x_type.indexOf('categor') >= 0 |
|
} |
|
ChartInternal.prototype.isCustomX = function() { |
|
var $$ = this, |
|
config = $$.config |
|
return !$$.isTimeSeries() && (config.data_x || notEmpty(config.data_xs)) |
|
} |
|
|
|
ChartInternal.prototype.isTimeSeriesY = function() { |
|
return this.config.axis_y_type === 'timeseries' |
|
} |
|
|
|
ChartInternal.prototype.getTranslate = function(target) { |
|
var $$ = this, |
|
config = $$.config, |
|
x, |
|
y |
|
if (target === 'main') { |
|
x = asHalfPixel($$.margin.left) |
|
y = asHalfPixel($$.margin.top) |
|
} else if (target === 'context') { |
|
x = asHalfPixel($$.margin2.left) |
|
y = asHalfPixel($$.margin2.top) |
|
} else if (target === 'legend') { |
|
x = $$.margin3.left |
|
y = $$.margin3.top |
|
} else if (target === 'x') { |
|
x = 0 |
|
y = config.axis_rotated ? 0 : $$.height |
|
} else if (target === 'y') { |
|
x = 0 |
|
y = config.axis_rotated ? $$.height : 0 |
|
} else if (target === 'y2') { |
|
x = config.axis_rotated ? 0 : $$.width |
|
y = config.axis_rotated ? 1 : 0 |
|
} else if (target === 'subx') { |
|
x = 0 |
|
y = config.axis_rotated ? 0 : $$.height2 |
|
} else if (target === 'arc') { |
|
x = $$.arcWidth / 2 |
|
y = $$.arcHeight / 2 - ($$.hasType('gauge') ? 6 : 0) // to prevent wrong display of min and max label |
|
} |
|
return 'translate(' + x + ',' + y + ')' |
|
} |
|
ChartInternal.prototype.initialOpacity = function(d) { |
|
return d.value !== null && this.withoutFadeIn[d.id] ? 1 : 0 |
|
} |
|
ChartInternal.prototype.initialOpacityForCircle = function(d) { |
|
return d.value !== null && this.withoutFadeIn[d.id] |
|
? this.opacityForCircle(d) |
|
: 0 |
|
} |
|
ChartInternal.prototype.opacityForCircle = function(d) { |
|
var isPointShouldBeShown = isFunction(this.config.point_show) |
|
? this.config.point_show(d) |
|
: this.config.point_show |
|
var opacity = isPointShouldBeShown || this.isStanfordType(d) ? 1 : 0 |
|
return isValue(d.value) ? (this.isScatterType(d) ? 0.5 : opacity) : 0 |
|
} |
|
ChartInternal.prototype.opacityForText = function() { |
|
return this.hasDataLabel() ? 1 : 0 |
|
} |
|
ChartInternal.prototype.xx = function(d) { |
|
return d ? this.x(d.x) : null |
|
} |
|
ChartInternal.prototype.xvCustom = function(d, xyValue) { |
|
var $$ = this, |
|
value = xyValue ? d[xyValue] : d.value |
|
if ($$.isTimeSeries()) { |
|
value = $$.parseDate(d.value) |
|
} else if ($$.isCategorized() && typeof d.value === 'string') { |
|
value = $$.config.axis_x_categories.indexOf(d.value) |
|
} |
|
return Math.ceil($$.x(value)) |
|
} |
|
ChartInternal.prototype.yvCustom = function(d, xyValue) { |
|
var $$ = this, |
|
yScale = d.axis && d.axis === 'y2' ? $$.y2 : $$.y, |
|
value = xyValue ? d[xyValue] : d.value |
|
return Math.ceil(yScale(value)) |
|
} |
|
ChartInternal.prototype.xv = function(d) { |
|
var $$ = this, |
|
value = d.value |
|
if ($$.isTimeSeries()) { |
|
value = $$.parseDate(d.value) |
|
} else if ($$.isCategorized() && typeof d.value === 'string') { |
|
value = $$.config.axis_x_categories.indexOf(d.value) |
|
} |
|
return Math.ceil($$.x(value)) |
|
} |
|
ChartInternal.prototype.yv = function(d) { |
|
var $$ = this, |
|
yScale = d.axis && d.axis === 'y2' ? $$.y2 : $$.y |
|
return Math.ceil(yScale(d.value)) |
|
} |
|
ChartInternal.prototype.subxx = function(d) { |
|
return d ? this.subX(d.x) : null |
|
} |
|
|
|
ChartInternal.prototype.transformMain = function(withTransition, transitions) { |
|
var $$ = this, |
|
xAxis, |
|
yAxis, |
|
y2Axis |
|
if (transitions && transitions.axisX) { |
|
xAxis = transitions.axisX |
|
} else { |
|
xAxis = $$.main.select('.' + CLASS.axisX) |
|
if (withTransition) { |
|
xAxis = xAxis.transition() |
|
} |
|
} |
|
if (transitions && transitions.axisY) { |
|
yAxis = transitions.axisY |
|
} else { |
|
yAxis = $$.main.select('.' + CLASS.axisY) |
|
if (withTransition) { |
|
yAxis = yAxis.transition() |
|
} |
|
} |
|
if (transitions && transitions.axisY2) { |
|
y2Axis = transitions.axisY2 |
|
} else { |
|
y2Axis = $$.main.select('.' + CLASS.axisY2) |
|
if (withTransition) { |
|
y2Axis = y2Axis.transition() |
|
} |
|
} |
|
;(withTransition ? $$.main.transition() : $$.main).attr( |
|
'transform', |
|
$$.getTranslate('main') |
|
) |
|
xAxis.attr('transform', $$.getTranslate('x')) |
|
yAxis.attr('transform', $$.getTranslate('y')) |
|
y2Axis.attr('transform', $$.getTranslate('y2')) |
|
$$.main |
|
.select('.' + CLASS.chartArcs) |
|
.attr('transform', $$.getTranslate('arc')) |
|
} |
|
ChartInternal.prototype.transformAll = function(withTransition, transitions) { |
|
var $$ = this |
|
$$.transformMain(withTransition, transitions) |
|
if ($$.config.subchart_show) { |
|
$$.transformContext(withTransition, transitions) |
|
} |
|
if ($$.legend) { |
|
$$.transformLegend(withTransition) |
|
} |
|
} |
|
|
|
ChartInternal.prototype.updateSvgSize = function() { |
|
var $$ = this, |
|
brush = $$.svg.select(`.${CLASS.brush} .overlay`) |
|
$$.svg.attr('width', $$.currentWidth).attr('height', $$.currentHeight) |
|
$$.svg |
|
.selectAll(['#' + $$.clipId, '#' + $$.clipIdForGrid]) |
|
.select('rect') |
|
.attr('width', $$.width) |
|
.attr('height', $$.height) |
|
$$.svg |
|
.select('#' + $$.clipIdForXAxis) |
|
.select('rect') |
|
.attr('x', $$.getXAxisClipX.bind($$)) |
|
.attr('y', $$.getXAxisClipY.bind($$)) |
|
.attr('width', $$.getXAxisClipWidth.bind($$)) |
|
.attr('height', $$.getXAxisClipHeight.bind($$)) |
|
$$.svg |
|
.select('#' + $$.clipIdForYAxis) |
|
.select('rect') |
|
.attr('x', $$.getYAxisClipX.bind($$)) |
|
.attr('y', $$.getYAxisClipY.bind($$)) |
|
.attr('width', $$.getYAxisClipWidth.bind($$)) |
|
.attr('height', $$.getYAxisClipHeight.bind($$)) |
|
$$.svg |
|
.select('#' + $$.clipIdForSubchart) |
|
.select('rect') |
|
.attr('width', $$.width) |
|
.attr('height', (brush.size() && brush.attr('height')) || 0) |
|
// MEMO: parent div's height will be bigger than svg when <!DOCTYPE html> |
|
$$.selectChart.style('max-height', $$.currentHeight + 'px') |
|
} |
|
|
|
ChartInternal.prototype.updateDimension = function(withoutAxis) { |
|
var $$ = this |
|
if (!withoutAxis) { |
|
if ($$.config.axis_rotated) { |
|
$$.axes.x.call($$.xAxis) |
|
$$.axes.subx.call($$.subXAxis) |
|
} else { |
|
$$.axes.y.call($$.yAxis) |
|
$$.axes.y2.call($$.y2Axis) |
|
} |
|
} |
|
$$.updateSizes() |
|
$$.updateScales() |
|
$$.updateSvgSize() |
|
$$.transformAll(false) |
|
} |
|
|
|
ChartInternal.prototype.observeInserted = function(selection) { |
|
var $$ = this, |
|
observer |
|
if (typeof MutationObserver === 'undefined') { |
|
window.console.error('MutationObserver not defined.') |
|
return |
|
} |
|
observer = new MutationObserver(function(mutations) { |
|
mutations.forEach(function(mutation) { |
|
if (mutation.type === 'childList' && mutation.previousSibling) { |
|
observer.disconnect() |
|
// need to wait for completion of load because size calculation requires the actual sizes determined after that completion |
|
$$.intervalForObserveInserted = window.setInterval(function() { |
|
// parentNode will NOT be null when completed |
|
if (selection.node().parentNode) { |
|
window.clearInterval($$.intervalForObserveInserted) |
|
$$.updateDimension() |
|
if ($$.brush) { |
|
$$.brush.update() |
|
} |
|
$$.config.oninit.call($$) |
|
$$.redraw({ |
|
withTransform: true, |
|
withUpdateXDomain: true, |
|
withUpdateOrgXDomain: true, |
|
withTransition: false, |
|
withTransitionForTransform: false, |
|
withLegend: true |
|
}) |
|
selection.transition().style('opacity', 1) |
|
} |
|
}, 10) |
|
} |
|
}) |
|
}) |
|
observer.observe(selection.node(), { |
|
attributes: true, |
|
childList: true, |
|
characterData: true |
|
}) |
|
} |
|
|
|
/** |
|
* Binds handlers to the window resize event. |
|
*/ |
|
ChartInternal.prototype.bindResize = function() { |
|
var $$ = this, |
|
config = $$.config |
|
|
|
$$.resizeFunction = $$.generateResize() // need to call .remove |
|
|
|
$$.resizeFunction.add(function() { |
|
config.onresize.call($$) |
|
}) |
|
if (config.resize_auto) { |
|
$$.resizeFunction.add(function() { |
|
if ($$.resizeTimeout !== undefined) { |
|
window.clearTimeout($$.resizeTimeout) |
|
} |
|
$$.resizeTimeout = window.setTimeout(function() { |
|
delete $$.resizeTimeout |
|
$$.updateAndRedraw({ |
|
withUpdateXDomain: false, |
|
withUpdateOrgXDomain: false, |
|
withTransition: false, |
|
withTransitionForTransform: false, |
|
withLegend: true |
|
}) |
|
if ($$.brush) { |
|
$$.brush.update() |
|
} |
|
}, 100) |
|
}) |
|
} |
|
$$.resizeFunction.add(function() { |
|
config.onresized.call($$) |
|
}) |
|
|
|
$$.resizeIfElementDisplayed = function() { |
|
// if element not displayed skip it |
|
if ($$.api == null || !$$.api.element.offsetParent) { |
|
return |
|
} |
|
|
|
$$.resizeFunction() |
|
} |
|
|
|
window.addEventListener('resize', $$.resizeIfElementDisplayed, false) |
|
} |
|
|
|
/** |
|
* Binds handlers to the window focus event. |
|
*/ |
|
ChartInternal.prototype.bindWindowFocus = function() { |
|
if (this.windowFocusHandler) { |
|
// The handler is already set |
|
return |
|
} |
|
|
|
this.windowFocusHandler = () => { |
|
this.redraw() |
|
} |
|
|
|
window.addEventListener('focus', this.windowFocusHandler) |
|
} |
|
|
|
/** |
|
* Unbinds from the window focus event. |
|
*/ |
|
ChartInternal.prototype.unbindWindowFocus = function() { |
|
window.removeEventListener('focus', this.windowFocusHandler) |
|
delete this.windowFocusHandler |
|
} |
|
|
|
ChartInternal.prototype.generateResize = function() { |
|
var resizeFunctions = [] |
|
|
|
function callResizeFunctions() { |
|
resizeFunctions.forEach(function(f) { |
|
f() |
|
}) |
|
} |
|
callResizeFunctions.add = function(f) { |
|
resizeFunctions.push(f) |
|
} |
|
callResizeFunctions.remove = function(f) { |
|
for (var i = 0; i < resizeFunctions.length; i++) { |
|
if (resizeFunctions[i] === f) { |
|
resizeFunctions.splice(i, 1) |
|
break |
|
} |
|
} |
|
} |
|
return callResizeFunctions |
|
} |
|
|
|
ChartInternal.prototype.endall = function(transition, callback) { |
|
var n = 0 |
|
transition |
|
.each(function() { |
|
++n |
|
}) |
|
.on('end', function() { |
|
if (!--n) { |
|
callback.apply(this, arguments) |
|
} |
|
}) |
|
} |
|
ChartInternal.prototype.generateWait = function() { |
|
var $$ = this |
|
var transitionsToWait = [], |
|
f = function(callback) { |
|
var timer = setInterval(function() { |
|
if (!$$.isTabVisible()) { |
|
return |
|
} |
|
|
|
var done = 0 |
|
transitionsToWait.forEach(function(t) { |
|
if (t.empty()) { |
|
done += 1 |
|
return |
|
} |
|
try { |
|
t.transition() |
|
} catch (e) { |
|
done += 1 |
|
} |
|
}) |
|
if (done === transitionsToWait.length) { |
|
clearInterval(timer) |
|
if (callback) { |
|
callback() |
|
} |
|
} |
|
}, 50) |
|
} |
|
;(f as any).add = function(transition) { |
|
transitionsToWait.push(transition) |
|
} |
|
return f |
|
} |
|
|
|
ChartInternal.prototype.parseDate = function(date) { |
|
var $$ = this, |
|
parsedDate |
|
if (date instanceof Date) { |
|
parsedDate = date |
|
} else if (typeof date === 'string') { |
|
parsedDate = $$.dataTimeParse(date) |
|
} else if (typeof date === 'object') { |
|
parsedDate = new Date(+date) |
|
} else if (typeof date === 'number' && !isNaN(date)) { |
|
parsedDate = new Date(+date) |
|
} |
|
if (!parsedDate || isNaN(+parsedDate)) { |
|
window.console.error("Failed to parse x '" + date + "' to Date object") |
|
} |
|
return parsedDate |
|
} |
|
|
|
ChartInternal.prototype.isTabVisible = function() { |
|
return !document.hidden |
|
} |
|
|
|
ChartInternal.prototype.getPathBox = getPathBox |
|
ChartInternal.prototype.CLASS = CLASS |
|
|
|
export { Chart } |
|
export { ChartInternal }
|
|
|