@glissade/scene 0.37.0-pre.1 → 0.38.0-pre.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/dist/describe.js +13 -1
- package/dist/gauge.d.ts +104 -0
- package/dist/gauge.js +171 -0
- package/package.json +6 -2
package/dist/describe.js
CHANGED
|
@@ -23,7 +23,7 @@ import { easings, listValueTypes } from "@glissade/core";
|
|
|
23
23
|
* never pulled onto the base embed path — a scene that never calls `describe()`
|
|
24
24
|
* pays zero bytes for it.
|
|
25
25
|
*/
|
|
26
|
-
const RAW_VERSION = "0.
|
|
26
|
+
const RAW_VERSION = "0.38.0-pre.0";
|
|
27
27
|
const PACKAGE_VERSION = RAW_VERSION.includes("GLISSADE_".concat("VERSION")) ? "0.0.0-dev" : RAW_VERSION;
|
|
28
28
|
/** Arity of a value type's numeric repr: vec2/vec2-arc → 2, number → 1; others (color/paint/path/string/boolean) carry no scalar arity. */
|
|
29
29
|
function arityOf(type) {
|
|
@@ -435,6 +435,18 @@ const HELPERS = [
|
|
|
435
435
|
import: "@glissade/scene/component",
|
|
436
436
|
usage: "defineComponent({ name, props: { <p>: { type, required? } }, build(props, childId): Group }): (props & { id }) => { node: Group, id, childId(sub?), targets(child, prop) }"
|
|
437
437
|
},
|
|
438
|
+
{
|
|
439
|
+
name: "Gauge",
|
|
440
|
+
summary: "Build-time radial gauge (data-viz, like Chart): a spec → N categorical stroked-arc zones + boundary ticks + a needle + separate labels, returning a Group. Angle deg: 0=up, +=clockwise. Needle takes AUTHORED keys (tl on targets(\"needle\",\"rotation\")) OR value→angle (Meter mode). Zones/ticks/needle/labels are each addressable sub-ids (zone-{i}, tick-{i}, needle, label-{i}, glow); labels draw z-above zones so a zone dim never crushes a label. Tree-shaken off the base scene index.",
|
|
441
|
+
import: "@glissade/scene/gauge",
|
|
442
|
+
usage: "Gauge({ id, radius, zones: { extent: [start,end], color, label?, labelStyle?: { family?, size?, fill?, weight? } }[], thickness?, gap?, needle?, needleAngle?, value?, domain?, sweep?, ticks?, apexEmphasis?: boolean | number, glow?, position? }): { node: Group, id, childId(sub?), targets(sub, prop): string[] }"
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
name: "Meter",
|
|
446
|
+
summary: "The Gauge value preset: a value (or () => value signal) mapped through domain across the sweep → the needle angle. Same result shape + sub-ids as Gauge. A function value binds live (the needle follows the signal). On the @glissade/scene/gauge subpath.",
|
|
447
|
+
import: "@glissade/scene/gauge",
|
|
448
|
+
usage: "Meter({ id, radius, zones, value: number | (() => number), domain?, sweep?, … }): { node: Group, id, childId, targets }"
|
|
449
|
+
},
|
|
438
450
|
{
|
|
439
451
|
name: "linearScale",
|
|
440
452
|
summary: "A serializable linear scale (value axis): maps a numeric domain onto a pixel/unit range. Pair with Chart({ yScale }). On the @glissade/scene/chart subpath.",
|
package/dist/gauge.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { a as Group } from "./nodes.js";
|
|
2
|
+
|
|
3
|
+
//#region src/gauge.d.ts
|
|
4
|
+
|
|
5
|
+
declare class GaugeError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
/** One categorical zone: an angular `extent` (degrees), a color, an optional label. */
|
|
9
|
+
interface GaugeZone {
|
|
10
|
+
/** `[start, end]` in degrees (0 = up, + = clockwise). start < end. */
|
|
11
|
+
readonly extent: readonly [number, number];
|
|
12
|
+
/** the zone arc's stroke color. */
|
|
13
|
+
readonly color: string;
|
|
14
|
+
/** optional label drawn at the zone's mid-angle (its own addressable node). */
|
|
15
|
+
readonly label?: string;
|
|
16
|
+
/**
|
|
17
|
+
* per-label style override (family / size / fill / weight) — layered OVER the
|
|
18
|
+
* computed default (uniform size/fill + the apex size-up). Lets a consumer keep
|
|
19
|
+
* a per-episode text override AND restyle any single `label-{i}`.
|
|
20
|
+
*/
|
|
21
|
+
readonly labelStyle?: {
|
|
22
|
+
readonly family?: string;
|
|
23
|
+
readonly size?: number;
|
|
24
|
+
readonly fill?: string;
|
|
25
|
+
readonly weight?: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
interface GaugeSpec {
|
|
29
|
+
/** stable id — every child is namespaced under it. */
|
|
30
|
+
id: string;
|
|
31
|
+
/** arc radius (px), measured to the centerline of the zone band. */
|
|
32
|
+
radius: number;
|
|
33
|
+
/** the categorical zones, in angular order. At least one. */
|
|
34
|
+
zones: readonly GaugeZone[];
|
|
35
|
+
/** zone-arc stroke width (px). Default `radius * 0.14`. */
|
|
36
|
+
thickness?: number;
|
|
37
|
+
/** degrees trimmed off EACH side of every zone boundary (a visual gap). Default 0. */
|
|
38
|
+
gap?: number;
|
|
39
|
+
/** draw a needle (default true). Object form overrides its geometry. */
|
|
40
|
+
needle?: boolean | {
|
|
41
|
+
length?: number;
|
|
42
|
+
width?: number;
|
|
43
|
+
color?: string;
|
|
44
|
+
};
|
|
45
|
+
/** authored initial needle angle (deg, 0 = up). Ignored when `value` is set. */
|
|
46
|
+
needleAngle?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Meter mode: a value (or `() => value` signal) mapped through `domain` across
|
|
49
|
+
* the gauge's total sweep → the needle angle. A function binds live (the needle
|
|
50
|
+
* follows the signal); a number sets the initial angle. Omit for authored mode.
|
|
51
|
+
*/
|
|
52
|
+
value?: number | (() => number);
|
|
53
|
+
/** value domain for Meter mode (default `[0, 1]`). */
|
|
54
|
+
domain?: readonly [number, number];
|
|
55
|
+
/** total angular sweep `[start, end]` the value maps across. Default = zone union. */
|
|
56
|
+
sweep?: readonly [number, number];
|
|
57
|
+
/** draw boundary ticks at each distinct zone edge (default true). */
|
|
58
|
+
ticks?: boolean;
|
|
59
|
+
/** label font size (px). Default `radius * 0.13`. The mid zone's label is a size up. */
|
|
60
|
+
labelSize?: number;
|
|
61
|
+
/** label color. Default `#eaf1ff`. */
|
|
62
|
+
labelFill?: string;
|
|
63
|
+
/** label font family. Default `sans-serif`. */
|
|
64
|
+
fontFamily?: string;
|
|
65
|
+
/**
|
|
66
|
+
* emphasize the apex zone's label (the one straddling straight-up): `true`
|
|
67
|
+
* (default) = ×1.18 size + bold; `false` = no size-up (portrait-safe — the
|
|
68
|
+
* narrower stage can't afford the bump); a number = a custom size scale + bold.
|
|
69
|
+
* Gauge is a build-time factory and can't see the stage aspect, so gate this on
|
|
70
|
+
* your own `isPortrait(size)`.
|
|
71
|
+
*/
|
|
72
|
+
apexEmphasis?: boolean | number;
|
|
73
|
+
/** add a center glow Circle (`glow` sub-id, opacity 0 — author animates it). */
|
|
74
|
+
glow?: boolean | {
|
|
75
|
+
color?: string;
|
|
76
|
+
radius?: number;
|
|
77
|
+
};
|
|
78
|
+
/** where to place the gauge center in the parent (default the parent origin). */
|
|
79
|
+
position?: readonly [number, number];
|
|
80
|
+
}
|
|
81
|
+
interface GaugeResult {
|
|
82
|
+
/** the built subtree (a Group whose id === the gauge id). */
|
|
83
|
+
readonly node: Group;
|
|
84
|
+
readonly id: string;
|
|
85
|
+
/** namespace a child id: `childId('needle')` → `'<id>/needle'`; no arg → the root id. */
|
|
86
|
+
childId(sub?: string): string;
|
|
87
|
+
/** ready-to-bind track targets: `targets('needle','rotation')` → `['<id>/needle/rotation']`. */
|
|
88
|
+
targets(sub: string, prop: string): string[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Build a radial arc gauge. Returns the subtree + the child-id/target helpers.
|
|
92
|
+
* Pure: runs once at construction, emits ordinary nodes, nothing at play time.
|
|
93
|
+
*/
|
|
94
|
+
declare function Gauge(spec: GaugeSpec): GaugeResult;
|
|
95
|
+
/**
|
|
96
|
+
* `Meter()` — the Gauge value preset: a single value (or signal) mapped through a
|
|
97
|
+
* domain to the needle across a colored track. Convenience over
|
|
98
|
+
* `Gauge({ value, domain, zones })`; the same result shape + sub-ids.
|
|
99
|
+
*/
|
|
100
|
+
declare function Meter(spec: Omit<GaugeSpec, 'value'> & {
|
|
101
|
+
value: number | (() => number);
|
|
102
|
+
}): GaugeResult;
|
|
103
|
+
//#endregion
|
|
104
|
+
export { Gauge, GaugeError, GaugeResult, GaugeSpec, GaugeZone, Meter };
|
package/dist/gauge.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { a as Path, o as Rect, r as Group, s as Text, t as Circle } from "./nodes.js";
|
|
2
|
+
//#region src/gauge.ts
|
|
3
|
+
var GaugeError = class extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "GaugeError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
const DEG = Math.PI / 180;
|
|
10
|
+
/** Linear remap of `v` from `domain` onto `range` (the value→angle map, Meter mode). */
|
|
11
|
+
function mapLinear(v, domain, range) {
|
|
12
|
+
const [d0, d1] = domain;
|
|
13
|
+
const [r0, r1] = range;
|
|
14
|
+
return d1 === d0 ? r0 : r0 + (v - d0) / (d1 - d0) * (r1 - r0);
|
|
15
|
+
}
|
|
16
|
+
/** A point on the gauge circle at angle `g` (deg, 0 = up, + = clockwise). */
|
|
17
|
+
function pointAt(radius, g) {
|
|
18
|
+
const r = g * DEG;
|
|
19
|
+
return [radius * Math.sin(r), -radius * Math.cos(r)];
|
|
20
|
+
}
|
|
21
|
+
/** An open polyline contour tracing the arc `a→b` at `radius`, sampled ≤ `step` deg. */
|
|
22
|
+
function arcContour(radius, a, b, step = 2) {
|
|
23
|
+
const n = Math.max(1, Math.ceil(Math.abs(b - a) / step));
|
|
24
|
+
const v = [];
|
|
25
|
+
for (let k = 0; k <= n; k++) v.push(pointAt(radius, a + (b - a) * k / n));
|
|
26
|
+
const zero = v.map(() => [0, 0]);
|
|
27
|
+
return [{
|
|
28
|
+
closed: false,
|
|
29
|
+
v,
|
|
30
|
+
in: zero,
|
|
31
|
+
out: zero
|
|
32
|
+
}];
|
|
33
|
+
}
|
|
34
|
+
/** A closed triangle pointing up (−y), tip at `(0,-len)`, base width `w`. */
|
|
35
|
+
function needleContour(len, w) {
|
|
36
|
+
const v = [
|
|
37
|
+
[0, -len],
|
|
38
|
+
[-w / 2, 0],
|
|
39
|
+
[w / 2, 0]
|
|
40
|
+
];
|
|
41
|
+
const zero = v.map(() => [0, 0]);
|
|
42
|
+
return [{
|
|
43
|
+
closed: true,
|
|
44
|
+
v,
|
|
45
|
+
in: zero,
|
|
46
|
+
out: zero
|
|
47
|
+
}];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build a radial arc gauge. Returns the subtree + the child-id/target helpers.
|
|
51
|
+
* Pure: runs once at construction, emits ordinary nodes, nothing at play time.
|
|
52
|
+
*/
|
|
53
|
+
function Gauge(spec) {
|
|
54
|
+
const { id } = spec;
|
|
55
|
+
if (!id) throw new GaugeError("Gauge needs a stable id (every child is namespaced under it)");
|
|
56
|
+
if (!(spec.radius > 0)) throw new GaugeError(`Gauge '${id}': radius must be > 0 (got ${spec.radius})`);
|
|
57
|
+
if (!spec.zones || spec.zones.length === 0) throw new GaugeError(`Gauge '${id}': needs at least one zone`);
|
|
58
|
+
const radius = spec.radius;
|
|
59
|
+
const thickness = spec.thickness ?? radius * .14;
|
|
60
|
+
const gap = spec.gap ?? 0;
|
|
61
|
+
const cid = (sub) => sub === void 0 || sub === "" ? id : `${id}/${sub}`;
|
|
62
|
+
let lo = Infinity;
|
|
63
|
+
let hi = -Infinity;
|
|
64
|
+
spec.zones.forEach((z, i) => {
|
|
65
|
+
const [a, b] = z.extent;
|
|
66
|
+
if (!(a < b)) throw new GaugeError(`Gauge '${id}': zone ${i} extent must be [start < end] (got [${a}, ${b}])`);
|
|
67
|
+
lo = Math.min(lo, a);
|
|
68
|
+
hi = Math.max(hi, b);
|
|
69
|
+
});
|
|
70
|
+
const children = [];
|
|
71
|
+
if (spec.glow) {
|
|
72
|
+
const g = typeof spec.glow === "object" ? spec.glow : {};
|
|
73
|
+
children.push(new Circle({
|
|
74
|
+
id: cid("glow"),
|
|
75
|
+
radius: g.radius ?? radius * .5,
|
|
76
|
+
fill: g.color ?? "#ffffff",
|
|
77
|
+
opacity: 0
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
spec.zones.forEach((z, i) => {
|
|
81
|
+
const [a, b] = z.extent;
|
|
82
|
+
children.push(new Path({
|
|
83
|
+
id: cid(`zone-${i}`),
|
|
84
|
+
data: arcContour(radius, a + gap / 2, b - gap / 2),
|
|
85
|
+
stroke: z.color,
|
|
86
|
+
strokeWidth: thickness
|
|
87
|
+
}));
|
|
88
|
+
});
|
|
89
|
+
if (spec.ticks !== false) {
|
|
90
|
+
const edges = /* @__PURE__ */ new Set();
|
|
91
|
+
spec.zones.forEach((z) => {
|
|
92
|
+
edges.add(z.extent[0]);
|
|
93
|
+
edges.add(z.extent[1]);
|
|
94
|
+
});
|
|
95
|
+
let t = 0;
|
|
96
|
+
for (const g of [...edges].sort((x, y) => x - y)) {
|
|
97
|
+
const [x, y] = pointAt(radius, g);
|
|
98
|
+
children.push(new Rect({
|
|
99
|
+
id: cid(`tick-${t++}`),
|
|
100
|
+
anchor: "center",
|
|
101
|
+
position: [x, y],
|
|
102
|
+
width: Math.max(1, thickness * .12),
|
|
103
|
+
height: thickness * 1.4,
|
|
104
|
+
rotation: g,
|
|
105
|
+
fill: "#ffffff"
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (spec.needle !== false) {
|
|
110
|
+
const n = typeof spec.needle === "object" ? spec.needle : {};
|
|
111
|
+
const len = n.length ?? radius * .92;
|
|
112
|
+
const w = n.width ?? Math.max(4, radius * .05);
|
|
113
|
+
let rotation;
|
|
114
|
+
if (spec.value !== void 0) {
|
|
115
|
+
const sweep = spec.sweep ?? [lo, hi];
|
|
116
|
+
const domain = spec.domain ?? [0, 1];
|
|
117
|
+
rotation = typeof spec.value === "function" ? (() => mapLinear(spec.value(), domain, sweep)) : mapLinear(spec.value, domain, sweep);
|
|
118
|
+
} else rotation = spec.needleAngle ?? 0;
|
|
119
|
+
children.push(new Path({
|
|
120
|
+
id: cid("needle"),
|
|
121
|
+
data: needleContour(len, w),
|
|
122
|
+
fill: n.color ?? "#eaf1ff",
|
|
123
|
+
rotation
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
const labelSize = spec.labelSize ?? radius * .13;
|
|
127
|
+
const labelFill = spec.labelFill ?? "#eaf1ff";
|
|
128
|
+
const fontFamily = spec.fontFamily ?? "sans-serif";
|
|
129
|
+
const apex = spec.apexEmphasis ?? true;
|
|
130
|
+
const apexScale = apex === false ? 1 : apex === true ? 1.18 : apex;
|
|
131
|
+
const apexWeight = apex === false ? 400 : 700;
|
|
132
|
+
spec.zones.forEach((z, i) => {
|
|
133
|
+
if (z.label === void 0) return;
|
|
134
|
+
const mid = (z.extent[0] + z.extent[1]) / 2;
|
|
135
|
+
const [x, y] = pointAt(radius + thickness / 2 + labelSize * .9, mid);
|
|
136
|
+
const isApex = z.extent[0] < 0 && z.extent[1] > 0;
|
|
137
|
+
const st = z.labelStyle ?? {};
|
|
138
|
+
children.push(new Text({
|
|
139
|
+
id: cid(`label-${i}`),
|
|
140
|
+
text: z.label,
|
|
141
|
+
position: [x, y],
|
|
142
|
+
anchor: "center",
|
|
143
|
+
align: "center",
|
|
144
|
+
fill: st.fill ?? labelFill,
|
|
145
|
+
fontFamily: st.family ?? fontFamily,
|
|
146
|
+
fontSize: st.size ?? (isApex ? labelSize * apexScale : labelSize),
|
|
147
|
+
fontWeight: st.weight ?? (isApex ? apexWeight : 400),
|
|
148
|
+
box: { valign: "center" }
|
|
149
|
+
}));
|
|
150
|
+
});
|
|
151
|
+
return {
|
|
152
|
+
node: new Group({
|
|
153
|
+
id,
|
|
154
|
+
children,
|
|
155
|
+
...spec.position ? { position: [spec.position[0], spec.position[1]] } : {}
|
|
156
|
+
}),
|
|
157
|
+
id,
|
|
158
|
+
childId: cid,
|
|
159
|
+
targets: (sub, prop) => [`${cid(sub)}/${prop}`]
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* `Meter()` — the Gauge value preset: a single value (or signal) mapped through a
|
|
164
|
+
* domain to the needle across a colored track. Convenience over
|
|
165
|
+
* `Gauge({ value, domain, zones })`; the same result shape + sub-ids.
|
|
166
|
+
*/
|
|
167
|
+
function Meter(spec) {
|
|
168
|
+
return Gauge(spec);
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
export { Gauge, GaugeError, Meter };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/scene",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0-pre.0",
|
|
4
4
|
"description": "glissade scene graph: nodes, transforms, DisplayList emission. Renderer-agnostic; zero DOM/Node dependencies.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"engines": {
|
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
"types": "./dist/chart.d.ts",
|
|
32
32
|
"default": "./dist/chart.js"
|
|
33
33
|
},
|
|
34
|
+
"./gauge": {
|
|
35
|
+
"types": "./dist/gauge.d.ts",
|
|
36
|
+
"default": "./dist/gauge.js"
|
|
37
|
+
},
|
|
34
38
|
"./component": {
|
|
35
39
|
"types": "./dist/component.d.ts",
|
|
36
40
|
"default": "./dist/component.js"
|
|
@@ -73,7 +77,7 @@
|
|
|
73
77
|
],
|
|
74
78
|
"dependencies": {
|
|
75
79
|
"yoga-layout": "^3.2.1",
|
|
76
|
-
"@glissade/core": "0.
|
|
80
|
+
"@glissade/core": "0.38.0-pre.0"
|
|
77
81
|
},
|
|
78
82
|
"repository": {
|
|
79
83
|
"type": "git",
|