@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.57 → 2.0.0-next.58
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/bundle/openbridge-webcomponents.bundle.js +592 -239
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +292 -8
- package/dist/building-blocks/instrument-radial/instrument-radial.d.ts +10 -0
- package/dist/building-blocks/instrument-radial/instrument-radial.d.ts.map +1 -1
- package/dist/building-blocks/instrument-radial/instrument-radial.js +86 -21
- package/dist/building-blocks/instrument-radial/instrument-radial.js.map +1 -1
- package/dist/navigation-instruments/gauge-radial/gauge-radial.css.js +99 -0
- package/dist/navigation-instruments/gauge-radial/gauge-radial.css.js.map +1 -0
- package/dist/navigation-instruments/gauge-radial/gauge-radial.d.ts +42 -7
- package/dist/navigation-instruments/gauge-radial/gauge-radial.d.ts.map +1 -1
- package/dist/navigation-instruments/gauge-radial/gauge-radial.js +178 -31
- package/dist/navigation-instruments/gauge-radial/gauge-radial.js.map +1 -1
- package/dist/navigation-instruments/readout/readout.css.js +4 -0
- package/dist/navigation-instruments/readout/readout.css.js.map +1 -1
- package/dist/navigation-instruments/watch/tickmark.d.ts +2 -1
- package/dist/navigation-instruments/watch/tickmark.d.ts.map +1 -1
- package/dist/navigation-instruments/watch/tickmark.js +24 -4
- package/dist/navigation-instruments/watch/tickmark.js.map +1 -1
- package/dist/navigation-instruments/watch/watch.d.ts +23 -1
- package/dist/navigation-instruments/watch/watch.d.ts.map +1 -1
- package/dist/navigation-instruments/watch/watch.js +48 -20
- package/dist/navigation-instruments/watch/watch.js.map +1 -1
- package/package.json +1 -1
- package/src/building-blocks/instrument-radial/instrument-radial.stories.ts +21 -0
- package/src/building-blocks/instrument-radial/instrument-radial.ts +118 -21
- package/src/navigation-instruments/gauge-radial/gauge-radial.css +85 -0
- package/src/navigation-instruments/gauge-radial/gauge-radial.stories.ts +149 -7
- package/src/navigation-instruments/gauge-radial/gauge-radial.ts +212 -33
- package/src/navigation-instruments/readout/readout.css +4 -0
- package/src/navigation-instruments/watch/tickmark.ts +33 -6
- package/src/navigation-instruments/watch/watch.ts +60 -25
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: block;
|
|
3
|
+
width: 100%;
|
|
4
|
+
height: 100%;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* Host takes each sector's crop aspect (see `sectorClips`): 270/90 square, 180
|
|
8
|
+
wide — so the dial is the same size at any width. */
|
|
9
|
+
:host([sector="270"]),
|
|
10
|
+
:host([sector="90-left"]),
|
|
11
|
+
:host([sector="90-right"]) {
|
|
12
|
+
height: auto;
|
|
13
|
+
aspect-ratio: 1;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* 448 / 251 = the 180° crop's aspect (448 wide × 448·(1 − 44%) tall); keep in
|
|
17
|
+
sync with `sectorClips` bottom=44 in gauge-radial.ts. */
|
|
18
|
+
:host([sector="180"]) {
|
|
19
|
+
height: auto;
|
|
20
|
+
aspect-ratio: 448 / 251;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.gauge-radial-root {
|
|
24
|
+
/* Readout vertical placement per layout (% of the cropped host), tuned to the
|
|
25
|
+
watch geometry — re-check if the ring radii or the 448 box change. Which
|
|
26
|
+
layout uses which is in renderReadouts(). */
|
|
27
|
+
--readout-meta-top: 72%;
|
|
28
|
+
--readout-meta-top-needle: 63%;
|
|
29
|
+
--readout-meta-top-180: 60%;
|
|
30
|
+
|
|
31
|
+
position: relative;
|
|
32
|
+
width: 100%;
|
|
33
|
+
height: 100%;
|
|
34
|
+
|
|
35
|
+
.gauge-readout-value {
|
|
36
|
+
position: absolute;
|
|
37
|
+
left: 50%;
|
|
38
|
+
top: 50%;
|
|
39
|
+
transform: translate(-50%, -50%);
|
|
40
|
+
z-index: 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.gauge-readout-meta {
|
|
44
|
+
position: absolute;
|
|
45
|
+
left: 50%;
|
|
46
|
+
top: var(--readout-meta-top);
|
|
47
|
+
transform: translateX(-50%);
|
|
48
|
+
z-index: 1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&.type-needle .gauge-readout-meta {
|
|
52
|
+
top: var(--readout-meta-top-needle);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&.sector-180 .gauge-readout-meta {
|
|
56
|
+
top: var(--readout-meta-top-180);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Nudge the corner readout inward (up + toward center) so it sits inside the
|
|
60
|
+
open quadrant rather than hugging the box corner. Symmetric for both
|
|
61
|
+
orientations — only the horizontal edge differs. */
|
|
62
|
+
&.sector-90-left .gauge-readout-meta,
|
|
63
|
+
&.sector-90-right .gauge-readout-meta {
|
|
64
|
+
--readout-90-inset: 6%;
|
|
65
|
+
bottom: var(--readout-90-inset);
|
|
66
|
+
top: auto;
|
|
67
|
+
transform: none;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
&.sector-90-left .gauge-readout-meta {
|
|
71
|
+
left: auto;
|
|
72
|
+
right: var(--readout-90-inset);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&.sector-90-right .gauge-readout-meta {
|
|
76
|
+
left: var(--readout-90-inset);
|
|
77
|
+
right: auto;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* 90-right readout sits in the bottom-left corner, so left-align the value
|
|
81
|
+
under the label (enhanced readouts right-align the value by default). */
|
|
82
|
+
&.sector-90-right .gauge-readout-meta::part(value-wrapper) {
|
|
83
|
+
justify-self: start;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -1,33 +1,57 @@
|
|
|
1
1
|
import type {Meta, StoryObj} from '@storybook/web-components-vite';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
GaugeRadialSector,
|
|
4
|
+
ObcGaugeRadial,
|
|
5
|
+
ObcGaugeRadialType,
|
|
6
|
+
} from './gauge-radial.js';
|
|
3
7
|
import './gauge-radial.js';
|
|
4
8
|
import {widthDecorator} from '../../storybook-util.js';
|
|
5
9
|
import {AdviceType} from '../watch/advice.js';
|
|
6
10
|
import {TickmarkStyle} from '../watch/tickmark.js';
|
|
7
11
|
import {InstrumentState, Priority} from '../types.js';
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
type GaugeRadialStoryArgs = Partial<ObcGaugeRadial> & {
|
|
14
|
+
width?: number;
|
|
15
|
+
height?: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const meta = {
|
|
10
19
|
title: 'Instruments/Gauge Radial',
|
|
11
|
-
tags: ['6.0'],
|
|
20
|
+
tags: ['autodocs', '6.0'],
|
|
12
21
|
component: 'obc-gauge-radial',
|
|
13
22
|
decorators: [widthDecorator],
|
|
14
23
|
args: {
|
|
15
24
|
width: 400,
|
|
16
25
|
},
|
|
17
26
|
argTypes: {
|
|
18
|
-
|
|
27
|
+
width: {control: {type: 'range', min: 100, max: 1000, step: 1}},
|
|
28
|
+
height: {control: {type: 'range', min: 100, max: 1000, step: 1}},
|
|
29
|
+
type: {control: 'select', options: Object.values(ObcGaugeRadialType)},
|
|
30
|
+
sector: {control: 'select', options: Object.values(GaugeRadialSector)},
|
|
19
31
|
priority: {control: 'select', options: Object.values(Priority)},
|
|
20
|
-
|
|
32
|
+
state: {control: 'select', options: Object.values(InstrumentState)},
|
|
21
33
|
tickmarkStyle: {
|
|
22
34
|
control: 'select',
|
|
23
35
|
options: Object.values(TickmarkStyle),
|
|
24
36
|
},
|
|
25
37
|
showLabels: {control: 'boolean'},
|
|
38
|
+
tickmarksInside: {control: 'boolean'},
|
|
39
|
+
showReadout: {control: 'boolean'},
|
|
40
|
+
value: {control: 'number'},
|
|
41
|
+
minValue: {control: 'number'},
|
|
42
|
+
maxValue: {control: 'number'},
|
|
43
|
+
primaryTickmarkInterval: {control: 'number'},
|
|
44
|
+
secondaryTickmarkInterval: {control: 'number'},
|
|
45
|
+
tertiaryTickmarkInterval: {control: 'number'},
|
|
46
|
+
fractionDigits: {control: 'number'},
|
|
47
|
+
label: {control: 'text'},
|
|
48
|
+
unit: {control: 'text'},
|
|
49
|
+
advices: {control: 'object'},
|
|
26
50
|
},
|
|
27
|
-
} satisfies Meta<
|
|
51
|
+
} satisfies Meta<GaugeRadialStoryArgs>;
|
|
28
52
|
|
|
29
53
|
export default meta;
|
|
30
|
-
type Story = StoryObj<
|
|
54
|
+
type Story = StoryObj<GaugeRadialStoryArgs>;
|
|
31
55
|
|
|
32
56
|
export const Positive: Story = {
|
|
33
57
|
args: {
|
|
@@ -128,6 +152,124 @@ export const IrregularRange: Story = {
|
|
|
128
152
|
},
|
|
129
153
|
};
|
|
130
154
|
|
|
155
|
+
const sectorStoryArgs = {
|
|
156
|
+
value: 50,
|
|
157
|
+
minValue: 0,
|
|
158
|
+
maxValue: 100,
|
|
159
|
+
type: ObcGaugeRadialType.filled,
|
|
160
|
+
showLabels: true,
|
|
161
|
+
} as const;
|
|
162
|
+
|
|
163
|
+
export const Sector270: Story = {
|
|
164
|
+
args: {
|
|
165
|
+
...sectorStoryArgs,
|
|
166
|
+
sector: GaugeRadialSector.deg270,
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export const Sector180: Story = {
|
|
171
|
+
args: {
|
|
172
|
+
...sectorStoryArgs,
|
|
173
|
+
sector: GaugeRadialSector.deg180,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const Sector180WithReadout: Story = {
|
|
178
|
+
args: {
|
|
179
|
+
...sectorStoryArgs,
|
|
180
|
+
sector: GaugeRadialSector.deg180,
|
|
181
|
+
showReadout: true,
|
|
182
|
+
label: 'Label',
|
|
183
|
+
unit: 'unit',
|
|
184
|
+
value: 123,
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// Regression for the width-derived "Max-min" end-label inset: wide labels on
|
|
189
|
+
// both ends (-250 / 1000). The old per-side literals (11/6) broke for these;
|
|
190
|
+
// min/max are interval multiples so the end labels sit on the primary grid.
|
|
191
|
+
export const Sector180WideEndLabels: Story = {
|
|
192
|
+
args: {
|
|
193
|
+
...sectorStoryArgs,
|
|
194
|
+
sector: GaugeRadialSector.deg180,
|
|
195
|
+
tickmarksInside: true,
|
|
196
|
+
minValue: -250,
|
|
197
|
+
maxValue: 1000,
|
|
198
|
+
value: 500,
|
|
199
|
+
primaryTickmarkInterval: 250,
|
|
200
|
+
secondaryTickmarkInterval: 50,
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export const Sector90Left: Story = {
|
|
205
|
+
args: {
|
|
206
|
+
...sectorStoryArgs,
|
|
207
|
+
sector: GaugeRadialSector.deg90Left,
|
|
208
|
+
width: 200,
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export const Sector90Right: Story = {
|
|
213
|
+
args: {
|
|
214
|
+
...sectorStoryArgs,
|
|
215
|
+
sector: GaugeRadialSector.deg90Right,
|
|
216
|
+
width: 200,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export const Sector90LeftWithReadout: Story = {
|
|
221
|
+
args: {
|
|
222
|
+
...sectorStoryArgs,
|
|
223
|
+
sector: GaugeRadialSector.deg90Left,
|
|
224
|
+
showReadout: true,
|
|
225
|
+
label: 'Label',
|
|
226
|
+
unit: 'unit',
|
|
227
|
+
value: 123,
|
|
228
|
+
width: 200,
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export const Sector90RightWithReadout: Story = {
|
|
233
|
+
args: {
|
|
234
|
+
...sectorStoryArgs,
|
|
235
|
+
sector: GaugeRadialSector.deg90Right,
|
|
236
|
+
showReadout: true,
|
|
237
|
+
label: 'Label',
|
|
238
|
+
unit: 'unit',
|
|
239
|
+
value: 123,
|
|
240
|
+
width: 200,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const readoutStoryArgs = {
|
|
245
|
+
value: 123,
|
|
246
|
+
minValue: 0,
|
|
247
|
+
maxValue: 100,
|
|
248
|
+
type: ObcGaugeRadialType.filled,
|
|
249
|
+
sector: GaugeRadialSector.deg270,
|
|
250
|
+
showReadout: true,
|
|
251
|
+
label: 'Label',
|
|
252
|
+
unit: 'unit',
|
|
253
|
+
} as const;
|
|
254
|
+
|
|
255
|
+
export const WithReadout: Story = {
|
|
256
|
+
args: readoutStoryArgs,
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export const WithReadoutEnhanced: Story = {
|
|
260
|
+
args: {
|
|
261
|
+
...readoutStoryArgs,
|
|
262
|
+
priority: Priority.enhanced,
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export const WithReadoutNeedle: Story = {
|
|
267
|
+
args: {
|
|
268
|
+
...readoutStoryArgs,
|
|
269
|
+
type: ObcGaugeRadialType.needle,
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
|
|
131
273
|
export const WideAdviceSpan: Story = {
|
|
132
274
|
args: {
|
|
133
275
|
value: 320,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {LitElement, html} from 'lit';
|
|
1
|
+
import {LitElement, html, nothing, unsafeCSS, type TemplateResult} from 'lit';
|
|
2
|
+
import {classMap} from 'lit/directives/class-map.js';
|
|
3
|
+
import componentStyle from './gauge-radial.css?inline';
|
|
2
4
|
import {customElement} from '../../decorator.js';
|
|
3
5
|
import {property} from 'lit/decorators.js';
|
|
4
6
|
import {AdviceType} from '../watch/advice.js';
|
|
@@ -6,6 +8,11 @@ import {InstrumentState, Priority} from '../types.js';
|
|
|
6
8
|
import {SetpointMixin} from '../../svghelpers/setpoint-mixin.js';
|
|
7
9
|
import '../../building-blocks/instrument-radial/instrument-radial.js';
|
|
8
10
|
import {TickmarkStyle} from '../watch/tickmark.js';
|
|
11
|
+
import '../readout/readout.js';
|
|
12
|
+
import {
|
|
13
|
+
ReadoutStackVerticalAlignment,
|
|
14
|
+
ReadoutVariant,
|
|
15
|
+
} from '../readout/readout.js';
|
|
9
16
|
|
|
10
17
|
export enum ObcGaugeRadialType {
|
|
11
18
|
filled = 'filled',
|
|
@@ -13,6 +20,13 @@ export enum ObcGaugeRadialType {
|
|
|
13
20
|
needle = 'needle',
|
|
14
21
|
}
|
|
15
22
|
|
|
23
|
+
export enum GaugeRadialSector {
|
|
24
|
+
deg270 = '270',
|
|
25
|
+
deg180 = '180',
|
|
26
|
+
deg90Left = '90-left',
|
|
27
|
+
deg90Right = '90-right',
|
|
28
|
+
}
|
|
29
|
+
|
|
16
30
|
export interface GaugeRadialAdvice {
|
|
17
31
|
minValue: number;
|
|
18
32
|
maxValue: number;
|
|
@@ -34,14 +48,17 @@ export interface GaugeRadialAdvice {
|
|
|
34
48
|
*
|
|
35
49
|
* - **Three display types**: `filled` (solid arc), `bar` (thinner arc), and
|
|
36
50
|
* `needle` (pointer indicator) via the `type` property.
|
|
37
|
-
* - **
|
|
38
|
-
*
|
|
51
|
+
* - **Sector sweep**: `sector` selects the arc span (`270`, `180`, `90-left`, or `90-right`).
|
|
52
|
+
* The configured `minValue..maxValue` always spans the full sector. For the
|
|
53
|
+
* centered sectors (`270`, `180`) a symmetric range places `0` at 12 o'clock;
|
|
54
|
+
* for `90-left`/`90-right` the range midpoint sits at the middle of the quadrant.
|
|
39
55
|
* - **Setpoint via mixin**: `setpoint`, `newSetpoint`, `touching`,
|
|
40
56
|
* `autoAtSetpointDeadband`, `setpointOverride`, and all other setpoint
|
|
41
57
|
* properties are provided by `SetpointMixin` and forwarded to the inner
|
|
42
58
|
* `<obc-instrument-radial>`.
|
|
43
59
|
* - **Advice zones**: Pass an array of {@link GaugeRadialAdvice} objects to
|
|
44
|
-
* render caution/alert arcs on the gauge.
|
|
60
|
+
* render caution/alert arcs on the gauge. Not shown on the `90-left` /
|
|
61
|
+
* `90-right` sectors.
|
|
45
62
|
*
|
|
46
63
|
* ## Usage Guidelines
|
|
47
64
|
*
|
|
@@ -50,6 +67,12 @@ export interface GaugeRadialAdvice {
|
|
|
50
67
|
* - Provide `primaryTickmarkInterval` and `secondaryTickmarkInterval` to
|
|
51
68
|
* control tickmark density.
|
|
52
69
|
* - Enable `showLabels` to show numeric labels at primary tickmarks.
|
|
70
|
+
* - Enable `showReadout` with optional `label` and `unit`. Layout depends on `sector`
|
|
71
|
+
* and `type`: **270** filled/bar — value at center + a label-only `meta` row in
|
|
72
|
+
* the bottom gap (two separate readouts); **180** filled/bar — a single centered
|
|
73
|
+
* stack (value + label/unit) at the bottom; **270** needle — bottom stack;
|
|
74
|
+
* **180** needle — no readout; **90-left** / **90-right** filled/bar — corner
|
|
75
|
+
* readout in a square host; **90** needle — no readout.
|
|
53
76
|
*
|
|
54
77
|
* ## Best Practices
|
|
55
78
|
*
|
|
@@ -66,7 +89,7 @@ export interface GaugeRadialAdvice {
|
|
|
66
89
|
* minValue="0"
|
|
67
90
|
* maxValue="100"
|
|
68
91
|
* type="filled"
|
|
69
|
-
* enhanced
|
|
92
|
+
* priority="enhanced"
|
|
70
93
|
* showLabels
|
|
71
94
|
* primaryTickmarkInterval="25"
|
|
72
95
|
* secondaryTickmarkInterval="5"
|
|
@@ -98,47 +121,203 @@ export class ObcGaugeRadial extends SetpointMixin(LitElement) {
|
|
|
98
121
|
@property({type: Boolean}) tickmarksInside: boolean = false;
|
|
99
122
|
@property({type: String}) tickmarkStyle: TickmarkStyle =
|
|
100
123
|
TickmarkStyle.regular;
|
|
124
|
+
/** Caution/alert arcs. Ignored on `sector: 90-left` / `90-right`. */
|
|
101
125
|
@property({type: Array, attribute: false}) advices: GaugeRadialAdvice[] = [];
|
|
126
|
+
@property({type: String, reflect: true}) sector: GaugeRadialSector =
|
|
127
|
+
GaugeRadialSector.deg270;
|
|
128
|
+
@property({type: Boolean}) showReadout = false;
|
|
129
|
+
@property({type: String}) label = '';
|
|
130
|
+
@property({type: String}) unit = '';
|
|
131
|
+
@property({type: Number}) fractionDigits = 0;
|
|
132
|
+
|
|
133
|
+
private get sectorAngles(): {sweep: number; start: number} {
|
|
134
|
+
switch (this.sector) {
|
|
135
|
+
case GaugeRadialSector.deg180:
|
|
136
|
+
return {sweep: 180, start: -90};
|
|
137
|
+
case GaugeRadialSector.deg90Left:
|
|
138
|
+
return {sweep: 90, start: -90};
|
|
139
|
+
case GaugeRadialSector.deg90Right:
|
|
140
|
+
return {sweep: 90, start: 0};
|
|
141
|
+
case GaugeRadialSector.deg270:
|
|
142
|
+
default:
|
|
143
|
+
return {sweep: 270, start: -135};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
102
146
|
|
|
103
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Per-edge crop (%) of the shared, origin-centered 448 SVG box each sector
|
|
149
|
+
* shows. All sectors render at the same natural scale, so the dial stays the
|
|
150
|
+
* same size; the sector only windows a different part (270 whole, 180 wide,
|
|
151
|
+
* 90 a quadrant).
|
|
152
|
+
*/
|
|
153
|
+
private get sectorClips(): {
|
|
154
|
+
top: number;
|
|
155
|
+
bottom: number;
|
|
156
|
+
left: number;
|
|
157
|
+
right: number;
|
|
158
|
+
} {
|
|
159
|
+
switch (this.sector) {
|
|
160
|
+
case GaugeRadialSector.deg180:
|
|
161
|
+
return {top: 0, bottom: 44, left: 0, right: 0};
|
|
162
|
+
case GaugeRadialSector.deg90Left:
|
|
163
|
+
return {top: 0, bottom: 45, left: 0, right: 45};
|
|
164
|
+
case GaugeRadialSector.deg90Right:
|
|
165
|
+
return {top: 0, bottom: 45, left: 45, right: 0};
|
|
166
|
+
case GaugeRadialSector.deg270:
|
|
167
|
+
default:
|
|
168
|
+
return {top: 0, bottom: 0, left: 0, right: 0};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private get isSector90(): boolean {
|
|
173
|
+
return (
|
|
174
|
+
this.sector === GaugeRadialSector.deg90Left ||
|
|
175
|
+
this.sector === GaugeRadialSector.deg90Right
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Arrow form so `this` binds when passed as `.getAngle=${this.getAngle}`
|
|
180
|
+
// to <obc-instrument-radial>. Do not convert to a method.
|
|
181
|
+
getAngle = (v: number): number => {
|
|
182
|
+
const {sweep, start} = this.sectorAngles;
|
|
104
183
|
const span = this.maxValue - this.minValue;
|
|
105
184
|
if (!Number.isFinite(span) || span <= 0) {
|
|
106
|
-
return
|
|
185
|
+
return start;
|
|
107
186
|
}
|
|
108
187
|
|
|
109
|
-
return ((v - this.minValue) / span) *
|
|
188
|
+
return ((v - this.minValue) / span) * sweep + start;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/** Renders one gauge readout; `withMeta`/`labelOnly` pick parts. */
|
|
192
|
+
private renderReadout({
|
|
193
|
+
className,
|
|
194
|
+
variant,
|
|
195
|
+
alignment = ReadoutStackVerticalAlignment.vertical,
|
|
196
|
+
withMeta = true,
|
|
197
|
+
labelOnly = false,
|
|
198
|
+
}: {
|
|
199
|
+
className: string;
|
|
200
|
+
variant: ReadoutVariant;
|
|
201
|
+
alignment?: ReadoutStackVerticalAlignment;
|
|
202
|
+
withMeta?: boolean;
|
|
203
|
+
labelOnly?: boolean;
|
|
204
|
+
}): TemplateResult {
|
|
205
|
+
// `labelOnly` already means "no value", so derive it instead of passing both.
|
|
206
|
+
const withValue = !labelOnly;
|
|
207
|
+
return html`
|
|
208
|
+
<obc-readout
|
|
209
|
+
class=${className}
|
|
210
|
+
direction="vertical"
|
|
211
|
+
?labelOnly=${labelOnly}
|
|
212
|
+
.variant=${variant}
|
|
213
|
+
.alignment=${alignment}
|
|
214
|
+
.valuePriority=${withValue ? this.priority : undefined}
|
|
215
|
+
.value=${withValue ? this.value : undefined}
|
|
216
|
+
.fractionDigits=${this.fractionDigits}
|
|
217
|
+
.label=${withMeta ? this.label : ''}
|
|
218
|
+
.unit=${withMeta ? this.unit : ''}
|
|
219
|
+
></obc-readout>
|
|
220
|
+
`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private renderReadouts() {
|
|
224
|
+
if (!this.showReadout) {
|
|
225
|
+
return nothing;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const isNeedle = this.type === ObcGaugeRadialType.needle;
|
|
229
|
+
const is90 = this.isSector90;
|
|
230
|
+
const is180 = this.sector === GaugeRadialSector.deg180;
|
|
231
|
+
|
|
232
|
+
if (isNeedle && (is180 || is90)) {
|
|
233
|
+
return nothing;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 90 filled/bar: enhanced corner readout (bold value + label/unit).
|
|
237
|
+
if (is90) {
|
|
238
|
+
return this.renderReadout({
|
|
239
|
+
className: 'gauge-readout-meta',
|
|
240
|
+
variant: ReadoutVariant.enhanced,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 270 needle and 180 filled/bar: centered stack (value weight follows `priority`).
|
|
245
|
+
if (isNeedle || is180) {
|
|
246
|
+
return this.renderReadout({
|
|
247
|
+
className: 'gauge-readout-meta',
|
|
248
|
+
variant: ReadoutVariant.stack,
|
|
249
|
+
alignment: ReadoutStackVerticalAlignment.center,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 270 filled/bar: the value sits at the circle center and the label/unit row
|
|
254
|
+
// lower in the bottom gap, so they are two separately-positioned readouts.
|
|
255
|
+
return html`
|
|
256
|
+
${this.renderReadout({
|
|
257
|
+
className: 'gauge-readout-value',
|
|
258
|
+
variant: ReadoutVariant.enhanced,
|
|
259
|
+
withMeta: false,
|
|
260
|
+
})}
|
|
261
|
+
${this.label || this.unit
|
|
262
|
+
? this.renderReadout({
|
|
263
|
+
className: 'gauge-readout-meta',
|
|
264
|
+
variant: ReadoutVariant.stack,
|
|
265
|
+
alignment: ReadoutStackVerticalAlignment.center,
|
|
266
|
+
labelOnly: true,
|
|
267
|
+
})
|
|
268
|
+
: nothing}
|
|
269
|
+
`;
|
|
110
270
|
}
|
|
111
271
|
|
|
112
272
|
override render() {
|
|
273
|
+
const clips = this.sectorClips;
|
|
113
274
|
return html`
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
.touching=${this.touching}
|
|
123
|
-
.autoAtSetpoint=${this.autoAtSetpoint}
|
|
124
|
-
.autoAtSetpointDeadband=${this.autoAtSetpointDeadband}
|
|
125
|
-
.animateSetpoint=${this.animateSetpoint}
|
|
126
|
-
.maxValue=${this.maxValue}
|
|
127
|
-
.minValue=${this.minValue}
|
|
128
|
-
.getAngle=${this.getAngle}
|
|
129
|
-
.showLabels=${this.showLabels}
|
|
130
|
-
.primaryTickmarkInterval=${this.primaryTickmarkInterval}
|
|
131
|
-
.secondaryTickmarkInterval=${this.secondaryTickmarkInterval}
|
|
132
|
-
.tertiaryTickmarkInterval=${this.tertiaryTickmarkInterval}
|
|
133
|
-
.type=${this.type}
|
|
134
|
-
.needleType=${this.type}
|
|
135
|
-
.tickmarksInside=${this.tickmarksInside}
|
|
136
|
-
.tickmarkStyle=${this.tickmarkStyle}
|
|
137
|
-
.advices=${this.advices}
|
|
275
|
+
<div
|
|
276
|
+
class=${classMap({
|
|
277
|
+
'gauge-radial-root': true,
|
|
278
|
+
'type-needle': this.type === ObcGaugeRadialType.needle,
|
|
279
|
+
'sector-180': this.sector === GaugeRadialSector.deg180,
|
|
280
|
+
'sector-90-left': this.sector === GaugeRadialSector.deg90Left,
|
|
281
|
+
'sector-90-right': this.sector === GaugeRadialSector.deg90Right,
|
|
282
|
+
})}
|
|
138
283
|
>
|
|
139
|
-
|
|
284
|
+
<obc-instrument-radial
|
|
285
|
+
.value=${this.value}
|
|
286
|
+
.state=${this.state}
|
|
287
|
+
.priority=${this.priority}
|
|
288
|
+
.setpoint=${this.setpoint}
|
|
289
|
+
.newSetpoint=${this.newSetpoint}
|
|
290
|
+
.setpointAtZeroDeadband=${this.setpointAtZeroDeadband}
|
|
291
|
+
.setpointOverride=${this.setpointOverride}
|
|
292
|
+
.touching=${this.touching}
|
|
293
|
+
.autoAtSetpoint=${this.autoAtSetpoint}
|
|
294
|
+
.autoAtSetpointDeadband=${this.autoAtSetpointDeadband}
|
|
295
|
+
.animateSetpoint=${this.animateSetpoint}
|
|
296
|
+
.maxValue=${this.maxValue}
|
|
297
|
+
.minValue=${this.minValue}
|
|
298
|
+
.getAngle=${this.getAngle}
|
|
299
|
+
.showLabels=${this.showLabels}
|
|
300
|
+
.primaryTickmarkInterval=${this.primaryTickmarkInterval}
|
|
301
|
+
.secondaryTickmarkInterval=${this.secondaryTickmarkInterval}
|
|
302
|
+
.tertiaryTickmarkInterval=${this.tertiaryTickmarkInterval}
|
|
303
|
+
.type=${this.type}
|
|
304
|
+
.needleType=${this.type}
|
|
305
|
+
.tickmarksInside=${this.tickmarksInside}
|
|
306
|
+
.tickmarkStyle=${this.tickmarkStyle}
|
|
307
|
+
.advices=${this.isSector90 ? [] : this.advices}
|
|
308
|
+
.clipTop=${clips.top}
|
|
309
|
+
.clipBottom=${clips.bottom}
|
|
310
|
+
.clipLeft=${clips.left}
|
|
311
|
+
.clipRight=${clips.right}
|
|
312
|
+
.endLabelsMaxMin=${this.sector === GaugeRadialSector.deg180}
|
|
313
|
+
>
|
|
314
|
+
</obc-instrument-radial>
|
|
315
|
+
${this.renderReadouts()}
|
|
316
|
+
</div>
|
|
140
317
|
`;
|
|
141
318
|
}
|
|
319
|
+
|
|
320
|
+
static override styles = unsafeCSS(componentStyle);
|
|
142
321
|
}
|
|
143
322
|
|
|
144
323
|
declare global {
|
|
@@ -49,6 +49,7 @@ export function tickmark(
|
|
|
49
49
|
maxDigits,
|
|
50
50
|
color,
|
|
51
51
|
radiusOffset = 0,
|
|
52
|
+
endLabelsMaxMin = false,
|
|
52
53
|
}: {
|
|
53
54
|
size: TickmarkType;
|
|
54
55
|
style: TickmarkStyle;
|
|
@@ -60,6 +61,7 @@ export function tickmark(
|
|
|
60
61
|
maxDigits: number;
|
|
61
62
|
color?: string;
|
|
62
63
|
radiusOffset?: number;
|
|
64
|
+
endLabelsMaxMin?: boolean;
|
|
63
65
|
}
|
|
64
66
|
): SVGTemplateResult | SVGTemplateResult[] {
|
|
65
67
|
// check if scale is not infinite
|
|
@@ -87,7 +89,9 @@ export function tickmark(
|
|
|
87
89
|
innerRadius = 328 / 2 + rOff;
|
|
88
90
|
outerRadius = 336 / 2 + rOff;
|
|
89
91
|
} else {
|
|
90
|
-
return [
|
|
92
|
+
return [
|
|
93
|
+
textSvg(text ?? '', {angle, inside, scale, textRadius, endLabelsMaxMin}),
|
|
94
|
+
];
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
// When inside, anchor ticks at the outer ring edge and grow inward,
|
|
@@ -115,7 +119,10 @@ export function tickmark(
|
|
|
115
119
|
const tick = svg`<line x1=${x1} y1=${y1} x2=${x2} y2=${y2} stroke=${colorName} stroke-width=${strokeWidth} vector-effect="non-scaling-stroke"/>`;
|
|
116
120
|
if (text) {
|
|
117
121
|
if (rotation === undefined) {
|
|
118
|
-
return [
|
|
122
|
+
return [
|
|
123
|
+
tick,
|
|
124
|
+
textSvg(text, {angle, inside, scale, textRadius, endLabelsMaxMin}),
|
|
125
|
+
];
|
|
119
126
|
} else {
|
|
120
127
|
const newRadius =
|
|
121
128
|
textRadius + ((4 / scale + 5) * (inside ? -1 : 1) * maxDigits) / 2;
|
|
@@ -132,11 +139,31 @@ export function tickmark(
|
|
|
132
139
|
|
|
133
140
|
function textSvg(
|
|
134
141
|
text: string,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
{
|
|
143
|
+
angle,
|
|
144
|
+
inside,
|
|
145
|
+
scale,
|
|
146
|
+
textRadius,
|
|
147
|
+
endLabelsMaxMin = false,
|
|
148
|
+
}: {
|
|
149
|
+
angle: number;
|
|
150
|
+
inside: boolean;
|
|
151
|
+
scale: number;
|
|
152
|
+
textRadius: number;
|
|
153
|
+
endLabelsMaxMin?: boolean;
|
|
154
|
+
}
|
|
139
155
|
) {
|
|
156
|
+
const radHoriz = (angle * Math.PI) / 180;
|
|
157
|
+
// "Max-min" placement: horizontal end labels (±90°) sit off the dead-center
|
|
158
|
+
// tick (below outside / lifted inside), inset inward by label width.
|
|
159
|
+
if (endLabelsMaxMin && Math.abs(Math.cos(radHoriz)) < 1e-6) {
|
|
160
|
+
const sin = Math.sin(radHoriz);
|
|
161
|
+
const inward = inside ? (6 + (text.length - 1) * 2.5) / scale : 14 / scale;
|
|
162
|
+
const x = sin * (textRadius - inward);
|
|
163
|
+
const y = inside ? -(6 / scale) : 12 / scale;
|
|
164
|
+
return svg`<text x=${x} y=${y} class="label bottom ${inside ? 'inside' : ''}">${text}</text>`;
|
|
165
|
+
}
|
|
166
|
+
|
|
140
167
|
let positionClass;
|
|
141
168
|
if (angle === 0) {
|
|
142
169
|
positionClass = 'top';
|