react-lightweight-charts-simple
Version:
A simple react wrapper for lightweight-charts library
739 lines (612 loc) • 24.2 kB
JavaScript
;
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