@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.110 → 2.0.0-next.112
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 +2370 -1522
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +641 -7
- package/dist/automation/indicator-battery/indicator-battery.css.js +52 -0
- package/dist/automation/indicator-battery/indicator-battery.css.js.map +1 -0
- package/dist/automation/indicator-battery/indicator-battery.d.ts +65 -0
- package/dist/automation/indicator-battery/indicator-battery.d.ts.map +1 -0
- package/dist/automation/indicator-battery/indicator-battery.js +119 -0
- package/dist/automation/indicator-battery/indicator-battery.js.map +1 -0
- package/dist/automation/indicator-engine/indicator-engine.css.js +45 -0
- package/dist/automation/indicator-engine/indicator-engine.css.js.map +1 -0
- package/dist/automation/indicator-engine/indicator-engine.d.ts +34 -0
- package/dist/automation/indicator-engine/indicator-engine.d.ts.map +1 -0
- package/dist/automation/indicator-engine/indicator-engine.js +73 -0
- package/dist/automation/indicator-engine/indicator-engine.js.map +1 -0
- package/dist/automation/indicator-generator/indicator-generator-geometry.d.ts +30 -0
- package/dist/automation/indicator-generator/indicator-generator-geometry.d.ts.map +1 -0
- package/dist/automation/indicator-generator/indicator-generator-geometry.js +61 -0
- package/dist/automation/indicator-generator/indicator-generator-geometry.js.map +1 -0
- package/dist/automation/indicator-generator/indicator-generator.css.js +121 -0
- package/dist/automation/indicator-generator/indicator-generator.css.js.map +1 -0
- package/dist/automation/indicator-generator/indicator-generator.d.ts +57 -0
- package/dist/automation/indicator-generator/indicator-generator.d.ts.map +1 -0
- package/dist/automation/indicator-generator/indicator-generator.js +136 -0
- package/dist/automation/indicator-generator/indicator-generator.js.map +1 -0
- package/dist/automation/indicator-shared/linear-indicator.d.ts +46 -0
- package/dist/automation/indicator-shared/linear-indicator.d.ts.map +1 -0
- package/dist/automation/indicator-shared/linear-indicator.js +175 -0
- package/dist/automation/indicator-shared/linear-indicator.js.map +1 -0
- package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.css.js +48 -0
- package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.css.js.map +1 -0
- package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.d.ts +65 -0
- package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.d.ts.map +1 -0
- package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.js +105 -0
- package/dist/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.js.map +1 -0
- package/dist/navigation-instruments/compass/compass.d.ts +1 -1
- package/dist/navigation-instruments/compass/compass.d.ts.map +1 -1
- package/dist/navigation-instruments/compass/compass.js +1 -1
- package/dist/navigation-instruments/compass/compass.js.map +1 -1
- package/dist/navigation-instruments/current/current.d.ts +25 -0
- package/dist/navigation-instruments/current/current.d.ts.map +1 -1
- package/dist/navigation-instruments/current/current.js +25 -4
- package/dist/navigation-instruments/current/current.js.map +1 -1
- package/dist/navigation-instruments/heading/heading.js +1 -1
- package/dist/navigation-instruments/heading/heading.js.map +1 -1
- package/dist/navigation-instruments/pitch-roll-heave/pitch-roll-heave.d.ts +6 -0
- package/dist/navigation-instruments/pitch-roll-heave/pitch-roll-heave.d.ts.map +1 -1
- package/dist/navigation-instruments/pitch-roll-heave/pitch-roll-heave.js +8 -1
- package/dist/navigation-instruments/pitch-roll-heave/pitch-roll-heave.js.map +1 -1
- package/dist/navigation-instruments/rate-of-turn/rate-of-turn.d.ts +5 -2
- package/dist/navigation-instruments/rate-of-turn/rate-of-turn.d.ts.map +1 -1
- package/dist/navigation-instruments/rate-of-turn/rate-of-turn.js.map +1 -1
- package/dist/navigation-instruments/top-view-propulsion/top-view-propulsion.d.ts +6 -0
- package/dist/navigation-instruments/top-view-propulsion/top-view-propulsion.d.ts.map +1 -1
- package/dist/navigation-instruments/top-view-propulsion/top-view-propulsion.js +19 -4
- package/dist/navigation-instruments/top-view-propulsion/top-view-propulsion.js.map +1 -1
- package/dist/navigation-instruments/watch/environment.d.ts +8 -0
- package/dist/navigation-instruments/watch/environment.d.ts.map +1 -1
- package/dist/navigation-instruments/watch/environment.js +3 -0
- package/dist/navigation-instruments/watch/environment.js.map +1 -1
- package/dist/navigation-instruments/watch/force-pattern.d.ts +20 -2
- package/dist/navigation-instruments/watch/force-pattern.d.ts.map +1 -1
- package/dist/navigation-instruments/watch/force-pattern.js +12 -5
- package/dist/navigation-instruments/watch/force-pattern.js.map +1 -1
- package/dist/navigation-instruments/watch/watch.d.ts.map +1 -1
- package/dist/navigation-instruments/watch/watch.js +6 -4
- package/dist/navigation-instruments/watch/watch.js.map +1 -1
- package/dist/navigation-instruments/wind/wind.d.ts +5 -2
- package/dist/navigation-instruments/wind/wind.d.ts.map +1 -1
- package/dist/navigation-instruments/wind/wind.js +5 -1
- package/dist/navigation-instruments/wind/wind.js.map +1 -1
- package/package.json +1 -1
- package/src/automation/indicator-battery/indicator-battery.css +42 -0
- package/src/automation/indicator-battery/indicator-battery.stories.ts +94 -0
- package/src/automation/indicator-battery/indicator-battery.ts +149 -0
- package/src/automation/indicator-engine/indicator-engine.css +35 -0
- package/src/automation/indicator-engine/indicator-engine.stories.ts +48 -0
- package/src/automation/indicator-engine/indicator-engine.ts +78 -0
- package/src/automation/indicator-generator/indicator-generator-geometry.spec.ts +62 -0
- package/src/automation/indicator-generator/indicator-generator-geometry.ts +63 -0
- package/src/automation/indicator-generator/indicator-generator.css +111 -0
- package/src/automation/indicator-generator/indicator-generator.stories.ts +103 -0
- package/src/automation/indicator-generator/indicator-generator.ts +184 -0
- package/src/automation/indicator-shared/linear-indicator.spec.ts +90 -0
- package/src/automation/indicator-shared/linear-indicator.ts +230 -0
- package/src/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.css +38 -0
- package/src/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.stories.ts +94 -0
- package/src/automation/indicator-tank-atmospheric/indicator-tank-atmospheric.ts +138 -0
- package/src/navigation-instruments/compass/compass.ts +2 -2
- package/src/navigation-instruments/current/current.stories.ts +33 -0
- package/src/navigation-instruments/current/current.ts +37 -3
- package/src/navigation-instruments/heading/heading.stories.ts +6 -4
- package/src/navigation-instruments/heading/heading.ts +1 -1
- package/src/navigation-instruments/pitch-roll-heave/pitch-roll-heave.ts +8 -1
- package/src/navigation-instruments/rate-of-turn/rate-of-turn.stories.ts +3 -3
- package/src/navigation-instruments/rate-of-turn/rate-of-turn.ts +5 -2
- package/src/navigation-instruments/top-view-propulsion/top-view-propulsion.ts +20 -2
- package/src/navigation-instruments/watch/environment.ts +10 -0
- package/src/navigation-instruments/watch/force-pattern.spec.ts +42 -0
- package/src/navigation-instruments/watch/force-pattern.ts +44 -7
- package/src/navigation-instruments/watch/watch.ts +20 -5
- package/src/navigation-instruments/wind/wind.spec.ts +3 -1
- package/src/navigation-instruments/wind/wind.ts +12 -3
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/web-components-vite';
|
|
2
|
+
import {html} from 'lit';
|
|
3
|
+
import {
|
|
4
|
+
ObcIndicatorGenerator,
|
|
5
|
+
IndicatorGeneratorVariant,
|
|
6
|
+
IndicatorGeneratorValue,
|
|
7
|
+
} from './indicator-generator.js';
|
|
8
|
+
import './indicator-generator.js';
|
|
9
|
+
|
|
10
|
+
const meta = {
|
|
11
|
+
title: 'Automation/Indicators/Indicator Generator',
|
|
12
|
+
component: 'obc-indicator-generator',
|
|
13
|
+
tags: ['autodocs', '6.0'],
|
|
14
|
+
args: {
|
|
15
|
+
variant: IndicatorGeneratorVariant.button,
|
|
16
|
+
value: IndicatorGeneratorValue.off,
|
|
17
|
+
level: 65,
|
|
18
|
+
secondaryLevel: 75,
|
|
19
|
+
},
|
|
20
|
+
argTypes: {
|
|
21
|
+
variant: {
|
|
22
|
+
control: 'radio',
|
|
23
|
+
options: Object.values(IndicatorGeneratorVariant),
|
|
24
|
+
},
|
|
25
|
+
value: {
|
|
26
|
+
control: 'radio',
|
|
27
|
+
options: Object.values(IndicatorGeneratorValue),
|
|
28
|
+
},
|
|
29
|
+
level: {control: {type: 'range', min: 0, max: 100}},
|
|
30
|
+
secondaryLevel: {control: {type: 'range', min: 0, max: 100}},
|
|
31
|
+
},
|
|
32
|
+
render: (args) => html`
|
|
33
|
+
<obc-indicator-generator
|
|
34
|
+
.variant=${args.variant}
|
|
35
|
+
.value=${args.value}
|
|
36
|
+
.level=${args.level}
|
|
37
|
+
.secondaryLevel=${args.secondaryLevel}
|
|
38
|
+
></obc-indicator-generator>
|
|
39
|
+
`,
|
|
40
|
+
} satisfies Meta<Partial<ObcIndicatorGenerator>>;
|
|
41
|
+
|
|
42
|
+
export default meta;
|
|
43
|
+
type Story = StoryObj<Partial<ObcIndicatorGenerator>>;
|
|
44
|
+
|
|
45
|
+
export const Default: Story = {};
|
|
46
|
+
|
|
47
|
+
export const ButtonRegular: Story = {
|
|
48
|
+
args: {value: IndicatorGeneratorValue.regular},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const ButtonEnhanced: Story = {
|
|
52
|
+
args: {value: IndicatorGeneratorValue.enhanced},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const ButtonCategorical: Story = {
|
|
56
|
+
args: {value: IndicatorGeneratorValue.categorical},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const BarRegular: Story = {
|
|
60
|
+
args: {
|
|
61
|
+
variant: IndicatorGeneratorVariant.bar,
|
|
62
|
+
value: IndicatorGeneratorValue.regular,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const BarEnhanced: Story = {
|
|
67
|
+
args: {
|
|
68
|
+
variant: IndicatorGeneratorVariant.bar,
|
|
69
|
+
value: IndicatorGeneratorValue.enhanced,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const BarCategorical: Story = {
|
|
74
|
+
args: {
|
|
75
|
+
variant: IndicatorGeneratorVariant.bar,
|
|
76
|
+
value: IndicatorGeneratorValue.categorical,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const ButtonFilled: Story = {
|
|
81
|
+
args: {value: IndicatorGeneratorValue.filled},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const BarFilled: Story = {
|
|
85
|
+
args: {
|
|
86
|
+
variant: IndicatorGeneratorVariant.bar,
|
|
87
|
+
value: IndicatorGeneratorValue.filled,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const DoubleBar: Story = {
|
|
92
|
+
args: {
|
|
93
|
+
variant: IndicatorGeneratorVariant.doubleBar,
|
|
94
|
+
value: IndicatorGeneratorValue.regular,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const DoubleBarFilled: Story = {
|
|
99
|
+
args: {
|
|
100
|
+
variant: IndicatorGeneratorVariant.doubleBar,
|
|
101
|
+
value: IndicatorGeneratorValue.filled,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import {LitElement, html, svg, unsafeCSS, nothing} from 'lit';
|
|
2
|
+
import {property} from 'lit/decorators.js';
|
|
3
|
+
import componentStyle from './indicator-generator.css?inline';
|
|
4
|
+
import {customElement} from '../../decorator.js';
|
|
5
|
+
import {
|
|
6
|
+
BUTTON_SILHOUETTE_RADIUS,
|
|
7
|
+
DISC_RADIUS,
|
|
8
|
+
FILLED_DISC_RADIUS,
|
|
9
|
+
BAR_SILHOUETTE_RADIUS,
|
|
10
|
+
BAR_SILHOUETTE_STROKE_WIDTH,
|
|
11
|
+
BAR_OUTLINE_RADIUS,
|
|
12
|
+
DOUBLE_BAR_OUTLINE_RADIUS,
|
|
13
|
+
OUTLINE_STROKE_WIDTH,
|
|
14
|
+
BAR_TRACK_RADIUS,
|
|
15
|
+
BAR_TRACK_STROKE_WIDTH,
|
|
16
|
+
DOUBLE_BAR_OUTER_RADIUS,
|
|
17
|
+
DOUBLE_BAR_OUTER_STROKE_WIDTH,
|
|
18
|
+
DOUBLE_BAR_INNER_RADIUS,
|
|
19
|
+
DOUBLE_BAR_INNER_STROKE_WIDTH,
|
|
20
|
+
arcPath,
|
|
21
|
+
progressSweep,
|
|
22
|
+
} from './indicator-generator-geometry.js';
|
|
23
|
+
|
|
24
|
+
export enum IndicatorGeneratorVariant {
|
|
25
|
+
button = 'button',
|
|
26
|
+
bar = 'bar',
|
|
27
|
+
doubleBar = 'double-bar',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export enum IndicatorGeneratorValue {
|
|
31
|
+
off = 'off',
|
|
32
|
+
regular = 'regular',
|
|
33
|
+
filled = 'filled',
|
|
34
|
+
enhanced = 'enhanced',
|
|
35
|
+
categorical = 'categorical',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ICON_G_PATH =
|
|
39
|
+
'M6.1686 5.216H10.6326V11.088C10.0353 11.2907 9.41127 11.4507 8.7606 11.568C8.1206 11.6853 7.38993 11.744 6.5686 11.744C5.43793 11.744 4.4726 11.52 3.6726 11.072C2.88327 10.6133 2.2806 9.94667 1.8646 9.072C1.4486 8.19733 1.2406 7.12533 1.2406 5.856C1.2406 4.66133 1.46993 3.62667 1.9286 2.752C2.39793 1.87733 3.07527 1.2 3.9606 0.72C4.84593 0.24 5.9286 0 7.2086 0C7.8166 0 8.41393 0.0640001 9.0006 0.192C9.58727 0.32 10.1206 0.490667 10.6006 0.704L9.8326 2.56C9.46993 2.37867 9.05927 2.22933 8.6006 2.112C8.1526 1.99467 7.68327 1.936 7.1926 1.936C6.4566 1.936 5.82193 2.10133 5.2886 2.432C4.76593 2.752 4.35527 3.21067 4.0566 3.808C3.7686 4.39467 3.6246 5.09333 3.6246 5.904C3.6246 6.66133 3.73127 7.33867 3.9446 7.936C4.15793 8.52267 4.49393 8.98133 4.9526 9.312C5.41127 9.64267 6.01393 9.808 6.7606 9.808C7.00593 9.808 7.21927 9.80267 7.4006 9.792C7.5926 9.77067 7.76327 9.74933 7.9126 9.728C8.0726 9.696 8.22727 9.66933 8.3766 9.648V7.152H6.1686V5.216Z';
|
|
40
|
+
|
|
41
|
+
const ICON_TILDE_PATH =
|
|
42
|
+
'M4.20117 14.0059C5.30264 14.0679 6.22793 14.5801 7.02344 15.2207C7.64824 15.7239 8.18172 15.9676 8.7041 15.9971C9.21748 16.026 9.88939 15.8574 10.8096 15.1904L11.6191 14.6035L12.793 16.2227L11.9834 16.8096C10.8203 17.6526 9.7024 18.0567 8.5918 17.9941C7.49033 17.9321 6.56504 17.4199 5.76953 16.7793C5.14473 16.2761 4.61125 16.0324 4.08887 16.0029C3.57549 15.974 2.90358 16.1426 1.9834 16.8096L1.17383 17.3965L0 15.7773L0.80957 15.1904C1.97263 14.3473 3.09057 13.9433 4.20117 14.0059Z';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Compact generator status indicator (48×48; 56×56 as double-bar).
|
|
46
|
+
*
|
|
47
|
+
* ## Features / Variants
|
|
48
|
+
* - `variant`: `button` (plain disc), `bar` (disc with a circular progress
|
|
49
|
+
* ring) or `double-bar` (disc with two concentric progress rings)
|
|
50
|
+
* - `value`: visual scheme — `off`, `regular`, `filled`, `enhanced` or
|
|
51
|
+
* `categorical`; `off` hides the progress rings
|
|
52
|
+
* - `level`: 0–100 (%), sweep of the (outer) progress ring, clockwise from
|
|
53
|
+
* 12 o'clock
|
|
54
|
+
* - `secondaryLevel`: 0–100 (%), sweep of the inner ring (double-bar only)
|
|
55
|
+
*
|
|
56
|
+
* ## Usage Guidelines
|
|
57
|
+
* Use as an at-a-glance generator load indicator in dashboards and lists.
|
|
58
|
+
* For schematic (P&ID) placement with button behavior, use the electric
|
|
59
|
+
* generator automation devices instead.
|
|
60
|
+
*
|
|
61
|
+
* ## Slots
|
|
62
|
+
* | Slot | Condition | Purpose |
|
|
63
|
+
* | ------ | --------- | ------------------------------------------------ |
|
|
64
|
+
* | `icon` | always | Center icon, defaults to the generator G~ glyph |
|
|
65
|
+
*
|
|
66
|
+
* @slot icon - Center icon, defaults to the generator G~ glyph
|
|
67
|
+
*/
|
|
68
|
+
@customElement('obc-indicator-generator')
|
|
69
|
+
export class ObcIndicatorGenerator extends LitElement {
|
|
70
|
+
@property({type: String, reflect: true}) variant: IndicatorGeneratorVariant =
|
|
71
|
+
IndicatorGeneratorVariant.button;
|
|
72
|
+
@property({type: String, reflect: true}) value: IndicatorGeneratorValue =
|
|
73
|
+
IndicatorGeneratorValue.off;
|
|
74
|
+
/** Outer progress ring sweep, 0-100 (%) */
|
|
75
|
+
@property({type: Number}) level = 0;
|
|
76
|
+
/** Inner progress ring sweep, 0-100 (%), double-bar only */
|
|
77
|
+
@property({type: Number}) secondaryLevel = 0;
|
|
78
|
+
|
|
79
|
+
private get center(): number {
|
|
80
|
+
return this.variant === IndicatorGeneratorVariant.doubleBar ? 28 : 24;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private get discRadius(): number {
|
|
84
|
+
if (this.value === IndicatorGeneratorValue.filled) {
|
|
85
|
+
return FILLED_DISC_RADIUS;
|
|
86
|
+
}
|
|
87
|
+
if (
|
|
88
|
+
this.variant === IndicatorGeneratorVariant.button ||
|
|
89
|
+
this.value === IndicatorGeneratorValue.off ||
|
|
90
|
+
this.value === IndicatorGeneratorValue.regular
|
|
91
|
+
) {
|
|
92
|
+
return DISC_RADIUS;
|
|
93
|
+
}
|
|
94
|
+
return FILLED_DISC_RADIUS;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private get showOutline(): boolean {
|
|
98
|
+
if (this.variant === IndicatorGeneratorVariant.bar) {
|
|
99
|
+
return [
|
|
100
|
+
IndicatorGeneratorValue.filled,
|
|
101
|
+
IndicatorGeneratorValue.enhanced,
|
|
102
|
+
IndicatorGeneratorValue.categorical,
|
|
103
|
+
].includes(this.value);
|
|
104
|
+
}
|
|
105
|
+
if (this.variant === IndicatorGeneratorVariant.doubleBar) {
|
|
106
|
+
return this.value !== IndicatorGeneratorValue.off;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private renderRing(radius: number, strokeWidth: number, level: number) {
|
|
112
|
+
const c = this.center;
|
|
113
|
+
const sweep = progressSweep(level);
|
|
114
|
+
return svg`
|
|
115
|
+
<circle class="ring-track" cx=${c} cy=${c} r=${radius} stroke-width=${strokeWidth} />
|
|
116
|
+
${
|
|
117
|
+
sweep >= 360
|
|
118
|
+
? svg`<circle class="ring-progress" cx=${c} cy=${c} r=${radius} stroke-width=${strokeWidth} />`
|
|
119
|
+
: sweep > 0
|
|
120
|
+
? svg`<path class="ring-progress" d=${arcPath(c, c, radius, 0, sweep)} stroke-width=${strokeWidth} />`
|
|
121
|
+
: nothing
|
|
122
|
+
}
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
override render() {
|
|
127
|
+
const c = this.center;
|
|
128
|
+
const doubleBar = this.variant === IndicatorGeneratorVariant.doubleBar;
|
|
129
|
+
const bar = this.variant === IndicatorGeneratorVariant.bar;
|
|
130
|
+
const showRings = this.value !== IndicatorGeneratorValue.off;
|
|
131
|
+
const size = doubleBar ? 56 : 48;
|
|
132
|
+
return html`
|
|
133
|
+
<svg viewBox="0 0 ${size} ${size}" aria-hidden="true">
|
|
134
|
+
${this.variant === IndicatorGeneratorVariant.button
|
|
135
|
+
? svg`<circle class="silhouette" cx=${c} cy=${c} r=${BUTTON_SILHOUETTE_RADIUS} />`
|
|
136
|
+
: nothing}
|
|
137
|
+
${bar
|
|
138
|
+
? svg`<circle class="silhouette-ring" cx=${c} cy=${c} r=${BAR_SILHOUETTE_RADIUS} stroke-width=${BAR_SILHOUETTE_STROKE_WIDTH} />`
|
|
139
|
+
: nothing}
|
|
140
|
+
<circle class="disc" cx=${c} cy=${c} r=${this.discRadius} />
|
|
141
|
+
${this.showOutline
|
|
142
|
+
? svg`<circle
|
|
143
|
+
class="outline"
|
|
144
|
+
cx=${c}
|
|
145
|
+
cy=${c}
|
|
146
|
+
r=${doubleBar ? DOUBLE_BAR_OUTLINE_RADIUS : BAR_OUTLINE_RADIUS}
|
|
147
|
+
stroke-width=${OUTLINE_STROKE_WIDTH}
|
|
148
|
+
/>`
|
|
149
|
+
: nothing}
|
|
150
|
+
${bar && showRings
|
|
151
|
+
? this.renderRing(
|
|
152
|
+
BAR_TRACK_RADIUS,
|
|
153
|
+
BAR_TRACK_STROKE_WIDTH,
|
|
154
|
+
this.level
|
|
155
|
+
)
|
|
156
|
+
: nothing}
|
|
157
|
+
${doubleBar && showRings
|
|
158
|
+
? svg`
|
|
159
|
+
${this.renderRing(DOUBLE_BAR_OUTER_RADIUS, DOUBLE_BAR_OUTER_STROKE_WIDTH, this.level)}
|
|
160
|
+
${this.renderRing(DOUBLE_BAR_INNER_RADIUS, DOUBLE_BAR_INNER_STROKE_WIDTH, this.secondaryLevel)}
|
|
161
|
+
`
|
|
162
|
+
: nothing}
|
|
163
|
+
</svg>
|
|
164
|
+
<div class="icon-wrapper">
|
|
165
|
+
<slot name="icon">
|
|
166
|
+
<svg class="default-icon" viewBox="0 0 24 24">
|
|
167
|
+
<g transform="translate(5.604 3)">
|
|
168
|
+
<path d=${ICON_G_PATH} />
|
|
169
|
+
<path d=${ICON_TILDE_PATH} />
|
|
170
|
+
</g>
|
|
171
|
+
</svg>
|
|
172
|
+
</slot>
|
|
173
|
+
</div>
|
|
174
|
+
`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
static override styles = unsafeCSS(componentStyle);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
declare global {
|
|
181
|
+
interface HTMLElementTagNameMap {
|
|
182
|
+
'obc-indicator-generator': ObcIndicatorGenerator;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {describe, it, expect} from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
clampPercent,
|
|
4
|
+
expandRect,
|
|
5
|
+
fillHeight,
|
|
6
|
+
trendPolylinePoints,
|
|
7
|
+
roundedLeftRectPath,
|
|
8
|
+
} from './linear-indicator.js';
|
|
9
|
+
|
|
10
|
+
describe('clampPercent', () => {
|
|
11
|
+
it('clamps below 0 to 0', () => {
|
|
12
|
+
expect(clampPercent(-5)).toBe(0);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('clamps above 100 to 100', () => {
|
|
16
|
+
expect(clampPercent(140)).toBe(100);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('maps NaN to 0', () => {
|
|
20
|
+
expect(clampPercent(Number.NaN)).toBe(0);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('passes through in-range values', () => {
|
|
24
|
+
expect(clampPercent(39.29)).toBeCloseTo(39.29);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('expandRect', () => {
|
|
29
|
+
it('grows the rect by the amount on every side', () => {
|
|
30
|
+
expect(expandRect({x: 9, y: 11, width: 30, height: 26}, 1)).toEqual({
|
|
31
|
+
x: 8,
|
|
32
|
+
y: 10,
|
|
33
|
+
width: 32,
|
|
34
|
+
height: 28,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('fillHeight', () => {
|
|
40
|
+
const rect = {x: 8, y: 10, width: 32, height: 28};
|
|
41
|
+
|
|
42
|
+
it('is 0 at level 0', () => {
|
|
43
|
+
expect(fillHeight(rect, 0)).toBe(0);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('is full height at 100', () => {
|
|
47
|
+
expect(fillHeight(rect, 100)).toBe(28);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('scales linearly', () => {
|
|
51
|
+
expect(fillHeight(rect, 50)).toBeCloseTo(14);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('trendPolylinePoints', () => {
|
|
56
|
+
const rect = {x: 10, y: 8, width: 28, height: 32};
|
|
57
|
+
|
|
58
|
+
it('returns empty string for fewer than 2 points', () => {
|
|
59
|
+
expect(trendPolylinePoints(rect, [], 23)).toBe('');
|
|
60
|
+
expect(trendPolylinePoints(rect, [50], 23)).toBe('');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('spans graphWidth and maps values top-down', () => {
|
|
64
|
+
const points = trendPolylinePoints(rect, [0, 100], 23).split(' ');
|
|
65
|
+
expect(points[0]).toBe('10,40');
|
|
66
|
+
expect(points[1]).toBe('33,8');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('clamps out-of-range data', () => {
|
|
70
|
+
const points = trendPolylinePoints(rect, [-20, 150], 23).split(' ');
|
|
71
|
+
expect(points[0]).toBe('10,40');
|
|
72
|
+
expect(points[1]).toBe('33,8');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('roundedLeftRectPath', () => {
|
|
77
|
+
const rect = {x: 10, y: 8, width: 28, height: 32};
|
|
78
|
+
|
|
79
|
+
it('rounds only the two left corners', () => {
|
|
80
|
+
const d = roundedLeftRectPath(rect, 23, 2);
|
|
81
|
+
expect(d.startsWith('M 12 8')).toBe(true);
|
|
82
|
+
expect((d.match(/A/g) ?? []).length).toBe(2);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('spans the given width with square right corners', () => {
|
|
86
|
+
const d = roundedLeftRectPath(rect, 23, 2);
|
|
87
|
+
expect(d).toContain('H 33');
|
|
88
|
+
expect(d.endsWith('Z')).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import {svg, SVGTemplateResult} from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared rendering core for linear automation indicators.
|
|
5
|
+
*
|
|
6
|
+
* Provides the internals used by `obc-indicator-battery` and
|
|
7
|
+
* `obc-indicator-tank-atmospheric`: a bottom-up percentage bar fill and a
|
|
8
|
+
* trend graph (history polyline with filled area plus a 4 px current-value
|
|
9
|
+
* micro-bar). The consuming component draws its own outer frame and places
|
|
10
|
+
* these templates inside its inner content rectangle.
|
|
11
|
+
*
|
|
12
|
+
* Coordinates are expressed in the component's 48×48 viewBox grid. Colors are
|
|
13
|
+
* resolved through CSS custom properties so the templates follow the active
|
|
14
|
+
* theme; the value scheme selects between the neutral, enhanced and
|
|
15
|
+
* categorical token sets.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export enum IndicatorDirection {
|
|
19
|
+
vertical = 'vertical',
|
|
20
|
+
horizontal = 'horizontal',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export enum LinearIndicatorScheme {
|
|
24
|
+
regular = 'regular',
|
|
25
|
+
enhanced = 'enhanced',
|
|
26
|
+
categorical = 'categorical',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Rect {
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const TREND_BAR_WIDTH = 4;
|
|
37
|
+
const TREND_BAR_GAP = 1;
|
|
38
|
+
|
|
39
|
+
export function clampPercent(value: number): number {
|
|
40
|
+
if (Number.isNaN(value)) return 0;
|
|
41
|
+
return Math.min(100, Math.max(0, value));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function fillHeight(rect: Rect, level: number): number {
|
|
45
|
+
return (rect.height * clampPercent(level)) / 100;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function expandRect(rect: Rect, amount: number): Rect {
|
|
49
|
+
return {
|
|
50
|
+
x: rect.x - amount,
|
|
51
|
+
y: rect.y - amount,
|
|
52
|
+
width: rect.width + 2 * amount,
|
|
53
|
+
height: rect.height + 2 * amount,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function round3(value: number): number {
|
|
58
|
+
return Math.round(value * 1000) / 1000;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function trendPolylinePoints(
|
|
62
|
+
rect: Rect,
|
|
63
|
+
data: number[],
|
|
64
|
+
graphWidth: number
|
|
65
|
+
): string {
|
|
66
|
+
if (data.length < 2) return '';
|
|
67
|
+
return data
|
|
68
|
+
.map((value, index) => {
|
|
69
|
+
const x = rect.x + (index / (data.length - 1)) * graphWidth;
|
|
70
|
+
const y = rect.y + rect.height * (1 - clampPercent(value) / 100);
|
|
71
|
+
return `${round3(x)},${round3(y)}`;
|
|
72
|
+
})
|
|
73
|
+
.join(' ');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function schemeFillToken(scheme: LinearIndicatorScheme): string {
|
|
77
|
+
switch (scheme) {
|
|
78
|
+
case LinearIndicatorScheme.enhanced:
|
|
79
|
+
return 'var(--element-neutral-enhanced-color)';
|
|
80
|
+
case LinearIndicatorScheme.categorical:
|
|
81
|
+
return 'var(--base-teal-400)';
|
|
82
|
+
default:
|
|
83
|
+
return 'var(--element-neutral-color)';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function schemeAccentToken(scheme: LinearIndicatorScheme): string {
|
|
88
|
+
switch (scheme) {
|
|
89
|
+
case LinearIndicatorScheme.enhanced:
|
|
90
|
+
return 'var(--element-neutral-enhanced-color)';
|
|
91
|
+
case LinearIndicatorScheme.categorical:
|
|
92
|
+
return 'var(--base-teal-500)';
|
|
93
|
+
default:
|
|
94
|
+
return 'var(--element-neutral-color)';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function schemeAreaToken(scheme: LinearIndicatorScheme): string {
|
|
99
|
+
switch (scheme) {
|
|
100
|
+
case LinearIndicatorScheme.enhanced:
|
|
101
|
+
return 'var(--instrument-enhanced-tertiary-color)';
|
|
102
|
+
case LinearIndicatorScheme.categorical:
|
|
103
|
+
return 'var(--base-teal-100)';
|
|
104
|
+
default:
|
|
105
|
+
return 'var(--instrument-regular-tertiary-color)';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Rectangle path with rounded left corners and square right corners, used
|
|
111
|
+
* to clip the trend graph so it follows the fill-container's rounded left
|
|
112
|
+
* edge while butting flat against the micro-bar gap on the right.
|
|
113
|
+
*/
|
|
114
|
+
export function roundedLeftRectPath(
|
|
115
|
+
rect: Rect,
|
|
116
|
+
width: number,
|
|
117
|
+
radius: number
|
|
118
|
+
): string {
|
|
119
|
+
const x = round3(rect.x);
|
|
120
|
+
const y = round3(rect.y);
|
|
121
|
+
const right = round3(rect.x + width);
|
|
122
|
+
const bottom = round3(rect.y + rect.height);
|
|
123
|
+
const r = round3(radius);
|
|
124
|
+
return (
|
|
125
|
+
`M ${round3(rect.x + radius)} ${y} H ${right} V ${bottom} ` +
|
|
126
|
+
`H ${round3(rect.x + radius)} A ${r} ${r} 0 0 1 ${x} ${round3(bottom - radius)} ` +
|
|
127
|
+
`V ${round3(rect.y + radius)} A ${r} ${r} 0 0 1 ${round3(rect.x + radius)} ${y} Z`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function renderLinearBar(
|
|
132
|
+
rect: Rect,
|
|
133
|
+
level: number,
|
|
134
|
+
scheme: LinearIndicatorScheme,
|
|
135
|
+
idPrefix: string
|
|
136
|
+
): SVGTemplateResult {
|
|
137
|
+
const fillRect = expandRect(rect, 0.5);
|
|
138
|
+
const height = fillHeight(fillRect, level);
|
|
139
|
+
const clipId = `${idPrefix}-bar-clip`;
|
|
140
|
+
return svg`
|
|
141
|
+
<clipPath id=${clipId}>
|
|
142
|
+
<rect x=${fillRect.x} y=${fillRect.y} width=${fillRect.width} height=${fillRect.height} rx="2.5"></rect>
|
|
143
|
+
</clipPath>
|
|
144
|
+
<rect
|
|
145
|
+
class="bar-background"
|
|
146
|
+
x=${rect.x}
|
|
147
|
+
y=${rect.y}
|
|
148
|
+
width=${rect.width}
|
|
149
|
+
height=${rect.height}
|
|
150
|
+
rx="2"
|
|
151
|
+
style="fill: var(--instrument-frame-secondary-color); stroke: var(--border-outline-color)"
|
|
152
|
+
></rect>
|
|
153
|
+
<rect
|
|
154
|
+
class="bar-fill"
|
|
155
|
+
clip-path="url(#${clipId})"
|
|
156
|
+
x=${fillRect.x}
|
|
157
|
+
y=${round3(fillRect.y + fillRect.height - height)}
|
|
158
|
+
width=${fillRect.width}
|
|
159
|
+
height=${round3(height)}
|
|
160
|
+
style="fill: ${schemeFillToken(scheme)}"
|
|
161
|
+
></rect>
|
|
162
|
+
`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function renderTrendGraph(
|
|
166
|
+
rect: Rect,
|
|
167
|
+
data: number[],
|
|
168
|
+
level: number,
|
|
169
|
+
scheme: LinearIndicatorScheme,
|
|
170
|
+
idPrefix: string
|
|
171
|
+
): SVGTemplateResult {
|
|
172
|
+
const graphWidth = rect.width - TREND_BAR_WIDTH - TREND_BAR_GAP;
|
|
173
|
+
const points = trendPolylinePoints(rect, data, graphWidth);
|
|
174
|
+
const bottom = round3(rect.y + rect.height);
|
|
175
|
+
const areaPath = points
|
|
176
|
+
? `M ${round3(rect.x)},${bottom} L ${points.split(' ').join(' L ')} L ${round3(rect.x + graphWidth)},${bottom} Z`
|
|
177
|
+
: '';
|
|
178
|
+
const barX = rect.x + rect.width - TREND_BAR_WIDTH;
|
|
179
|
+
const height = fillHeight(rect, level);
|
|
180
|
+
const accent = schemeAccentToken(scheme);
|
|
181
|
+
const clipId = `${idPrefix}-trend-clip`;
|
|
182
|
+
const barClipId = `${idPrefix}-trend-bar-clip`;
|
|
183
|
+
return svg`
|
|
184
|
+
<clipPath id=${clipId}>
|
|
185
|
+
<path d=${roundedLeftRectPath(rect, graphWidth, 2)}></path>
|
|
186
|
+
</clipPath>
|
|
187
|
+
<clipPath id=${barClipId}>
|
|
188
|
+
<rect x=${barX} y=${rect.y} width=${TREND_BAR_WIDTH} height=${rect.height} rx="2"></rect>
|
|
189
|
+
</clipPath>
|
|
190
|
+
${
|
|
191
|
+
areaPath
|
|
192
|
+
? svg`
|
|
193
|
+
<path
|
|
194
|
+
class="trend-area"
|
|
195
|
+
clip-path="url(#${clipId})"
|
|
196
|
+
d=${areaPath}
|
|
197
|
+
style="fill: ${schemeAreaToken(scheme)}"
|
|
198
|
+
></path>
|
|
199
|
+
<polyline
|
|
200
|
+
class="trend-line"
|
|
201
|
+
clip-path="url(#${clipId})"
|
|
202
|
+
points=${points}
|
|
203
|
+
fill="none"
|
|
204
|
+
style="stroke: ${accent}"
|
|
205
|
+
stroke-width="1"
|
|
206
|
+
vector-effect="non-scaling-stroke"
|
|
207
|
+
></polyline>
|
|
208
|
+
`
|
|
209
|
+
: ''
|
|
210
|
+
}
|
|
211
|
+
<rect
|
|
212
|
+
class="trend-track"
|
|
213
|
+
x=${barX}
|
|
214
|
+
y=${rect.y}
|
|
215
|
+
width=${TREND_BAR_WIDTH}
|
|
216
|
+
height=${rect.height}
|
|
217
|
+
rx="2"
|
|
218
|
+
style="fill: var(--border-divider-color)"
|
|
219
|
+
></rect>
|
|
220
|
+
<rect
|
|
221
|
+
class="trend-bar-fill"
|
|
222
|
+
clip-path="url(#${barClipId})"
|
|
223
|
+
x=${barX}
|
|
224
|
+
y=${round3(rect.y + rect.height - height)}
|
|
225
|
+
width=${TREND_BAR_WIDTH}
|
|
226
|
+
height=${round3(height)}
|
|
227
|
+
style="fill: ${accent}"
|
|
228
|
+
></rect>
|
|
229
|
+
`;
|
|
230
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: block;
|
|
3
|
+
position: relative;
|
|
4
|
+
width: 48px;
|
|
5
|
+
height: 48px;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
svg {
|
|
9
|
+
display: block;
|
|
10
|
+
width: 100%;
|
|
11
|
+
height: 100%;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.frame {
|
|
15
|
+
fill: var(--instrument-frame-primary-color);
|
|
16
|
+
stroke: var(--instrument-frame-tertiary-color);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.icon-wrapper {
|
|
20
|
+
position: absolute;
|
|
21
|
+
inset: 12px;
|
|
22
|
+
color: var(--element-neutral-color);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
:host([value="enhanced"]) .icon-wrapper {
|
|
26
|
+
color: var(--element-neutral-enhanced-color);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
:host([value="medium"]) .icon-wrapper {
|
|
30
|
+
color: var(--base-teal-500);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.icon-wrapper ::slotted(*),
|
|
34
|
+
.icon-wrapper obi-tank {
|
|
35
|
+
display: block;
|
|
36
|
+
width: 100%;
|
|
37
|
+
height: 100%;
|
|
38
|
+
}
|