billboard.js
Version:
Re-usable easy interface JavaScript chart library, based on D3 v4+
256 lines (206 loc) • 7.44 kB
text/typescript
/**
* Copyright (c) 2017 ~ present NAVER Corp.
* billboard.js project is licensed under the MIT license
*/
import {transition as d3Transition} from "d3-transition";
import {$COMMON, $SELECT, $TEXT} from "../../config/classes";
import {generateWait} from "../../module/generator";
import {callFn, capitalize, getOption, isTabVisible, notEmpty} from "../../module/util";
export default {
redraw(options: any = {}): void {
const $$ = this;
const {config, state, $el} = $$;
const {main, treemap} = $el;
state.redrawing = true;
const targetsToShow = $$.filterTargetsToShow($$.data.targets);
const {flow, initializing} = options;
const wth = $$.getWithOption(options);
const duration = wth.Transition ? config.transition_duration : 0;
const durationForExit = wth.TransitionForExit ? duration : 0;
const durationForAxis = wth.TransitionForAxis ? duration : 0;
const transitions = $$.axis?.generateTransitions(durationForAxis);
$$.updateSizes(initializing);
// update legend and transform each g
if (wth.Legend && config.legend_show) {
options.withTransition = !!duration;
!treemap && $$.updateLegend($$.mapToIds($$.data.targets), options, transitions);
} else if (wth.Dimension) {
// 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);
}
// Data empty label positioning and text.
config.data_empty_label_text && main.select(`text.${$TEXT.text}.${$COMMON.empty}`)
.attr("x", state.width / 2)
.attr("y", state.height / 2)
.text(config.data_empty_label_text)
.style("display", targetsToShow.length ? "none" : null);
// update axis
if (state.hasAxis) {
// @TODO: Make 'init' state to be accessible everywhere not passing as argument.
$$.axis.redrawAxis(targetsToShow, wth, transitions, flow, initializing);
// grid
$$.hasGrid() && $$.updateGrid();
// rect for regions
config.regions.length && $$.updateRegion();
["bar", "candlestick", "line", "area"].forEach(v => {
const name = capitalize(v);
if ((/^(line|area)$/.test(v) && $$.hasTypeOf(name)) || $$.hasType(v)) {
$$[`update${name}`](wth.TransitionForExit);
}
});
// circles for select
$el.text && main.selectAll(`.${$SELECT.selectedCircles}`)
.filter($$.isBarType.bind($$))
.selectAll("circle")
.remove();
// event rects will redrawn when flow called
if (config.interaction_enabled && !flow && wth.EventRect) {
$$.redrawEventRect();
$$.bindZoomEvent?.();
}
} else {
// arc
$el.arcs && $$.redrawArc(duration, durationForExit, wth.Transform);
// radar
$el.radar && $$.redrawRadar();
// polar
$el.polar && $$.redrawPolar();
// funnel
$el.funnel && $$.redrawFunnel();
// treemap
treemap && $$.updateTreemap(durationForExit);
}
if (!state.resizing && !treemap && ($$.hasPointType() || state.hasRadar)) {
$$.updateCircle();
} else if ($$.hasLegendDefsPoint?.()) {
$$.data.targets.forEach($$.point("create", this));
}
// text
$$.hasDataLabel() && !$$.hasArcType(null, ["radar"]) && $$.updateText();
// title
$$.redrawTitle?.();
initializing && $$.updateTypesElements();
$$.generateRedrawList(targetsToShow, flow, duration, wth.Subchart);
$$.updateTooltipOnRedraw();
$$.callPluginHook("$redraw", options, duration);
},
/**
* Generate redraw list
* @param {object} targets targets data to be shown
* @param {object} flow flow object
* @param {number} duration duration value
* @param {boolean} withSubchart whether or not to show subchart
* @private
*/
generateRedrawList(targets, flow: any, duration: number, withSubchart: boolean): void {
const $$ = this;
const {config, state} = $$;
const shape = $$.getDrawShape();
if (state.hasAxis) {
// subchart
config.subchart_show && $$.redrawSubchart(withSubchart, duration, shape);
}
// generate flow
const flowFn = flow && $$.generateFlow({
targets,
flow,
duration: flow.duration,
shape,
xv: $$.xv.bind($$)
});
const withTransition = (duration || flowFn) && isTabVisible();
// redraw list
const redrawList = $$.getRedrawList(shape, flow, flowFn, withTransition);
// callback function after redraw ends
const afterRedraw = () => {
flowFn && flowFn();
state.redrawing = false;
callFn(config.onrendered, $$.api);
};
if (afterRedraw) {
// Only use transition when current tab is visible.
if (withTransition && redrawList.length) {
// Wait for end of transitions for callback
const waitForDraw = generateWait();
// transition should be derived from one transition
d3Transition().duration(duration)
.each(() => {
redrawList
.reduce((acc, t1) => acc.concat(t1), [])
.forEach(t => waitForDraw.add(t));
})
.call(waitForDraw, afterRedraw);
} else if (!state.transiting) {
afterRedraw();
}
}
// update fadein condition
$$.mapToIds($$.data.targets).forEach(id => {
state.withoutFadeIn[id] = true;
});
},
getRedrawList(shape, flow, flowFn, withTransition: boolean): Function[] {
const $$ = <any>this;
const {config, state: {hasAxis, hasRadar, hasTreemap}, $el: {grid}} = $$;
const {cx, cy, xForText, yForText} = shape.pos;
const list: Function[] = [];
if (hasAxis) {
if (config.grid_x_lines.length || config.grid_y_lines.length) {
list.push($$.redrawGrid(withTransition));
}
if (config.regions.length) {
list.push($$.redrawRegion(withTransition));
}
Object.keys(shape.type).forEach(v => {
const name = capitalize(v);
const drawFn = shape.type[v];
if ((/^(area|line)$/.test(v) && $$.hasTypeOf(name)) || $$.hasType(v)) {
list.push($$[`redraw${name}`](drawFn, withTransition));
}
});
!flow && grid.main && list.push($$.updateGridFocus());
}
if (!$$.hasArcType() || hasRadar) {
notEmpty(config.data_labels) && config.data_labels !== false &&
list.push($$.redrawText(xForText, yForText, flow, withTransition));
}
if (($$.hasPointType() || hasRadar) && !$$.isPointFocusOnly()) {
$$.redrawCircle && list.push($$.redrawCircle(cx, cy, withTransition, flowFn));
}
if (hasTreemap) {
list.push($$.redrawTreemap(withTransition));
}
return list;
},
updateAndRedraw(options: any = {}): void {
const $$ = this;
const {config, state} = $$;
let transitions;
// 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 = true;
options.withUpdateOrgXDomain = true;
options.withTransitionForExit = false;
options.withTransitionForTransform = getOption(options, "withTransitionForTransform",
options.withTransition);
// MEMO: called in updateLegend in redraw if withLegend
if (!(options.withLegend && config.legend_show)) {
if (state.hasAxis) {
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);
}
};