UNPKG

billboard.js

Version:

Re-usable easy interface JavaScript chart library, based on D3 v4+

194 lines (177 loc) • 5.03 kB
/** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ import type {HTMLProps} from "react"; import {createElement, forwardRef, useEffect, useImperativeHandle, useRef} from "react"; import type {bb, Chart as BillboardChart, ChartOptions} from "../../types"; import type {ChartTypes} from "../../types/types"; export type {ChartOptions as IChartOptions}; export type ChartTypeResolver = () => ChartTypes; export type ChartModuleResolver = () => unknown; export interface IProp extends Pick<HTMLProps<HTMLDivElement>, "className" | "style"> { bb: typeof bb; options: ChartOptions; type?: ChartTypes | ChartTypeResolver; modules?: ChartModuleResolver[]; } export interface IChart { instance: BillboardChart; } /** * Resolve chart type from a direct string or resolver function. * @param {string|function} [type] Chart type value or resolver * @returns {string|undefined} Chart type string * @private */ function resolveType(type?: IProp["type"]): ChartTypes | undefined { return typeof type === "function" ? type() : type; } /** * Clone options and apply the optional chart type helper. * @param {object} options Chart options * @param {string|function} [type] Chart type value or resolver * @returns {object} Chart options with resolved type * @private */ function getOptions(options: ChartOptions, type?: IProp["type"]): ChartOptions { const resolvedType = resolveType(type); return { ...options, ...(resolvedType ? { data: { ...options.data, type: resolvedType } } : {}) }; } /** * React component wrapper for billboard.js. * @module billboard.js/react * @description * Use the `billboard.js/react` subpath to render billboard.js charts in React. * The component preserves the existing `ChartOptions` API and receives the * billboard namespace through the `bb` prop, so importing this React subpath * does not pull the root billboard.js bundle into non-React user bundles. * @example * import bb, {line} from "billboard.js"; * import BillboardJS from "billboard.js/react"; * * <BillboardJS * bb={bb} * options={{ * data: { * columns: [["data1", 30, 120, 80]], * type: line() * } * }} * />; * @example * import bb, {canvas, line} from "billboard.js/canvas"; * import BillboardJS from "billboard.js/react"; * * <BillboardJS * bb={bb} * type={line} * modules={[canvas]} * style={{ * width: "480px", * height: "320px" * }} * options={{ * render: { * mode: "canvas" * }, * size: { * width: 480, * height: 320 * }, * data: { * columns: [["data1", 30, 120, 80]] * } * }} * />; * @example * import bb, {line, zoom} from "billboard.js"; * import {Chart} from "billboard.js/react"; * * <Chart * bb={bb} * type={line} * modules={[zoom]} * options={{ * data: { * columns: [["data1", 30, 120, 80]] * }, * zoom: { * enabled: true * } * }} * />; * @example * // UMD: 'dist/billboard.react.js' exposes the 'BillboardReact' global, holding * // the same component as both '.Chart' and '.default'. React is expected on the * // page as the 'React' global, so this path needs a UMD build of React - React * // 18 and below ship one, React 19 does not. * <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> * <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> * * <link rel="stylesheet" href="$YOUR_PATH/billboard.css"> * <script src="$YOUR_PATH/billboard.pkgd.js"></script> * <script src="$YOUR_PATH/billboard.react.js"></script> * * <div id="root"></div> * <script> * const {Chart} = BillboardReact; * * ReactDOM.createRoot(document.getElementById("root")).render( * React.createElement(Chart, { * bb, * options: { * data: { * columns: [["data1", 30, 120, 80]], * type: "line" * } * } * }) * ); * </script> */ const Chart = forwardRef<IChart, IProp>((props, ref) => { const container = useRef<HTMLDivElement>(null); const instance = useRef<BillboardChart | null>(null); const {bb: billboard, options, type, modules, ...htmlDivProps} = props; useEffect(() => { if (!billboard || !options) { // eslint-disable-next-line no-console console.warn("Required props('bb' or 'options') are not defined."); return; } modules?.forEach(register => register()); const chartOptions = getOptions(options, type); chartOptions.bindto = container.current; instance.current = billboard.generate(chartOptions); return () => { instance.current?.destroy(); instance.current = null; }; }, []); useImperativeHandle( ref, () => ({ get instance() { return instance.current as BillboardChart; } }), [] ); return createElement("div", { ...htmlDivProps, ref: container }); }); export {Chart}; export default Chart;