hyperprop-charting-library 0.1.44 → 0.1.46

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/index.d.cts CHANGED
@@ -37,6 +37,7 @@ interface ChartOptions {
37
37
  priceLines?: PriceLineOptions[];
38
38
  orderLines?: OrderLineOptions[];
39
39
  tickerLine?: TickerLineOptions;
40
+ labels?: LabelsOptions;
40
41
  dashPatterns?: Partial<DashPatternOptions>;
41
42
  indicators?: IndicatorInstanceOptions[];
42
43
  }
@@ -259,10 +260,43 @@ interface TickerLineOptions {
259
260
  color?: string;
260
261
  labelBackgroundColor?: string;
261
262
  labelTextColor?: string;
263
+ labelSubtext?: string;
264
+ labelSubtextColor?: string;
265
+ labelSubtextFontSize?: number;
266
+ showCountdownInLabel?: boolean;
262
267
  labelBorderRadius?: number;
263
268
  smoothing?: boolean;
264
269
  smoothingSpeed?: number;
265
270
  }
271
+ interface LabelsOptions {
272
+ visible?: boolean;
273
+ symbolName?: string;
274
+ showSymbolName?: boolean;
275
+ showLastPrice?: boolean;
276
+ showPreviousClose?: boolean;
277
+ previousClosePrice?: number;
278
+ showHighLow?: boolean;
279
+ showBidAsk?: boolean;
280
+ bidPrice?: number;
281
+ askPrice?: number;
282
+ showIndicatorNames?: boolean;
283
+ showIndicatorValues?: boolean;
284
+ showCountdownToBarClose?: boolean;
285
+ noOverlapping?: boolean;
286
+ backgroundColor?: string;
287
+ textColor?: string;
288
+ mutedTextColor?: string;
289
+ symbolNameBackgroundColor?: string;
290
+ symbolNameTextColor?: string;
291
+ previousCloseColor?: string;
292
+ highLowColor?: string;
293
+ bidColor?: string;
294
+ askColor?: string;
295
+ indicatorTextColor?: string;
296
+ borderRadius?: number;
297
+ labelHeight?: number;
298
+ labelPaddingX?: number;
299
+ }
266
300
  interface ChartInstance {
267
301
  updateOptions: (options: ChartOptions) => void;
268
302
  setData: (data: OhlcDataPoint[]) => void;
@@ -326,4 +360,4 @@ interface ViewportState {
326
360
  }
327
361
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
328
362
 
329
- export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
363
+ export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
package/dist/index.d.ts CHANGED
@@ -37,6 +37,7 @@ interface ChartOptions {
37
37
  priceLines?: PriceLineOptions[];
38
38
  orderLines?: OrderLineOptions[];
39
39
  tickerLine?: TickerLineOptions;
40
+ labels?: LabelsOptions;
40
41
  dashPatterns?: Partial<DashPatternOptions>;
41
42
  indicators?: IndicatorInstanceOptions[];
42
43
  }
@@ -259,10 +260,43 @@ interface TickerLineOptions {
259
260
  color?: string;
260
261
  labelBackgroundColor?: string;
261
262
  labelTextColor?: string;
263
+ labelSubtext?: string;
264
+ labelSubtextColor?: string;
265
+ labelSubtextFontSize?: number;
266
+ showCountdownInLabel?: boolean;
262
267
  labelBorderRadius?: number;
263
268
  smoothing?: boolean;
264
269
  smoothingSpeed?: number;
265
270
  }
271
+ interface LabelsOptions {
272
+ visible?: boolean;
273
+ symbolName?: string;
274
+ showSymbolName?: boolean;
275
+ showLastPrice?: boolean;
276
+ showPreviousClose?: boolean;
277
+ previousClosePrice?: number;
278
+ showHighLow?: boolean;
279
+ showBidAsk?: boolean;
280
+ bidPrice?: number;
281
+ askPrice?: number;
282
+ showIndicatorNames?: boolean;
283
+ showIndicatorValues?: boolean;
284
+ showCountdownToBarClose?: boolean;
285
+ noOverlapping?: boolean;
286
+ backgroundColor?: string;
287
+ textColor?: string;
288
+ mutedTextColor?: string;
289
+ symbolNameBackgroundColor?: string;
290
+ symbolNameTextColor?: string;
291
+ previousCloseColor?: string;
292
+ highLowColor?: string;
293
+ bidColor?: string;
294
+ askColor?: string;
295
+ indicatorTextColor?: string;
296
+ borderRadius?: number;
297
+ labelHeight?: number;
298
+ labelPaddingX?: number;
299
+ }
266
300
  interface ChartInstance {
267
301
  updateOptions: (options: ChartOptions) => void;
268
302
  setData: (data: OhlcDataPoint[]) => void;
@@ -326,4 +360,4 @@ interface ViewportState {
326
360
  }
327
361
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
328
362
 
329
- export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
363
+ export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
package/dist/index.js CHANGED
@@ -61,6 +61,35 @@ var DEFAULT_DASH_PATTERNS = {
61
61
  borderDotted: [2, 2],
62
62
  borderDashed: [6, 4]
63
63
  };
64
+ var DEFAULT_LABELS_OPTIONS = {
65
+ visible: true,
66
+ symbolName: "",
67
+ showSymbolName: false,
68
+ showLastPrice: true,
69
+ showPreviousClose: false,
70
+ previousClosePrice: Number.NaN,
71
+ showHighLow: false,
72
+ showBidAsk: false,
73
+ bidPrice: Number.NaN,
74
+ askPrice: Number.NaN,
75
+ showIndicatorNames: false,
76
+ showIndicatorValues: false,
77
+ showCountdownToBarClose: false,
78
+ noOverlapping: true,
79
+ backgroundColor: "#0b1220",
80
+ textColor: "#cbd5e1",
81
+ mutedTextColor: "#94a3b8",
82
+ symbolNameBackgroundColor: "#1f2937",
83
+ symbolNameTextColor: "#e5e7eb",
84
+ previousCloseColor: "#94a3b8",
85
+ highLowColor: "#a78bfa",
86
+ bidColor: "#ef4444",
87
+ askColor: "#22c55e",
88
+ indicatorTextColor: "#cbd5e1",
89
+ borderRadius: 3,
90
+ labelHeight: 20,
91
+ labelPaddingX: 8
92
+ };
64
93
  var DEFAULT_PRICE_LINE_OPTIONS = {
65
94
  visible: true,
66
95
  style: "solid",
@@ -148,8 +177,13 @@ var DEFAULT_OPTIONS = {
148
177
  style: "dotted",
149
178
  thickness: 1,
150
179
  labelTextColor: "#0b1220",
180
+ labelSubtext: "",
181
+ labelSubtextColor: "#0b1220",
182
+ labelSubtextFontSize: 0,
183
+ showCountdownInLabel: false,
151
184
  labelBorderRadius: 3
152
185
  },
186
+ labels: DEFAULT_LABELS_OPTIONS,
153
187
  dashPatterns: DEFAULT_DASH_PATTERNS,
154
188
  indicators: []
155
189
  };
@@ -189,6 +223,10 @@ var mergeChartOptions = (baseOptions, options = {}) => ({
189
223
  ...baseOptions.tickerLine,
190
224
  ...options.tickerLine ?? {}
191
225
  },
226
+ labels: {
227
+ ...baseOptions.labels,
228
+ ...options.labels ?? {}
229
+ },
192
230
  dashPatterns: {
193
231
  ...baseOptions.dashPatterns,
194
232
  ...options.dashPatterns ?? {}
@@ -1255,6 +1293,14 @@ function createChart(element, options = {}) {
1255
1293
  ctx.textBaseline = baseline;
1256
1294
  ctx.fillText(text, x, y);
1257
1295
  };
1296
+ const formatDuration = (ms) => {
1297
+ const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
1298
+ const hours = Math.floor(totalSeconds / 3600);
1299
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
1300
+ const seconds = totalSeconds % 60;
1301
+ const pad = (value) => String(value).padStart(2, "0");
1302
+ return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`;
1303
+ };
1258
1304
  const drawPriceLine = (line, yFromPrice, chartLeft, chartTop, chartRight, chartBottom) => {
1259
1305
  const mergedLine = { ...DEFAULT_PRICE_LINE_OPTIONS, ...line };
1260
1306
  if (!mergedLine.visible || !Number.isFinite(mergedLine.price)) {
@@ -2014,14 +2060,49 @@ function createChart(element, options = {}) {
2014
2060
  drawText(formatPrice(price), chartRight + 6, y, "left", "middle", yAxis.textColor);
2015
2061
  ctx.font = prevFont;
2016
2062
  }
2063
+ const labels = { ...DEFAULT_LABELS_OPTIONS, ...mergedOptions.labels ?? {} };
2064
+ const priceAxisLabels = [];
2065
+ const addPriceAxisLabel = (label) => {
2066
+ if (!labels.visible || !Number.isFinite(label.price) || label.text.length === 0) {
2067
+ return;
2068
+ }
2069
+ priceAxisLabels.push(label);
2070
+ };
2071
+ const drawReferenceLine = (price, color, style = "dotted") => {
2072
+ if (!Number.isFinite(price)) {
2073
+ return;
2074
+ }
2075
+ const y = clamp(yFromPrice(price), chartTop + 1, chartBottom - 1);
2076
+ ctx.save();
2077
+ ctx.strokeStyle = color;
2078
+ ctx.lineWidth = 1;
2079
+ applyDashPattern(style, dashPatterns.dotted, dashPatterns.dashed);
2080
+ ctx.beginPath();
2081
+ ctx.moveTo(crisp(chartLeft), crisp(y));
2082
+ ctx.lineTo(crisp(chartRight), crisp(y));
2083
+ ctx.stroke();
2084
+ ctx.restore();
2085
+ };
2086
+ const getCountdownText = () => {
2087
+ const last = data[data.length - 1];
2088
+ if (!last) {
2089
+ return null;
2090
+ }
2091
+ const stepMs = getTimeStepMs();
2092
+ const rawRemainingMs = last.time.getTime() + stepMs - Date.now();
2093
+ const countdownMs = rawRemainingMs >= 0 && rawRemainingMs <= stepMs * 2 ? rawRemainingMs : stepMs - Date.now() % stepMs;
2094
+ return formatDuration(countdownMs);
2095
+ };
2017
2096
  const ticker = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
2018
2097
  const lastPoint = data[data.length - 1];
2098
+ let tickerPrice = null;
2099
+ let tickerColor = null;
2019
2100
  if ((ticker.visible ?? true) && lastPoint) {
2020
- const tickerPrice = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint.c;
2101
+ tickerPrice = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint.c;
2021
2102
  const tickerY = yFromPrice(tickerPrice);
2022
2103
  const lineY = clamp(tickerY, chartTop + 1, chartBottom - 1);
2023
2104
  const lastDirection = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice >= lastPoint.o ? "up" : "down" : getCandleDirectionByIndex(data.length - 1);
2024
- const tickerColor = ticker.color ?? (lastDirection === "up" ? mergedOptions.upColor : mergedOptions.downColor);
2105
+ tickerColor = ticker.color ?? (lastDirection === "up" ? mergedOptions.upColor : mergedOptions.downColor);
2025
2106
  const tickerThickness = Math.max(1, ticker.thickness ?? 1);
2026
2107
  const tickerStyle = ticker.style ?? "solid";
2027
2108
  ctx.save();
@@ -2034,24 +2115,155 @@ function createChart(element, options = {}) {
2034
2115
  ctx.stroke();
2035
2116
  ctx.setLineDash([]);
2036
2117
  ctx.restore();
2037
- const tickerLabel = formatPrice(tickerPrice);
2038
- ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
2039
- const labelPaddingX = 8;
2040
- const labelHeight = 20;
2041
- const labelWidth = getPriceLabelWidth(tickerLabel, labelPaddingX);
2118
+ }
2119
+ if ((ticker.visible ?? true) && labels.showLastPrice && tickerPrice !== null && tickerColor !== null) {
2120
+ const tickerSubtext = ticker.showCountdownInLabel ? getCountdownText() : ticker.labelSubtext?.trim();
2121
+ addPriceAxisLabel({
2122
+ text: formatPrice(tickerPrice),
2123
+ ...tickerSubtext ? { subtext: tickerSubtext } : {},
2124
+ subtextColor: ticker.labelSubtextColor ?? ticker.labelTextColor ?? "#0b1220",
2125
+ ...ticker.labelSubtextFontSize === void 0 ? {} : { subtextFontSize: ticker.labelSubtextFontSize },
2126
+ price: tickerPrice,
2127
+ backgroundColor: ticker.labelBackgroundColor ?? tickerColor,
2128
+ textColor: ticker.labelTextColor ?? "#0b1220",
2129
+ color: tickerColor,
2130
+ priority: 100
2131
+ });
2132
+ }
2133
+ if (labels.showSymbolName && labels.symbolName.trim().length > 0 && tickerPrice !== null) {
2134
+ addPriceAxisLabel({
2135
+ text: labels.symbolName.trim(),
2136
+ price: tickerPrice,
2137
+ backgroundColor: labels.symbolNameBackgroundColor,
2138
+ textColor: labels.symbolNameTextColor,
2139
+ color: labels.symbolNameBackgroundColor,
2140
+ priority: 95
2141
+ });
2142
+ }
2143
+ if (labels.showPreviousClose) {
2144
+ const previousCloseCandidate = Number.isFinite(labels.previousClosePrice) ? labels.previousClosePrice : data.length > 1 ? data[data.length - 2]?.c : Number.NaN;
2145
+ if (previousCloseCandidate !== void 0 && Number.isFinite(previousCloseCandidate)) {
2146
+ const previousClose = previousCloseCandidate;
2147
+ drawReferenceLine(previousClose, labels.previousCloseColor, "dashed");
2148
+ addPriceAxisLabel({
2149
+ text: `PDC ${formatPrice(previousClose)}`,
2150
+ price: previousClose,
2151
+ backgroundColor: labels.backgroundColor,
2152
+ textColor: labels.mutedTextColor,
2153
+ color: labels.previousCloseColor,
2154
+ priority: 50
2155
+ });
2156
+ }
2157
+ }
2158
+ if (labels.showHighLow && visibleData.length > 0) {
2159
+ const visibleHigh = Math.max(...visibleData.map((point) => point.h));
2160
+ const visibleLow = Math.min(...visibleData.map((point) => point.l));
2161
+ addPriceAxisLabel({
2162
+ text: `H ${formatPrice(visibleHigh)}`,
2163
+ price: visibleHigh,
2164
+ backgroundColor: labels.backgroundColor,
2165
+ textColor: labels.textColor,
2166
+ color: labels.highLowColor,
2167
+ priority: 40
2168
+ });
2169
+ addPriceAxisLabel({
2170
+ text: `L ${formatPrice(visibleLow)}`,
2171
+ price: visibleLow,
2172
+ backgroundColor: labels.backgroundColor,
2173
+ textColor: labels.textColor,
2174
+ color: labels.highLowColor,
2175
+ priority: 40
2176
+ });
2177
+ }
2178
+ if (labels.showBidAsk) {
2179
+ if (Number.isFinite(labels.bidPrice)) {
2180
+ drawReferenceLine(labels.bidPrice, labels.bidColor, "dotted");
2181
+ addPriceAxisLabel({
2182
+ text: `B ${formatPrice(labels.bidPrice)}`,
2183
+ price: labels.bidPrice,
2184
+ backgroundColor: labels.bidColor,
2185
+ textColor: "#0b1220",
2186
+ color: labels.bidColor,
2187
+ priority: 80
2188
+ });
2189
+ }
2190
+ if (Number.isFinite(labels.askPrice)) {
2191
+ drawReferenceLine(labels.askPrice, labels.askColor, "dotted");
2192
+ addPriceAxisLabel({
2193
+ text: `A ${formatPrice(labels.askPrice)}`,
2194
+ price: labels.askPrice,
2195
+ backgroundColor: labels.askColor,
2196
+ textColor: "#0b1220",
2197
+ color: labels.askColor,
2198
+ priority: 80
2199
+ });
2200
+ }
2201
+ }
2202
+ if (priceAxisLabels.length > 0) {
2203
+ const labelPaddingX = Math.max(4, labels.labelPaddingX);
2204
+ const baseLabelHeight = Math.max(14, labels.labelHeight);
2205
+ const labelRadius = Math.max(0, labels.borderRadius);
2206
+ const priceLabelFontSize = Math.max(8, axis.fontSize);
2207
+ ctx.font = `${priceLabelFontSize}px ${mergedOptions.fontFamily}`;
2208
+ const positionedLabels = priceAxisLabels.map((label) => {
2209
+ const subtext = label.subtext?.trim();
2210
+ const subtextFontSize = label.subtextFontSize !== void 0 && label.subtextFontSize > 0 ? Math.max(8, Math.round(label.subtextFontSize)) : priceLabelFontSize;
2211
+ const labelHeight = baseLabelHeight + (subtext ? subtextFontSize + 5 : 0);
2212
+ const primaryWidth = label.text === formatPrice(label.price) ? getPriceLabelWidth(label.text, labelPaddingX) : Math.ceil(ctx.measureText(label.text).width) + labelPaddingX * 2;
2213
+ let subtextWidth = 0;
2214
+ if (subtext) {
2215
+ const baseFont = ctx.font;
2216
+ ctx.font = `${subtextFontSize}px ${mergedOptions.fontFamily}`;
2217
+ subtextWidth = Math.ceil(ctx.measureText(subtext).width) + labelPaddingX * 2;
2218
+ ctx.font = baseFont;
2219
+ }
2220
+ const labelTextWidth = Math.max(primaryWidth, subtextWidth);
2221
+ return {
2222
+ ...label,
2223
+ subtext,
2224
+ subtextFontSize,
2225
+ height: labelHeight,
2226
+ width: labelTextWidth,
2227
+ targetY: clamp(yFromPrice(label.price), chartTop + 1, chartBottom - 1) - labelHeight / 2,
2228
+ y: 0
2229
+ };
2230
+ }).sort((a, b) => a.targetY - b.targetY || b.priority - a.priority);
2231
+ const minY = chartTop;
2232
+ let cursorY = minY;
2233
+ for (const label of positionedLabels) {
2234
+ const maxY = chartBottom - label.height;
2235
+ label.y = labels.noOverlapping ? Math.max(clamp(label.targetY, minY, maxY), cursorY) : clamp(label.targetY, minY, maxY);
2236
+ cursorY = label.y + label.height + 2;
2237
+ }
2238
+ if (labels.noOverlapping && positionedLabels.length > 0) {
2239
+ const lastLabel = positionedLabels[positionedLabels.length - 1];
2240
+ const overflow = lastLabel ? lastLabel.y + lastLabel.height - chartBottom : 0;
2241
+ if (overflow > 0) {
2242
+ for (const label of positionedLabels) {
2243
+ label.y = Math.max(minY, label.y - overflow);
2244
+ }
2245
+ }
2246
+ }
2042
2247
  const labelX = chartRight + 4;
2043
- const labelY = clamp(lineY - labelHeight / 2, chartTop, chartBottom - labelHeight);
2044
- const labelRadius = Math.max(0, ticker.labelBorderRadius ?? 0);
2045
- ctx.fillStyle = ticker.labelBackgroundColor ?? tickerColor;
2046
- fillRoundedRect(Math.round(labelX), Math.round(labelY), labelWidth, labelHeight, labelRadius);
2047
- drawText(
2048
- tickerLabel,
2049
- labelX + labelPaddingX,
2050
- labelY + labelHeight / 2,
2051
- "left",
2052
- "middle",
2053
- ticker.labelTextColor ?? "#0b1220"
2054
- );
2248
+ for (const label of positionedLabels) {
2249
+ ctx.fillStyle = label.backgroundColor;
2250
+ fillRoundedRect(Math.round(labelX), Math.round(label.y), label.width, label.height, labelRadius);
2251
+ const primaryY = label.subtext ? label.y + baseLabelHeight / 2 - 1 : label.y + label.height / 2;
2252
+ drawText(label.text, labelX + labelPaddingX, primaryY, "left", "middle", label.textColor);
2253
+ if (label.subtext) {
2254
+ const baseFont = ctx.font;
2255
+ ctx.font = `${label.subtextFontSize}px ${mergedOptions.fontFamily}`;
2256
+ drawText(
2257
+ label.subtext,
2258
+ labelX + labelPaddingX,
2259
+ label.y + baseLabelHeight + label.subtextFontSize / 2,
2260
+ "left",
2261
+ "middle",
2262
+ label.subtextColor ?? label.textColor
2263
+ );
2264
+ ctx.font = baseFont;
2265
+ }
2266
+ }
2055
2267
  }
2056
2268
  for (const priceLine of priceLines) {
2057
2269
  drawPriceLine(priceLine, yFromPrice, chartLeft, chartTop, chartRight, chartBottom);
@@ -2059,6 +2271,25 @@ function createChart(element, options = {}) {
2059
2271
  for (const orderLine of orderLines) {
2060
2272
  drawOrderLine(orderLine, yFromPrice, chartLeft, chartTop, chartRight, chartBottom);
2061
2273
  }
2274
+ if (labels.visible && (labels.showIndicatorNames || labels.showIndicatorValues)) {
2275
+ const labelEntries = [...activeOverlayIndicators, ...activeSeparateIndicators].map(({ indicator, plugin }) => {
2276
+ const inputValues = Object.entries(indicator.inputs).filter(([, value]) => typeof value === "number" || typeof value === "string" || typeof value === "boolean").slice(0, 2).map(([, value]) => String(value));
2277
+ if (labels.showIndicatorNames && labels.showIndicatorValues && inputValues.length > 0) {
2278
+ return `${plugin.name} ${inputValues.join(" ")}`;
2279
+ }
2280
+ if (labels.showIndicatorNames) {
2281
+ return plugin.name;
2282
+ }
2283
+ return inputValues.join(" ");
2284
+ }).filter((entry) => entry.length > 0);
2285
+ if (labelEntries.length > 0) {
2286
+ const prevFont = ctx.font;
2287
+ ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
2288
+ const legendText = labelEntries.join(" ");
2289
+ drawText(legendText, chartLeft + 10, chartTop + 10, "left", "top", labels.indicatorTextColor);
2290
+ ctx.font = prevFont;
2291
+ }
2292
+ }
2062
2293
  for (let index = tickStartIndex; index <= visibleTickEnd; index += xStep) {
2063
2294
  const tickTime = getTimeForIndex(index);
2064
2295
  if (!tickTime) {
@@ -2074,6 +2305,16 @@ function createChart(element, options = {}) {
2074
2305
  drawText(timeLabel, x, fullChartBottom + 8, "center", "top", xAxis.textColor);
2075
2306
  ctx.font = prevFont;
2076
2307
  }
2308
+ if (labels.visible && labels.showCountdownToBarClose && lastPoint) {
2309
+ const stepMs = getTimeStepMs();
2310
+ const rawRemainingMs = lastPoint.time.getTime() + stepMs - Date.now();
2311
+ const countdownMs = rawRemainingMs >= 0 && rawRemainingMs <= stepMs * 2 ? rawRemainingMs : stepMs - Date.now() % stepMs;
2312
+ const countdownText = formatDuration(countdownMs);
2313
+ const prevFont = ctx.font;
2314
+ ctx.font = `${xAxisFontSize}px ${mergedOptions.fontFamily}`;
2315
+ drawText(countdownText, chartRight + 6, fullChartBottom + 8, "left", "top", xAxis.textColor);
2316
+ ctx.font = prevFont;
2317
+ }
2077
2318
  if (crosshair.visible && crosshairPoint) {
2078
2319
  const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
2079
2320
  const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
package/docs/API.md CHANGED
@@ -65,6 +65,7 @@ Top-level options:
65
65
  - `priceLines?: PriceLineOptions[]`
66
66
  - `orderLines?: OrderLineOptions[]`
67
67
  - `tickerLine?: TickerLineOptions`
68
+ - `labels?: LabelsOptions` (TradingView-style price-scale/indicator label controls)
68
69
  - `dashPatterns?: Partial<DashPatternOptions>` (controls dotted/dashed spacing)
69
70
  - `indicators?: IndicatorInstanceOptions[]` (initial indicator instances, built-in includes `"volume"`)
70
71
 
@@ -148,8 +149,52 @@ watermark: {
148
149
  - `color` (default `#38bdf8`)
149
150
  - `labelBackgroundColor` (default `#38bdf8`)
150
151
  - `labelTextColor` (default `#0b1220`)
152
+ - `labelSubtext` (optional second line inside the current-price label, for example `"MNQ"`)
153
+ - `labelSubtextColor` (defaults to the label text color)
154
+ - `labelSubtextFontSize` (default `0`, meaning use the same font size as the price)
155
+ - `showCountdownInLabel` (default `false`; renders bar-close countdown as the second line)
151
156
  - `labelBorderRadius` (default `3`)
152
157
 
158
+ ### `LabelsOptions`
159
+
160
+ TradingView-style labels can be controlled from a single top-level object:
161
+
162
+ - `visible` (default `true`, master switch for this label layer)
163
+ - `symbolName` (text for the symbol-name label)
164
+ - `showSymbolName` (default `false`)
165
+ - `showLastPrice` (default `true`; controls the ticker price tag, while `tickerLine.visible` still controls the line)
166
+ - `showPreviousClose` (default `false`)
167
+ - `previousClosePrice` (optional; falls back to the prior candle close)
168
+ - `showHighLow` (default `false`; labels visible-range high/low)
169
+ - `showBidAsk` (default `false`)
170
+ - `bidPrice`, `askPrice` (optional market data values for bid/ask labels)
171
+ - `showIndicatorNames` (default `false`; draws active indicator names in the chart)
172
+ - `showIndicatorValues` (default `false`; appends simple indicator input values)
173
+ - `showCountdownToBarClose` (default `false`; draws a bottom-axis countdown based on candle time spacing)
174
+ - `noOverlapping` (default `true`; stacks price-scale labels so they do not cover each other)
175
+ - Style fields: `backgroundColor`, `textColor`, `mutedTextColor`, `symbolNameBackgroundColor`, `symbolNameTextColor`, `previousCloseColor`, `highLowColor`, `bidColor`, `askColor`, `indicatorTextColor`, `borderRadius`, `labelHeight`, `labelPaddingX`
176
+
177
+ Example:
178
+
179
+ ```ts
180
+ createChart(root, {
181
+ labels: {
182
+ symbolName: "ESH6",
183
+ showSymbolName: true,
184
+ showPreviousClose: true,
185
+ previousClosePrice: 5231.25,
186
+ showHighLow: true,
187
+ showBidAsk: true,
188
+ bidPrice: 5234.75,
189
+ askPrice: 5235.0,
190
+ showIndicatorNames: true,
191
+ showIndicatorValues: true,
192
+ showCountdownToBarClose: true,
193
+ noOverlapping: true
194
+ }
195
+ });
196
+ ```
197
+
153
198
  ### `DashPatternOptions`
154
199
 
155
200
  - `dotted` (default `[2, 2]`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.44",
3
+ "version": "0.1.46",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",