Skip to main content
Module

x/billboardjs/Plugin/sparkline/index.ts

πŸ“Š Re-usable, easy interface JavaScript chart library based on D3.js
Latest
File
/** * Copyright (c) 2021 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */import type {IData} from "../../ChartInternal/data/IData";import {$COMMON} from "../../config/classes";import {loadConfig} from "../../config/config";import Plugin from "../Plugin";import Options from "./Options";
/** * Sparkline plugin.<br> * Generates sparkline charts * - **NOTE:** * - Plugins aren't built-in. Need to be loaded or imported to be used. * - Non required modules from billboard.js core, need to be installed separately. * * - **Bear in mind:** * - Use this plugin to visualize multiple tiny chart only and chart APIs won't work properly. * - Sparkline chart size will be based on the main chart element size. To control spakrline charts, is highly recommended to set `size` option. * - Bubble, scatter and Arc(pie, donut, ratdar) types aren't supported. * - Some options will be stricted to be: * - `resize.auto = false` * - `axis.x.show = false` * - `axis.y.show = false` * - `axis.y.padding = 10` * - `legend.show = false` * * @class plugin-sparkline * @param {object} options sparkline plugin options * @augments Plugin * @returns {Sparkline} * @example * // Plugin must be loaded before the use. * <script src="$YOUR_PATH/plugin/billboardjs-plugin-sparkline.js"></script> * * var chart = bb.generate({ * ... * plugins: [ * new bb.plugin.sparkline({ * selector: ".sparkline" * }), * ] * }); * @example * import {bb} from "billboard.js"; * import Sparkline from "billboard.js/dist/billboardjs-plugin-sparkline"; * * bb.generate({ * ... * plugins: [ * new Sparkline({ ... }) * ] * }) */export default class Sparkline extends Plugin { static version = `0.0.1`; private config; private element;
constructor(options) { super(options); this.config = new Options();
return this; }
$beforeInit(): void { loadConfig.call(this, this.options);
this.validate(); this.element = [].slice.call(document.querySelectorAll(this.config.selector));
// override internal methods this.overrideInternals();
// override options this.overrideOptions();
// bind event handlers's context this.overHandler = this.overHandler.bind(this); this.moveHandler = this.moveHandler.bind(this); this.outHandler = this.outHandler.bind(this); }
validate(): void { const {$$, config} = this; let msg = "";
if (!config.selector || !document.querySelector(config.selector)) { msg = "No holder elements found from given selector option."; }
if ($$.hasType("bubble") || $$.hasType("scatter") || $$.hasArcType($$.data.targets)) { msg = "Contains non supported chart types."; }
if (msg) { throw new Error(`[Sparkline plugin] ${msg}`); } }
overrideInternals(): void { const {$$} = this; const {getBarW, getIndices} = $$;
// override internal methods to positioning bars $$.getIndices = function(indices, d, caller) { return caller === "getShapeX" ? {} : getIndices.call(this, indices, d); };
$$.getBarW = function(type, axis) { return getBarW.call(this, type, axis, 1); }; }
overrideOptions(): void { const {config} = this.$$;
config.legend_show = false; config.resize_auto = false; config.axis_x_show = false;
// set default axes padding if (config.padding !== false) { const hasOption = o => Object.keys(o || {}).length > 0;
if (hasOption(config.axis_x_padding)) { config.axis_x_padding = { left: 15, right: 15, unit: "px" }; }
if (hasOption(config.axis_y_padding)) { config.axis_y_padding = 5; } }
config.axis_y_show = false;
if (!config.tooltip_position) { config.tooltip_position = function(data, width, height) { const {internal: {state: {event}}} = this; let top = event.pageY - (height * 1.35); let left = event.pageX - (width / 2);
if (top < 0) { top = 0; }
if (left < 0) { left = 0; }
return {top, left}; }; } }
$init(): void { const {$$: {$el}} = this;
// make disable-ish main chart element $el.chart .style("width", "0") .style("height", "0") .style("pointer-events", "none");
$el.tooltip?.node() && document.body.appendChild($el.tooltip.node()); }
$afterInit(): void { const {$$} = this;
$$.$el.svg.attr("style", null) .style("width", "0") .style("height", "0");
this.bindEvents(true); }
/** * Bind tooltip event handlers for each sparkline elements. * @param {boolean} bind or unbind * @private */ bindEvents(bind = true): void { const {$$: {config}} = this;
if (config.interaction_enabled && config.tooltip_show) { const method = `${bind ? "add" : "remove"}EventListener`;
this.element .forEach(el => { const svg = el.querySelector("svg");
svg[method]("mouseover", this.overHandler); svg[method]("mousemove", this.moveHandler); svg[method]("mouseout", this.outHandler); }); } }
overHandler(e): void { const {$$} = this; const {state: {eventReceiver}} = $$;
eventReceiver.rect = e.target.getBoundingClientRect(); }
moveHandler(e): void { const {$$} = this; const index = $$.getDataIndexFromEvent(e); const data = $$.api.data(e.target.__id)?.[0] as IData; const d = data?.values?.[index];
if (d && !d.name) { d.name = d.id; }
$$.state.event = e;
if ($$.isPointFocusOnly?.() && d) { $$.showCircleFocus?.([d]); }
$$.setExpand(index, data.id, true); $$.showTooltip([d], e.target); }
outHandler(e): void { const {$$} = this;
$$.state.event = e;
$$.isPointFocusOnly() ? $$.hideCircleFocus() : $$.unexpandCircles();
$$.hideTooltip(); }
$redraw(): void { const {$$} = this; const {$el} = $$;
let el = this.element; const data = $$.api.data(); const svgWrapper = $el.chart.html().match(/<svg[^>]*>/)?.[0];
// append sparkline holder if is less than the data length if (el.length < data.length) { const chart = $el.chart.node();
for (let i = data.length - el.length; i > 0; i--) { chart.parentNode.insertBefore(el[0].cloneNode(), chart.nextSibling); }
this.element = document.querySelectorAll(this.config.selector); el = this.element; }
data.map(v => v.id) .forEach((id, i) => { const selector = `.${$COMMON.target}-${id}`; const shape = $el.main.selectAll(selector); let svg = el[i].querySelector("svg");
if (!svg) { el[i].innerHTML = `${svgWrapper}</svg>`; svg = el[i].querySelector("svg"); svg.__id = id; }
if (!svg.querySelector(selector)) { shape.style("opacity", null); }
shape .style("fill", "none") .style("opacity", null);
svg.innerHTML = ""; svg.appendChild(shape.node()); }); }
$willDestroy(): void { this.bindEvents(false); this.element .forEach(el => { el.innerHTML = ""; }); }}