billboard.js
Version:
Re-usable easy interface JavaScript chart library, based on D3 v4+
134 lines (131 loc) • 4.93 kB
JavaScript
/*!
* Copyright (c) 2017 ~ present NAVER Corp.
* billboard.js project is licensed under the MIT license
*
* billboard.js, JavaScript chart library
* https://naver.github.io/billboard.js/
*
* @version 4.0.1
*/
import { select } from 'd3-selection';
import { $DRAG, $SHAPE, $COMMON, $SELECT, $CIRCLE, $BAR } from '../../config/classes.js';
import { scheduleRAFUpdate, getPathBox } from '../../module/util/dom.js';
/**
* Copyright (c) 2017 ~ present NAVER Corp.
* billboard.js project is licensed under the MIT license
*/
/**
* Module used for data.selection.draggable option
*/
var drag = {
/**
* Called when dragging.
* Data points can be selected.
* @private
* @param {object} mouse Object
*/
drag(mouse) {
const $$ = this;
const { config, state, $el: { main } } = $$;
const isSelectionGrouped = config.data_selection_grouped;
const isSelectable = config.interaction_enabled && config.data_selection_isselectable;
if ($$.hasArcType() ||
!config.data_selection_enabled || // do nothing if not selectable
(config.zoom_enabled && !$$.zoom.altDomain) || // skip if zoomable because of conflict drag behavior
!config.data_selection_multiple // skip when single selection because drag is used for multiple selection
) {
return;
}
const [sx, sy] = state.dragStart || [0, 0];
const [mx, my] = mouse;
const minX = Math.min(sx, mx);
const maxX = Math.max(sx, mx);
const minY = isSelectionGrouped ? state.margin.top : Math.min(sy, my);
const maxY = isSelectionGrouped ? state.height : Math.max(sy, my);
// Use RAF batching to smooth out rapid drag events
const executeDrag = () => {
// Check if chart still exists before executing
if (!$$ || !$$.$el || !$$.$el.main) {
return;
}
main.select(`.${$DRAG.dragarea}`)
.attr("x", minX)
.attr("y", minY)
.attr("width", maxX - minX)
.attr("height", maxY - minY);
// TODO: binary search when multiple xs
main.selectAll(`.${$SHAPE.shapes}`)
.selectAll(`.${$SHAPE.shape}`)
.filter(d => isSelectable?.bind($$.api)(d))
.each(function (d, i) {
const shape = select(this);
const isSelected = shape.classed($SELECT.SELECTED);
const isIncluded = shape.classed($DRAG.INCLUDED);
let isWithin;
let toggle;
if (shape.classed($CIRCLE.circle)) {
const x = +shape.attr("cx");
const y = +shape.attr("cy");
toggle = $$.togglePoint;
isWithin = minX < x && x < maxX && minY < y && y < maxY;
}
else if (shape.classed($BAR.bar)) {
const { x, y, width, height } = getPathBox(this);
toggle = $$.togglePath;
isWithin = !(maxX < x || x + width < minX) &&
!(maxY < y || y + height < minY);
}
else {
// line/area selection not supported yet
return;
}
// @ts-ignore
if (isWithin ^ isIncluded) {
shape.classed($DRAG.INCLUDED, !isIncluded);
// TODO: included/unincluded callback here
shape.classed($SELECT.SELECTED, !isSelected);
toggle.call($$, !isSelected, shape, d, i);
}
});
};
scheduleRAFUpdate($$.state, executeDrag);
},
/**
* Called when the drag starts.
* Adds and Shows the drag area.
* @private
* @param {object} mouse Object
*/
dragstart(mouse) {
const $$ = this;
const { config, state, $el: { main } } = $$;
if ($$.hasArcType() || !config.data_selection_enabled) {
return;
}
state.dragStart = mouse;
main.select(`.${$COMMON.chart}`)
.append("rect")
.attr("class", $DRAG.dragarea)
.style("opacity", "0.1");
$$.setDragStatus(true);
},
/**
* Called when the drag finishes.
* Removes the drag area.
* @private
*/
dragend() {
const $$ = this;
const { config, $el: { main }, $T } = $$;
if ($$.hasArcType() || !config.data_selection_enabled) { // do nothing if not selectable
return;
}
$T(main.select(`.${$DRAG.dragarea}`))
.style("opacity", "0")
.remove();
main.selectAll(`.${$SHAPE.shape}`)
.classed($DRAG.INCLUDED, false);
$$.setDragStatus(false);
}
};
export { drag as default };