hyperprop-charting-library 0.1.45 → 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/README.md CHANGED
@@ -60,6 +60,18 @@ const chart = createChart(root, {
60
60
  });
61
61
  ```
62
62
 
63
+ ## Current Price Label Subtext
64
+
65
+ ```ts
66
+ const chart = createChart(root, {
67
+ tickerLine: {
68
+ labelSubtext: "MNQ"
69
+ // or:
70
+ // showCountdownInLabel: true
71
+ }
72
+ });
73
+ ```
74
+
63
75
  ## Candle Color Behavior
64
76
 
65
77
  You can control how up/down candle color is decided:
@@ -201,6 +201,10 @@ var DEFAULT_OPTIONS = {
201
201
  style: "dotted",
202
202
  thickness: 1,
203
203
  labelTextColor: "#0b1220",
204
+ labelSubtext: "",
205
+ labelSubtextColor: "#0b1220",
206
+ labelSubtextFontSize: 0,
207
+ showCountdownInLabel: false,
204
208
  labelBorderRadius: 3
205
209
  },
206
210
  labels: DEFAULT_LABELS_OPTIONS,
@@ -2103,6 +2107,16 @@ function createChart(element, options = {}) {
2103
2107
  ctx.stroke();
2104
2108
  ctx.restore();
2105
2109
  };
2110
+ const getCountdownText = () => {
2111
+ const last = data[data.length - 1];
2112
+ if (!last) {
2113
+ return null;
2114
+ }
2115
+ const stepMs = getTimeStepMs();
2116
+ const rawRemainingMs = last.time.getTime() + stepMs - Date.now();
2117
+ const countdownMs = rawRemainingMs >= 0 && rawRemainingMs <= stepMs * 2 ? rawRemainingMs : stepMs - Date.now() % stepMs;
2118
+ return formatDuration(countdownMs);
2119
+ };
2106
2120
  const ticker = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
2107
2121
  const lastPoint = data[data.length - 1];
2108
2122
  let tickerPrice = null;
@@ -2127,8 +2141,12 @@ function createChart(element, options = {}) {
2127
2141
  ctx.restore();
2128
2142
  }
2129
2143
  if ((ticker.visible ?? true) && labels.showLastPrice && tickerPrice !== null && tickerColor !== null) {
2144
+ const tickerSubtext = ticker.showCountdownInLabel ? getCountdownText() : ticker.labelSubtext?.trim();
2130
2145
  addPriceAxisLabel({
2131
2146
  text: formatPrice(tickerPrice),
2147
+ ...tickerSubtext ? { subtext: tickerSubtext } : {},
2148
+ subtextColor: ticker.labelSubtextColor ?? ticker.labelTextColor ?? "#0b1220",
2149
+ ...ticker.labelSubtextFontSize === void 0 ? {} : { subtextFontSize: ticker.labelSubtextFontSize },
2132
2150
  price: tickerPrice,
2133
2151
  backgroundColor: ticker.labelBackgroundColor ?? tickerColor,
2134
2152
  textColor: ticker.labelTextColor ?? "#0b1220",
@@ -2207,28 +2225,43 @@ function createChart(element, options = {}) {
2207
2225
  }
2208
2226
  if (priceAxisLabels.length > 0) {
2209
2227
  const labelPaddingX = Math.max(4, labels.labelPaddingX);
2210
- const labelHeight = Math.max(14, labels.labelHeight);
2228
+ const baseLabelHeight = Math.max(14, labels.labelHeight);
2211
2229
  const labelRadius = Math.max(0, labels.borderRadius);
2212
- ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
2230
+ const priceLabelFontSize = Math.max(8, axis.fontSize);
2231
+ ctx.font = `${priceLabelFontSize}px ${mergedOptions.fontFamily}`;
2213
2232
  const positionedLabels = priceAxisLabels.map((label) => {
2214
- const labelTextWidth = label.text === formatPrice(label.price) ? getPriceLabelWidth(label.text, labelPaddingX) : Math.ceil(ctx.measureText(label.text).width) + labelPaddingX * 2;
2233
+ const subtext = label.subtext?.trim();
2234
+ const subtextFontSize = label.subtextFontSize !== void 0 && label.subtextFontSize > 0 ? Math.max(8, Math.round(label.subtextFontSize)) : priceLabelFontSize;
2235
+ const labelHeight = baseLabelHeight + (subtext ? subtextFontSize + 5 : 0);
2236
+ const primaryWidth = label.text === formatPrice(label.price) ? getPriceLabelWidth(label.text, labelPaddingX) : Math.ceil(ctx.measureText(label.text).width) + labelPaddingX * 2;
2237
+ let subtextWidth = 0;
2238
+ if (subtext) {
2239
+ const baseFont = ctx.font;
2240
+ ctx.font = `${subtextFontSize}px ${mergedOptions.fontFamily}`;
2241
+ subtextWidth = Math.ceil(ctx.measureText(subtext).width) + labelPaddingX * 2;
2242
+ ctx.font = baseFont;
2243
+ }
2244
+ const labelTextWidth = Math.max(primaryWidth, subtextWidth);
2215
2245
  return {
2216
2246
  ...label,
2247
+ subtext,
2248
+ subtextFontSize,
2249
+ height: labelHeight,
2217
2250
  width: labelTextWidth,
2218
2251
  targetY: clamp(yFromPrice(label.price), chartTop + 1, chartBottom - 1) - labelHeight / 2,
2219
2252
  y: 0
2220
2253
  };
2221
2254
  }).sort((a, b) => a.targetY - b.targetY || b.priority - a.priority);
2222
2255
  const minY = chartTop;
2223
- const maxY = chartBottom - labelHeight;
2224
2256
  let cursorY = minY;
2225
2257
  for (const label of positionedLabels) {
2258
+ const maxY = chartBottom - label.height;
2226
2259
  label.y = labels.noOverlapping ? Math.max(clamp(label.targetY, minY, maxY), cursorY) : clamp(label.targetY, minY, maxY);
2227
- cursorY = label.y + labelHeight + 2;
2260
+ cursorY = label.y + label.height + 2;
2228
2261
  }
2229
2262
  if (labels.noOverlapping && positionedLabels.length > 0) {
2230
2263
  const lastLabel = positionedLabels[positionedLabels.length - 1];
2231
- const overflow = lastLabel ? lastLabel.y + labelHeight - maxY : 0;
2264
+ const overflow = lastLabel ? lastLabel.y + lastLabel.height - chartBottom : 0;
2232
2265
  if (overflow > 0) {
2233
2266
  for (const label of positionedLabels) {
2234
2267
  label.y = Math.max(minY, label.y - overflow);
@@ -2238,8 +2271,22 @@ function createChart(element, options = {}) {
2238
2271
  const labelX = chartRight + 4;
2239
2272
  for (const label of positionedLabels) {
2240
2273
  ctx.fillStyle = label.backgroundColor;
2241
- fillRoundedRect(Math.round(labelX), Math.round(label.y), label.width, labelHeight, labelRadius);
2242
- drawText(label.text, labelX + labelPaddingX, label.y + labelHeight / 2, "left", "middle", label.textColor);
2274
+ fillRoundedRect(Math.round(labelX), Math.round(label.y), label.width, label.height, labelRadius);
2275
+ const primaryY = label.subtext ? label.y + baseLabelHeight / 2 - 1 : label.y + label.height / 2;
2276
+ drawText(label.text, labelX + labelPaddingX, primaryY, "left", "middle", label.textColor);
2277
+ if (label.subtext) {
2278
+ const baseFont = ctx.font;
2279
+ ctx.font = `${label.subtextFontSize}px ${mergedOptions.fontFamily}`;
2280
+ drawText(
2281
+ label.subtext,
2282
+ labelX + labelPaddingX,
2283
+ label.y + baseLabelHeight + label.subtextFontSize / 2,
2284
+ "left",
2285
+ "middle",
2286
+ label.subtextColor ?? label.textColor
2287
+ );
2288
+ ctx.font = baseFont;
2289
+ }
2243
2290
  }
2244
2291
  }
2245
2292
  for (const priceLine of priceLines) {
@@ -260,6 +260,10 @@ interface TickerLineOptions {
260
260
  color?: string;
261
261
  labelBackgroundColor?: string;
262
262
  labelTextColor?: string;
263
+ labelSubtext?: string;
264
+ labelSubtextColor?: string;
265
+ labelSubtextFontSize?: number;
266
+ showCountdownInLabel?: boolean;
263
267
  labelBorderRadius?: number;
264
268
  smoothing?: boolean;
265
269
  smoothingSpeed?: number;
@@ -177,6 +177,10 @@ var DEFAULT_OPTIONS = {
177
177
  style: "dotted",
178
178
  thickness: 1,
179
179
  labelTextColor: "#0b1220",
180
+ labelSubtext: "",
181
+ labelSubtextColor: "#0b1220",
182
+ labelSubtextFontSize: 0,
183
+ showCountdownInLabel: false,
180
184
  labelBorderRadius: 3
181
185
  },
182
186
  labels: DEFAULT_LABELS_OPTIONS,
@@ -2079,6 +2083,16 @@ function createChart(element, options = {}) {
2079
2083
  ctx.stroke();
2080
2084
  ctx.restore();
2081
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
+ };
2082
2096
  const ticker = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
2083
2097
  const lastPoint = data[data.length - 1];
2084
2098
  let tickerPrice = null;
@@ -2103,8 +2117,12 @@ function createChart(element, options = {}) {
2103
2117
  ctx.restore();
2104
2118
  }
2105
2119
  if ((ticker.visible ?? true) && labels.showLastPrice && tickerPrice !== null && tickerColor !== null) {
2120
+ const tickerSubtext = ticker.showCountdownInLabel ? getCountdownText() : ticker.labelSubtext?.trim();
2106
2121
  addPriceAxisLabel({
2107
2122
  text: formatPrice(tickerPrice),
2123
+ ...tickerSubtext ? { subtext: tickerSubtext } : {},
2124
+ subtextColor: ticker.labelSubtextColor ?? ticker.labelTextColor ?? "#0b1220",
2125
+ ...ticker.labelSubtextFontSize === void 0 ? {} : { subtextFontSize: ticker.labelSubtextFontSize },
2108
2126
  price: tickerPrice,
2109
2127
  backgroundColor: ticker.labelBackgroundColor ?? tickerColor,
2110
2128
  textColor: ticker.labelTextColor ?? "#0b1220",
@@ -2183,28 +2201,43 @@ function createChart(element, options = {}) {
2183
2201
  }
2184
2202
  if (priceAxisLabels.length > 0) {
2185
2203
  const labelPaddingX = Math.max(4, labels.labelPaddingX);
2186
- const labelHeight = Math.max(14, labels.labelHeight);
2204
+ const baseLabelHeight = Math.max(14, labels.labelHeight);
2187
2205
  const labelRadius = Math.max(0, labels.borderRadius);
2188
- ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
2206
+ const priceLabelFontSize = Math.max(8, axis.fontSize);
2207
+ ctx.font = `${priceLabelFontSize}px ${mergedOptions.fontFamily}`;
2189
2208
  const positionedLabels = priceAxisLabels.map((label) => {
2190
- const labelTextWidth = label.text === formatPrice(label.price) ? getPriceLabelWidth(label.text, labelPaddingX) : Math.ceil(ctx.measureText(label.text).width) + labelPaddingX * 2;
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);
2191
2221
  return {
2192
2222
  ...label,
2223
+ subtext,
2224
+ subtextFontSize,
2225
+ height: labelHeight,
2193
2226
  width: labelTextWidth,
2194
2227
  targetY: clamp(yFromPrice(label.price), chartTop + 1, chartBottom - 1) - labelHeight / 2,
2195
2228
  y: 0
2196
2229
  };
2197
2230
  }).sort((a, b) => a.targetY - b.targetY || b.priority - a.priority);
2198
2231
  const minY = chartTop;
2199
- const maxY = chartBottom - labelHeight;
2200
2232
  let cursorY = minY;
2201
2233
  for (const label of positionedLabels) {
2234
+ const maxY = chartBottom - label.height;
2202
2235
  label.y = labels.noOverlapping ? Math.max(clamp(label.targetY, minY, maxY), cursorY) : clamp(label.targetY, minY, maxY);
2203
- cursorY = label.y + labelHeight + 2;
2236
+ cursorY = label.y + label.height + 2;
2204
2237
  }
2205
2238
  if (labels.noOverlapping && positionedLabels.length > 0) {
2206
2239
  const lastLabel = positionedLabels[positionedLabels.length - 1];
2207
- const overflow = lastLabel ? lastLabel.y + labelHeight - maxY : 0;
2240
+ const overflow = lastLabel ? lastLabel.y + lastLabel.height - chartBottom : 0;
2208
2241
  if (overflow > 0) {
2209
2242
  for (const label of positionedLabels) {
2210
2243
  label.y = Math.max(minY, label.y - overflow);
@@ -2214,8 +2247,22 @@ function createChart(element, options = {}) {
2214
2247
  const labelX = chartRight + 4;
2215
2248
  for (const label of positionedLabels) {
2216
2249
  ctx.fillStyle = label.backgroundColor;
2217
- fillRoundedRect(Math.round(labelX), Math.round(label.y), label.width, labelHeight, labelRadius);
2218
- drawText(label.text, labelX + labelPaddingX, label.y + labelHeight / 2, "left", "middle", label.textColor);
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
+ }
2219
2266
  }
2220
2267
  }
2221
2268
  for (const priceLine of priceLines) {
package/dist/index.cjs CHANGED
@@ -201,6 +201,10 @@ var DEFAULT_OPTIONS = {
201
201
  style: "dotted",
202
202
  thickness: 1,
203
203
  labelTextColor: "#0b1220",
204
+ labelSubtext: "",
205
+ labelSubtextColor: "#0b1220",
206
+ labelSubtextFontSize: 0,
207
+ showCountdownInLabel: false,
204
208
  labelBorderRadius: 3
205
209
  },
206
210
  labels: DEFAULT_LABELS_OPTIONS,
@@ -2103,6 +2107,16 @@ function createChart(element, options = {}) {
2103
2107
  ctx.stroke();
2104
2108
  ctx.restore();
2105
2109
  };
2110
+ const getCountdownText = () => {
2111
+ const last = data[data.length - 1];
2112
+ if (!last) {
2113
+ return null;
2114
+ }
2115
+ const stepMs = getTimeStepMs();
2116
+ const rawRemainingMs = last.time.getTime() + stepMs - Date.now();
2117
+ const countdownMs = rawRemainingMs >= 0 && rawRemainingMs <= stepMs * 2 ? rawRemainingMs : stepMs - Date.now() % stepMs;
2118
+ return formatDuration(countdownMs);
2119
+ };
2106
2120
  const ticker = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
2107
2121
  const lastPoint = data[data.length - 1];
2108
2122
  let tickerPrice = null;
@@ -2127,8 +2141,12 @@ function createChart(element, options = {}) {
2127
2141
  ctx.restore();
2128
2142
  }
2129
2143
  if ((ticker.visible ?? true) && labels.showLastPrice && tickerPrice !== null && tickerColor !== null) {
2144
+ const tickerSubtext = ticker.showCountdownInLabel ? getCountdownText() : ticker.labelSubtext?.trim();
2130
2145
  addPriceAxisLabel({
2131
2146
  text: formatPrice(tickerPrice),
2147
+ ...tickerSubtext ? { subtext: tickerSubtext } : {},
2148
+ subtextColor: ticker.labelSubtextColor ?? ticker.labelTextColor ?? "#0b1220",
2149
+ ...ticker.labelSubtextFontSize === void 0 ? {} : { subtextFontSize: ticker.labelSubtextFontSize },
2132
2150
  price: tickerPrice,
2133
2151
  backgroundColor: ticker.labelBackgroundColor ?? tickerColor,
2134
2152
  textColor: ticker.labelTextColor ?? "#0b1220",
@@ -2207,28 +2225,43 @@ function createChart(element, options = {}) {
2207
2225
  }
2208
2226
  if (priceAxisLabels.length > 0) {
2209
2227
  const labelPaddingX = Math.max(4, labels.labelPaddingX);
2210
- const labelHeight = Math.max(14, labels.labelHeight);
2228
+ const baseLabelHeight = Math.max(14, labels.labelHeight);
2211
2229
  const labelRadius = Math.max(0, labels.borderRadius);
2212
- ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
2230
+ const priceLabelFontSize = Math.max(8, axis.fontSize);
2231
+ ctx.font = `${priceLabelFontSize}px ${mergedOptions.fontFamily}`;
2213
2232
  const positionedLabels = priceAxisLabels.map((label) => {
2214
- const labelTextWidth = label.text === formatPrice(label.price) ? getPriceLabelWidth(label.text, labelPaddingX) : Math.ceil(ctx.measureText(label.text).width) + labelPaddingX * 2;
2233
+ const subtext = label.subtext?.trim();
2234
+ const subtextFontSize = label.subtextFontSize !== void 0 && label.subtextFontSize > 0 ? Math.max(8, Math.round(label.subtextFontSize)) : priceLabelFontSize;
2235
+ const labelHeight = baseLabelHeight + (subtext ? subtextFontSize + 5 : 0);
2236
+ const primaryWidth = label.text === formatPrice(label.price) ? getPriceLabelWidth(label.text, labelPaddingX) : Math.ceil(ctx.measureText(label.text).width) + labelPaddingX * 2;
2237
+ let subtextWidth = 0;
2238
+ if (subtext) {
2239
+ const baseFont = ctx.font;
2240
+ ctx.font = `${subtextFontSize}px ${mergedOptions.fontFamily}`;
2241
+ subtextWidth = Math.ceil(ctx.measureText(subtext).width) + labelPaddingX * 2;
2242
+ ctx.font = baseFont;
2243
+ }
2244
+ const labelTextWidth = Math.max(primaryWidth, subtextWidth);
2215
2245
  return {
2216
2246
  ...label,
2247
+ subtext,
2248
+ subtextFontSize,
2249
+ height: labelHeight,
2217
2250
  width: labelTextWidth,
2218
2251
  targetY: clamp(yFromPrice(label.price), chartTop + 1, chartBottom - 1) - labelHeight / 2,
2219
2252
  y: 0
2220
2253
  };
2221
2254
  }).sort((a, b) => a.targetY - b.targetY || b.priority - a.priority);
2222
2255
  const minY = chartTop;
2223
- const maxY = chartBottom - labelHeight;
2224
2256
  let cursorY = minY;
2225
2257
  for (const label of positionedLabels) {
2258
+ const maxY = chartBottom - label.height;
2226
2259
  label.y = labels.noOverlapping ? Math.max(clamp(label.targetY, minY, maxY), cursorY) : clamp(label.targetY, minY, maxY);
2227
- cursorY = label.y + labelHeight + 2;
2260
+ cursorY = label.y + label.height + 2;
2228
2261
  }
2229
2262
  if (labels.noOverlapping && positionedLabels.length > 0) {
2230
2263
  const lastLabel = positionedLabels[positionedLabels.length - 1];
2231
- const overflow = lastLabel ? lastLabel.y + labelHeight - maxY : 0;
2264
+ const overflow = lastLabel ? lastLabel.y + lastLabel.height - chartBottom : 0;
2232
2265
  if (overflow > 0) {
2233
2266
  for (const label of positionedLabels) {
2234
2267
  label.y = Math.max(minY, label.y - overflow);
@@ -2238,8 +2271,22 @@ function createChart(element, options = {}) {
2238
2271
  const labelX = chartRight + 4;
2239
2272
  for (const label of positionedLabels) {
2240
2273
  ctx.fillStyle = label.backgroundColor;
2241
- fillRoundedRect(Math.round(labelX), Math.round(label.y), label.width, labelHeight, labelRadius);
2242
- drawText(label.text, labelX + labelPaddingX, label.y + labelHeight / 2, "left", "middle", label.textColor);
2274
+ fillRoundedRect(Math.round(labelX), Math.round(label.y), label.width, label.height, labelRadius);
2275
+ const primaryY = label.subtext ? label.y + baseLabelHeight / 2 - 1 : label.y + label.height / 2;
2276
+ drawText(label.text, labelX + labelPaddingX, primaryY, "left", "middle", label.textColor);
2277
+ if (label.subtext) {
2278
+ const baseFont = ctx.font;
2279
+ ctx.font = `${label.subtextFontSize}px ${mergedOptions.fontFamily}`;
2280
+ drawText(
2281
+ label.subtext,
2282
+ labelX + labelPaddingX,
2283
+ label.y + baseLabelHeight + label.subtextFontSize / 2,
2284
+ "left",
2285
+ "middle",
2286
+ label.subtextColor ?? label.textColor
2287
+ );
2288
+ ctx.font = baseFont;
2289
+ }
2243
2290
  }
2244
2291
  }
2245
2292
  for (const priceLine of priceLines) {
package/dist/index.d.cts CHANGED
@@ -260,6 +260,10 @@ interface TickerLineOptions {
260
260
  color?: string;
261
261
  labelBackgroundColor?: string;
262
262
  labelTextColor?: string;
263
+ labelSubtext?: string;
264
+ labelSubtextColor?: string;
265
+ labelSubtextFontSize?: number;
266
+ showCountdownInLabel?: boolean;
263
267
  labelBorderRadius?: number;
264
268
  smoothing?: boolean;
265
269
  smoothingSpeed?: number;
package/dist/index.d.ts CHANGED
@@ -260,6 +260,10 @@ interface TickerLineOptions {
260
260
  color?: string;
261
261
  labelBackgroundColor?: string;
262
262
  labelTextColor?: string;
263
+ labelSubtext?: string;
264
+ labelSubtextColor?: string;
265
+ labelSubtextFontSize?: number;
266
+ showCountdownInLabel?: boolean;
263
267
  labelBorderRadius?: number;
264
268
  smoothing?: boolean;
265
269
  smoothingSpeed?: number;
package/dist/index.js CHANGED
@@ -177,6 +177,10 @@ var DEFAULT_OPTIONS = {
177
177
  style: "dotted",
178
178
  thickness: 1,
179
179
  labelTextColor: "#0b1220",
180
+ labelSubtext: "",
181
+ labelSubtextColor: "#0b1220",
182
+ labelSubtextFontSize: 0,
183
+ showCountdownInLabel: false,
180
184
  labelBorderRadius: 3
181
185
  },
182
186
  labels: DEFAULT_LABELS_OPTIONS,
@@ -2079,6 +2083,16 @@ function createChart(element, options = {}) {
2079
2083
  ctx.stroke();
2080
2084
  ctx.restore();
2081
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
+ };
2082
2096
  const ticker = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
2083
2097
  const lastPoint = data[data.length - 1];
2084
2098
  let tickerPrice = null;
@@ -2103,8 +2117,12 @@ function createChart(element, options = {}) {
2103
2117
  ctx.restore();
2104
2118
  }
2105
2119
  if ((ticker.visible ?? true) && labels.showLastPrice && tickerPrice !== null && tickerColor !== null) {
2120
+ const tickerSubtext = ticker.showCountdownInLabel ? getCountdownText() : ticker.labelSubtext?.trim();
2106
2121
  addPriceAxisLabel({
2107
2122
  text: formatPrice(tickerPrice),
2123
+ ...tickerSubtext ? { subtext: tickerSubtext } : {},
2124
+ subtextColor: ticker.labelSubtextColor ?? ticker.labelTextColor ?? "#0b1220",
2125
+ ...ticker.labelSubtextFontSize === void 0 ? {} : { subtextFontSize: ticker.labelSubtextFontSize },
2108
2126
  price: tickerPrice,
2109
2127
  backgroundColor: ticker.labelBackgroundColor ?? tickerColor,
2110
2128
  textColor: ticker.labelTextColor ?? "#0b1220",
@@ -2183,28 +2201,43 @@ function createChart(element, options = {}) {
2183
2201
  }
2184
2202
  if (priceAxisLabels.length > 0) {
2185
2203
  const labelPaddingX = Math.max(4, labels.labelPaddingX);
2186
- const labelHeight = Math.max(14, labels.labelHeight);
2204
+ const baseLabelHeight = Math.max(14, labels.labelHeight);
2187
2205
  const labelRadius = Math.max(0, labels.borderRadius);
2188
- ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
2206
+ const priceLabelFontSize = Math.max(8, axis.fontSize);
2207
+ ctx.font = `${priceLabelFontSize}px ${mergedOptions.fontFamily}`;
2189
2208
  const positionedLabels = priceAxisLabels.map((label) => {
2190
- const labelTextWidth = label.text === formatPrice(label.price) ? getPriceLabelWidth(label.text, labelPaddingX) : Math.ceil(ctx.measureText(label.text).width) + labelPaddingX * 2;
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);
2191
2221
  return {
2192
2222
  ...label,
2223
+ subtext,
2224
+ subtextFontSize,
2225
+ height: labelHeight,
2193
2226
  width: labelTextWidth,
2194
2227
  targetY: clamp(yFromPrice(label.price), chartTop + 1, chartBottom - 1) - labelHeight / 2,
2195
2228
  y: 0
2196
2229
  };
2197
2230
  }).sort((a, b) => a.targetY - b.targetY || b.priority - a.priority);
2198
2231
  const minY = chartTop;
2199
- const maxY = chartBottom - labelHeight;
2200
2232
  let cursorY = minY;
2201
2233
  for (const label of positionedLabels) {
2234
+ const maxY = chartBottom - label.height;
2202
2235
  label.y = labels.noOverlapping ? Math.max(clamp(label.targetY, minY, maxY), cursorY) : clamp(label.targetY, minY, maxY);
2203
- cursorY = label.y + labelHeight + 2;
2236
+ cursorY = label.y + label.height + 2;
2204
2237
  }
2205
2238
  if (labels.noOverlapping && positionedLabels.length > 0) {
2206
2239
  const lastLabel = positionedLabels[positionedLabels.length - 1];
2207
- const overflow = lastLabel ? lastLabel.y + labelHeight - maxY : 0;
2240
+ const overflow = lastLabel ? lastLabel.y + lastLabel.height - chartBottom : 0;
2208
2241
  if (overflow > 0) {
2209
2242
  for (const label of positionedLabels) {
2210
2243
  label.y = Math.max(minY, label.y - overflow);
@@ -2214,8 +2247,22 @@ function createChart(element, options = {}) {
2214
2247
  const labelX = chartRight + 4;
2215
2248
  for (const label of positionedLabels) {
2216
2249
  ctx.fillStyle = label.backgroundColor;
2217
- fillRoundedRect(Math.round(labelX), Math.round(label.y), label.width, labelHeight, labelRadius);
2218
- drawText(label.text, labelX + labelPaddingX, label.y + labelHeight / 2, "left", "middle", label.textColor);
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
+ }
2219
2266
  }
2220
2267
  }
2221
2268
  for (const priceLine of priceLines) {
package/docs/API.md CHANGED
@@ -149,6 +149,10 @@ watermark: {
149
149
  - `color` (default `#38bdf8`)
150
150
  - `labelBackgroundColor` (default `#38bdf8`)
151
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)
152
156
  - `labelBorderRadius` (default `3`)
153
157
 
154
158
  ### `LabelsOptions`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.45",
3
+ "version": "0.1.46",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",