hyperprop-charting-library 0.1.142 → 0.1.143
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hyperprop-charting-library.cjs +198 -34
- package/dist/hyperprop-charting-library.d.ts +65 -1
- package/dist/hyperprop-charting-library.js +198 -34
- package/dist/index.cjs +198 -34
- package/dist/index.d.cts +65 -1
- package/dist/index.d.ts +65 -1
- package/dist/index.js +198 -34
- package/docs/API.md +58 -0
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -83,6 +83,9 @@ interface ChartOptions {
|
|
|
83
83
|
downsampling?: DownsamplingOptions;
|
|
84
84
|
touch?: TouchOptions;
|
|
85
85
|
datafeedOptions?: DatafeedOptions;
|
|
86
|
+
dataLine?: DataLineOptions;
|
|
87
|
+
animation?: AnimationOptions;
|
|
88
|
+
indicatorUpdate?: IndicatorUpdateOptions;
|
|
86
89
|
crosshair?: CrosshairOptions;
|
|
87
90
|
grid?: GridOptions;
|
|
88
91
|
watermark?: WatermarkOptions;
|
|
@@ -636,6 +639,67 @@ interface ChartTheme {
|
|
|
636
639
|
labelBackground: string;
|
|
637
640
|
}
|
|
638
641
|
type ChartThemeName = "midnight" | "light" | "brand" | "dark" | "high-contrast";
|
|
642
|
+
/**
|
|
643
|
+
* The symbol/OHLC line drawn inside the top-left of the plot, like
|
|
644
|
+
* TradingView's data line. Values follow the crosshair while hovering and fall
|
|
645
|
+
* back to the latest bar otherwise. Opt-in, because hosts that already render
|
|
646
|
+
* their own DOM overlay shouldn't suddenly get two.
|
|
647
|
+
*/
|
|
648
|
+
interface DataLineOptions {
|
|
649
|
+
/** Default false. */
|
|
650
|
+
visible?: boolean;
|
|
651
|
+
/** e.g. "NQU6" — drawn first, in the emphasis color. */
|
|
652
|
+
symbol?: string;
|
|
653
|
+
/** e.g. "1h". */
|
|
654
|
+
interval?: string;
|
|
655
|
+
/** e.g. "CME". */
|
|
656
|
+
exchange?: string;
|
|
657
|
+
/** O/H/L/C values for the hovered (or latest) bar. Default true. */
|
|
658
|
+
showOhlc?: boolean;
|
|
659
|
+
/** Absolute and percent change of that bar, colored by direction. Default true. */
|
|
660
|
+
showChange?: boolean;
|
|
661
|
+
/** Bar volume. Default false. */
|
|
662
|
+
showVolume?: boolean;
|
|
663
|
+
/**
|
|
664
|
+
* Extra chips after the values — session, currency, a "Closed" badge. Each
|
|
665
|
+
* renders as `label value` with an optional color and pill background.
|
|
666
|
+
*/
|
|
667
|
+
details?: Array<{
|
|
668
|
+
label?: string;
|
|
669
|
+
value: string;
|
|
670
|
+
color?: string;
|
|
671
|
+
background?: string;
|
|
672
|
+
}>;
|
|
673
|
+
/** Colored dot before the symbol, e.g. green when the market is open. */
|
|
674
|
+
statusColor?: string;
|
|
675
|
+
fontSize?: number;
|
|
676
|
+
/** Symbol color. Defaults to the axis text color. */
|
|
677
|
+
symbolColor?: string;
|
|
678
|
+
/** Everything after the symbol. Defaults to the indicator legend color. */
|
|
679
|
+
textColor?: string;
|
|
680
|
+
}
|
|
681
|
+
interface IndicatorUpdateOptions {
|
|
682
|
+
/**
|
|
683
|
+
* While only the forming bar is ticking, recompute indicator series at most
|
|
684
|
+
* this often (ms) rather than on every frame. Values are always exact — this
|
|
685
|
+
* changes how often the live bar's output refreshes, not how it's computed,
|
|
686
|
+
* and anything structural (a bar closing, history loading, inputs changing)
|
|
687
|
+
* recomputes immediately. Default 80; set 0 to recompute every frame.
|
|
688
|
+
*/
|
|
689
|
+
liveThrottleMs?: number;
|
|
690
|
+
}
|
|
691
|
+
interface AnimationOptions {
|
|
692
|
+
/**
|
|
693
|
+
* Ease the big viewport jumps — reset, fit-all, scroll-to-realtime — instead
|
|
694
|
+
* of teleporting, so it stays obvious which way the chart moved. Continuous
|
|
695
|
+
* gestures (drag, wheel, pinch) are never animated; they already follow the
|
|
696
|
+
* hand. Default true, and always skipped when the OS asks for reduced
|
|
697
|
+
* motion.
|
|
698
|
+
*/
|
|
699
|
+
viewportTransitions?: boolean;
|
|
700
|
+
/** Transition length in ms. Default 260. */
|
|
701
|
+
durationMs?: number;
|
|
702
|
+
}
|
|
639
703
|
/** Touch ergonomics. Fingers are blunter than a mouse cursor. */
|
|
640
704
|
interface TouchOptions {
|
|
641
705
|
/**
|
|
@@ -1078,4 +1142,4 @@ declare const CHART_THEME_NAMES: ChartThemeName[];
|
|
|
1078
1142
|
|
|
1079
1143
|
declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
|
|
1080
1144
|
|
|
1081
|
-
export { type AccessibilityOptions, type AxisOptions, type BuiltInIndicatorInfo, CHART_THEMES, CHART_THEME_NAMES, type ChartClickEvent, type ChartContextMenuEvent, type ChartContextMenuRegion, type ChartDatafeed, type ChartInstance, type ChartKeyboardAction, type ChartKeyboardShortcutEvent, type ChartLongPressEvent, type ChartOptions, type ChartSavedState, type ChartTheme, type ChartThemeName, type ChartType, type CompareSeriesOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DatafeedBarsRequest, type DatafeedOptions, type DownsamplingOptions, type DrawingBounds, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingSelectionChangeEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneActionEvent, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneHeightChangeEvent, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type KeyboardOptions, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type PriceScaleMode, type PriceScaleOptions, SCRIPT_SOURCE_INPUT_VALUES, type ScriptComputeResult, type ScriptIndicatorDefinition, type ScriptIndicatorInputDef, type ScriptInputHelper, type ScriptInputOptions, type ScriptPlotSpec, type TickerLineOptions, type TouchOptions, type TradeMarkerOptions, type ViewportState, type WatermarkOptions, compileScriptIndicator, createChart, extractScriptInputs, validateScriptSource };
|
|
1145
|
+
export { type AccessibilityOptions, type AnimationOptions, type AxisOptions, type BuiltInIndicatorInfo, CHART_THEMES, CHART_THEME_NAMES, type ChartClickEvent, type ChartContextMenuEvent, type ChartContextMenuRegion, type ChartDatafeed, type ChartInstance, type ChartKeyboardAction, type ChartKeyboardShortcutEvent, type ChartLongPressEvent, type ChartOptions, type ChartSavedState, type ChartTheme, type ChartThemeName, type ChartType, type CompareSeriesOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DataLineOptions, type DatafeedBarsRequest, type DatafeedOptions, type DownsamplingOptions, type DrawingBounds, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingSelectionChangeEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneActionEvent, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneHeightChangeEvent, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type IndicatorUpdateOptions, type KeyboardOptions, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type PriceScaleMode, type PriceScaleOptions, SCRIPT_SOURCE_INPUT_VALUES, type ScriptComputeResult, type ScriptIndicatorDefinition, type ScriptIndicatorInputDef, type ScriptInputHelper, type ScriptInputOptions, type ScriptPlotSpec, type TickerLineOptions, type TouchOptions, type TradeMarkerOptions, type ViewportState, type WatermarkOptions, compileScriptIndicator, createChart, extractScriptInputs, validateScriptSource };
|
package/dist/index.d.ts
CHANGED
|
@@ -83,6 +83,9 @@ interface ChartOptions {
|
|
|
83
83
|
downsampling?: DownsamplingOptions;
|
|
84
84
|
touch?: TouchOptions;
|
|
85
85
|
datafeedOptions?: DatafeedOptions;
|
|
86
|
+
dataLine?: DataLineOptions;
|
|
87
|
+
animation?: AnimationOptions;
|
|
88
|
+
indicatorUpdate?: IndicatorUpdateOptions;
|
|
86
89
|
crosshair?: CrosshairOptions;
|
|
87
90
|
grid?: GridOptions;
|
|
88
91
|
watermark?: WatermarkOptions;
|
|
@@ -636,6 +639,67 @@ interface ChartTheme {
|
|
|
636
639
|
labelBackground: string;
|
|
637
640
|
}
|
|
638
641
|
type ChartThemeName = "midnight" | "light" | "brand" | "dark" | "high-contrast";
|
|
642
|
+
/**
|
|
643
|
+
* The symbol/OHLC line drawn inside the top-left of the plot, like
|
|
644
|
+
* TradingView's data line. Values follow the crosshair while hovering and fall
|
|
645
|
+
* back to the latest bar otherwise. Opt-in, because hosts that already render
|
|
646
|
+
* their own DOM overlay shouldn't suddenly get two.
|
|
647
|
+
*/
|
|
648
|
+
interface DataLineOptions {
|
|
649
|
+
/** Default false. */
|
|
650
|
+
visible?: boolean;
|
|
651
|
+
/** e.g. "NQU6" — drawn first, in the emphasis color. */
|
|
652
|
+
symbol?: string;
|
|
653
|
+
/** e.g. "1h". */
|
|
654
|
+
interval?: string;
|
|
655
|
+
/** e.g. "CME". */
|
|
656
|
+
exchange?: string;
|
|
657
|
+
/** O/H/L/C values for the hovered (or latest) bar. Default true. */
|
|
658
|
+
showOhlc?: boolean;
|
|
659
|
+
/** Absolute and percent change of that bar, colored by direction. Default true. */
|
|
660
|
+
showChange?: boolean;
|
|
661
|
+
/** Bar volume. Default false. */
|
|
662
|
+
showVolume?: boolean;
|
|
663
|
+
/**
|
|
664
|
+
* Extra chips after the values — session, currency, a "Closed" badge. Each
|
|
665
|
+
* renders as `label value` with an optional color and pill background.
|
|
666
|
+
*/
|
|
667
|
+
details?: Array<{
|
|
668
|
+
label?: string;
|
|
669
|
+
value: string;
|
|
670
|
+
color?: string;
|
|
671
|
+
background?: string;
|
|
672
|
+
}>;
|
|
673
|
+
/** Colored dot before the symbol, e.g. green when the market is open. */
|
|
674
|
+
statusColor?: string;
|
|
675
|
+
fontSize?: number;
|
|
676
|
+
/** Symbol color. Defaults to the axis text color. */
|
|
677
|
+
symbolColor?: string;
|
|
678
|
+
/** Everything after the symbol. Defaults to the indicator legend color. */
|
|
679
|
+
textColor?: string;
|
|
680
|
+
}
|
|
681
|
+
interface IndicatorUpdateOptions {
|
|
682
|
+
/**
|
|
683
|
+
* While only the forming bar is ticking, recompute indicator series at most
|
|
684
|
+
* this often (ms) rather than on every frame. Values are always exact — this
|
|
685
|
+
* changes how often the live bar's output refreshes, not how it's computed,
|
|
686
|
+
* and anything structural (a bar closing, history loading, inputs changing)
|
|
687
|
+
* recomputes immediately. Default 80; set 0 to recompute every frame.
|
|
688
|
+
*/
|
|
689
|
+
liveThrottleMs?: number;
|
|
690
|
+
}
|
|
691
|
+
interface AnimationOptions {
|
|
692
|
+
/**
|
|
693
|
+
* Ease the big viewport jumps — reset, fit-all, scroll-to-realtime — instead
|
|
694
|
+
* of teleporting, so it stays obvious which way the chart moved. Continuous
|
|
695
|
+
* gestures (drag, wheel, pinch) are never animated; they already follow the
|
|
696
|
+
* hand. Default true, and always skipped when the OS asks for reduced
|
|
697
|
+
* motion.
|
|
698
|
+
*/
|
|
699
|
+
viewportTransitions?: boolean;
|
|
700
|
+
/** Transition length in ms. Default 260. */
|
|
701
|
+
durationMs?: number;
|
|
702
|
+
}
|
|
639
703
|
/** Touch ergonomics. Fingers are blunter than a mouse cursor. */
|
|
640
704
|
interface TouchOptions {
|
|
641
705
|
/**
|
|
@@ -1078,4 +1142,4 @@ declare const CHART_THEME_NAMES: ChartThemeName[];
|
|
|
1078
1142
|
|
|
1079
1143
|
declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
|
|
1080
1144
|
|
|
1081
|
-
export { type AccessibilityOptions, type AxisOptions, type BuiltInIndicatorInfo, CHART_THEMES, CHART_THEME_NAMES, type ChartClickEvent, type ChartContextMenuEvent, type ChartContextMenuRegion, type ChartDatafeed, type ChartInstance, type ChartKeyboardAction, type ChartKeyboardShortcutEvent, type ChartLongPressEvent, type ChartOptions, type ChartSavedState, type ChartTheme, type ChartThemeName, type ChartType, type CompareSeriesOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DatafeedBarsRequest, type DatafeedOptions, type DownsamplingOptions, type DrawingBounds, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingSelectionChangeEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneActionEvent, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneHeightChangeEvent, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type KeyboardOptions, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type PriceScaleMode, type PriceScaleOptions, SCRIPT_SOURCE_INPUT_VALUES, type ScriptComputeResult, type ScriptIndicatorDefinition, type ScriptIndicatorInputDef, type ScriptInputHelper, type ScriptInputOptions, type ScriptPlotSpec, type TickerLineOptions, type TouchOptions, type TradeMarkerOptions, type ViewportState, type WatermarkOptions, compileScriptIndicator, createChart, extractScriptInputs, validateScriptSource };
|
|
1145
|
+
export { type AccessibilityOptions, type AnimationOptions, type AxisOptions, type BuiltInIndicatorInfo, CHART_THEMES, CHART_THEME_NAMES, type ChartClickEvent, type ChartContextMenuEvent, type ChartContextMenuRegion, type ChartDatafeed, type ChartInstance, type ChartKeyboardAction, type ChartKeyboardShortcutEvent, type ChartLongPressEvent, type ChartOptions, type ChartSavedState, type ChartTheme, type ChartThemeName, type ChartType, type CompareSeriesOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DataLineOptions, type DatafeedBarsRequest, type DatafeedOptions, type DownsamplingOptions, type DrawingBounds, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingSelectionChangeEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneActionEvent, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneHeightChangeEvent, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type IndicatorUpdateOptions, type KeyboardOptions, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type PriceScaleMode, type PriceScaleOptions, SCRIPT_SOURCE_INPUT_VALUES, type ScriptComputeResult, type ScriptIndicatorDefinition, type ScriptIndicatorInputDef, type ScriptInputHelper, type ScriptInputOptions, type ScriptPlotSpec, type TickerLineOptions, type TouchOptions, type TradeMarkerOptions, type ViewportState, type WatermarkOptions, compileScriptIndicator, createChart, extractScriptInputs, validateScriptSource };
|
package/dist/index.js
CHANGED
|
@@ -335,26 +335,45 @@ var getSeriesFingerprint = (data) => {
|
|
|
335
335
|
var withCachedSeries = (key, data, compute) => {
|
|
336
336
|
const fingerprint = getSeriesFingerprint(data);
|
|
337
337
|
const existing = builtInSeriesCache.get(key);
|
|
338
|
-
|
|
339
|
-
|
|
338
|
+
const shape = getSeriesShape(data);
|
|
339
|
+
if (existing) {
|
|
340
|
+
const unchanged = existing.fingerprint === fingerprint;
|
|
341
|
+
const liveTickWithinThrottle = !unchanged && indicatorLiveThrottleMs > 0 && existing.shape === shape && Date.now() - existing.computedAt < indicatorLiveThrottleMs;
|
|
342
|
+
if (unchanged || liveTickWithinThrottle) {
|
|
343
|
+
return existing.values;
|
|
344
|
+
}
|
|
340
345
|
}
|
|
341
346
|
const values = compute();
|
|
342
|
-
builtInSeriesCache.set(key, { fingerprint, values });
|
|
347
|
+
builtInSeriesCache.set(key, { fingerprint, values, shape, computedAt: Date.now() });
|
|
343
348
|
return values;
|
|
344
349
|
};
|
|
345
350
|
var builtInComputationCache = /* @__PURE__ */ new Map();
|
|
351
|
+
var indicatorLiveThrottleMs = 80;
|
|
352
|
+
var setIndicatorLiveThrottleMs = (ms) => {
|
|
353
|
+
indicatorLiveThrottleMs = Math.max(0, ms);
|
|
354
|
+
};
|
|
355
|
+
var getSeriesShape = (data) => {
|
|
356
|
+
const length = data.length;
|
|
357
|
+
const last = length > 0 ? data[length - 1] : void 0;
|
|
358
|
+
return last ? `${length}|${last.time.getTime()}` : "empty";
|
|
359
|
+
};
|
|
346
360
|
var COMPUTATION_CACHE_MAX_ENTRIES = 128;
|
|
347
361
|
var withCachedComputation = (key, data, compute) => {
|
|
348
362
|
const fingerprint = getSeriesFingerprint(data);
|
|
349
363
|
const existing = builtInComputationCache.get(key);
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
364
|
+
const shape = getSeriesShape(data);
|
|
365
|
+
if (existing) {
|
|
366
|
+
const unchanged = existing.fingerprint === fingerprint;
|
|
367
|
+
const liveTickWithinThrottle = !unchanged && indicatorLiveThrottleMs > 0 && existing.shape === shape && Date.now() - existing.computedAt < indicatorLiveThrottleMs;
|
|
368
|
+
if (unchanged || liveTickWithinThrottle) {
|
|
369
|
+
builtInComputationCache.delete(key);
|
|
370
|
+
builtInComputationCache.set(key, existing);
|
|
371
|
+
return existing.value;
|
|
372
|
+
}
|
|
354
373
|
}
|
|
355
374
|
const value = compute();
|
|
356
375
|
builtInComputationCache.delete(key);
|
|
357
|
-
builtInComputationCache.set(key, { fingerprint, value });
|
|
376
|
+
builtInComputationCache.set(key, { fingerprint, value, shape, computedAt: Date.now() });
|
|
358
377
|
while (builtInComputationCache.size > COMPUTATION_CACHE_MAX_ENTRIES) {
|
|
359
378
|
const oldest = builtInComputationCache.keys().next().value;
|
|
360
379
|
if (oldest === void 0) break;
|
|
@@ -2924,7 +2943,23 @@ var DEFAULT_OPTIONS = {
|
|
|
2924
2943
|
accessibility: { label: "Price chart", description: "", focusable: true },
|
|
2925
2944
|
downsampling: { enabled: true, thresholdPx: 1.5 },
|
|
2926
2945
|
touch: { hitToleranceScale: 2.2, longPressTooltip: true, longPressMs: 400 },
|
|
2946
|
+
dataLine: {
|
|
2947
|
+
visible: false,
|
|
2948
|
+
symbol: "",
|
|
2949
|
+
interval: "",
|
|
2950
|
+
exchange: "",
|
|
2951
|
+
showOhlc: true,
|
|
2952
|
+
showChange: true,
|
|
2953
|
+
showVolume: false,
|
|
2954
|
+
details: [],
|
|
2955
|
+
statusColor: "",
|
|
2956
|
+
fontSize: 0,
|
|
2957
|
+
symbolColor: "",
|
|
2958
|
+
textColor: ""
|
|
2959
|
+
},
|
|
2927
2960
|
datafeedOptions: { prefetchThresholdBars: 150, cooldownMs: 1200, chunkBars: 1500 },
|
|
2961
|
+
animation: { viewportTransitions: true, durationMs: 260 },
|
|
2962
|
+
indicatorUpdate: { liveThrottleMs: 80 },
|
|
2928
2963
|
crosshair: DEFAULT_CROSSHAIR_OPTIONS,
|
|
2929
2964
|
grid: DEFAULT_GRID_OPTIONS,
|
|
2930
2965
|
watermark: DEFAULT_WATERMARK_OPTIONS,
|
|
@@ -2983,6 +3018,18 @@ var mergeChartOptions = (baseOptions, options = {}) => ({
|
|
|
2983
3018
|
...baseOptions.touch,
|
|
2984
3019
|
...options.touch ?? {}
|
|
2985
3020
|
},
|
|
3021
|
+
dataLine: {
|
|
3022
|
+
...baseOptions.dataLine,
|
|
3023
|
+
...options.dataLine ?? {}
|
|
3024
|
+
},
|
|
3025
|
+
animation: {
|
|
3026
|
+
...baseOptions.animation,
|
|
3027
|
+
...options.animation ?? {}
|
|
3028
|
+
},
|
|
3029
|
+
indicatorUpdate: {
|
|
3030
|
+
...baseOptions.indicatorUpdate,
|
|
3031
|
+
...options.indicatorUpdate ?? {}
|
|
3032
|
+
},
|
|
2986
3033
|
datafeedOptions: {
|
|
2987
3034
|
...baseOptions.datafeedOptions,
|
|
2988
3035
|
...options.datafeedOptions ?? {}
|
|
@@ -3242,6 +3289,7 @@ function createChart(element, options = {}) {
|
|
|
3242
3289
|
let crosshairPoint = null;
|
|
3243
3290
|
let doubleClickEnabled = mergedOptions.doubleClickEnabled;
|
|
3244
3291
|
let doubleClickAction = mergedOptions.doubleClickAction;
|
|
3292
|
+
setIndicatorLiveThrottleMs(mergedOptions.indicatorUpdate?.liveThrottleMs ?? 80);
|
|
3245
3293
|
let noOverlappingLineLabels = DEFAULT_LABELS_OPTIONS.noOverlapping;
|
|
3246
3294
|
let rightAxisLabelSlots = [];
|
|
3247
3295
|
let plotLabelSlots = [];
|
|
@@ -3483,21 +3531,69 @@ function createChart(element, options = {}) {
|
|
|
3483
3531
|
Math.min(highCenter, count + maxPanBars)
|
|
3484
3532
|
);
|
|
3485
3533
|
};
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
if (
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3534
|
+
let viewportTweenRaf = null;
|
|
3535
|
+
const cancelViewportTween = () => {
|
|
3536
|
+
if (viewportTweenRaf !== null) {
|
|
3537
|
+
cancelAnimationFrame(viewportTweenRaf);
|
|
3538
|
+
viewportTweenRaf = null;
|
|
3539
|
+
}
|
|
3540
|
+
};
|
|
3541
|
+
const prefersReducedMotion = () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
3542
|
+
const animateViewportTo = (targetCenter, targetSpan, onComplete) => {
|
|
3543
|
+
const animation = mergedOptions.animation ?? DEFAULT_OPTIONS.animation;
|
|
3544
|
+
const duration = Math.max(0, animation?.durationMs ?? 260);
|
|
3545
|
+
const startCenter = xCenter;
|
|
3546
|
+
const startSpan = xSpan;
|
|
3547
|
+
const centerDelta = targetCenter - startCenter;
|
|
3548
|
+
const spanRatio = startSpan > 0 ? targetSpan / startSpan : 1;
|
|
3549
|
+
const negligible = Math.abs(centerDelta) < 0.5 && Math.abs(spanRatio - 1) < 0.01;
|
|
3550
|
+
if (animation?.viewportTransitions === false || duration === 0 || negligible || prefersReducedMotion() || typeof requestAnimationFrame !== "function") {
|
|
3551
|
+
xCenter = targetCenter;
|
|
3552
|
+
xSpan = targetSpan;
|
|
3553
|
+
clampXViewport();
|
|
3554
|
+
onComplete?.();
|
|
3555
|
+
emitViewportChange();
|
|
3556
|
+
scheduleDraw();
|
|
3557
|
+
return false;
|
|
3492
3558
|
}
|
|
3559
|
+
cancelViewportTween();
|
|
3560
|
+
const startedAt = performance.now();
|
|
3561
|
+
const step = (now) => {
|
|
3562
|
+
const t = clamp((now - startedAt) / duration, 0, 1);
|
|
3563
|
+
const eased = 1 - Math.pow(1 - t, 3);
|
|
3564
|
+
xCenter = startCenter + centerDelta * eased;
|
|
3565
|
+
xSpan = startSpan * Math.pow(spanRatio, eased);
|
|
3566
|
+
clampXViewport();
|
|
3567
|
+
scheduleDraw();
|
|
3568
|
+
if (t < 1) {
|
|
3569
|
+
viewportTweenRaf = requestAnimationFrame(step);
|
|
3570
|
+
return;
|
|
3571
|
+
}
|
|
3572
|
+
viewportTweenRaf = null;
|
|
3573
|
+
xCenter = targetCenter;
|
|
3574
|
+
xSpan = targetSpan;
|
|
3575
|
+
clampXViewport();
|
|
3576
|
+
onComplete?.();
|
|
3577
|
+
emitViewportChange();
|
|
3578
|
+
scheduleDraw();
|
|
3579
|
+
};
|
|
3580
|
+
viewportTweenRaf = requestAnimationFrame(step);
|
|
3581
|
+
return true;
|
|
3582
|
+
};
|
|
3583
|
+
const getFitXTarget = () => {
|
|
3584
|
+
const count = data.length;
|
|
3585
|
+
if (count === 0) return { center: 0, span: 60 };
|
|
3493
3586
|
const maxSpan = Math.min(maxVisibleBars, Math.max(minVisibleBars, count + maxPanBars * 2));
|
|
3494
|
-
const
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
}
|
|
3499
|
-
|
|
3500
|
-
|
|
3587
|
+
const span = clamp(Math.floor(mergedOptions.initialVisibleBars), minVisibleBars, maxSpan);
|
|
3588
|
+
return {
|
|
3589
|
+
center: mergedOptions.initialViewport === "center" ? count / 2 : count - span / 2 + rightEdgePaddingBars,
|
|
3590
|
+
span
|
|
3591
|
+
};
|
|
3592
|
+
};
|
|
3593
|
+
const fitXViewport = () => {
|
|
3594
|
+
const target = getFitXTarget();
|
|
3595
|
+
xSpan = target.span;
|
|
3596
|
+
xCenter = target.center;
|
|
3501
3597
|
clampXViewport();
|
|
3502
3598
|
};
|
|
3503
3599
|
const getYBounds = () => {
|
|
@@ -6894,6 +6990,73 @@ function createChart(element, options = {}) {
|
|
|
6894
6990
|
for (const orderLine of orderLines) {
|
|
6895
6991
|
drawOrderLine(orderLine, yFromPrice, chartLeft, chartTop, chartRight, chartBottom);
|
|
6896
6992
|
}
|
|
6993
|
+
let dataLineBottom = chartTop;
|
|
6994
|
+
const dataLine = mergedOptions.dataLine;
|
|
6995
|
+
if (dataLine?.visible && data.length > 0) {
|
|
6996
|
+
const hoverIndex = crosshairPoint && getHitRegion(crosshairPoint.x, crosshairPoint.y) === "plot" ? indexFromCanvasX(crosshairPoint.x) : null;
|
|
6997
|
+
const barIndex = hoverIndex !== null && hoverIndex >= 0 && hoverIndex < data.length ? hoverIndex : data.length - 1;
|
|
6998
|
+
const bar = data[barIndex];
|
|
6999
|
+
if (bar) {
|
|
7000
|
+
const prevFont = ctx.font;
|
|
7001
|
+
const fontSize = Math.max(9, dataLine.fontSize || axis.fontSize);
|
|
7002
|
+
ctx.font = `${fontSize}px ${mergedOptions.fontFamily}`;
|
|
7003
|
+
ctx.textBaseline = "top";
|
|
7004
|
+
ctx.textAlign = "left";
|
|
7005
|
+
const symbolColor = dataLine.symbolColor || axis.textColor;
|
|
7006
|
+
const textColor = dataLine.textColor || labels.indicatorTextColor;
|
|
7007
|
+
const changeAbs = bar.c - bar.o;
|
|
7008
|
+
const changeColor = changeAbs >= 0 ? mergedOptions.upColor : mergedOptions.downColor;
|
|
7009
|
+
const rowY = chartTop + 6;
|
|
7010
|
+
let cursorX = chartLeft + 10;
|
|
7011
|
+
if (dataLine.statusColor) {
|
|
7012
|
+
const dotRadius = Math.max(2.5, fontSize / 4);
|
|
7013
|
+
ctx.fillStyle = dataLine.statusColor;
|
|
7014
|
+
ctx.beginPath();
|
|
7015
|
+
ctx.arc(cursorX + dotRadius, rowY + fontSize / 2, dotRadius, 0, Math.PI * 2);
|
|
7016
|
+
ctx.fill();
|
|
7017
|
+
cursorX += dotRadius * 2 + 6;
|
|
7018
|
+
}
|
|
7019
|
+
const headline = [dataLine.symbol, dataLine.interval, dataLine.exchange].filter((part) => part && part.length > 0).join(" \xB7 ");
|
|
7020
|
+
if (headline) {
|
|
7021
|
+
ctx.fillStyle = symbolColor;
|
|
7022
|
+
ctx.fillText(headline, cursorX, rowY);
|
|
7023
|
+
cursorX += measureTextWidth(headline) + 10;
|
|
7024
|
+
}
|
|
7025
|
+
if (dataLine.showOhlc !== false) {
|
|
7026
|
+
const ohlc = `O${formatPrice(bar.o)} H${formatPrice(bar.h)} L${formatPrice(bar.l)} C${formatPrice(bar.c)}`;
|
|
7027
|
+
ctx.fillStyle = changeColor;
|
|
7028
|
+
ctx.fillText(ohlc, cursorX, rowY);
|
|
7029
|
+
cursorX += measureTextWidth(ohlc) + 8;
|
|
7030
|
+
}
|
|
7031
|
+
if (dataLine.showChange !== false) {
|
|
7032
|
+
const pct = bar.o !== 0 ? changeAbs / bar.o * 100 : 0;
|
|
7033
|
+
const changeText = `${changeAbs >= 0 ? "+" : ""}${formatPrice(changeAbs)} (${changeAbs >= 0 ? "+" : ""}${pct.toFixed(2)}%)`;
|
|
7034
|
+
ctx.fillStyle = changeColor;
|
|
7035
|
+
ctx.fillText(changeText, cursorX, rowY);
|
|
7036
|
+
cursorX += measureTextWidth(changeText) + 10;
|
|
7037
|
+
}
|
|
7038
|
+
if (dataLine.showVolume && bar.v !== void 0) {
|
|
7039
|
+
const volumeText = `Vol ${bar.v >= 1e6 ? `${(bar.v / 1e6).toFixed(2)}M` : bar.v >= 1e3 ? `${(bar.v / 1e3).toFixed(1)}K` : String(Math.round(bar.v))}`;
|
|
7040
|
+
ctx.fillStyle = textColor;
|
|
7041
|
+
ctx.fillText(volumeText, cursorX, rowY);
|
|
7042
|
+
cursorX += measureTextWidth(volumeText) + 10;
|
|
7043
|
+
}
|
|
7044
|
+
for (const detail of dataLine.details ?? []) {
|
|
7045
|
+
const text = detail.label ? `${detail.label} ${detail.value}` : detail.value;
|
|
7046
|
+
const width2 = measureTextWidth(text);
|
|
7047
|
+
if (cursorX + width2 > chartRight - 4) break;
|
|
7048
|
+
if (detail.background) {
|
|
7049
|
+
ctx.fillStyle = detail.background;
|
|
7050
|
+
fillRoundedRect(cursorX - 4, rowY - 2, width2 + 8, fontSize + 4, 3);
|
|
7051
|
+
}
|
|
7052
|
+
ctx.fillStyle = detail.color || textColor;
|
|
7053
|
+
ctx.fillText(text, cursorX, rowY);
|
|
7054
|
+
cursorX += width2 + 12;
|
|
7055
|
+
}
|
|
7056
|
+
dataLineBottom = rowY + fontSize + 4;
|
|
7057
|
+
ctx.font = prevFont;
|
|
7058
|
+
}
|
|
7059
|
+
}
|
|
6897
7060
|
if (labels.visible && (labels.showIndicatorNames || labels.showIndicatorValues)) {
|
|
6898
7061
|
const isLegendInputValue = (value) => {
|
|
6899
7062
|
if (typeof value === "number" || typeof value === "boolean") {
|
|
@@ -6922,7 +7085,7 @@ function createChart(element, options = {}) {
|
|
|
6922
7085
|
const isRight = position === "top-right" || position === "bottom-right";
|
|
6923
7086
|
const isBottom = position === "bottom-left" || position === "bottom-right";
|
|
6924
7087
|
const legendX = isRight ? chartRight - offsetX : chartLeft + offsetX;
|
|
6925
|
-
const legendY = isBottom ? chartBottom - offsetY : chartTop + offsetY;
|
|
7088
|
+
const legendY = isBottom ? chartBottom - offsetY : Math.max(chartTop + offsetY, dataLineBottom);
|
|
6926
7089
|
drawText(legendText, legendX, legendY, isRight ? "right" : "left", isBottom ? "bottom" : "top", labels.indicatorTextColor);
|
|
6927
7090
|
ctx.font = prevFont;
|
|
6928
7091
|
}
|
|
@@ -7463,27 +7626,24 @@ function createChart(element, options = {}) {
|
|
|
7463
7626
|
scheduleDraw();
|
|
7464
7627
|
};
|
|
7465
7628
|
const fitContent = () => {
|
|
7466
|
-
|
|
7467
|
-
updateFollowLatest(true);
|
|
7468
|
-
emitViewportChange();
|
|
7469
|
-
scheduleDraw();
|
|
7629
|
+
const target = getFitXTarget();
|
|
7630
|
+
animateViewportTo(target.center, target.span, () => updateFollowLatest(true));
|
|
7470
7631
|
};
|
|
7471
7632
|
const resetViewport = () => {
|
|
7472
7633
|
cancelKineticPan();
|
|
7473
|
-
|
|
7634
|
+
const target = getFitXTarget();
|
|
7474
7635
|
resetYViewport();
|
|
7475
|
-
updateFollowLatest(true);
|
|
7476
|
-
emitViewportChange();
|
|
7477
|
-
scheduleDraw();
|
|
7636
|
+
animateViewportTo(target.center, target.span, () => updateFollowLatest(true));
|
|
7478
7637
|
};
|
|
7479
7638
|
const isFollowingLatest = () => followLatest;
|
|
7480
7639
|
const setFollowingLatest = (follow) => {
|
|
7481
7640
|
if (follow && data.length > 0) {
|
|
7482
7641
|
cancelKineticPan();
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7642
|
+
animateViewportTo(
|
|
7643
|
+
data.length - xSpan / 2 + rightEdgePaddingBars,
|
|
7644
|
+
xSpan,
|
|
7645
|
+
() => updateFollowLatest(true)
|
|
7646
|
+
);
|
|
7487
7647
|
} else {
|
|
7488
7648
|
updateFollowLatest(follow);
|
|
7489
7649
|
}
|
|
@@ -8766,6 +8926,7 @@ function createChart(element, options = {}) {
|
|
|
8766
8926
|
canvas.focus({ preventScroll: true });
|
|
8767
8927
|
}
|
|
8768
8928
|
cancelKineticPan();
|
|
8929
|
+
cancelViewportTween();
|
|
8769
8930
|
panVelocitySamples.length = 0;
|
|
8770
8931
|
magnetModifierActive = event.metaKey || event.ctrlKey;
|
|
8771
8932
|
shiftKeyActive = event.shiftKey;
|
|
@@ -9430,6 +9591,7 @@ function createChart(element, options = {}) {
|
|
|
9430
9591
|
return;
|
|
9431
9592
|
}
|
|
9432
9593
|
cancelKineticPan();
|
|
9594
|
+
cancelViewportTween();
|
|
9433
9595
|
const point = getCanvasPoint(event);
|
|
9434
9596
|
const region = getHitRegion(point.x, point.y);
|
|
9435
9597
|
if (region === "outside") {
|
|
@@ -9707,6 +9869,7 @@ function createChart(element, options = {}) {
|
|
|
9707
9869
|
resetRightMarginCache();
|
|
9708
9870
|
doubleClickEnabled = mergedOptions.doubleClickEnabled;
|
|
9709
9871
|
doubleClickAction = mergedOptions.doubleClickAction;
|
|
9872
|
+
setIndicatorLiveThrottleMs(mergedOptions.indicatorUpdate?.liveThrottleMs ?? 80);
|
|
9710
9873
|
applyAccessibilityAttributes();
|
|
9711
9874
|
const isTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
|
|
9712
9875
|
if (!isTickerSmoothingEnabled) {
|
|
@@ -10185,6 +10348,7 @@ function createChart(element, options = {}) {
|
|
|
10185
10348
|
return canvas.toDataURL(options2.type ?? "image/png", options2.quality);
|
|
10186
10349
|
};
|
|
10187
10350
|
const destroy = () => {
|
|
10351
|
+
cancelViewportTween();
|
|
10188
10352
|
cancelLongPressTimer();
|
|
10189
10353
|
datafeedUnsubscribe?.();
|
|
10190
10354
|
datafeedUnsubscribe = null;
|
package/docs/API.md
CHANGED
|
@@ -614,6 +614,64 @@ link.click();
|
|
|
614
614
|
|
|
615
615
|
---
|
|
616
616
|
|
|
617
|
+
## In-chart data line
|
|
618
|
+
|
|
619
|
+
The symbol/OHLC line TradingView draws in the top-left of the plot. Hosts have
|
|
620
|
+
been building this in DOM and re-rendering it on every crosshair move; drawn
|
|
621
|
+
in-canvas it just follows the frame, inherits the theme's colors, and costs
|
|
622
|
+
nothing per pointer move.
|
|
623
|
+
|
|
624
|
+
```ts
|
|
625
|
+
createChart(el, {
|
|
626
|
+
dataLine: {
|
|
627
|
+
visible: true,
|
|
628
|
+
symbol: "NQU6",
|
|
629
|
+
interval: "1h",
|
|
630
|
+
exchange: "CME",
|
|
631
|
+
showVolume: true,
|
|
632
|
+
statusColor: marketOpen ? "#22c55e" : "#ef4444",
|
|
633
|
+
details: [
|
|
634
|
+
{ label: "Session", value: "RTH" },
|
|
635
|
+
{ value: "Closed", color: "#fff", background: "rgba(242,54,69,0.85)" },
|
|
636
|
+
],
|
|
637
|
+
},
|
|
638
|
+
});
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
Values track the hovered bar and fall back to the latest one, the change is
|
|
642
|
+
colored by direction, and the indicator legend moves down to sit under it.
|
|
643
|
+
Detail chips are dropped rather than clipped when the row runs out of width.
|
|
644
|
+
Off by default so hosts with their own overlay don't suddenly render two.
|
|
645
|
+
|
|
646
|
+
## Viewport transitions
|
|
647
|
+
|
|
648
|
+
The discrete jumps — `resetViewport()`, `fitContent()`,
|
|
649
|
+
`setFollowingLatest(true)` and the `Home`/`End`/`R` shortcuts — ease into place
|
|
650
|
+
over `animation.durationMs` (default 260) instead of teleporting, so it stays
|
|
651
|
+
obvious which way the chart moved. Zoom interpolates geometrically, so each
|
|
652
|
+
frame changes scale by the same ratio rather than appearing to accelerate.
|
|
653
|
+
|
|
654
|
+
Continuous gestures (drag, wheel, pinch) are never animated — they already
|
|
655
|
+
track the input — and any pointer interaction cancels a transition in flight.
|
|
656
|
+
The whole thing is skipped when the OS asks for reduced motion, or with
|
|
657
|
+
`animation: { viewportTransitions: false }`.
|
|
658
|
+
|
|
659
|
+
## Indicator recomputation
|
|
660
|
+
|
|
661
|
+
Indicator series are memoised per (indicator, inputs, series fingerprint). A
|
|
662
|
+
live tick changes the fingerprint, which used to mean every indicator
|
|
663
|
+
recomputed its whole series on every animation frame — measured at 50k bars
|
|
664
|
+
with 10 indicators, that was ~11ms of the frame budget and dropped the chart to
|
|
665
|
+
51fps.
|
|
666
|
+
|
|
667
|
+
While *only the forming bar* is ticking, recomputation is now throttled to
|
|
668
|
+
`indicatorUpdate.liveThrottleMs` (default 80ms). The values are always exact —
|
|
669
|
+
this changes how often the live bar's output refreshes, never how it is
|
|
670
|
+
computed — and anything structural (a bar closing, history prepending, inputs
|
|
671
|
+
or length changing) recomputes immediately, so closed bars are never stale.
|
|
672
|
+
Same benchmark after: 8.4ms and 118fps. Set `liveThrottleMs: 0` to recompute on
|
|
673
|
+
every frame.
|
|
674
|
+
|
|
617
675
|
## Themes
|
|
618
676
|
|
|
619
677
|
A theme is the chart's whole palette as one object, so hosts stop setting a
|