UNPKG

react-lightweight-charts-simple

Version:

A simple react wrapper for lightweight-charts library

739 lines (612 loc) 24.2 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var lightweightCharts = require('lightweight-charts'); var React = require('react'); function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } var ChartContext = /*#__PURE__*/React.createContext({ containerRef: { current: null } }); var SeriesContext = /*#__PURE__*/React.createContext(undefined); /** * Get the `lightweight-charts` chart api object and the container `<div />` referrence. * * ❗Only use inside `<Chart />`. * * @returns object.chart: `IChartApi` * @returns object.containerRef: `React.Ref<HTMLDivElement>` */ function useChart() { return React.useContext(ChartContext); } /** * Return width and height for the `entry` * @param entry ResizeObserverEntry * @returns [width, height] as `[number, number]` */ var getSizeFromEntry = function getSizeFromEntry(entry) { if (entry.contentBoxSize) { if (Array.isArray(entry.contentBoxSize)) { return [entry.contentBoxSize[0].inlineSize, entry.contentBoxSize[0].blockSize]; } else { // Firefox implements `contentBoxSize` as a single content rect, rather than an array /* eslint-disable @typescript-eslint/no-explicit-any */ return [entry.contentBoxSize.inlineSize, entry.contentBoxSize.blockSize]; /* eslint-enable @typescript-eslint/no-explicit-any */ } } return [entry.contentRect.width, entry.contentRect.height]; }; (function (ChartAutoResizerTriggerSource) { /** * Indicate to use `window.addEventListener('resize', handler)`. */ ChartAutoResizerTriggerSource[ChartAutoResizerTriggerSource["windowResize"] = 1] = "windowResize"; /** * Indicate to use `new ResizeObserver(handler).observe(parentElement)`. */ ChartAutoResizerTriggerSource[ChartAutoResizerTriggerSource["parentResize"] = 2] = "parentResize"; })(exports.ChartAutoResizerTriggerSource || (exports.ChartAutoResizerTriggerSource = {})); var DEFAULT_TRIGGER_SOURCE = typeof window !== 'undefined' && 'ResizeObserver' in window ? exports.ChartAutoResizerTriggerSource.parentResize : exports.ChartAutoResizerTriggerSource.windowResize; /** * Utility component for resizing the chart when window size changed. * * ❗Only use inside `<Chart />`. * Turn chart option `disableAutoResize = true` to prevent multiple resizer called. */ var ChartAutoResizer = function ChartAutoResizer(_ref) { var _ref$triggerSource = _ref.triggerSource, triggerSource = _ref$triggerSource === void 0 ? DEFAULT_TRIGGER_SOURCE : _ref$triggerSource, onResize = _ref.onResize; var _useChart = useChart(), chart = _useChart.chart, containerRef = _useChart.containerRef; React.useEffect(function () { var _containerRef$current3; if (triggerSource === 0 || !chart) return; var handler = function handler(entriesOrEvent) { var _containerRef$current, _containerRef$current2; var options = chart.options(); var _ref2 = Array.isArray(entriesOrEvent) ? getSizeFromEntry(entriesOrEvent[0]) : [(_containerRef$current = containerRef.current) != null && _containerRef$current.parentElement ? parseInt(getComputedStyle(containerRef.current.parentElement).width) : 0, (_containerRef$current2 = containerRef.current) != null && _containerRef$current2.parentElement ? parseInt(getComputedStyle(containerRef.current.parentElement).height) : 0], observedWidth = _ref2[0], observedHeight = _ref2[1]; var width = options.width || observedWidth; var height = options.height || observedHeight; chart.resize(width, height); onResize == null ? void 0 : onResize(chart, width, height, containerRef.current); }; // first run handler(); // listen window's resize event if (triggerSource & exports.ChartAutoResizerTriggerSource.windowResize) { window.addEventListener('resize', handler); } // observe parent size change var resizeObserver; var parent = (_containerRef$current3 = containerRef.current) == null ? void 0 : _containerRef$current3.parentElement; if (triggerSource & exports.ChartAutoResizerTriggerSource.parentResize && parent) { resizeObserver = new ResizeObserver(handler); resizeObserver.observe(parent); } return function () { if (triggerSource & exports.ChartAutoResizerTriggerSource.windowResize) { window.removeEventListener('resize', handler); } // don't need to check trigger source here because `resizeObserver` is only created when needed. if (parent) { var _resizeObserver; (_resizeObserver = resizeObserver) == null ? void 0 : _resizeObserver.unobserve(parent); } }; }, [chart, triggerSource, containerRef, onResize]); return null; }; /** * Utility component for triggering `chart.timeScale().fitContent()` depends on dependency list `deps`. * * ❗Only use inside `<Chart />`. * Turn chart option `disableAutoContentFitOnInit = true` to prevent multiple fit content called. * @example * ```js * const App = () => { * const data = useMemo(() => getData(), []); * return ( * <Chart disableAutoContentFitOnInit> * <AreaSeries data={data} /> * <ChartFitContentTrigger deps={[data]}> * </Chart> * ); * } * ``` */ var ChartFitContentTrigger = function ChartFitContentTrigger(_ref) { var _ref$deps = _ref.deps, deps = _ref$deps === void 0 ? [] : _ref$deps; var _useChart = useChart(), chart = _useChart.chart; React.useEffect(function () { if (!chart) return; // fit content in next runloop cycle to make sure the data changed if the data is in the `deps` list setTimeout(function () { return chart.timeScale().fitContent(); }, 0); }, [chart].concat(deps)); // eslint-disable-line react-hooks/exhaustive-deps return null; }; /** * Subscribe `handler` via `chart.subscribeClick()`. * * ❗Only use inside `<Chart />`. */ var ChartOnClickSubscriber = function ChartOnClickSubscriber(_ref) { var handler = _ref.handler; var _useChart = useChart(), chart = _useChart.chart; React.useEffect(function () { chart == null ? void 0 : chart.subscribeClick(handler); return function () { return chart == null ? void 0 : chart.unsubscribeClick(handler); }; }, [chart, handler]); return null; }; /** * Subscribe `handler` via `chart.subscribeCrosshairMove()`. * * ❗Only use inside `<Chart />`. */ var ChartOnCrosshairMoveSubscriber = function ChartOnCrosshairMoveSubscriber(_ref2) { var handler = _ref2.handler; var _useChart2 = useChart(), chart = _useChart2.chart; React.useEffect(function () { chart == null ? void 0 : chart.subscribeCrosshairMove(handler); return function () { return chart == null ? void 0 : chart.unsubscribeCrosshairMove(handler); }; }, [chart, handler]); return null; }; /** * Subscribe `handler` via `chart.timeScale().subscribeSizeChange()`. * * ❗Only use inside `<Chart />`. */ var TimeScaleOnSizeChangeSubscriber = function TimeScaleOnSizeChangeSubscriber(_ref3) { var handler = _ref3.handler; var _useChart3 = useChart(), chart = _useChart3.chart; React.useEffect(function () { chart == null ? void 0 : chart.timeScale().subscribeSizeChange(handler); return function () { return chart == null ? void 0 : chart.timeScale().unsubscribeSizeChange(handler); }; }, [chart, handler]); return null; }; /** * Subscribe `handler` via `chart.timeScale().subscribeVisibleTimeRangeChange()`. * * ❗Only use inside `<Chart />`. */ var TimeScaleOnVisibleTimeRangeChangeSubscriber = function TimeScaleOnVisibleTimeRangeChangeSubscriber(_ref4) { var handler = _ref4.handler; var _useChart4 = useChart(), chart = _useChart4.chart; React.useEffect(function () { chart == null ? void 0 : chart.timeScale().subscribeVisibleTimeRangeChange(handler); return function () { return chart == null ? void 0 : chart.timeScale().unsubscribeVisibleTimeRangeChange(handler); }; }, [chart, handler]); return null; }; /** * Subscribe `handler` via `chart.timeScale().subscribeVisibleLogicalRangeChange()`. * * ❗Only use inside `<Chart />`. */ var TimeScaleOnVisibleLogicalRangeChangeSubscriber = function TimeScaleOnVisibleLogicalRangeChangeSubscriber(_ref5) { var handler = _ref5.handler; var _useChart5 = useChart(), chart = _useChart5.chart; React.useEffect(function () { chart == null ? void 0 : chart.timeScale().subscribeVisibleLogicalRangeChange(handler); return function () { return chart == null ? void 0 : chart.timeScale().unsubscribeVisibleLogicalRangeChange(handler); }; }, [chart, handler]); return null; }; var _excluded = ["width", "height", "options", "disableAutoResize", "disableAutoContentFitOnInit", "onClick", "onCrosshairMove", "onTimeScaleSizeChange", "onVisibleTimeRangeChange", "onVisibleLogicalRangeChange", "onInit", "children", "style"]; /** * The main wrapper for the series. */ var Chart = /*#__PURE__*/React.forwardRef(function Chart(_ref, ref) { var width = _ref.width, height = _ref.height, options = _ref.options, disableAutoResize = _ref.disableAutoResize, disableAutoContentFitOnInit = _ref.disableAutoContentFitOnInit, onClick = _ref.onClick, onCrosshairMove = _ref.onCrosshairMove, onTimeScaleSizeChange = _ref.onTimeScaleSizeChange, onVisibleTimeRangeChange = _ref.onVisibleTimeRangeChange, onVisibleLogicalRangeChange = _ref.onVisibleLogicalRangeChange, onInit = _ref.onInit, children = _ref.children, style = _ref.style, rest = _objectWithoutPropertiesLoose(_ref, _excluded); var divRef = React.useRef(); var _React$useState = React.useState(), chart = _React$useState[0], setChart = _React$useState[1]; var chartRef = React.useRef(chart); React.useImperativeHandle(ref, function () { return { chart: chartRef.current, container: divRef.current }; }); var mergedOptions = React.useMemo(function () { return _extends({ width: width, height: height }, options); }, [width, height, options]); // create or update chart by options React.useEffect(function () { if (!divRef.current) return; if (!chartRef.current) { var _chart = lightweightCharts.createChart(divRef.current, mergedOptions); chartRef.current = _chart; setChart(_chart); } else { chartRef.current.applyOptions(mergedOptions); } }, [mergedOptions]); // resize if width or height option provided React.useEffect(function () { if (mergedOptions.width && mergedOptions.height) { chart == null ? void 0 : chart.resize(mergedOptions.width, mergedOptions.height); } }, [chart, mergedOptions.width, mergedOptions.height]); // remove chart when unmount React.useEffect(function () { return function () { var _chartRef$current; (_chartRef$current = chartRef.current) == null ? void 0 : _chartRef$current.remove(); chartRef.current = undefined; }; }, []); // trigger onInit when chart is created React.useEffect(function () { if (!chart) return; onInit == null ? void 0 : onInit(chart, divRef.current); }, [chart, onInit]); var contextValue = React.useMemo(function () { return { chart: chart, containerRef: divRef }; }, [chart]); return React.createElement(ChartContext.Provider, { value: contextValue }, React.createElement("div", Object.assign({}, rest, { style: _extends({}, style, { position: 'relative' }), ref: divRef }), children, !disableAutoResize && React.createElement(ChartAutoResizer, null), !disableAutoContentFitOnInit && React.createElement(ChartFitContentTrigger, null), onClick && React.createElement(ChartOnClickSubscriber, { handler: onClick }), onCrosshairMove && React.createElement(ChartOnCrosshairMoveSubscriber, { handler: onCrosshairMove }), onTimeScaleSizeChange && React.createElement(TimeScaleOnSizeChangeSubscriber, { handler: onTimeScaleSizeChange }), onVisibleTimeRangeChange && React.createElement(TimeScaleOnVisibleTimeRangeChangeSubscriber, { handler: onVisibleTimeRangeChange }), onVisibleLogicalRangeChange && React.createElement(TimeScaleOnVisibleLogicalRangeChangeSubscriber, { handler: onVisibleLogicalRangeChange }))); }); /** * Get the series api object. * * ❗Only use inside `<Series />`. * * @returns series: `ISeriesApi` */ function useSeries() { return React.useContext(SeriesContext); } /** * Create a price line for the series. * * ❗Only use inside `<Series />`. */ var PriceLine = function PriceLine(props) { var series = useSeries(); var priceLineRef = React.useRef(); // create price line React.useEffect(function () { if (!priceLineRef.current) { priceLineRef.current = series == null ? void 0 : series.createPriceLine(props); } else { priceLineRef.current.applyOptions(props); } }, [series, props]); // remove price line React.useEffect(function () { return function () { if (!priceLineRef.current || !series) return; // suppress error when series is trying remove a removed price line or the chart is already removed in parent lifecycle try { series.removePriceLine(priceLineRef.current); } catch (_unused) {} // eslint-disable-line no-empty priceLineRef.current = undefined; }; }, [series]); return null; }; function makeSeries(displayName, create) { function BaseSeries(props, ref) { var data = props.data, latestItem = props.latestItem, options = props.options, markers = props.markers, children = props.children; var _useChart = useChart(), chart = _useChart.chart; var _React$useState = React.useState(undefined), series = _React$useState[0], setSeries = _React$useState[1]; var seriesRef = React.useRef(series); React.useImperativeHandle(ref, function () { return seriesRef.current; }); // create or update series by its options React.useEffect(function () { if (!chart) return; if (!seriesRef.current) { var _series = create(chart, options); seriesRef.current = _series; setSeries(_series); } else if (options) { seriesRef.current.applyOptions(options); } }, [chart, options]); // remove series on unmount React.useEffect(function () { return function () { if (!seriesRef.current || !chart) return; // suppress error when chart is trying remove a removed series or the chart is already removed in parent lifecycle try { chart.removeSeries(seriesRef.current); } catch (_unused) {} // eslint-disable-line no-empty seriesRef.current = undefined; }; }, [chart]); // update data React.useEffect(function () { series == null ? void 0 : series.setData(data != null ? data : []); }, [series, data]); // new data for update React.useEffect(function () { latestItem && (series == null ? void 0 : series.update(latestItem)); }, [latestItem, series]); // update markers React.useEffect(function () { series == null ? void 0 : series.setMarkers(markers != null ? markers : []); }, [series, markers]); return React.createElement(SeriesContext.Provider, { value: series }, children); } BaseSeries.displayName = displayName; return BaseSeries; } /** * Create an area series for the chart * * ❗Only use inside `<Chart />`. */ var AreaSeries = /*#__PURE__*/React.forwardRef( /*#__PURE__*/makeSeries('AreaSeries', function (chart, options) { return chart.addAreaSeries(options); })); /** * Create a bar series for the chart. * * ❗Only use inside `<Chart />`. */ var BarSeries = /*#__PURE__*/React.forwardRef( /*#__PURE__*/makeSeries('BarSeries', function (chart, options) { return chart.addBarSeries(options); })); /** * Create a baseline series for the chart. * * ❗Only use inside `<Chart />`. */ var BaselineSeries = /*#__PURE__*/React.forwardRef( /*#__PURE__*/makeSeries('BaselineSeries', function (chart, options) { return chart.addBaselineSeries(options); })); /** * Create a candlestick series for the chart. * * ❗Only use inside `<Chart />`. */ var CandlestickSeries = /*#__PURE__*/React.forwardRef( /*#__PURE__*/makeSeries('CandlestickSeries', function (chart, options) { return chart.addCandlestickSeries(options); })); /** * Create a histogram series for the chart. * * ❗Only use inside `<Chart />`. */ var HistogramSeries = /*#__PURE__*/React.forwardRef( /*#__PURE__*/makeSeries('HistogramSeries', function (chart, options) { return chart.addHistogramSeries(options); })); /** * Create a line series for the chart. * * ❗Only use inside `<Chart />`. */ var LineSeries = /*#__PURE__*/React.forwardRef( /*#__PURE__*/makeSeries('LineSeries', function (chart, options) { return chart.addLineSeries(options); })); var _excluded$1 = ["content", "makeTransform", "style"]; var DEFAULT_TOOLTIP_STYLE = { pointerEvents: 'none', position: 'absolute', top: 0, left: 0, zIndex: 10, transitionDuration: '150ms', transitionTimingFunction: 'ease-out' }; var DEFAULT_SIZE = [0, 0]; /** * Default transform makes tooltip to be horizontally centered with cursor, and above the cursor. * Tooltip is also bounded inside the chart box and may be below the cursor if not enough vertical space. */ var DEFAULT_TOOLTIP_MAKE_TRANSFORM = function DEFAULT_TOOLTIP_MAKE_TRANSFORM(chart, event, container, size) { var _chart$priceScale$wid, _chart$priceScale$wid2; var _ref = event != null ? event : {}, point = _ref.point; if (container == null || point == null) return undefined; var paddingLeft = (_chart$priceScale$wid = chart == null ? void 0 : chart.priceScale('left').width()) != null ? _chart$priceScale$wid : 0; var paddingRight = (_chart$priceScale$wid2 = chart == null ? void 0 : chart.priceScale('right').width()) != null ? _chart$priceScale$wid2 : 0; var _container$getBoundin = container.getBoundingClientRect(), containerWidth = _container$getBoundin.width; var adjustedStartingX = point.x + paddingLeft; var x = Math.max(paddingLeft, Math.min(adjustedStartingX - size.width / 2, containerWidth - paddingRight - size.width)); var y = point.y - size.height - 8; return "translate(" + x + "px, " + (y < 0 ? point.y + 20 : y) + "px)"; }; /** * A tooltip will be flow in front of the chart and follow the crosshair movement. * * ❗Only use inside `<Chart />` or `<Series />`. */ var Tooltip = /*#__PURE__*/React.forwardRef(function Tooltip(_ref2, ref) { var content = _ref2.content, makeTransform = _ref2.makeTransform, style = _ref2.style, rest = _objectWithoutPropertiesLoose(_ref2, _excluded$1); var _useChart = useChart(), chart = _useChart.chart, containerRef = _useChart.containerRef; var series = useSeries(); // container ref var divRef = React.useRef(); // used to keep the previous transform string var previousTransformRef = React.useRef(''); var _React$useState = React.useState(), eventState = _React$useState[0], setEventState = _React$useState[1]; var _React$useState2 = React.useState(DEFAULT_SIZE), size = _React$useState2[0], setSize = _React$useState2[1]; React.useImperativeHandle(ref, function () { return divRef.current; }); // make a merged css style for tooltip wrapper var mergedStyle = React.useMemo(function () { var _ref3; var shouldHide = typeof (eventState == null ? void 0 : eventState.time) !== 'number'; var opacity = shouldHide ? 0 : 1; var width = size[0], height = size[1]; var transform = shouldHide ? previousTransformRef.current : (_ref3 = (makeTransform != null ? makeTransform : DEFAULT_TOOLTIP_MAKE_TRANSFORM)(chart, eventState, containerRef.current, { width: width, height: height })) != null ? _ref3 : previousTransformRef.current; var transitionProperty = previousTransformRef.current ? 'transform,opacity' : 'opacity'; previousTransformRef.current = transform; return _extends({}, DEFAULT_TOOLTIP_STYLE, { opacity: opacity, transform: transform, transitionProperty: transitionProperty }, style); }, [eventState, makeTransform, chart, containerRef, size, style]); // crosshair move event handler var handler = React.useCallback(function (event) { if (!divRef.current) return; setEventState(event); }, []); // observe self content size change React.useEffect(function () { var element = divRef.current; if (!element) return; var observer = new ResizeObserver(function (entries) { var entry = entries[0]; var size = getSizeFromEntry(entry); setSize(function (prev) { return size[0] === prev[0] && size[1] === prev[1] ? prev : size; }); }); observer.observe(element); return function () { return observer.unobserve(element); }; }, []); var children = React.useMemo(function () { return eventState && (content == null ? void 0 : content({ event: eventState, chart: chart, series: series })); }, [chart, content, eventState, series]); return React.createElement("div", Object.assign({}, rest, { style: mergedStyle, ref: divRef }), React.createElement(ChartOnCrosshairMoveSubscriber, { handler: handler }), children); }); /** * Call `handler` when hovering the chart, with current pointed time and value. * * ❗Only use inside `<Series />`. */ var SeriesHoverDataSubscriber = function SeriesHoverDataSubscriber(_ref) { var handler = _ref.handler; var _useChart = useChart(), chart = _useChart.chart; var series = useSeries(); React.useEffect(function () { if (!series || !chart) return; var subscriber = function subscriber(event) { handler(event.seriesPrices.get(series), event, series, chart); }; chart.subscribeCrosshairMove(subscriber); return function () { return chart.unsubscribeCrosshairMove(subscriber); }; }, [chart, series, handler]); return null; }; exports.AreaSeries = AreaSeries; exports.BarSeries = BarSeries; exports.BaselineSeries = BaselineSeries; exports.CandlestickSeries = CandlestickSeries; exports.Chart = Chart; exports.ChartAutoResizer = ChartAutoResizer; exports.ChartFitContentTrigger = ChartFitContentTrigger; exports.ChartOnClickSubscriber = ChartOnClickSubscriber; exports.ChartOnCrosshairMoveSubscriber = ChartOnCrosshairMoveSubscriber; exports.DEFAULT_TOOLTIP_MAKE_TRANSFORM = DEFAULT_TOOLTIP_MAKE_TRANSFORM; exports.DEFAULT_TOOLTIP_STYLE = DEFAULT_TOOLTIP_STYLE; exports.HistogramSeries = HistogramSeries; exports.LineSeries = LineSeries; exports.PriceLine = PriceLine; exports.SeriesHoverDataSubscriber = SeriesHoverDataSubscriber; exports.TimeScaleOnSizeChangeSubscriber = TimeScaleOnSizeChangeSubscriber; exports.TimeScaleOnVisibleLogicalRangeChangeSubscriber = TimeScaleOnVisibleLogicalRangeChangeSubscriber; exports.TimeScaleOnVisibleTimeRangeChangeSubscriber = TimeScaleOnVisibleTimeRangeChangeSubscriber; exports.Tooltip = Tooltip; exports.useChart = useChart; exports.useSeries = useSeries; //# sourceMappingURL=react-lightweight-charts-simple.cjs.development.js.map