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.
- package/dist/hyperprop-charting-library.cjs +62 -38
- package/dist/hyperprop-charting-library.js +62 -38
- package/dist/index.cjs +62 -38
- package/dist/index.js +62 -38
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
2927
|
-
const
|
|
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 =
|
|
2936
|
-
const arrowColor =
|
|
2937
|
-
const textColor =
|
|
2938
|
-
const
|
|
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
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
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
|
|
3429
|
-
|
|
3430
|
-
|
|
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
|
|
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
|
|
2901
|
-
const
|
|
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 =
|
|
2910
|
-
const arrowColor =
|
|
2911
|
-
const textColor =
|
|
2912
|
-
const
|
|
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
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
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
|
|
3403
|
-
|
|
3404
|
-
|
|
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
|
|
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
|
|
2927
|
-
const
|
|
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 =
|
|
2936
|
-
const arrowColor =
|
|
2937
|
-
const textColor =
|
|
2938
|
-
const
|
|
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
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
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
|
|
3429
|
-
|
|
3430
|
-
|
|
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
|
|
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
|
|
2901
|
-
const
|
|
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 =
|
|
2910
|
-
const arrowColor =
|
|
2911
|
-
const textColor =
|
|
2912
|
-
const
|
|
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
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
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
|
|
3403
|
-
|
|
3404
|
-
|
|
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);
|