@dcg-overseas/number-line 0.1.7 → 0.1.8
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.cjs +47 -35
- package/dist/index.js +47 -35
- package/package.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -489,6 +489,32 @@ function computeLabelStep(containerWidth, range) {
|
|
|
489
489
|
const candidates = [1, 2, 5, 10, 20, 25, 50, 100, 200, 500];
|
|
490
490
|
return candidates.find((c) => c >= raw) ?? candidates[candidates.length - 1];
|
|
491
491
|
}
|
|
492
|
+
function computeHorizontalLayout(minVal, maxVal) {
|
|
493
|
+
const gutter = 16;
|
|
494
|
+
const charWidth = 8;
|
|
495
|
+
const leftLabel = String(Math.round(minVal));
|
|
496
|
+
const rightLabel = String(Math.round(maxVal));
|
|
497
|
+
return {
|
|
498
|
+
padLeft: gutter,
|
|
499
|
+
padRight: gutter,
|
|
500
|
+
labelInsetLeft: Math.max(8, Math.ceil(leftLabel.length * charWidth / 2) + 2),
|
|
501
|
+
labelInsetRight: Math.max(12, Math.ceil(rightLabel.length * charWidth / 2) + 4)
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function createValueToX(viewMin, viewMax, viewWidth, layout) {
|
|
505
|
+
const range = Math.max(viewMax - viewMin, 1e-6);
|
|
506
|
+
const usable = Math.max(viewWidth - layout.padLeft - layout.padRight, 1);
|
|
507
|
+
const innerUsable = Math.max(
|
|
508
|
+
usable - layout.labelInsetLeft - layout.labelInsetRight,
|
|
509
|
+
1
|
|
510
|
+
);
|
|
511
|
+
return (value) => layout.padLeft + layout.labelInsetLeft + (value - viewMin) / range * innerUsable;
|
|
512
|
+
}
|
|
513
|
+
function edgeTextAnchor(value, domainMin, domainMax) {
|
|
514
|
+
if (Math.abs(value - domainMin) < 1e-9) return "start";
|
|
515
|
+
if (Math.abs(value - domainMax) < 1e-9) return "end";
|
|
516
|
+
return "middle";
|
|
517
|
+
}
|
|
492
518
|
const ARC_COLORS = [
|
|
493
519
|
"#7c3aed",
|
|
494
520
|
"#0891b2",
|
|
@@ -504,11 +530,11 @@ function gcd(a, b) {
|
|
|
504
530
|
}
|
|
505
531
|
const AxisLayer = React.memo(function AxisLayer2({
|
|
506
532
|
min,
|
|
533
|
+
max,
|
|
507
534
|
viewMin,
|
|
508
535
|
viewMax,
|
|
509
536
|
axisY,
|
|
510
|
-
|
|
511
|
-
padRight,
|
|
537
|
+
horizontalLayout,
|
|
512
538
|
viewWidth,
|
|
513
539
|
containerWidth,
|
|
514
540
|
groupSize,
|
|
@@ -520,8 +546,9 @@ const AxisLayer = React.memo(function AxisLayer2({
|
|
|
520
546
|
}) {
|
|
521
547
|
const range = viewMax - viewMin;
|
|
522
548
|
if (range <= 0) return null;
|
|
523
|
-
const
|
|
524
|
-
const
|
|
549
|
+
const toX = createValueToX(viewMin, viewMax, viewWidth, horizontalLayout);
|
|
550
|
+
const axisStartX = toX(viewMin);
|
|
551
|
+
const axisEndX = toX(viewMax);
|
|
525
552
|
const step = Math.max(1, Math.round(tickStep));
|
|
526
553
|
const rawLabelStep = computeLabelStep(containerWidth, range);
|
|
527
554
|
const labelStep = Math.max(step, Math.ceil(rawLabelStep / step) * step);
|
|
@@ -538,9 +565,9 @@ const AxisLayer = React.memo(function AxisLayer2({
|
|
|
538
565
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
539
566
|
"line",
|
|
540
567
|
{
|
|
541
|
-
x1:
|
|
568
|
+
x1: axisStartX - 4,
|
|
542
569
|
y1: axisY,
|
|
543
|
-
x2:
|
|
570
|
+
x2: axisEndX,
|
|
544
571
|
y2: axisY,
|
|
545
572
|
stroke: "#374151",
|
|
546
573
|
strokeWidth: 2
|
|
@@ -577,7 +604,7 @@ const AxisLayer = React.memo(function AxisLayer2({
|
|
|
577
604
|
{
|
|
578
605
|
x,
|
|
579
606
|
y: axisY + 22,
|
|
580
|
-
textAnchor:
|
|
607
|
+
textAnchor: edgeTextAnchor(v, min, max),
|
|
581
608
|
fontSize: 11,
|
|
582
609
|
fill: "#374151",
|
|
583
610
|
pointerEvents: "none",
|
|
@@ -597,8 +624,7 @@ const GroupArcsLayer = React.memo(function GroupArcsLayer2({
|
|
|
597
624
|
groupSize,
|
|
598
625
|
groupCount,
|
|
599
626
|
axisY,
|
|
600
|
-
|
|
601
|
-
padRight,
|
|
627
|
+
horizontalLayout,
|
|
602
628
|
viewWidth,
|
|
603
629
|
arcMaxHeight,
|
|
604
630
|
tool,
|
|
@@ -608,10 +634,7 @@ const GroupArcsLayer = React.memo(function GroupArcsLayer2({
|
|
|
608
634
|
arcColors = ARC_COLORS
|
|
609
635
|
}) {
|
|
610
636
|
if (groupSize <= 0 || groupCount <= 0) return null;
|
|
611
|
-
const
|
|
612
|
-
const usable = viewWidth - padLeft - padRight;
|
|
613
|
-
const toX = (v) => padLeft + (v - viewMin) / range * usable;
|
|
614
|
-
const clipId = React.useId().replace(/:/g, "_") + "-arcs-clip";
|
|
637
|
+
const toX = createValueToX(viewMin, viewMax, viewWidth, horizontalLayout);
|
|
615
638
|
const arcs = [];
|
|
616
639
|
for (let g = 0; g < groupCount; g++) {
|
|
617
640
|
if (deletedArcIndices.has(g)) continue;
|
|
@@ -677,7 +700,7 @@ const GroupArcsLayer = React.memo(function GroupArcsLayer2({
|
|
|
677
700
|
{
|
|
678
701
|
x: x2,
|
|
679
702
|
y: textY,
|
|
680
|
-
textAnchor:
|
|
703
|
+
textAnchor: edgeTextAnchor(end, min, max),
|
|
681
704
|
fontSize: 10,
|
|
682
705
|
fill: isSelected ? "#1d4ed8" : color,
|
|
683
706
|
fontWeight: "600",
|
|
@@ -709,18 +732,7 @@ const GroupArcsLayer = React.memo(function GroupArcsLayer2({
|
|
|
709
732
|
] }, g)
|
|
710
733
|
);
|
|
711
734
|
}
|
|
712
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
713
|
-
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsx("clipPath", { id: clipId, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
714
|
-
"rect",
|
|
715
|
-
{
|
|
716
|
-
x: padLeft,
|
|
717
|
-
y: 0,
|
|
718
|
-
width: usable,
|
|
719
|
-
height: axisY + 1
|
|
720
|
-
}
|
|
721
|
-
) }) }),
|
|
722
|
-
/* @__PURE__ */ jsxRuntime.jsx("g", { clipPath: `url(#${clipId})`, children: arcs })
|
|
723
|
-
] });
|
|
735
|
+
return /* @__PURE__ */ jsxRuntime.jsx("g", { className: "nl-arcs-layer", children: arcs });
|
|
724
736
|
});
|
|
725
737
|
function DrawingLayer({ strokes, liveStroke, tool, width, height, onStrokeClick }) {
|
|
726
738
|
return /* @__PURE__ */ jsxRuntime.jsxs("g", { className: "nl-drawing-layer", transform: `scale(${width} ${height})`, children: [
|
|
@@ -788,9 +800,6 @@ function DrawingLayer({ strokes, liveStroke, tool, width, height, onStrokeClick
|
|
|
788
800
|
)
|
|
789
801
|
] });
|
|
790
802
|
}
|
|
791
|
-
const PAD_LEFT_LEGACY = 30;
|
|
792
|
-
const PAD_RIGHT_LEGACY = 40;
|
|
793
|
-
const PAD_X = (PAD_LEFT_LEGACY + PAD_RIGHT_LEGACY) / 2;
|
|
794
803
|
const LABEL_SPACE_BELOW_AXIS = 42;
|
|
795
804
|
const LABEL_ABOVE_AXIS_SLOP = 32;
|
|
796
805
|
const ARC_TOP_PADDING = 16;
|
|
@@ -829,8 +838,12 @@ function NumberLine({ className }) {
|
|
|
829
838
|
} = useNumberLineContext();
|
|
830
839
|
const w = containerWidth;
|
|
831
840
|
const h = containerHeight;
|
|
841
|
+
const horizontalLayout = computeHorizontalLayout(min, max);
|
|
832
842
|
const range = Math.max(viewMax - viewMin, 1e-6);
|
|
833
|
-
const usable = Math.max(
|
|
843
|
+
const usable = Math.max(
|
|
844
|
+
w - horizontalLayout.padLeft - horizontalLayout.padRight - horizontalLayout.labelInsetLeft - horizontalLayout.labelInsetRight,
|
|
845
|
+
1
|
|
846
|
+
);
|
|
834
847
|
let ryTypical = MIN_ARC_HEIGHT * 0.5;
|
|
835
848
|
if (groupSize > 0 && groupCount > 0) {
|
|
836
849
|
const rx = usable * groupSize / range / 2;
|
|
@@ -986,7 +999,8 @@ function NumberLine({ className }) {
|
|
|
986
999
|
display: "block",
|
|
987
1000
|
outline: enableZoom && isZoomActive ? "2px solid rgba(59, 130, 246, 0.5)" : "none",
|
|
988
1001
|
cursor: svgCursor,
|
|
989
|
-
touchAction: "none"
|
|
1002
|
+
touchAction: "none",
|
|
1003
|
+
overflow: "visible"
|
|
990
1004
|
},
|
|
991
1005
|
onPointerDown: handlePointerDown,
|
|
992
1006
|
onPointerMove: handlePointerMove,
|
|
@@ -1006,8 +1020,7 @@ function NumberLine({ className }) {
|
|
|
1006
1020
|
viewMin,
|
|
1007
1021
|
viewMax,
|
|
1008
1022
|
axisY,
|
|
1009
|
-
|
|
1010
|
-
padRight: PAD_X,
|
|
1023
|
+
horizontalLayout,
|
|
1011
1024
|
viewWidth: w,
|
|
1012
1025
|
containerWidth: w,
|
|
1013
1026
|
groupSize,
|
|
@@ -1028,8 +1041,7 @@ function NumberLine({ className }) {
|
|
|
1028
1041
|
groupSize,
|
|
1029
1042
|
groupCount,
|
|
1030
1043
|
axisY,
|
|
1031
|
-
|
|
1032
|
-
padRight: PAD_X,
|
|
1044
|
+
horizontalLayout,
|
|
1033
1045
|
viewWidth: w,
|
|
1034
1046
|
arcMaxHeight,
|
|
1035
1047
|
tool,
|
package/dist/index.js
CHANGED
|
@@ -487,6 +487,32 @@ function computeLabelStep(containerWidth, range) {
|
|
|
487
487
|
const candidates = [1, 2, 5, 10, 20, 25, 50, 100, 200, 500];
|
|
488
488
|
return candidates.find((c) => c >= raw) ?? candidates[candidates.length - 1];
|
|
489
489
|
}
|
|
490
|
+
function computeHorizontalLayout(minVal, maxVal) {
|
|
491
|
+
const gutter = 16;
|
|
492
|
+
const charWidth = 8;
|
|
493
|
+
const leftLabel = String(Math.round(minVal));
|
|
494
|
+
const rightLabel = String(Math.round(maxVal));
|
|
495
|
+
return {
|
|
496
|
+
padLeft: gutter,
|
|
497
|
+
padRight: gutter,
|
|
498
|
+
labelInsetLeft: Math.max(8, Math.ceil(leftLabel.length * charWidth / 2) + 2),
|
|
499
|
+
labelInsetRight: Math.max(12, Math.ceil(rightLabel.length * charWidth / 2) + 4)
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
function createValueToX(viewMin, viewMax, viewWidth, layout) {
|
|
503
|
+
const range = Math.max(viewMax - viewMin, 1e-6);
|
|
504
|
+
const usable = Math.max(viewWidth - layout.padLeft - layout.padRight, 1);
|
|
505
|
+
const innerUsable = Math.max(
|
|
506
|
+
usable - layout.labelInsetLeft - layout.labelInsetRight,
|
|
507
|
+
1
|
|
508
|
+
);
|
|
509
|
+
return (value) => layout.padLeft + layout.labelInsetLeft + (value - viewMin) / range * innerUsable;
|
|
510
|
+
}
|
|
511
|
+
function edgeTextAnchor(value, domainMin, domainMax) {
|
|
512
|
+
if (Math.abs(value - domainMin) < 1e-9) return "start";
|
|
513
|
+
if (Math.abs(value - domainMax) < 1e-9) return "end";
|
|
514
|
+
return "middle";
|
|
515
|
+
}
|
|
490
516
|
const ARC_COLORS = [
|
|
491
517
|
"#7c3aed",
|
|
492
518
|
"#0891b2",
|
|
@@ -502,11 +528,11 @@ function gcd(a, b) {
|
|
|
502
528
|
}
|
|
503
529
|
const AxisLayer = React.memo(function AxisLayer2({
|
|
504
530
|
min,
|
|
531
|
+
max,
|
|
505
532
|
viewMin,
|
|
506
533
|
viewMax,
|
|
507
534
|
axisY,
|
|
508
|
-
|
|
509
|
-
padRight,
|
|
535
|
+
horizontalLayout,
|
|
510
536
|
viewWidth,
|
|
511
537
|
containerWidth,
|
|
512
538
|
groupSize,
|
|
@@ -518,8 +544,9 @@ const AxisLayer = React.memo(function AxisLayer2({
|
|
|
518
544
|
}) {
|
|
519
545
|
const range = viewMax - viewMin;
|
|
520
546
|
if (range <= 0) return null;
|
|
521
|
-
const
|
|
522
|
-
const
|
|
547
|
+
const toX = createValueToX(viewMin, viewMax, viewWidth, horizontalLayout);
|
|
548
|
+
const axisStartX = toX(viewMin);
|
|
549
|
+
const axisEndX = toX(viewMax);
|
|
523
550
|
const step = Math.max(1, Math.round(tickStep));
|
|
524
551
|
const rawLabelStep = computeLabelStep(containerWidth, range);
|
|
525
552
|
const labelStep = Math.max(step, Math.ceil(rawLabelStep / step) * step);
|
|
@@ -536,9 +563,9 @@ const AxisLayer = React.memo(function AxisLayer2({
|
|
|
536
563
|
/* @__PURE__ */ jsx(
|
|
537
564
|
"line",
|
|
538
565
|
{
|
|
539
|
-
x1:
|
|
566
|
+
x1: axisStartX - 4,
|
|
540
567
|
y1: axisY,
|
|
541
|
-
x2:
|
|
568
|
+
x2: axisEndX,
|
|
542
569
|
y2: axisY,
|
|
543
570
|
stroke: "#374151",
|
|
544
571
|
strokeWidth: 2
|
|
@@ -575,7 +602,7 @@ const AxisLayer = React.memo(function AxisLayer2({
|
|
|
575
602
|
{
|
|
576
603
|
x,
|
|
577
604
|
y: axisY + 22,
|
|
578
|
-
textAnchor:
|
|
605
|
+
textAnchor: edgeTextAnchor(v, min, max),
|
|
579
606
|
fontSize: 11,
|
|
580
607
|
fill: "#374151",
|
|
581
608
|
pointerEvents: "none",
|
|
@@ -595,8 +622,7 @@ const GroupArcsLayer = memo(function GroupArcsLayer2({
|
|
|
595
622
|
groupSize,
|
|
596
623
|
groupCount,
|
|
597
624
|
axisY,
|
|
598
|
-
|
|
599
|
-
padRight,
|
|
625
|
+
horizontalLayout,
|
|
600
626
|
viewWidth,
|
|
601
627
|
arcMaxHeight,
|
|
602
628
|
tool,
|
|
@@ -606,10 +632,7 @@ const GroupArcsLayer = memo(function GroupArcsLayer2({
|
|
|
606
632
|
arcColors = ARC_COLORS
|
|
607
633
|
}) {
|
|
608
634
|
if (groupSize <= 0 || groupCount <= 0) return null;
|
|
609
|
-
const
|
|
610
|
-
const usable = viewWidth - padLeft - padRight;
|
|
611
|
-
const toX = (v) => padLeft + (v - viewMin) / range * usable;
|
|
612
|
-
const clipId = React.useId().replace(/:/g, "_") + "-arcs-clip";
|
|
635
|
+
const toX = createValueToX(viewMin, viewMax, viewWidth, horizontalLayout);
|
|
613
636
|
const arcs = [];
|
|
614
637
|
for (let g = 0; g < groupCount; g++) {
|
|
615
638
|
if (deletedArcIndices.has(g)) continue;
|
|
@@ -675,7 +698,7 @@ const GroupArcsLayer = memo(function GroupArcsLayer2({
|
|
|
675
698
|
{
|
|
676
699
|
x: x2,
|
|
677
700
|
y: textY,
|
|
678
|
-
textAnchor:
|
|
701
|
+
textAnchor: edgeTextAnchor(end, min, max),
|
|
679
702
|
fontSize: 10,
|
|
680
703
|
fill: isSelected ? "#1d4ed8" : color,
|
|
681
704
|
fontWeight: "600",
|
|
@@ -707,18 +730,7 @@ const GroupArcsLayer = memo(function GroupArcsLayer2({
|
|
|
707
730
|
] }, g)
|
|
708
731
|
);
|
|
709
732
|
}
|
|
710
|
-
return /* @__PURE__ */
|
|
711
|
-
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: clipId, children: /* @__PURE__ */ jsx(
|
|
712
|
-
"rect",
|
|
713
|
-
{
|
|
714
|
-
x: padLeft,
|
|
715
|
-
y: 0,
|
|
716
|
-
width: usable,
|
|
717
|
-
height: axisY + 1
|
|
718
|
-
}
|
|
719
|
-
) }) }),
|
|
720
|
-
/* @__PURE__ */ jsx("g", { clipPath: `url(#${clipId})`, children: arcs })
|
|
721
|
-
] });
|
|
733
|
+
return /* @__PURE__ */ jsx("g", { className: "nl-arcs-layer", children: arcs });
|
|
722
734
|
});
|
|
723
735
|
function DrawingLayer({ strokes, liveStroke, tool, width, height, onStrokeClick }) {
|
|
724
736
|
return /* @__PURE__ */ jsxs("g", { className: "nl-drawing-layer", transform: `scale(${width} ${height})`, children: [
|
|
@@ -786,9 +798,6 @@ function DrawingLayer({ strokes, liveStroke, tool, width, height, onStrokeClick
|
|
|
786
798
|
)
|
|
787
799
|
] });
|
|
788
800
|
}
|
|
789
|
-
const PAD_LEFT_LEGACY = 30;
|
|
790
|
-
const PAD_RIGHT_LEGACY = 40;
|
|
791
|
-
const PAD_X = (PAD_LEFT_LEGACY + PAD_RIGHT_LEGACY) / 2;
|
|
792
801
|
const LABEL_SPACE_BELOW_AXIS = 42;
|
|
793
802
|
const LABEL_ABOVE_AXIS_SLOP = 32;
|
|
794
803
|
const ARC_TOP_PADDING = 16;
|
|
@@ -827,8 +836,12 @@ function NumberLine({ className }) {
|
|
|
827
836
|
} = useNumberLineContext();
|
|
828
837
|
const w = containerWidth;
|
|
829
838
|
const h = containerHeight;
|
|
839
|
+
const horizontalLayout = computeHorizontalLayout(min, max);
|
|
830
840
|
const range = Math.max(viewMax - viewMin, 1e-6);
|
|
831
|
-
const usable = Math.max(
|
|
841
|
+
const usable = Math.max(
|
|
842
|
+
w - horizontalLayout.padLeft - horizontalLayout.padRight - horizontalLayout.labelInsetLeft - horizontalLayout.labelInsetRight,
|
|
843
|
+
1
|
|
844
|
+
);
|
|
832
845
|
let ryTypical = MIN_ARC_HEIGHT * 0.5;
|
|
833
846
|
if (groupSize > 0 && groupCount > 0) {
|
|
834
847
|
const rx = usable * groupSize / range / 2;
|
|
@@ -984,7 +997,8 @@ function NumberLine({ className }) {
|
|
|
984
997
|
display: "block",
|
|
985
998
|
outline: enableZoom && isZoomActive ? "2px solid rgba(59, 130, 246, 0.5)" : "none",
|
|
986
999
|
cursor: svgCursor,
|
|
987
|
-
touchAction: "none"
|
|
1000
|
+
touchAction: "none",
|
|
1001
|
+
overflow: "visible"
|
|
988
1002
|
},
|
|
989
1003
|
onPointerDown: handlePointerDown,
|
|
990
1004
|
onPointerMove: handlePointerMove,
|
|
@@ -1004,8 +1018,7 @@ function NumberLine({ className }) {
|
|
|
1004
1018
|
viewMin,
|
|
1005
1019
|
viewMax,
|
|
1006
1020
|
axisY,
|
|
1007
|
-
|
|
1008
|
-
padRight: PAD_X,
|
|
1021
|
+
horizontalLayout,
|
|
1009
1022
|
viewWidth: w,
|
|
1010
1023
|
containerWidth: w,
|
|
1011
1024
|
groupSize,
|
|
@@ -1026,8 +1039,7 @@ function NumberLine({ className }) {
|
|
|
1026
1039
|
groupSize,
|
|
1027
1040
|
groupCount,
|
|
1028
1041
|
axisY,
|
|
1029
|
-
|
|
1030
|
-
padRight: PAD_X,
|
|
1042
|
+
horizontalLayout,
|
|
1031
1043
|
viewWidth: w,
|
|
1032
1044
|
arcMaxHeight,
|
|
1033
1045
|
tool,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dcg-overseas/number-line",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Interactive number line component with group arcs and freehand drawing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,12 +23,6 @@
|
|
|
23
23
|
"dist"
|
|
24
24
|
],
|
|
25
25
|
"sideEffects": false,
|
|
26
|
-
"scripts": {
|
|
27
|
-
"build": "vite build",
|
|
28
|
-
"build:watch": "vite build --watch",
|
|
29
|
-
"typecheck": "tsc --noEmit",
|
|
30
|
-
"clean": "rm -rf dist *.tsbuildinfo"
|
|
31
|
-
},
|
|
32
26
|
"peerDependencies": {
|
|
33
27
|
"react": "^18.0.0",
|
|
34
28
|
"react-dom": "^18.0.0"
|
|
@@ -43,5 +37,11 @@
|
|
|
43
37
|
"publishConfig": {
|
|
44
38
|
"access": "public",
|
|
45
39
|
"registry": "https://registry.npmjs.org/"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "vite build",
|
|
43
|
+
"build:watch": "vite build --watch",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
46
46
|
}
|
|
47
|
-
}
|
|
47
|
+
}
|