@invinite-org/chartlang-adapter-kit 1.3.0 → 1.4.0
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/CHANGELOG.md +52 -0
- package/README.md +7 -0
- package/dist/canvas/index.d.ts +5 -0
- package/dist/canvas/index.d.ts.map +1 -0
- package/dist/canvas/index.js +5 -0
- package/dist/canvas/index.js.map +1 -0
- package/dist/canvas/mockContext.d.ts +168 -0
- package/dist/canvas/mockContext.d.ts.map +1 -0
- package/dist/canvas/mockContext.js +198 -0
- package/dist/canvas/mockContext.js.map +1 -0
- package/dist/canvas/paintPrimitive.d.ts +35 -0
- package/dist/canvas/paintPrimitive.d.ts.map +1 -0
- package/dist/canvas/paintPrimitive.js +171 -0
- package/dist/canvas/paintPrimitive.js.map +1 -0
- package/dist/canvas/renderCtx.d.ts +40 -0
- package/dist/canvas/renderCtx.d.ts.map +1 -0
- package/dist/canvas/renderCtx.js +4 -0
- package/dist/canvas/renderCtx.js.map +1 -0
- package/dist/geometry/_lib/arrowhead.d.ts +18 -0
- package/dist/geometry/_lib/arrowhead.d.ts.map +1 -0
- package/dist/geometry/_lib/arrowhead.js +38 -0
- package/dist/geometry/_lib/arrowhead.js.map +1 -0
- package/dist/geometry/_lib/bezier.d.ts +57 -0
- package/dist/geometry/_lib/bezier.d.ts.map +1 -0
- package/dist/geometry/_lib/bezier.js +84 -0
- package/dist/geometry/_lib/bezier.js.map +1 -0
- package/dist/geometry/_lib/chevron.d.ts +29 -0
- package/dist/geometry/_lib/chevron.d.ts.map +1 -0
- package/dist/geometry/_lib/chevron.js +37 -0
- package/dist/geometry/_lib/chevron.js.map +1 -0
- package/dist/geometry/_lib/dash.d.ts +30 -0
- package/dist/geometry/_lib/dash.d.ts.map +1 -0
- package/dist/geometry/_lib/dash.js +40 -0
- package/dist/geometry/_lib/dash.js.map +1 -0
- package/dist/geometry/_lib/fibLevels.d.ts +36 -0
- package/dist/geometry/_lib/fibLevels.d.ts.map +1 -0
- package/dist/geometry/_lib/fibLevels.js +44 -0
- package/dist/geometry/_lib/fibLevels.js.map +1 -0
- package/dist/geometry/_lib/gannLevels.d.ts +54 -0
- package/dist/geometry/_lib/gannLevels.d.ts.map +1 -0
- package/dist/geometry/_lib/gannLevels.js +88 -0
- package/dist/geometry/_lib/gannLevels.js.map +1 -0
- package/dist/geometry/_lib/lineExtend.d.ts +31 -0
- package/dist/geometry/_lib/lineExtend.d.ts.map +1 -0
- package/dist/geometry/_lib/lineExtend.js +48 -0
- package/dist/geometry/_lib/lineExtend.js.map +1 -0
- package/dist/geometry/_lib/namedPolyline.d.ts +25 -0
- package/dist/geometry/_lib/namedPolyline.d.ts.map +1 -0
- package/dist/geometry/_lib/namedPolyline.js +64 -0
- package/dist/geometry/_lib/namedPolyline.js.map +1 -0
- package/dist/geometry/_lib/pitchforkGeom.d.ts +46 -0
- package/dist/geometry/_lib/pitchforkGeom.d.ts.map +1 -0
- package/dist/geometry/_lib/pitchforkGeom.js +70 -0
- package/dist/geometry/_lib/pitchforkGeom.js.map +1 -0
- package/dist/geometry/_lib/shapeStyle.d.ts +21 -0
- package/dist/geometry/_lib/shapeStyle.d.ts.map +1 -0
- package/dist/geometry/_lib/shapeStyle.js +41 -0
- package/dist/geometry/_lib/shapeStyle.js.map +1 -0
- package/dist/geometry/_lib/strokeStyle.d.ts +34 -0
- package/dist/geometry/_lib/strokeStyle.d.ts.map +1 -0
- package/dist/geometry/_lib/strokeStyle.js +26 -0
- package/dist/geometry/_lib/strokeStyle.js.map +1 -0
- package/dist/geometry/_lib/textStyle.d.ts +70 -0
- package/dist/geometry/_lib/textStyle.d.ts.map +1 -0
- package/dist/geometry/_lib/textStyle.js +78 -0
- package/dist/geometry/_lib/textStyle.js.map +1 -0
- package/dist/geometry/decompose.d.ts +28 -0
- package/dist/geometry/decompose.d.ts.map +1 -0
- package/dist/geometry/decompose.js +176 -0
- package/dist/geometry/decompose.js.map +1 -0
- package/dist/geometry/index.d.ts +4 -0
- package/dist/geometry/index.d.ts.map +1 -0
- package/dist/geometry/index.js +5 -0
- package/dist/geometry/index.js.map +1 -0
- package/dist/geometry/kinds/annotations.d.ts +77 -0
- package/dist/geometry/kinds/annotations.d.ts.map +1 -0
- package/dist/geometry/kinds/annotations.js +219 -0
- package/dist/geometry/kinds/annotations.js.map +1 -0
- package/dist/geometry/kinds/boxes.d.ts +116 -0
- package/dist/geometry/kinds/boxes.d.ts.map +1 -0
- package/dist/geometry/kinds/boxes.js +285 -0
- package/dist/geometry/kinds/boxes.js.map +1 -0
- package/dist/geometry/kinds/channels.d.ts +72 -0
- package/dist/geometry/kinds/channels.d.ts.map +1 -0
- package/dist/geometry/kinds/channels.js +148 -0
- package/dist/geometry/kinds/channels.js.map +1 -0
- package/dist/geometry/kinds/containers.d.ts +54 -0
- package/dist/geometry/kinds/containers.d.ts.map +1 -0
- package/dist/geometry/kinds/containers.js +268 -0
- package/dist/geometry/kinds/containers.js.map +1 -0
- package/dist/geometry/kinds/curves.d.ts +53 -0
- package/dist/geometry/kinds/curves.d.ts.map +1 -0
- package/dist/geometry/kinds/curves.js +110 -0
- package/dist/geometry/kinds/curves.js.map +1 -0
- package/dist/geometry/kinds/cycles.d.ts +52 -0
- package/dist/geometry/kinds/cycles.d.ts.map +1 -0
- package/dist/geometry/kinds/cycles.js +158 -0
- package/dist/geometry/kinds/cycles.js.map +1 -0
- package/dist/geometry/kinds/elliott.d.ts +73 -0
- package/dist/geometry/kinds/elliott.d.ts.map +1 -0
- package/dist/geometry/kinds/elliott.js +116 -0
- package/dist/geometry/kinds/elliott.js.map +1 -0
- package/dist/geometry/kinds/fibonacci.d.ts +166 -0
- package/dist/geometry/kinds/fibonacci.d.ts.map +1 -0
- package/dist/geometry/kinds/fibonacci.js +458 -0
- package/dist/geometry/kinds/fibonacci.js.map +1 -0
- package/dist/geometry/kinds/freehand.d.ts +53 -0
- package/dist/geometry/kinds/freehand.d.ts.map +1 -0
- package/dist/geometry/kinds/freehand.js +115 -0
- package/dist/geometry/kinds/freehand.js.map +1 -0
- package/dist/geometry/kinds/gann.d.ts +63 -0
- package/dist/geometry/kinds/gann.d.ts.map +1 -0
- package/dist/geometry/kinds/gann.js +153 -0
- package/dist/geometry/kinds/gann.js.map +1 -0
- package/dist/geometry/kinds/lines.d.ts +90 -0
- package/dist/geometry/kinds/lines.d.ts.map +1 -0
- package/dist/geometry/kinds/lines.js +201 -0
- package/dist/geometry/kinds/lines.js.map +1 -0
- package/dist/geometry/kinds/marker.d.ts +21 -0
- package/dist/geometry/kinds/marker.d.ts.map +1 -0
- package/dist/geometry/kinds/marker.js +47 -0
- package/dist/geometry/kinds/marker.js.map +1 -0
- package/dist/geometry/kinds/patterns.d.ts +85 -0
- package/dist/geometry/kinds/patterns.d.ts.map +1 -0
- package/dist/geometry/kinds/patterns.js +133 -0
- package/dist/geometry/kinds/patterns.js.map +1 -0
- package/dist/geometry/kinds/pitchforks.d.ts +36 -0
- package/dist/geometry/kinds/pitchforks.d.ts.map +1 -0
- package/dist/geometry/kinds/pitchforks.js +109 -0
- package/dist/geometry/kinds/pitchforks.js.map +1 -0
- package/dist/geometry/project.d.ts +50 -0
- package/dist/geometry/project.d.ts.map +1 -0
- package/dist/geometry/project.js +62 -0
- package/dist/geometry/project.js.map +1 -0
- package/dist/geometry/types.d.ts +146 -0
- package/dist/geometry/types.d.ts.map +1 -0
- package/dist/geometry/types.js +4 -0
- package/dist/geometry/types.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Copyright (c) 2026 Invinite. Licensed under the MIT License.
|
|
2
|
+
// See the LICENSE file in the repo root for full license text.
|
|
3
|
+
//
|
|
4
|
+
// Freehand geometry moved from the canvas2d adapter's per-kind renderers
|
|
5
|
+
// examples/canvas2d-adapter/src/render/draw/{pen,highlighter,brush}.ts.
|
|
6
|
+
// The originating math is invinite's pen / highlighter / brush tools
|
|
7
|
+
// (commit 078f41fe2569d659d5aba726da8bcb5d3e2ced02, © Invinite);
|
|
8
|
+
// re-licensed MIT for chartlang.
|
|
9
|
+
import { dashPattern } from "../_lib/dash.js";
|
|
10
|
+
import { worldPointToPixel } from "../project.js";
|
|
11
|
+
const DEFAULT_COLOR = "#000000";
|
|
12
|
+
const DEFAULT_LINE_WIDTH = 1;
|
|
13
|
+
/**
|
|
14
|
+
* Stroke width of a `highlighter` freehand stroke — fixed at 6 px to
|
|
15
|
+
* match invinite's chunky-highlighter appearance (the `HighlighterStyle`
|
|
16
|
+
* type carries no `lineWidth` field), matching the canvas2d source.
|
|
17
|
+
*/
|
|
18
|
+
const HIGHLIGHTER_LINE_WIDTH = 6;
|
|
19
|
+
/**
|
|
20
|
+
* Fill opacity of a `brush` freehand region — fully opaque, mirroring
|
|
21
|
+
* the canvas2d source's `ctx.fill()` at the default `globalAlpha = 1`.
|
|
22
|
+
*/
|
|
23
|
+
const BRUSH_FILL_ALPHA = 1;
|
|
24
|
+
/**
|
|
25
|
+
* Decompose a `pen` drawing — a freehand stroke as one open polyline
|
|
26
|
+
* through the projected anchors, stroke-only with a `LineDrawStyle`.
|
|
27
|
+
*
|
|
28
|
+
* @since 1.3
|
|
29
|
+
* @stable
|
|
30
|
+
* @example
|
|
31
|
+
* declare const s: PenState;
|
|
32
|
+
* declare const v: Viewport;
|
|
33
|
+
* const prims = decomposePen(s, v);
|
|
34
|
+
* // prims[0].kind === "polyline"; prims[0].closed === false
|
|
35
|
+
* void prims;
|
|
36
|
+
*/
|
|
37
|
+
export function decomposePen(state, view) {
|
|
38
|
+
return [
|
|
39
|
+
{
|
|
40
|
+
kind: "polyline",
|
|
41
|
+
points: state.anchors.map((p) => worldPointToPixel(p, view)),
|
|
42
|
+
closed: false,
|
|
43
|
+
stroke: {
|
|
44
|
+
color: state.style.color ?? DEFAULT_COLOR,
|
|
45
|
+
width: state.style.lineWidth ?? DEFAULT_LINE_WIDTH,
|
|
46
|
+
dash: dashPattern(state.style.lineStyle ?? "solid"),
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Decompose a `highlighter` drawing — a thick translucent freehand
|
|
53
|
+
* stroke as one open polyline. The translucency rides on the IR
|
|
54
|
+
* `StrokeStyle.alpha` (set to `style.alpha`); the painter brackets the
|
|
55
|
+
* `stroke()` in `globalAlpha`, scoping it to this drawing only — exactly
|
|
56
|
+
* the canvas2d source's `globalAlpha` bracket. Width is the fixed
|
|
57
|
+
* {@link HIGHLIGHTER_LINE_WIDTH}; both `color` and `alpha` are required
|
|
58
|
+
* by `HighlighterStyle`.
|
|
59
|
+
*
|
|
60
|
+
* @since 1.3
|
|
61
|
+
* @stable
|
|
62
|
+
* @example
|
|
63
|
+
* declare const s: HighlighterState;
|
|
64
|
+
* declare const v: Viewport;
|
|
65
|
+
* const prims = decomposeHighlighter(s, v);
|
|
66
|
+
* // prims[0].kind === "polyline"; prims[0].stroke?.alpha is set
|
|
67
|
+
* void prims;
|
|
68
|
+
*/
|
|
69
|
+
export function decomposeHighlighter(state, view) {
|
|
70
|
+
return [
|
|
71
|
+
{
|
|
72
|
+
kind: "polyline",
|
|
73
|
+
points: state.anchors.map((p) => worldPointToPixel(p, view)),
|
|
74
|
+
closed: false,
|
|
75
|
+
stroke: {
|
|
76
|
+
color: state.style.color,
|
|
77
|
+
width: HIGHLIGHTER_LINE_WIDTH,
|
|
78
|
+
dash: dashPattern("solid"),
|
|
79
|
+
alpha: state.style.alpha,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Decompose a `brush` drawing — a freehand region as one closed polyline
|
|
86
|
+
* carrying both a `fill` (`style.fill` at full opacity) and a `stroke`
|
|
87
|
+
* (`style.stroke`, width 1). The painter fills before stroking, so the
|
|
88
|
+
* outline draws on top of the filled region. Both colours are required
|
|
89
|
+
* by `BrushStyle`.
|
|
90
|
+
*
|
|
91
|
+
* @since 1.3
|
|
92
|
+
* @stable
|
|
93
|
+
* @example
|
|
94
|
+
* declare const s: BrushState;
|
|
95
|
+
* declare const v: Viewport;
|
|
96
|
+
* const prims = decomposeBrush(s, v);
|
|
97
|
+
* // prims[0].kind === "polyline"; prims[0].closed === true
|
|
98
|
+
* void prims;
|
|
99
|
+
*/
|
|
100
|
+
export function decomposeBrush(state, view) {
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
kind: "polyline",
|
|
104
|
+
points: state.anchors.map((p) => worldPointToPixel(p, view)),
|
|
105
|
+
closed: true,
|
|
106
|
+
stroke: {
|
|
107
|
+
color: state.style.stroke,
|
|
108
|
+
width: DEFAULT_LINE_WIDTH,
|
|
109
|
+
dash: dashPattern("solid"),
|
|
110
|
+
},
|
|
111
|
+
fill: { color: state.style.fill, alpha: BRUSH_FILL_ALPHA },
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=freehand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"freehand.js","sourceRoot":"","sources":["../../../src/geometry/kinds/freehand.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAC/D,EAAE;AACF,yEAAyE;AACzE,0EAA0E;AAC1E,qEAAqE;AACrE,iEAAiE;AACjE,iCAAiC;AAIjC,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGlD,MAAM,aAAa,GAAG,SAAS,CAAC;AAChC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAE7B;;;;GAIG;AACH,MAAM,sBAAsB,GAAG,CAAC,CAAC;AAEjC;;;GAGG;AACH,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAE3B;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAAC,KAAe,EAAE,IAAc;IACxD,OAAO;QACH;YACI,IAAI,EAAE,UAAU;YAChB,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;YAC5D,MAAM,EAAE,KAAK;YACb,MAAM,EAAE;gBACJ,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,aAAa;gBACzC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS,IAAI,kBAAkB;gBAClD,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,IAAI,OAAO,CAAC;aACtD;SACJ;KACJ,CAAC;AACN,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,oBAAoB,CAChC,KAAuB,EACvB,IAAc;IAEd,OAAO;QACH;YACI,IAAI,EAAE,UAAU;YAChB,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;YAC5D,MAAM,EAAE,KAAK;YACb,MAAM,EAAE;gBACJ,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK;gBACxB,KAAK,EAAE,sBAAsB;gBAC7B,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC;gBAC1B,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK;aAC3B;SACJ;KACJ,CAAC;AACN,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,cAAc,CAAC,KAAiB,EAAE,IAAc;IAC5D,OAAO;QACH;YACI,IAAI,EAAE,UAAU;YAChB,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;YAC5D,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE;gBACJ,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM;gBACzB,KAAK,EAAE,kBAAkB;gBACzB,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC;aAC7B;YACD,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,gBAAgB,EAAE;SAC7D;KACJ,CAAC;AACN,CAAC","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n//\n// Freehand geometry moved from the canvas2d adapter's per-kind renderers\n// examples/canvas2d-adapter/src/render/draw/{pen,highlighter,brush}.ts.\n// The originating math is invinite's pen / highlighter / brush tools\n// (commit 078f41fe2569d659d5aba726da8bcb5d3e2ced02, © Invinite);\n// re-licensed MIT for chartlang.\n\nimport type { BrushState, HighlighterState, PenState } from \"@invinite-org/chartlang-core\";\n\nimport { dashPattern } from \"../_lib/dash.js\";\nimport { worldPointToPixel } from \"../project.js\";\nimport type { DrawPrimitive, Viewport } from \"../types.js\";\n\nconst DEFAULT_COLOR = \"#000000\";\nconst DEFAULT_LINE_WIDTH = 1;\n\n/**\n * Stroke width of a `highlighter` freehand stroke — fixed at 6 px to\n * match invinite's chunky-highlighter appearance (the `HighlighterStyle`\n * type carries no `lineWidth` field), matching the canvas2d source.\n */\nconst HIGHLIGHTER_LINE_WIDTH = 6;\n\n/**\n * Fill opacity of a `brush` freehand region — fully opaque, mirroring\n * the canvas2d source's `ctx.fill()` at the default `globalAlpha = 1`.\n */\nconst BRUSH_FILL_ALPHA = 1;\n\n/**\n * Decompose a `pen` drawing — a freehand stroke as one open polyline\n * through the projected anchors, stroke-only with a `LineDrawStyle`.\n *\n * @since 1.3\n * @stable\n * @example\n * declare const s: PenState;\n * declare const v: Viewport;\n * const prims = decomposePen(s, v);\n * // prims[0].kind === \"polyline\"; prims[0].closed === false\n * void prims;\n */\nexport function decomposePen(state: PenState, view: Viewport): ReadonlyArray<DrawPrimitive> {\n return [\n {\n kind: \"polyline\",\n points: state.anchors.map((p) => worldPointToPixel(p, view)),\n closed: false,\n stroke: {\n color: state.style.color ?? DEFAULT_COLOR,\n width: state.style.lineWidth ?? DEFAULT_LINE_WIDTH,\n dash: dashPattern(state.style.lineStyle ?? \"solid\"),\n },\n },\n ];\n}\n\n/**\n * Decompose a `highlighter` drawing — a thick translucent freehand\n * stroke as one open polyline. The translucency rides on the IR\n * `StrokeStyle.alpha` (set to `style.alpha`); the painter brackets the\n * `stroke()` in `globalAlpha`, scoping it to this drawing only — exactly\n * the canvas2d source's `globalAlpha` bracket. Width is the fixed\n * {@link HIGHLIGHTER_LINE_WIDTH}; both `color` and `alpha` are required\n * by `HighlighterStyle`.\n *\n * @since 1.3\n * @stable\n * @example\n * declare const s: HighlighterState;\n * declare const v: Viewport;\n * const prims = decomposeHighlighter(s, v);\n * // prims[0].kind === \"polyline\"; prims[0].stroke?.alpha is set\n * void prims;\n */\nexport function decomposeHighlighter(\n state: HighlighterState,\n view: Viewport,\n): ReadonlyArray<DrawPrimitive> {\n return [\n {\n kind: \"polyline\",\n points: state.anchors.map((p) => worldPointToPixel(p, view)),\n closed: false,\n stroke: {\n color: state.style.color,\n width: HIGHLIGHTER_LINE_WIDTH,\n dash: dashPattern(\"solid\"),\n alpha: state.style.alpha,\n },\n },\n ];\n}\n\n/**\n * Decompose a `brush` drawing — a freehand region as one closed polyline\n * carrying both a `fill` (`style.fill` at full opacity) and a `stroke`\n * (`style.stroke`, width 1). The painter fills before stroking, so the\n * outline draws on top of the filled region. Both colours are required\n * by `BrushStyle`.\n *\n * @since 1.3\n * @stable\n * @example\n * declare const s: BrushState;\n * declare const v: Viewport;\n * const prims = decomposeBrush(s, v);\n * // prims[0].kind === \"polyline\"; prims[0].closed === true\n * void prims;\n */\nexport function decomposeBrush(state: BrushState, view: Viewport): ReadonlyArray<DrawPrimitive> {\n return [\n {\n kind: \"polyline\",\n points: state.anchors.map((p) => worldPointToPixel(p, view)),\n closed: true,\n stroke: {\n color: state.style.stroke,\n width: DEFAULT_LINE_WIDTH,\n dash: dashPattern(\"solid\"),\n },\n fill: { color: state.style.fill, alpha: BRUSH_FILL_ALPHA },\n },\n ];\n}\n"]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { GannBoxState, GannFanState, GannSquareFixedState, GannSquareState } from "@invinite-org/chartlang-core";
|
|
2
|
+
import type { DrawPrimitive, Viewport } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Decompose a `gann-box` drawing — a ratio grid spanning the bounding
|
|
5
|
+
* rectangle of the two world anchors, one horizontal + one vertical
|
|
6
|
+
* line at each {@link GANN_LEVELS} entry (5 + 5 = 10 polylines for the
|
|
7
|
+
* default 1/4 subdivisions, including the outer rectangle at 0 and 1.0).
|
|
8
|
+
*
|
|
9
|
+
* @since 1.3
|
|
10
|
+
* @stable
|
|
11
|
+
* @example
|
|
12
|
+
* declare const s: GannBoxState;
|
|
13
|
+
* declare const v: Viewport;
|
|
14
|
+
* const prims = decomposeGannBox(s, v);
|
|
15
|
+
* void prims;
|
|
16
|
+
*/
|
|
17
|
+
export declare function decomposeGannBox(state: GannBoxState, view: Viewport): ReadonlyArray<DrawPrimitive>;
|
|
18
|
+
/**
|
|
19
|
+
* Decompose a `gann-square-fixed` drawing — an `80×80` pixel square
|
|
20
|
+
* anchored at `anchor`, subdivided by {@link GANN_LEVELS}. The fixed
|
|
21
|
+
* pixel side mirrors the canvas2d source's deterministic constant.
|
|
22
|
+
*
|
|
23
|
+
* @since 1.3
|
|
24
|
+
* @stable
|
|
25
|
+
* @example
|
|
26
|
+
* declare const s: GannSquareFixedState;
|
|
27
|
+
* declare const v: Viewport;
|
|
28
|
+
* const prims = decomposeGannSquareFixed(s, v);
|
|
29
|
+
* void prims;
|
|
30
|
+
*/
|
|
31
|
+
export declare function decomposeGannSquareFixed(state: GannSquareFixedState, view: Viewport): ReadonlyArray<DrawPrimitive>;
|
|
32
|
+
/**
|
|
33
|
+
* Decompose a `gann-square` drawing — a square anchored at `anchors[0]`
|
|
34
|
+
* with side `max(|dx|, |dy|)` in pixel space (the Gann-1×1 default),
|
|
35
|
+
* extended in the direction of `anchors[1]`. Subdivisions follow
|
|
36
|
+
* {@link GANN_LEVELS}.
|
|
37
|
+
*
|
|
38
|
+
* @since 1.3
|
|
39
|
+
* @stable
|
|
40
|
+
* @example
|
|
41
|
+
* declare const s: GannSquareState;
|
|
42
|
+
* declare const v: Viewport;
|
|
43
|
+
* const prims = decomposeGannSquare(s, v);
|
|
44
|
+
* void prims;
|
|
45
|
+
*/
|
|
46
|
+
export declare function decomposeGannSquare(state: GannSquareState, view: Viewport): ReadonlyArray<DrawPrimitive>;
|
|
47
|
+
/**
|
|
48
|
+
* Decompose a `gann-fan` drawing — 9 rays from `anchors[0]`, each with
|
|
49
|
+
* direction `(dx, ratio · dy)` where `(dx, dy)` is the `anchors[0] →
|
|
50
|
+
* anchors[1]` vector and `ratio` cycles through {@link GANN_FAN_RATIOS}.
|
|
51
|
+
* Rays extend to `max(pxWidth, pxHeight) · 2` so they exit the viewport;
|
|
52
|
+
* a zero-magnitude ray is skipped (matching the source `continue`).
|
|
53
|
+
*
|
|
54
|
+
* @since 1.3
|
|
55
|
+
* @stable
|
|
56
|
+
* @example
|
|
57
|
+
* declare const s: GannFanState;
|
|
58
|
+
* declare const v: Viewport;
|
|
59
|
+
* const prims = decomposeGannFan(s, v);
|
|
60
|
+
* void prims;
|
|
61
|
+
*/
|
|
62
|
+
export declare function decomposeGannFan(state: GannFanState, view: Viewport): ReadonlyArray<DrawPrimitive>;
|
|
63
|
+
//# sourceMappingURL=gann.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gann.d.ts","sourceRoot":"","sources":["../../../src/geometry/kinds/gann.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACR,YAAY,EACZ,YAAY,EACZ,oBAAoB,EACpB,eAAe,EAClB,MAAM,8BAA8B,CAAC;AAKtC,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAgD3D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC5B,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,QAAQ,GACf,aAAa,CAAC,aAAa,CAAC,CAW9B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CACpC,KAAK,EAAE,oBAAoB,EAC3B,IAAI,EAAE,QAAQ,GACf,aAAa,CAAC,aAAa,CAAC,CAU9B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAC/B,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,QAAQ,GACf,aAAa,CAAC,aAAa,CAAC,CAkB9B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC5B,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,QAAQ,GACf,aAAa,CAAC,aAAa,CAAC,CAwB9B"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Copyright (c) 2026 Invinite. Licensed under the MIT License.
|
|
2
|
+
// See the LICENSE file in the repo root for full license text.
|
|
3
|
+
//
|
|
4
|
+
// Gann geometry moved from the canvas2d adapter's per-kind renderers
|
|
5
|
+
// examples/canvas2d-adapter/src/render/draw/{gannBox,gannSquareFixed,
|
|
6
|
+
// gannSquare,gannFan}.ts.
|
|
7
|
+
// The originating math is invinite's gann-box / gann-square /
|
|
8
|
+
// gann-fan tools (commit 078f41fe2569d659d5aba726da8bcb5d3e2ced02,
|
|
9
|
+
// © Invinite); re-licensed MIT for chartlang.
|
|
10
|
+
import { SOLID_DASH } from "../_lib/dash.js";
|
|
11
|
+
import { GANN_FAN_RATIOS, GANN_LEVELS } from "../_lib/gannLevels.js";
|
|
12
|
+
import { worldPointToPixel } from "../project.js";
|
|
13
|
+
const DEFAULT_COLOR = "#a855f7";
|
|
14
|
+
const DEFAULT_LINE_WIDTH = 1;
|
|
15
|
+
const SIDE_PX = 80;
|
|
16
|
+
/**
|
|
17
|
+
* Shared subdivision-grid builder for the three Gann box kinds — one
|
|
18
|
+
* horizontal + one vertical polyline at every {@link GANN_LEVELS} ratio
|
|
19
|
+
* across the `[left, right] × [top, bottom]` rectangle.
|
|
20
|
+
*/
|
|
21
|
+
function gridPolylines(left, right, top, bottom, color, width) {
|
|
22
|
+
const stroke = { color, width, dash: SOLID_DASH };
|
|
23
|
+
const out = [];
|
|
24
|
+
for (const level of GANN_LEVELS) {
|
|
25
|
+
const y = top + level * (bottom - top);
|
|
26
|
+
out.push({
|
|
27
|
+
kind: "polyline",
|
|
28
|
+
points: [
|
|
29
|
+
{ x: left, y },
|
|
30
|
+
{ x: right, y },
|
|
31
|
+
],
|
|
32
|
+
closed: false,
|
|
33
|
+
stroke,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
for (const level of GANN_LEVELS) {
|
|
37
|
+
const x = left + level * (right - left);
|
|
38
|
+
out.push({
|
|
39
|
+
kind: "polyline",
|
|
40
|
+
points: [
|
|
41
|
+
{ x, y: top },
|
|
42
|
+
{ x, y: bottom },
|
|
43
|
+
],
|
|
44
|
+
closed: false,
|
|
45
|
+
stroke,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Decompose a `gann-box` drawing — a ratio grid spanning the bounding
|
|
52
|
+
* rectangle of the two world anchors, one horizontal + one vertical
|
|
53
|
+
* line at each {@link GANN_LEVELS} entry (5 + 5 = 10 polylines for the
|
|
54
|
+
* default 1/4 subdivisions, including the outer rectangle at 0 and 1.0).
|
|
55
|
+
*
|
|
56
|
+
* @since 1.3
|
|
57
|
+
* @stable
|
|
58
|
+
* @example
|
|
59
|
+
* declare const s: GannBoxState;
|
|
60
|
+
* declare const v: Viewport;
|
|
61
|
+
* const prims = decomposeGannBox(s, v);
|
|
62
|
+
* void prims;
|
|
63
|
+
*/
|
|
64
|
+
export function decomposeGannBox(state, view) {
|
|
65
|
+
const a = worldPointToPixel(state.anchors[0], view);
|
|
66
|
+
const b = worldPointToPixel(state.anchors[1], view);
|
|
67
|
+
return gridPolylines(Math.min(a.x, b.x), Math.max(a.x, b.x), Math.min(a.y, b.y), Math.max(a.y, b.y), state.style.color ?? DEFAULT_COLOR, state.style.lineWidth ?? DEFAULT_LINE_WIDTH);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Decompose a `gann-square-fixed` drawing — an `80×80` pixel square
|
|
71
|
+
* anchored at `anchor`, subdivided by {@link GANN_LEVELS}. The fixed
|
|
72
|
+
* pixel side mirrors the canvas2d source's deterministic constant.
|
|
73
|
+
*
|
|
74
|
+
* @since 1.3
|
|
75
|
+
* @stable
|
|
76
|
+
* @example
|
|
77
|
+
* declare const s: GannSquareFixedState;
|
|
78
|
+
* declare const v: Viewport;
|
|
79
|
+
* const prims = decomposeGannSquareFixed(s, v);
|
|
80
|
+
* void prims;
|
|
81
|
+
*/
|
|
82
|
+
export function decomposeGannSquareFixed(state, view) {
|
|
83
|
+
const origin = worldPointToPixel(state.anchor, view);
|
|
84
|
+
return gridPolylines(origin.x, origin.x + SIDE_PX, origin.y, origin.y + SIDE_PX, state.style.color ?? DEFAULT_COLOR, state.style.lineWidth ?? DEFAULT_LINE_WIDTH);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Decompose a `gann-square` drawing — a square anchored at `anchors[0]`
|
|
88
|
+
* with side `max(|dx|, |dy|)` in pixel space (the Gann-1×1 default),
|
|
89
|
+
* extended in the direction of `anchors[1]`. Subdivisions follow
|
|
90
|
+
* {@link GANN_LEVELS}.
|
|
91
|
+
*
|
|
92
|
+
* @since 1.3
|
|
93
|
+
* @stable
|
|
94
|
+
* @example
|
|
95
|
+
* declare const s: GannSquareState;
|
|
96
|
+
* declare const v: Viewport;
|
|
97
|
+
* const prims = decomposeGannSquare(s, v);
|
|
98
|
+
* void prims;
|
|
99
|
+
*/
|
|
100
|
+
export function decomposeGannSquare(state, view) {
|
|
101
|
+
const a = worldPointToPixel(state.anchors[0], view);
|
|
102
|
+
const b = worldPointToPixel(state.anchors[1], view);
|
|
103
|
+
const side = Math.max(Math.abs(b.x - a.x), Math.abs(b.y - a.y));
|
|
104
|
+
const signX = b.x >= a.x ? 1 : -1;
|
|
105
|
+
const signY = b.y >= a.y ? 1 : -1;
|
|
106
|
+
const left = signX === 1 ? a.x : a.x - side;
|
|
107
|
+
const right = signX === 1 ? a.x + side : a.x;
|
|
108
|
+
const top = signY === 1 ? a.y : a.y - side;
|
|
109
|
+
const bottom = signY === 1 ? a.y + side : a.y;
|
|
110
|
+
return gridPolylines(left, right, top, bottom, state.style.color ?? DEFAULT_COLOR, state.style.lineWidth ?? DEFAULT_LINE_WIDTH);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Decompose a `gann-fan` drawing — 9 rays from `anchors[0]`, each with
|
|
114
|
+
* direction `(dx, ratio · dy)` where `(dx, dy)` is the `anchors[0] →
|
|
115
|
+
* anchors[1]` vector and `ratio` cycles through {@link GANN_FAN_RATIOS}.
|
|
116
|
+
* Rays extend to `max(pxWidth, pxHeight) · 2` so they exit the viewport;
|
|
117
|
+
* a zero-magnitude ray is skipped (matching the source `continue`).
|
|
118
|
+
*
|
|
119
|
+
* @since 1.3
|
|
120
|
+
* @stable
|
|
121
|
+
* @example
|
|
122
|
+
* declare const s: GannFanState;
|
|
123
|
+
* declare const v: Viewport;
|
|
124
|
+
* const prims = decomposeGannFan(s, v);
|
|
125
|
+
* void prims;
|
|
126
|
+
*/
|
|
127
|
+
export function decomposeGannFan(state, view) {
|
|
128
|
+
const pivot = worldPointToPixel(state.anchors[0], view);
|
|
129
|
+
const ref = worldPointToPixel(state.anchors[1], view);
|
|
130
|
+
const dx = ref.x - pivot.x;
|
|
131
|
+
const dy = ref.y - pivot.y;
|
|
132
|
+
const color = state.style.color ?? DEFAULT_COLOR;
|
|
133
|
+
const width = state.style.lineWidth ?? DEFAULT_LINE_WIDTH;
|
|
134
|
+
const rayLength = Math.max(view.pxWidth, view.pxHeight) * 2;
|
|
135
|
+
const out = [];
|
|
136
|
+
for (const ratio of GANN_FAN_RATIOS) {
|
|
137
|
+
const rx = dx;
|
|
138
|
+
const ry = ratio * dy;
|
|
139
|
+
const mag = Math.hypot(rx, ry);
|
|
140
|
+
if (mag === 0)
|
|
141
|
+
continue;
|
|
142
|
+
const ux = rx / mag;
|
|
143
|
+
const uy = ry / mag;
|
|
144
|
+
out.push({
|
|
145
|
+
kind: "polyline",
|
|
146
|
+
points: [pivot, { x: pivot.x + ux * rayLength, y: pivot.y + uy * rayLength }],
|
|
147
|
+
closed: false,
|
|
148
|
+
stroke: { color, width, dash: SOLID_DASH },
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=gann.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gann.js","sourceRoot":"","sources":["../../../src/geometry/kinds/gann.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAC/D,EAAE;AACF,qEAAqE;AACrE,wEAAwE;AACxE,4BAA4B;AAC5B,8DAA8D;AAC9D,mEAAmE;AACnE,8CAA8C;AAS9C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGlD,MAAM,aAAa,GAAG,SAAS,CAAC;AAChC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAC7B,MAAM,OAAO,GAAG,EAAE,CAAC;AAEnB;;;;GAIG;AACH,SAAS,aAAa,CAClB,IAAY,EACZ,KAAa,EACb,GAAW,EACX,MAAc,EACd,KAAa,EACb,KAAa;IAEb,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAClD,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,GAAG,GAAG,KAAK,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;QACvC,GAAG,CAAC,IAAI,CAAC;YACL,IAAI,EAAE,UAAU;YAChB,MAAM,EAAE;gBACJ,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE;gBACd,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;aAClB;YACD,MAAM,EAAE,KAAK;YACb,MAAM;SACT,CAAC,CAAC;IACP,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,IAAI,GAAG,KAAK,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;QACxC,GAAG,CAAC,IAAI,CAAC;YACL,IAAI,EAAE,UAAU;YAChB,MAAM,EAAE;gBACJ,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE;gBACb,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE;aACnB;YACD,MAAM,EAAE,KAAK;YACb,MAAM;SACT,CAAC,CAAC;IACP,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAC5B,KAAmB,EACnB,IAAc;IAEd,MAAM,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACpD,OAAO,aAAa,CAChB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAClB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAClB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAClB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAClB,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,aAAa,EAClC,KAAK,CAAC,KAAK,CAAC,SAAS,IAAI,kBAAkB,CAC9C,CAAC;AACN,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,wBAAwB,CACpC,KAA2B,EAC3B,IAAc;IAEd,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACrD,OAAO,aAAa,CAChB,MAAM,CAAC,CAAC,EACR,MAAM,CAAC,CAAC,GAAG,OAAO,EAClB,MAAM,CAAC,CAAC,EACR,MAAM,CAAC,CAAC,GAAG,OAAO,EAClB,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,aAAa,EAClC,KAAK,CAAC,KAAK,CAAC,SAAS,IAAI,kBAAkB,CAC9C,CAAC;AACN,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB,CAC/B,KAAsB,EACtB,IAAc;IAEd,MAAM,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChE,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC5C,MAAM,KAAK,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7C,MAAM,GAAG,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC3C,MAAM,MAAM,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,OAAO,aAAa,CAChB,IAAI,EACJ,KAAK,EACL,GAAG,EACH,MAAM,EACN,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,aAAa,EAClC,KAAK,CAAC,KAAK,CAAC,SAAS,IAAI,kBAAkB,CAC9C,CAAC;AACN,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gBAAgB,CAC5B,KAAmB,EACnB,IAAc;IAEd,MAAM,KAAK,GAAG,iBAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACxD,MAAM,GAAG,GAAG,iBAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACtD,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;IAC3B,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;IAC3B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,aAAa,CAAC;IACjD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,IAAI,kBAAkB,CAAC;IAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC5D,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,CAAC;QAClC,MAAM,EAAE,GAAG,EAAE,CAAC;QACd,MAAM,EAAE,GAAG,KAAK,GAAG,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAC/B,IAAI,GAAG,KAAK,CAAC;YAAE,SAAS;QACxB,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;QACpB,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;QACpB,GAAG,CAAC,IAAI,CAAC;YACL,IAAI,EAAE,UAAU;YAChB,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,EAAE,GAAG,SAAS,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,EAAE,GAAG,SAAS,EAAE,CAAC;YAC7E,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE;SAC7C,CAAC,CAAC;IACP,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n//\n// Gann geometry moved from the canvas2d adapter's per-kind renderers\n// examples/canvas2d-adapter/src/render/draw/{gannBox,gannSquareFixed,\n// gannSquare,gannFan}.ts.\n// The originating math is invinite's gann-box / gann-square /\n// gann-fan tools (commit 078f41fe2569d659d5aba726da8bcb5d3e2ced02,\n// © Invinite); re-licensed MIT for chartlang.\n\nimport type {\n GannBoxState,\n GannFanState,\n GannSquareFixedState,\n GannSquareState,\n} from \"@invinite-org/chartlang-core\";\n\nimport { SOLID_DASH } from \"../_lib/dash.js\";\nimport { GANN_FAN_RATIOS, GANN_LEVELS } from \"../_lib/gannLevels.js\";\nimport { worldPointToPixel } from \"../project.js\";\nimport type { DrawPrimitive, Viewport } from \"../types.js\";\n\nconst DEFAULT_COLOR = \"#a855f7\";\nconst DEFAULT_LINE_WIDTH = 1;\nconst SIDE_PX = 80;\n\n/**\n * Shared subdivision-grid builder for the three Gann box kinds — one\n * horizontal + one vertical polyline at every {@link GANN_LEVELS} ratio\n * across the `[left, right] × [top, bottom]` rectangle.\n */\nfunction gridPolylines(\n left: number,\n right: number,\n top: number,\n bottom: number,\n color: string,\n width: number,\n): DrawPrimitive[] {\n const stroke = { color, width, dash: SOLID_DASH };\n const out: DrawPrimitive[] = [];\n for (const level of GANN_LEVELS) {\n const y = top + level * (bottom - top);\n out.push({\n kind: \"polyline\",\n points: [\n { x: left, y },\n { x: right, y },\n ],\n closed: false,\n stroke,\n });\n }\n for (const level of GANN_LEVELS) {\n const x = left + level * (right - left);\n out.push({\n kind: \"polyline\",\n points: [\n { x, y: top },\n { x, y: bottom },\n ],\n closed: false,\n stroke,\n });\n }\n return out;\n}\n\n/**\n * Decompose a `gann-box` drawing — a ratio grid spanning the bounding\n * rectangle of the two world anchors, one horizontal + one vertical\n * line at each {@link GANN_LEVELS} entry (5 + 5 = 10 polylines for the\n * default 1/4 subdivisions, including the outer rectangle at 0 and 1.0).\n *\n * @since 1.3\n * @stable\n * @example\n * declare const s: GannBoxState;\n * declare const v: Viewport;\n * const prims = decomposeGannBox(s, v);\n * void prims;\n */\nexport function decomposeGannBox(\n state: GannBoxState,\n view: Viewport,\n): ReadonlyArray<DrawPrimitive> {\n const a = worldPointToPixel(state.anchors[0], view);\n const b = worldPointToPixel(state.anchors[1], view);\n return gridPolylines(\n Math.min(a.x, b.x),\n Math.max(a.x, b.x),\n Math.min(a.y, b.y),\n Math.max(a.y, b.y),\n state.style.color ?? DEFAULT_COLOR,\n state.style.lineWidth ?? DEFAULT_LINE_WIDTH,\n );\n}\n\n/**\n * Decompose a `gann-square-fixed` drawing — an `80×80` pixel square\n * anchored at `anchor`, subdivided by {@link GANN_LEVELS}. The fixed\n * pixel side mirrors the canvas2d source's deterministic constant.\n *\n * @since 1.3\n * @stable\n * @example\n * declare const s: GannSquareFixedState;\n * declare const v: Viewport;\n * const prims = decomposeGannSquareFixed(s, v);\n * void prims;\n */\nexport function decomposeGannSquareFixed(\n state: GannSquareFixedState,\n view: Viewport,\n): ReadonlyArray<DrawPrimitive> {\n const origin = worldPointToPixel(state.anchor, view);\n return gridPolylines(\n origin.x,\n origin.x + SIDE_PX,\n origin.y,\n origin.y + SIDE_PX,\n state.style.color ?? DEFAULT_COLOR,\n state.style.lineWidth ?? DEFAULT_LINE_WIDTH,\n );\n}\n\n/**\n * Decompose a `gann-square` drawing — a square anchored at `anchors[0]`\n * with side `max(|dx|, |dy|)` in pixel space (the Gann-1×1 default),\n * extended in the direction of `anchors[1]`. Subdivisions follow\n * {@link GANN_LEVELS}.\n *\n * @since 1.3\n * @stable\n * @example\n * declare const s: GannSquareState;\n * declare const v: Viewport;\n * const prims = decomposeGannSquare(s, v);\n * void prims;\n */\nexport function decomposeGannSquare(\n state: GannSquareState,\n view: Viewport,\n): ReadonlyArray<DrawPrimitive> {\n const a = worldPointToPixel(state.anchors[0], view);\n const b = worldPointToPixel(state.anchors[1], view);\n const side = Math.max(Math.abs(b.x - a.x), Math.abs(b.y - a.y));\n const signX = b.x >= a.x ? 1 : -1;\n const signY = b.y >= a.y ? 1 : -1;\n const left = signX === 1 ? a.x : a.x - side;\n const right = signX === 1 ? a.x + side : a.x;\n const top = signY === 1 ? a.y : a.y - side;\n const bottom = signY === 1 ? a.y + side : a.y;\n return gridPolylines(\n left,\n right,\n top,\n bottom,\n state.style.color ?? DEFAULT_COLOR,\n state.style.lineWidth ?? DEFAULT_LINE_WIDTH,\n );\n}\n\n/**\n * Decompose a `gann-fan` drawing — 9 rays from `anchors[0]`, each with\n * direction `(dx, ratio · dy)` where `(dx, dy)` is the `anchors[0] →\n * anchors[1]` vector and `ratio` cycles through {@link GANN_FAN_RATIOS}.\n * Rays extend to `max(pxWidth, pxHeight) · 2` so they exit the viewport;\n * a zero-magnitude ray is skipped (matching the source `continue`).\n *\n * @since 1.3\n * @stable\n * @example\n * declare const s: GannFanState;\n * declare const v: Viewport;\n * const prims = decomposeGannFan(s, v);\n * void prims;\n */\nexport function decomposeGannFan(\n state: GannFanState,\n view: Viewport,\n): ReadonlyArray<DrawPrimitive> {\n const pivot = worldPointToPixel(state.anchors[0], view);\n const ref = worldPointToPixel(state.anchors[1], view);\n const dx = ref.x - pivot.x;\n const dy = ref.y - pivot.y;\n const color = state.style.color ?? DEFAULT_COLOR;\n const width = state.style.lineWidth ?? DEFAULT_LINE_WIDTH;\n const rayLength = Math.max(view.pxWidth, view.pxHeight) * 2;\n const out: DrawPrimitive[] = [];\n for (const ratio of GANN_FAN_RATIOS) {\n const rx = dx;\n const ry = ratio * dy;\n const mag = Math.hypot(rx, ry);\n if (mag === 0) continue;\n const ux = rx / mag;\n const uy = ry / mag;\n out.push({\n kind: \"polyline\",\n points: [pivot, { x: pivot.x + ux * rayLength, y: pivot.y + uy * rayLength }],\n closed: false,\n stroke: { color, width, dash: SOLID_DASH },\n });\n }\n return out;\n}\n"]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { CrossLineState, HorizontalLineState, HorizontalRayState, LineState, TrendAngleState, VerticalLineState } from "@invinite-org/chartlang-core";
|
|
2
|
+
import type { DrawPrimitive, Viewport } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Decompose a `line` drawing — a single segment, optionally extended to
|
|
5
|
+
* the viewport edges via {@link extendLineSegment} (the `ray` /
|
|
6
|
+
* `extended-line` collapse).
|
|
7
|
+
*
|
|
8
|
+
* @since 1.3
|
|
9
|
+
* @stable
|
|
10
|
+
* @example
|
|
11
|
+
* declare const s: LineState;
|
|
12
|
+
* declare const v: Viewport;
|
|
13
|
+
* const prims = decomposeLine(s, v);
|
|
14
|
+
* // prims[0].kind === "polyline"
|
|
15
|
+
* void prims;
|
|
16
|
+
*/
|
|
17
|
+
export declare function decomposeLine(state: LineState, view: Viewport): ReadonlyArray<DrawPrimitive>;
|
|
18
|
+
/**
|
|
19
|
+
* Decompose a `horizontal-line` drawing — a segment from `x = 0` to
|
|
20
|
+
* `x = view.pxWidth` at `priceToY(state.price)`.
|
|
21
|
+
*
|
|
22
|
+
* @since 1.3
|
|
23
|
+
* @stable
|
|
24
|
+
* @example
|
|
25
|
+
* declare const s: HorizontalLineState;
|
|
26
|
+
* declare const v: Viewport;
|
|
27
|
+
* const prims = decomposeHorizontalLine(s, v);
|
|
28
|
+
* void prims;
|
|
29
|
+
*/
|
|
30
|
+
export declare function decomposeHorizontalLine(state: HorizontalLineState, view: Viewport): ReadonlyArray<DrawPrimitive>;
|
|
31
|
+
/**
|
|
32
|
+
* Decompose a `horizontal-ray` drawing — a segment from the projected
|
|
33
|
+
* anchor across the right edge at constant y.
|
|
34
|
+
*
|
|
35
|
+
* @since 1.3
|
|
36
|
+
* @stable
|
|
37
|
+
* @example
|
|
38
|
+
* declare const s: HorizontalRayState;
|
|
39
|
+
* declare const v: Viewport;
|
|
40
|
+
* const prims = decomposeHorizontalRay(s, v);
|
|
41
|
+
* void prims;
|
|
42
|
+
*/
|
|
43
|
+
export declare function decomposeHorizontalRay(state: HorizontalRayState, view: Viewport): ReadonlyArray<DrawPrimitive>;
|
|
44
|
+
/**
|
|
45
|
+
* Decompose a `vertical-line` drawing — a segment from `y = 0` to
|
|
46
|
+
* `y = view.pxHeight` at `timeToX(state.time)`.
|
|
47
|
+
*
|
|
48
|
+
* @since 1.3
|
|
49
|
+
* @stable
|
|
50
|
+
* @example
|
|
51
|
+
* declare const s: VerticalLineState;
|
|
52
|
+
* declare const v: Viewport;
|
|
53
|
+
* const prims = decomposeVerticalLine(s, v);
|
|
54
|
+
* void prims;
|
|
55
|
+
*/
|
|
56
|
+
export declare function decomposeVerticalLine(state: VerticalLineState, view: Viewport): ReadonlyArray<DrawPrimitive>;
|
|
57
|
+
/**
|
|
58
|
+
* Decompose a `cross-line` drawing — a horizontal segment and a
|
|
59
|
+
* vertical segment crossing at the projected anchor. Two polylines
|
|
60
|
+
* sharing one stroke style.
|
|
61
|
+
*
|
|
62
|
+
* @since 1.3
|
|
63
|
+
* @stable
|
|
64
|
+
* @example
|
|
65
|
+
* declare const s: CrossLineState;
|
|
66
|
+
* declare const v: Viewport;
|
|
67
|
+
* const prims = decomposeCrossLine(s, v);
|
|
68
|
+
* // prims.length === 2
|
|
69
|
+
* void prims;
|
|
70
|
+
*/
|
|
71
|
+
export declare function decomposeCrossLine(state: CrossLineState, view: Viewport): ReadonlyArray<DrawPrimitive>;
|
|
72
|
+
/**
|
|
73
|
+
* Decompose a `trend-angle` drawing — the main segment, a small arc
|
|
74
|
+
* spanning the screen-space angle off the first anchor, and the angle
|
|
75
|
+
* label in degrees. The arc is solid regardless of `lineStyle` so it
|
|
76
|
+
* reads cleanly; the angle is measured in screen-pixel space with the
|
|
77
|
+
* canvas y-axis flipped (`-dy`) so a positive angle reads "upward to
|
|
78
|
+
* the right" (matching the source renderer's convention).
|
|
79
|
+
*
|
|
80
|
+
* @since 1.3
|
|
81
|
+
* @stable
|
|
82
|
+
* @example
|
|
83
|
+
* declare const s: TrendAngleState;
|
|
84
|
+
* declare const v: Viewport;
|
|
85
|
+
* const prims = decomposeTrendAngle(s, v);
|
|
86
|
+
* // prims[0].kind === "polyline"; prims[1].kind === "arc"; prims[2].kind === "text"
|
|
87
|
+
* void prims;
|
|
88
|
+
*/
|
|
89
|
+
export declare function decomposeTrendAngle(state: TrendAngleState, view: Viewport): ReadonlyArray<DrawPrimitive>;
|
|
90
|
+
//# sourceMappingURL=lines.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lines.d.ts","sourceRoot":"","sources":["../../../src/geometry/kinds/lines.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACR,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,SAAS,EACT,eAAe,EACf,iBAAiB,EACpB,MAAM,8BAA8B,CAAC;AAMtC,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAO3D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,GAAG,aAAa,CAAC,aAAa,CAAC,CAU5F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACnC,KAAK,EAAE,mBAAmB,EAC1B,IAAI,EAAE,QAAQ,GACf,aAAa,CAAC,aAAa,CAAC,CAa9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAClC,KAAK,EAAE,kBAAkB,EACzB,IAAI,EAAE,QAAQ,GACf,aAAa,CAAC,aAAa,CAAC,CAU9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACjC,KAAK,EAAE,iBAAiB,EACxB,IAAI,EAAE,QAAQ,GACf,aAAa,CAAC,aAAa,CAAC,CAa9B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAC9B,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,QAAQ,GACf,aAAa,CAAC,aAAa,CAAC,CAuB9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAC/B,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,QAAQ,GACf,aAAa,CAAC,aAAa,CAAC,CA8B9B"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// Copyright (c) 2026 Invinite. Licensed under the MIT License.
|
|
2
|
+
// See the LICENSE file in the repo root for full license text.
|
|
3
|
+
//
|
|
4
|
+
// Stroke + extension geometry moved from the canvas2d adapter's
|
|
5
|
+
// per-kind line renderers
|
|
6
|
+
// examples/canvas2d-adapter/src/render/draw/{line,horizontalLine,
|
|
7
|
+
// horizontalRay,verticalLine,crossLine,trendAngle}.ts.
|
|
8
|
+
// The originating math is invinite's line / ray / extended-line /
|
|
9
|
+
// horizontal-line / horizontal-ray / vertical-line / cross-line /
|
|
10
|
+
// trend-angle tools (commit 078f41fe2569d659d5aba726da8bcb5d3e2ced02,
|
|
11
|
+
// © Invinite); re-licensed MIT for chartlang.
|
|
12
|
+
import { dashPattern } from "../_lib/dash.js";
|
|
13
|
+
import { extendLineSegment } from "../_lib/lineExtend.js";
|
|
14
|
+
import { strokeOf } from "../_lib/strokeStyle.js";
|
|
15
|
+
import { priceToY, timeToX, worldPointToPixel } from "../project.js";
|
|
16
|
+
const DEFAULT_COLOR = "#000000";
|
|
17
|
+
const ANGLE_ARC_RADIUS_PX = 24;
|
|
18
|
+
const ANGLE_TEXT_FONT = "12px sans-serif";
|
|
19
|
+
const ANGLE_TEXT_OFFSET_PX = 6;
|
|
20
|
+
/**
|
|
21
|
+
* Decompose a `line` drawing — a single segment, optionally extended to
|
|
22
|
+
* the viewport edges via {@link extendLineSegment} (the `ray` /
|
|
23
|
+
* `extended-line` collapse).
|
|
24
|
+
*
|
|
25
|
+
* @since 1.3
|
|
26
|
+
* @stable
|
|
27
|
+
* @example
|
|
28
|
+
* declare const s: LineState;
|
|
29
|
+
* declare const v: Viewport;
|
|
30
|
+
* const prims = decomposeLine(s, v);
|
|
31
|
+
* // prims[0].kind === "polyline"
|
|
32
|
+
* void prims;
|
|
33
|
+
*/
|
|
34
|
+
export function decomposeLine(state, view) {
|
|
35
|
+
const a = worldPointToPixel(state.anchors[0], view);
|
|
36
|
+
const b = worldPointToPixel(state.anchors[1], view);
|
|
37
|
+
const { from, to } = extendLineSegment(a, b, { extendLeft: state.style.extendLeft, extendRight: state.style.extendRight }, view);
|
|
38
|
+
return [{ kind: "polyline", points: [from, to], closed: false, stroke: strokeOf(state.style) }];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Decompose a `horizontal-line` drawing — a segment from `x = 0` to
|
|
42
|
+
* `x = view.pxWidth` at `priceToY(state.price)`.
|
|
43
|
+
*
|
|
44
|
+
* @since 1.3
|
|
45
|
+
* @stable
|
|
46
|
+
* @example
|
|
47
|
+
* declare const s: HorizontalLineState;
|
|
48
|
+
* declare const v: Viewport;
|
|
49
|
+
* const prims = decomposeHorizontalLine(s, v);
|
|
50
|
+
* void prims;
|
|
51
|
+
*/
|
|
52
|
+
export function decomposeHorizontalLine(state, view) {
|
|
53
|
+
const y = priceToY(state.price, view);
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
kind: "polyline",
|
|
57
|
+
points: [
|
|
58
|
+
{ x: 0, y },
|
|
59
|
+
{ x: view.pxWidth, y },
|
|
60
|
+
],
|
|
61
|
+
closed: false,
|
|
62
|
+
stroke: strokeOf(state.style),
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Decompose a `horizontal-ray` drawing — a segment from the projected
|
|
68
|
+
* anchor across the right edge at constant y.
|
|
69
|
+
*
|
|
70
|
+
* @since 1.3
|
|
71
|
+
* @stable
|
|
72
|
+
* @example
|
|
73
|
+
* declare const s: HorizontalRayState;
|
|
74
|
+
* declare const v: Viewport;
|
|
75
|
+
* const prims = decomposeHorizontalRay(s, v);
|
|
76
|
+
* void prims;
|
|
77
|
+
*/
|
|
78
|
+
export function decomposeHorizontalRay(state, view) {
|
|
79
|
+
const origin = worldPointToPixel(state.anchor, view);
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
kind: "polyline",
|
|
83
|
+
points: [origin, { x: view.pxWidth, y: origin.y }],
|
|
84
|
+
closed: false,
|
|
85
|
+
stroke: strokeOf(state.style),
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Decompose a `vertical-line` drawing — a segment from `y = 0` to
|
|
91
|
+
* `y = view.pxHeight` at `timeToX(state.time)`.
|
|
92
|
+
*
|
|
93
|
+
* @since 1.3
|
|
94
|
+
* @stable
|
|
95
|
+
* @example
|
|
96
|
+
* declare const s: VerticalLineState;
|
|
97
|
+
* declare const v: Viewport;
|
|
98
|
+
* const prims = decomposeVerticalLine(s, v);
|
|
99
|
+
* void prims;
|
|
100
|
+
*/
|
|
101
|
+
export function decomposeVerticalLine(state, view) {
|
|
102
|
+
const x = timeToX(state.time, view);
|
|
103
|
+
return [
|
|
104
|
+
{
|
|
105
|
+
kind: "polyline",
|
|
106
|
+
points: [
|
|
107
|
+
{ x, y: 0 },
|
|
108
|
+
{ x, y: view.pxHeight },
|
|
109
|
+
],
|
|
110
|
+
closed: false,
|
|
111
|
+
stroke: strokeOf(state.style),
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Decompose a `cross-line` drawing — a horizontal segment and a
|
|
117
|
+
* vertical segment crossing at the projected anchor. Two polylines
|
|
118
|
+
* sharing one stroke style.
|
|
119
|
+
*
|
|
120
|
+
* @since 1.3
|
|
121
|
+
* @stable
|
|
122
|
+
* @example
|
|
123
|
+
* declare const s: CrossLineState;
|
|
124
|
+
* declare const v: Viewport;
|
|
125
|
+
* const prims = decomposeCrossLine(s, v);
|
|
126
|
+
* // prims.length === 2
|
|
127
|
+
* void prims;
|
|
128
|
+
*/
|
|
129
|
+
export function decomposeCrossLine(state, view) {
|
|
130
|
+
const p = worldPointToPixel(state.anchor, view);
|
|
131
|
+
const stroke = strokeOf(state.style);
|
|
132
|
+
return [
|
|
133
|
+
{
|
|
134
|
+
kind: "polyline",
|
|
135
|
+
points: [
|
|
136
|
+
{ x: 0, y: p.y },
|
|
137
|
+
{ x: view.pxWidth, y: p.y },
|
|
138
|
+
],
|
|
139
|
+
closed: false,
|
|
140
|
+
stroke,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
kind: "polyline",
|
|
144
|
+
points: [
|
|
145
|
+
{ x: p.x, y: 0 },
|
|
146
|
+
{ x: p.x, y: view.pxHeight },
|
|
147
|
+
],
|
|
148
|
+
closed: false,
|
|
149
|
+
stroke,
|
|
150
|
+
},
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Decompose a `trend-angle` drawing — the main segment, a small arc
|
|
155
|
+
* spanning the screen-space angle off the first anchor, and the angle
|
|
156
|
+
* label in degrees. The arc is solid regardless of `lineStyle` so it
|
|
157
|
+
* reads cleanly; the angle is measured in screen-pixel space with the
|
|
158
|
+
* canvas y-axis flipped (`-dy`) so a positive angle reads "upward to
|
|
159
|
+
* the right" (matching the source renderer's convention).
|
|
160
|
+
*
|
|
161
|
+
* @since 1.3
|
|
162
|
+
* @stable
|
|
163
|
+
* @example
|
|
164
|
+
* declare const s: TrendAngleState;
|
|
165
|
+
* declare const v: Viewport;
|
|
166
|
+
* const prims = decomposeTrendAngle(s, v);
|
|
167
|
+
* // prims[0].kind === "polyline"; prims[1].kind === "arc"; prims[2].kind === "text"
|
|
168
|
+
* void prims;
|
|
169
|
+
*/
|
|
170
|
+
export function decomposeTrendAngle(state, view) {
|
|
171
|
+
const a = worldPointToPixel(state.anchors[0], view);
|
|
172
|
+
const b = worldPointToPixel(state.anchors[1], view);
|
|
173
|
+
const color = state.style.color ?? DEFAULT_COLOR;
|
|
174
|
+
const stroke = strokeOf(state.style);
|
|
175
|
+
const angleRad = Math.atan2(-(b.y - a.y), b.x - a.x);
|
|
176
|
+
const degrees = (angleRad * 180) / Math.PI;
|
|
177
|
+
return [
|
|
178
|
+
{ kind: "polyline", points: [a, b], closed: false, stroke },
|
|
179
|
+
{
|
|
180
|
+
kind: "arc",
|
|
181
|
+
cx: a.x,
|
|
182
|
+
cy: a.y,
|
|
183
|
+
r: ANGLE_ARC_RADIUS_PX,
|
|
184
|
+
start: -angleRad,
|
|
185
|
+
end: 0,
|
|
186
|
+
closed: false,
|
|
187
|
+
stroke: { color, width: stroke.width, dash: dashPattern("solid") },
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
kind: "text",
|
|
191
|
+
x: a.x + ANGLE_ARC_RADIUS_PX + ANGLE_TEXT_OFFSET_PX,
|
|
192
|
+
y: a.y,
|
|
193
|
+
text: `${degrees.toFixed(1)}°`,
|
|
194
|
+
color,
|
|
195
|
+
font: ANGLE_TEXT_FONT,
|
|
196
|
+
align: "left",
|
|
197
|
+
baseline: "middle",
|
|
198
|
+
},
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=lines.js.map
|