hyperprop-charting-library 0.1.102 → 0.1.104

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.
@@ -1292,10 +1292,19 @@ function createChart(element, options = {}) {
1292
1292
  }
1293
1293
  return Math.round(price / tickSize) * tickSize;
1294
1294
  };
1295
+ const groupThousands = (value, decimals) => {
1296
+ const negative = value < 0;
1297
+ const fixed = Math.abs(value).toFixed(decimals);
1298
+ const dot = fixed.indexOf(".");
1299
+ const intPart = dot === -1 ? fixed : fixed.slice(0, dot);
1300
+ const decPart = dot === -1 ? "" : fixed.slice(dot);
1301
+ const grouped = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1302
+ return `${negative ? "-" : ""}${grouped}${decPart}`;
1303
+ };
1295
1304
  const formatPrice = (price) => {
1296
1305
  const rounded = quantizeToTickSize(price);
1297
1306
  const decimals = getDisplayPriceDecimals();
1298
- return rounded.toFixed(decimals);
1307
+ return groupThousands(rounded, decimals);
1299
1308
  };
1300
1309
  const roundToPricePrecision = (price) => {
1301
1310
  const rounded = quantizeToTickSize(price);
@@ -1333,7 +1342,7 @@ function createChart(element, options = {}) {
1333
1342
  }
1334
1343
  const observedDigits = maxAbsPrice >= 1 ? Math.floor(Math.log10(maxAbsPrice)) + 1 : 1;
1335
1344
  const integerDigits = Math.max(configuredDigits, observedDigits);
1336
- const integerPart = "8".repeat(integerDigits);
1345
+ const integerPart = "8".repeat(integerDigits).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1337
1346
  const decimalPart = decimals > 0 ? `.${"8".repeat(decimals)}` : "";
1338
1347
  return `${integerPart}${decimalPart}`;
1339
1348
  };
@@ -2913,6 +2922,32 @@ function createChart(element, options = {}) {
2913
2922
  }
2914
2923
  return lo;
2915
2924
  };
2925
+ const groups = /* @__PURE__ */ new Map();
2926
+ for (const marker of tradeMarkers) {
2927
+ const ms = typeof marker.time === "number" ? marker.time : Date.parse(String(marker.time));
2928
+ if (!Number.isFinite(ms)) continue;
2929
+ const index = indexForTime(ms);
2930
+ if (index === null || index < visibleStart || index > visibleEnd) continue;
2931
+ const side = marker.side === "buy" ? "buy" : "sell";
2932
+ const qty = Math.max(0, marker.qty ?? 0);
2933
+ const weight = qty > 0 ? qty : 1;
2934
+ const key = `${index}:${side}`;
2935
+ const existing = groups.get(key);
2936
+ if (existing) {
2937
+ existing.qty += qty;
2938
+ existing.priceQty += marker.price * weight;
2939
+ } else {
2940
+ groups.set(key, {
2941
+ index,
2942
+ side,
2943
+ qty,
2944
+ priceQty: marker.price * weight,
2945
+ ...marker.color === void 0 ? {} : { color: marker.color },
2946
+ ...marker.textColor === void 0 ? {} : { textColor: marker.textColor },
2947
+ ...marker.text === void 0 ? {} : { text: marker.text }
2948
+ });
2949
+ }
2950
+ }
2916
2951
  const prevFont = ctx.font;
2917
2952
  const prevAlign = ctx.textAlign;
2918
2953
  const prevBaseline = ctx.textBaseline;
@@ -2923,42 +2958,29 @@ function createChart(element, options = {}) {
2923
2958
  const arrowH = 8;
2924
2959
  const gap = 5;
2925
2960
  const labelOffset = arrowH + 8;
2926
- for (const marker of tradeMarkers) {
2927
- const ms = typeof marker.time === "number" ? marker.time : Date.parse(String(marker.time));
2928
- if (!Number.isFinite(ms)) continue;
2929
- const index = indexForTime(ms);
2930
- if (index === null || index < visibleStart || index > visibleEnd) continue;
2931
- const bar = data[index];
2961
+ for (const group of groups.values()) {
2962
+ const bar = data[group.index];
2932
2963
  if (!bar) continue;
2933
- const x = chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
2964
+ const x = chartLeft + (group.index + 0.5 - xStart) / xSpan * chartWidth;
2934
2965
  if (x < chartLeft - 20 || x > chartRight + 20) continue;
2935
- const isBuy = marker.side === "buy";
2936
- const arrowColor = marker.color ?? (isBuy ? "#2962ff" : "#f23645");
2937
- const textColor = marker.textColor ?? "#d1d4dc";
2938
- const label = marker.text ?? `${marker.qty ?? ""}${marker.qty != null ? " @ " : ""}${formatPrice(marker.price)}`.trim();
2966
+ const isBuy = group.side === "buy";
2967
+ const arrowColor = group.color ?? (isBuy ? "#2962ff" : "#f23645");
2968
+ const textColor = group.textColor ?? "#d1d4dc";
2969
+ const weightTotal = group.qty > 0 ? group.qty : 1;
2970
+ const avgPrice = group.priceQty / weightTotal;
2971
+ const label = group.text ?? `${group.qty > 0 ? `${group.qty} @ ` : ""}${formatPrice(avgPrice)}`.trim();
2939
2972
  ctx.save();
2940
2973
  ctx.fillStyle = arrowColor;
2941
- if (isBuy) {
2942
- const tipY = yFromPrice(bar.l) + gap;
2943
- ctx.beginPath();
2944
- ctx.moveTo(x, tipY);
2945
- ctx.lineTo(x - arrowHalf, tipY + arrowH);
2946
- ctx.lineTo(x + arrowHalf, tipY + arrowH);
2947
- ctx.closePath();
2948
- ctx.fill();
2949
- ctx.fillStyle = textColor;
2950
- ctx.fillText(label, Math.round(x), Math.round(tipY + labelOffset));
2951
- } else {
2952
- const tipY = yFromPrice(bar.h) - gap;
2953
- ctx.beginPath();
2954
- ctx.moveTo(x, tipY);
2955
- ctx.lineTo(x - arrowHalf, tipY - arrowH);
2956
- ctx.lineTo(x + arrowHalf, tipY - arrowH);
2957
- ctx.closePath();
2958
- ctx.fill();
2959
- ctx.fillStyle = textColor;
2960
- ctx.fillText(label, Math.round(x), Math.round(tipY - labelOffset));
2961
- }
2974
+ const tipY = isBuy ? yFromPrice(bar.l) + gap : yFromPrice(bar.h) - gap;
2975
+ const dir = isBuy ? 1 : -1;
2976
+ ctx.beginPath();
2977
+ ctx.moveTo(x, tipY);
2978
+ ctx.lineTo(x - arrowHalf, tipY + arrowH * dir);
2979
+ ctx.lineTo(x + arrowHalf, tipY + arrowH * dir);
2980
+ ctx.closePath();
2981
+ ctx.fill();
2982
+ ctx.fillStyle = textColor;
2983
+ ctx.fillText(label, Math.round(x), Math.round(tipY + labelOffset * dir));
2962
2984
  ctx.restore();
2963
2985
  }
2964
2986
  ctx.font = prevFont;
@@ -3419,16 +3441,18 @@ function createChart(element, options = {}) {
3419
3441
  ctx.font = prevFont;
3420
3442
  }
3421
3443
  }
3444
+ const axisStepMs = getTimeStepMs();
3445
+ const axisIntraday = axisStepMs > 0 && axisStepMs < 24 * 60 * 60 * 1e3;
3446
+ let prevAxisTickTime = null;
3422
3447
  for (let index = tickStartIndex; index <= visibleTickEnd; index += xStep) {
3423
3448
  const tickTime = getTimeForIndex(index);
3424
3449
  if (!tickTime) {
3425
3450
  continue;
3426
3451
  }
3427
3452
  const x = chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
3428
- const timeLabel = tickTime.toLocaleDateString(void 0, {
3429
- month: "short",
3430
- day: "numeric"
3431
- });
3453
+ const isNewDay = !prevAxisTickTime || prevAxisTickTime.getDate() !== tickTime.getDate() || prevAxisTickTime.getMonth() !== tickTime.getMonth() || prevAxisTickTime.getFullYear() !== tickTime.getFullYear();
3454
+ const timeLabel = axisIntraday && !isNewDay ? tickTime.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit", hour12: false }) : tickTime.toLocaleDateString(void 0, { month: "short", day: "numeric" });
3455
+ prevAxisTickTime = tickTime;
3432
3456
  const prevFont = ctx.font;
3433
3457
  ctx.font = `${xAxisFontSize}px ${mergedOptions.fontFamily}`;
3434
3458
  drawText(timeLabel, x, fullChartBottom + 8, "center", "top", xAxis.textColor);
@@ -1266,10 +1266,19 @@ function createChart(element, options = {}) {
1266
1266
  }
1267
1267
  return Math.round(price / tickSize) * tickSize;
1268
1268
  };
1269
+ const groupThousands = (value, decimals) => {
1270
+ const negative = value < 0;
1271
+ const fixed = Math.abs(value).toFixed(decimals);
1272
+ const dot = fixed.indexOf(".");
1273
+ const intPart = dot === -1 ? fixed : fixed.slice(0, dot);
1274
+ const decPart = dot === -1 ? "" : fixed.slice(dot);
1275
+ const grouped = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1276
+ return `${negative ? "-" : ""}${grouped}${decPart}`;
1277
+ };
1269
1278
  const formatPrice = (price) => {
1270
1279
  const rounded = quantizeToTickSize(price);
1271
1280
  const decimals = getDisplayPriceDecimals();
1272
- return rounded.toFixed(decimals);
1281
+ return groupThousands(rounded, decimals);
1273
1282
  };
1274
1283
  const roundToPricePrecision = (price) => {
1275
1284
  const rounded = quantizeToTickSize(price);
@@ -1307,7 +1316,7 @@ function createChart(element, options = {}) {
1307
1316
  }
1308
1317
  const observedDigits = maxAbsPrice >= 1 ? Math.floor(Math.log10(maxAbsPrice)) + 1 : 1;
1309
1318
  const integerDigits = Math.max(configuredDigits, observedDigits);
1310
- const integerPart = "8".repeat(integerDigits);
1319
+ const integerPart = "8".repeat(integerDigits).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1311
1320
  const decimalPart = decimals > 0 ? `.${"8".repeat(decimals)}` : "";
1312
1321
  return `${integerPart}${decimalPart}`;
1313
1322
  };
@@ -2887,6 +2896,32 @@ function createChart(element, options = {}) {
2887
2896
  }
2888
2897
  return lo;
2889
2898
  };
2899
+ const groups = /* @__PURE__ */ new Map();
2900
+ for (const marker of tradeMarkers) {
2901
+ const ms = typeof marker.time === "number" ? marker.time : Date.parse(String(marker.time));
2902
+ if (!Number.isFinite(ms)) continue;
2903
+ const index = indexForTime(ms);
2904
+ if (index === null || index < visibleStart || index > visibleEnd) continue;
2905
+ const side = marker.side === "buy" ? "buy" : "sell";
2906
+ const qty = Math.max(0, marker.qty ?? 0);
2907
+ const weight = qty > 0 ? qty : 1;
2908
+ const key = `${index}:${side}`;
2909
+ const existing = groups.get(key);
2910
+ if (existing) {
2911
+ existing.qty += qty;
2912
+ existing.priceQty += marker.price * weight;
2913
+ } else {
2914
+ groups.set(key, {
2915
+ index,
2916
+ side,
2917
+ qty,
2918
+ priceQty: marker.price * weight,
2919
+ ...marker.color === void 0 ? {} : { color: marker.color },
2920
+ ...marker.textColor === void 0 ? {} : { textColor: marker.textColor },
2921
+ ...marker.text === void 0 ? {} : { text: marker.text }
2922
+ });
2923
+ }
2924
+ }
2890
2925
  const prevFont = ctx.font;
2891
2926
  const prevAlign = ctx.textAlign;
2892
2927
  const prevBaseline = ctx.textBaseline;
@@ -2897,42 +2932,29 @@ function createChart(element, options = {}) {
2897
2932
  const arrowH = 8;
2898
2933
  const gap = 5;
2899
2934
  const labelOffset = arrowH + 8;
2900
- for (const marker of tradeMarkers) {
2901
- const ms = typeof marker.time === "number" ? marker.time : Date.parse(String(marker.time));
2902
- if (!Number.isFinite(ms)) continue;
2903
- const index = indexForTime(ms);
2904
- if (index === null || index < visibleStart || index > visibleEnd) continue;
2905
- const bar = data[index];
2935
+ for (const group of groups.values()) {
2936
+ const bar = data[group.index];
2906
2937
  if (!bar) continue;
2907
- const x = chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
2938
+ const x = chartLeft + (group.index + 0.5 - xStart) / xSpan * chartWidth;
2908
2939
  if (x < chartLeft - 20 || x > chartRight + 20) continue;
2909
- const isBuy = marker.side === "buy";
2910
- const arrowColor = marker.color ?? (isBuy ? "#2962ff" : "#f23645");
2911
- const textColor = marker.textColor ?? "#d1d4dc";
2912
- const label = marker.text ?? `${marker.qty ?? ""}${marker.qty != null ? " @ " : ""}${formatPrice(marker.price)}`.trim();
2940
+ const isBuy = group.side === "buy";
2941
+ const arrowColor = group.color ?? (isBuy ? "#2962ff" : "#f23645");
2942
+ const textColor = group.textColor ?? "#d1d4dc";
2943
+ const weightTotal = group.qty > 0 ? group.qty : 1;
2944
+ const avgPrice = group.priceQty / weightTotal;
2945
+ const label = group.text ?? `${group.qty > 0 ? `${group.qty} @ ` : ""}${formatPrice(avgPrice)}`.trim();
2913
2946
  ctx.save();
2914
2947
  ctx.fillStyle = arrowColor;
2915
- if (isBuy) {
2916
- const tipY = yFromPrice(bar.l) + gap;
2917
- ctx.beginPath();
2918
- ctx.moveTo(x, tipY);
2919
- ctx.lineTo(x - arrowHalf, tipY + arrowH);
2920
- ctx.lineTo(x + arrowHalf, tipY + arrowH);
2921
- ctx.closePath();
2922
- ctx.fill();
2923
- ctx.fillStyle = textColor;
2924
- ctx.fillText(label, Math.round(x), Math.round(tipY + labelOffset));
2925
- } else {
2926
- const tipY = yFromPrice(bar.h) - gap;
2927
- ctx.beginPath();
2928
- ctx.moveTo(x, tipY);
2929
- ctx.lineTo(x - arrowHalf, tipY - arrowH);
2930
- ctx.lineTo(x + arrowHalf, tipY - arrowH);
2931
- ctx.closePath();
2932
- ctx.fill();
2933
- ctx.fillStyle = textColor;
2934
- ctx.fillText(label, Math.round(x), Math.round(tipY - labelOffset));
2935
- }
2948
+ const tipY = isBuy ? yFromPrice(bar.l) + gap : yFromPrice(bar.h) - gap;
2949
+ const dir = isBuy ? 1 : -1;
2950
+ ctx.beginPath();
2951
+ ctx.moveTo(x, tipY);
2952
+ ctx.lineTo(x - arrowHalf, tipY + arrowH * dir);
2953
+ ctx.lineTo(x + arrowHalf, tipY + arrowH * dir);
2954
+ ctx.closePath();
2955
+ ctx.fill();
2956
+ ctx.fillStyle = textColor;
2957
+ ctx.fillText(label, Math.round(x), Math.round(tipY + labelOffset * dir));
2936
2958
  ctx.restore();
2937
2959
  }
2938
2960
  ctx.font = prevFont;
@@ -3393,16 +3415,18 @@ function createChart(element, options = {}) {
3393
3415
  ctx.font = prevFont;
3394
3416
  }
3395
3417
  }
3418
+ const axisStepMs = getTimeStepMs();
3419
+ const axisIntraday = axisStepMs > 0 && axisStepMs < 24 * 60 * 60 * 1e3;
3420
+ let prevAxisTickTime = null;
3396
3421
  for (let index = tickStartIndex; index <= visibleTickEnd; index += xStep) {
3397
3422
  const tickTime = getTimeForIndex(index);
3398
3423
  if (!tickTime) {
3399
3424
  continue;
3400
3425
  }
3401
3426
  const x = chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
3402
- const timeLabel = tickTime.toLocaleDateString(void 0, {
3403
- month: "short",
3404
- day: "numeric"
3405
- });
3427
+ const isNewDay = !prevAxisTickTime || prevAxisTickTime.getDate() !== tickTime.getDate() || prevAxisTickTime.getMonth() !== tickTime.getMonth() || prevAxisTickTime.getFullYear() !== tickTime.getFullYear();
3428
+ const timeLabel = axisIntraday && !isNewDay ? tickTime.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit", hour12: false }) : tickTime.toLocaleDateString(void 0, { month: "short", day: "numeric" });
3429
+ prevAxisTickTime = tickTime;
3406
3430
  const prevFont = ctx.font;
3407
3431
  ctx.font = `${xAxisFontSize}px ${mergedOptions.fontFamily}`;
3408
3432
  drawText(timeLabel, x, fullChartBottom + 8, "center", "top", xAxis.textColor);
package/dist/index.cjs CHANGED
@@ -1292,10 +1292,19 @@ function createChart(element, options = {}) {
1292
1292
  }
1293
1293
  return Math.round(price / tickSize) * tickSize;
1294
1294
  };
1295
+ const groupThousands = (value, decimals) => {
1296
+ const negative = value < 0;
1297
+ const fixed = Math.abs(value).toFixed(decimals);
1298
+ const dot = fixed.indexOf(".");
1299
+ const intPart = dot === -1 ? fixed : fixed.slice(0, dot);
1300
+ const decPart = dot === -1 ? "" : fixed.slice(dot);
1301
+ const grouped = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1302
+ return `${negative ? "-" : ""}${grouped}${decPart}`;
1303
+ };
1295
1304
  const formatPrice = (price) => {
1296
1305
  const rounded = quantizeToTickSize(price);
1297
1306
  const decimals = getDisplayPriceDecimals();
1298
- return rounded.toFixed(decimals);
1307
+ return groupThousands(rounded, decimals);
1299
1308
  };
1300
1309
  const roundToPricePrecision = (price) => {
1301
1310
  const rounded = quantizeToTickSize(price);
@@ -1333,7 +1342,7 @@ function createChart(element, options = {}) {
1333
1342
  }
1334
1343
  const observedDigits = maxAbsPrice >= 1 ? Math.floor(Math.log10(maxAbsPrice)) + 1 : 1;
1335
1344
  const integerDigits = Math.max(configuredDigits, observedDigits);
1336
- const integerPart = "8".repeat(integerDigits);
1345
+ const integerPart = "8".repeat(integerDigits).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1337
1346
  const decimalPart = decimals > 0 ? `.${"8".repeat(decimals)}` : "";
1338
1347
  return `${integerPart}${decimalPart}`;
1339
1348
  };
@@ -2913,6 +2922,32 @@ function createChart(element, options = {}) {
2913
2922
  }
2914
2923
  return lo;
2915
2924
  };
2925
+ const groups = /* @__PURE__ */ new Map();
2926
+ for (const marker of tradeMarkers) {
2927
+ const ms = typeof marker.time === "number" ? marker.time : Date.parse(String(marker.time));
2928
+ if (!Number.isFinite(ms)) continue;
2929
+ const index = indexForTime(ms);
2930
+ if (index === null || index < visibleStart || index > visibleEnd) continue;
2931
+ const side = marker.side === "buy" ? "buy" : "sell";
2932
+ const qty = Math.max(0, marker.qty ?? 0);
2933
+ const weight = qty > 0 ? qty : 1;
2934
+ const key = `${index}:${side}`;
2935
+ const existing = groups.get(key);
2936
+ if (existing) {
2937
+ existing.qty += qty;
2938
+ existing.priceQty += marker.price * weight;
2939
+ } else {
2940
+ groups.set(key, {
2941
+ index,
2942
+ side,
2943
+ qty,
2944
+ priceQty: marker.price * weight,
2945
+ ...marker.color === void 0 ? {} : { color: marker.color },
2946
+ ...marker.textColor === void 0 ? {} : { textColor: marker.textColor },
2947
+ ...marker.text === void 0 ? {} : { text: marker.text }
2948
+ });
2949
+ }
2950
+ }
2916
2951
  const prevFont = ctx.font;
2917
2952
  const prevAlign = ctx.textAlign;
2918
2953
  const prevBaseline = ctx.textBaseline;
@@ -2923,42 +2958,29 @@ function createChart(element, options = {}) {
2923
2958
  const arrowH = 8;
2924
2959
  const gap = 5;
2925
2960
  const labelOffset = arrowH + 8;
2926
- for (const marker of tradeMarkers) {
2927
- const ms = typeof marker.time === "number" ? marker.time : Date.parse(String(marker.time));
2928
- if (!Number.isFinite(ms)) continue;
2929
- const index = indexForTime(ms);
2930
- if (index === null || index < visibleStart || index > visibleEnd) continue;
2931
- const bar = data[index];
2961
+ for (const group of groups.values()) {
2962
+ const bar = data[group.index];
2932
2963
  if (!bar) continue;
2933
- const x = chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
2964
+ const x = chartLeft + (group.index + 0.5 - xStart) / xSpan * chartWidth;
2934
2965
  if (x < chartLeft - 20 || x > chartRight + 20) continue;
2935
- const isBuy = marker.side === "buy";
2936
- const arrowColor = marker.color ?? (isBuy ? "#2962ff" : "#f23645");
2937
- const textColor = marker.textColor ?? "#d1d4dc";
2938
- const label = marker.text ?? `${marker.qty ?? ""}${marker.qty != null ? " @ " : ""}${formatPrice(marker.price)}`.trim();
2966
+ const isBuy = group.side === "buy";
2967
+ const arrowColor = group.color ?? (isBuy ? "#2962ff" : "#f23645");
2968
+ const textColor = group.textColor ?? "#d1d4dc";
2969
+ const weightTotal = group.qty > 0 ? group.qty : 1;
2970
+ const avgPrice = group.priceQty / weightTotal;
2971
+ const label = group.text ?? `${group.qty > 0 ? `${group.qty} @ ` : ""}${formatPrice(avgPrice)}`.trim();
2939
2972
  ctx.save();
2940
2973
  ctx.fillStyle = arrowColor;
2941
- if (isBuy) {
2942
- const tipY = yFromPrice(bar.l) + gap;
2943
- ctx.beginPath();
2944
- ctx.moveTo(x, tipY);
2945
- ctx.lineTo(x - arrowHalf, tipY + arrowH);
2946
- ctx.lineTo(x + arrowHalf, tipY + arrowH);
2947
- ctx.closePath();
2948
- ctx.fill();
2949
- ctx.fillStyle = textColor;
2950
- ctx.fillText(label, Math.round(x), Math.round(tipY + labelOffset));
2951
- } else {
2952
- const tipY = yFromPrice(bar.h) - gap;
2953
- ctx.beginPath();
2954
- ctx.moveTo(x, tipY);
2955
- ctx.lineTo(x - arrowHalf, tipY - arrowH);
2956
- ctx.lineTo(x + arrowHalf, tipY - arrowH);
2957
- ctx.closePath();
2958
- ctx.fill();
2959
- ctx.fillStyle = textColor;
2960
- ctx.fillText(label, Math.round(x), Math.round(tipY - labelOffset));
2961
- }
2974
+ const tipY = isBuy ? yFromPrice(bar.l) + gap : yFromPrice(bar.h) - gap;
2975
+ const dir = isBuy ? 1 : -1;
2976
+ ctx.beginPath();
2977
+ ctx.moveTo(x, tipY);
2978
+ ctx.lineTo(x - arrowHalf, tipY + arrowH * dir);
2979
+ ctx.lineTo(x + arrowHalf, tipY + arrowH * dir);
2980
+ ctx.closePath();
2981
+ ctx.fill();
2982
+ ctx.fillStyle = textColor;
2983
+ ctx.fillText(label, Math.round(x), Math.round(tipY + labelOffset * dir));
2962
2984
  ctx.restore();
2963
2985
  }
2964
2986
  ctx.font = prevFont;
@@ -3419,16 +3441,18 @@ function createChart(element, options = {}) {
3419
3441
  ctx.font = prevFont;
3420
3442
  }
3421
3443
  }
3444
+ const axisStepMs = getTimeStepMs();
3445
+ const axisIntraday = axisStepMs > 0 && axisStepMs < 24 * 60 * 60 * 1e3;
3446
+ let prevAxisTickTime = null;
3422
3447
  for (let index = tickStartIndex; index <= visibleTickEnd; index += xStep) {
3423
3448
  const tickTime = getTimeForIndex(index);
3424
3449
  if (!tickTime) {
3425
3450
  continue;
3426
3451
  }
3427
3452
  const x = chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
3428
- const timeLabel = tickTime.toLocaleDateString(void 0, {
3429
- month: "short",
3430
- day: "numeric"
3431
- });
3453
+ const isNewDay = !prevAxisTickTime || prevAxisTickTime.getDate() !== tickTime.getDate() || prevAxisTickTime.getMonth() !== tickTime.getMonth() || prevAxisTickTime.getFullYear() !== tickTime.getFullYear();
3454
+ const timeLabel = axisIntraday && !isNewDay ? tickTime.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit", hour12: false }) : tickTime.toLocaleDateString(void 0, { month: "short", day: "numeric" });
3455
+ prevAxisTickTime = tickTime;
3432
3456
  const prevFont = ctx.font;
3433
3457
  ctx.font = `${xAxisFontSize}px ${mergedOptions.fontFamily}`;
3434
3458
  drawText(timeLabel, x, fullChartBottom + 8, "center", "top", xAxis.textColor);
package/dist/index.js CHANGED
@@ -1266,10 +1266,19 @@ function createChart(element, options = {}) {
1266
1266
  }
1267
1267
  return Math.round(price / tickSize) * tickSize;
1268
1268
  };
1269
+ const groupThousands = (value, decimals) => {
1270
+ const negative = value < 0;
1271
+ const fixed = Math.abs(value).toFixed(decimals);
1272
+ const dot = fixed.indexOf(".");
1273
+ const intPart = dot === -1 ? fixed : fixed.slice(0, dot);
1274
+ const decPart = dot === -1 ? "" : fixed.slice(dot);
1275
+ const grouped = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1276
+ return `${negative ? "-" : ""}${grouped}${decPart}`;
1277
+ };
1269
1278
  const formatPrice = (price) => {
1270
1279
  const rounded = quantizeToTickSize(price);
1271
1280
  const decimals = getDisplayPriceDecimals();
1272
- return rounded.toFixed(decimals);
1281
+ return groupThousands(rounded, decimals);
1273
1282
  };
1274
1283
  const roundToPricePrecision = (price) => {
1275
1284
  const rounded = quantizeToTickSize(price);
@@ -1307,7 +1316,7 @@ function createChart(element, options = {}) {
1307
1316
  }
1308
1317
  const observedDigits = maxAbsPrice >= 1 ? Math.floor(Math.log10(maxAbsPrice)) + 1 : 1;
1309
1318
  const integerDigits = Math.max(configuredDigits, observedDigits);
1310
- const integerPart = "8".repeat(integerDigits);
1319
+ const integerPart = "8".repeat(integerDigits).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1311
1320
  const decimalPart = decimals > 0 ? `.${"8".repeat(decimals)}` : "";
1312
1321
  return `${integerPart}${decimalPart}`;
1313
1322
  };
@@ -2887,6 +2896,32 @@ function createChart(element, options = {}) {
2887
2896
  }
2888
2897
  return lo;
2889
2898
  };
2899
+ const groups = /* @__PURE__ */ new Map();
2900
+ for (const marker of tradeMarkers) {
2901
+ const ms = typeof marker.time === "number" ? marker.time : Date.parse(String(marker.time));
2902
+ if (!Number.isFinite(ms)) continue;
2903
+ const index = indexForTime(ms);
2904
+ if (index === null || index < visibleStart || index > visibleEnd) continue;
2905
+ const side = marker.side === "buy" ? "buy" : "sell";
2906
+ const qty = Math.max(0, marker.qty ?? 0);
2907
+ const weight = qty > 0 ? qty : 1;
2908
+ const key = `${index}:${side}`;
2909
+ const existing = groups.get(key);
2910
+ if (existing) {
2911
+ existing.qty += qty;
2912
+ existing.priceQty += marker.price * weight;
2913
+ } else {
2914
+ groups.set(key, {
2915
+ index,
2916
+ side,
2917
+ qty,
2918
+ priceQty: marker.price * weight,
2919
+ ...marker.color === void 0 ? {} : { color: marker.color },
2920
+ ...marker.textColor === void 0 ? {} : { textColor: marker.textColor },
2921
+ ...marker.text === void 0 ? {} : { text: marker.text }
2922
+ });
2923
+ }
2924
+ }
2890
2925
  const prevFont = ctx.font;
2891
2926
  const prevAlign = ctx.textAlign;
2892
2927
  const prevBaseline = ctx.textBaseline;
@@ -2897,42 +2932,29 @@ function createChart(element, options = {}) {
2897
2932
  const arrowH = 8;
2898
2933
  const gap = 5;
2899
2934
  const labelOffset = arrowH + 8;
2900
- for (const marker of tradeMarkers) {
2901
- const ms = typeof marker.time === "number" ? marker.time : Date.parse(String(marker.time));
2902
- if (!Number.isFinite(ms)) continue;
2903
- const index = indexForTime(ms);
2904
- if (index === null || index < visibleStart || index > visibleEnd) continue;
2905
- const bar = data[index];
2935
+ for (const group of groups.values()) {
2936
+ const bar = data[group.index];
2906
2937
  if (!bar) continue;
2907
- const x = chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
2938
+ const x = chartLeft + (group.index + 0.5 - xStart) / xSpan * chartWidth;
2908
2939
  if (x < chartLeft - 20 || x > chartRight + 20) continue;
2909
- const isBuy = marker.side === "buy";
2910
- const arrowColor = marker.color ?? (isBuy ? "#2962ff" : "#f23645");
2911
- const textColor = marker.textColor ?? "#d1d4dc";
2912
- const label = marker.text ?? `${marker.qty ?? ""}${marker.qty != null ? " @ " : ""}${formatPrice(marker.price)}`.trim();
2940
+ const isBuy = group.side === "buy";
2941
+ const arrowColor = group.color ?? (isBuy ? "#2962ff" : "#f23645");
2942
+ const textColor = group.textColor ?? "#d1d4dc";
2943
+ const weightTotal = group.qty > 0 ? group.qty : 1;
2944
+ const avgPrice = group.priceQty / weightTotal;
2945
+ const label = group.text ?? `${group.qty > 0 ? `${group.qty} @ ` : ""}${formatPrice(avgPrice)}`.trim();
2913
2946
  ctx.save();
2914
2947
  ctx.fillStyle = arrowColor;
2915
- if (isBuy) {
2916
- const tipY = yFromPrice(bar.l) + gap;
2917
- ctx.beginPath();
2918
- ctx.moveTo(x, tipY);
2919
- ctx.lineTo(x - arrowHalf, tipY + arrowH);
2920
- ctx.lineTo(x + arrowHalf, tipY + arrowH);
2921
- ctx.closePath();
2922
- ctx.fill();
2923
- ctx.fillStyle = textColor;
2924
- ctx.fillText(label, Math.round(x), Math.round(tipY + labelOffset));
2925
- } else {
2926
- const tipY = yFromPrice(bar.h) - gap;
2927
- ctx.beginPath();
2928
- ctx.moveTo(x, tipY);
2929
- ctx.lineTo(x - arrowHalf, tipY - arrowH);
2930
- ctx.lineTo(x + arrowHalf, tipY - arrowH);
2931
- ctx.closePath();
2932
- ctx.fill();
2933
- ctx.fillStyle = textColor;
2934
- ctx.fillText(label, Math.round(x), Math.round(tipY - labelOffset));
2935
- }
2948
+ const tipY = isBuy ? yFromPrice(bar.l) + gap : yFromPrice(bar.h) - gap;
2949
+ const dir = isBuy ? 1 : -1;
2950
+ ctx.beginPath();
2951
+ ctx.moveTo(x, tipY);
2952
+ ctx.lineTo(x - arrowHalf, tipY + arrowH * dir);
2953
+ ctx.lineTo(x + arrowHalf, tipY + arrowH * dir);
2954
+ ctx.closePath();
2955
+ ctx.fill();
2956
+ ctx.fillStyle = textColor;
2957
+ ctx.fillText(label, Math.round(x), Math.round(tipY + labelOffset * dir));
2936
2958
  ctx.restore();
2937
2959
  }
2938
2960
  ctx.font = prevFont;
@@ -3393,16 +3415,18 @@ function createChart(element, options = {}) {
3393
3415
  ctx.font = prevFont;
3394
3416
  }
3395
3417
  }
3418
+ const axisStepMs = getTimeStepMs();
3419
+ const axisIntraday = axisStepMs > 0 && axisStepMs < 24 * 60 * 60 * 1e3;
3420
+ let prevAxisTickTime = null;
3396
3421
  for (let index = tickStartIndex; index <= visibleTickEnd; index += xStep) {
3397
3422
  const tickTime = getTimeForIndex(index);
3398
3423
  if (!tickTime) {
3399
3424
  continue;
3400
3425
  }
3401
3426
  const x = chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
3402
- const timeLabel = tickTime.toLocaleDateString(void 0, {
3403
- month: "short",
3404
- day: "numeric"
3405
- });
3427
+ const isNewDay = !prevAxisTickTime || prevAxisTickTime.getDate() !== tickTime.getDate() || prevAxisTickTime.getMonth() !== tickTime.getMonth() || prevAxisTickTime.getFullYear() !== tickTime.getFullYear();
3428
+ const timeLabel = axisIntraday && !isNewDay ? tickTime.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit", hour12: false }) : tickTime.toLocaleDateString(void 0, { month: "short", day: "numeric" });
3429
+ prevAxisTickTime = tickTime;
3406
3430
  const prevFont = ctx.font;
3407
3431
  ctx.font = `${xAxisFontSize}px ${mergedOptions.fontFamily}`;
3408
3432
  drawText(timeLabel, x, fullChartBottom + 8, "center", "top", xAxis.textColor);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.102",
3
+ "version": "0.1.104",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",