@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.110 → 2.0.0-next.111
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 +2297 -1506
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +562 -0
- 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/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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/web-components-vite';
|
|
2
|
+
import {html} from 'lit';
|
|
3
|
+
import {
|
|
4
|
+
ObcIndicatorTankAtmospheric,
|
|
5
|
+
IndicatorTankAtmosphericVariant,
|
|
6
|
+
IndicatorTankAtmosphericValue,
|
|
7
|
+
} from './indicator-tank-atmospheric.js';
|
|
8
|
+
import './indicator-tank-atmospheric.js';
|
|
9
|
+
import {IndicatorDirection} from '../indicator-shared/linear-indicator.js';
|
|
10
|
+
|
|
11
|
+
const meta = {
|
|
12
|
+
title: 'Automation/Indicators/Indicator Tank Atmospheric',
|
|
13
|
+
component: 'obc-indicator-tank-atmospheric',
|
|
14
|
+
tags: ['autodocs', '6.0'],
|
|
15
|
+
args: {
|
|
16
|
+
direction: IndicatorDirection.vertical,
|
|
17
|
+
variant: IndicatorTankAtmosphericVariant.bar,
|
|
18
|
+
value: IndicatorTankAtmosphericValue.regular,
|
|
19
|
+
level: 40,
|
|
20
|
+
data: [60, 52, 58, 45, 50, 42, 44, 40],
|
|
21
|
+
hasIcon: false,
|
|
22
|
+
},
|
|
23
|
+
argTypes: {
|
|
24
|
+
direction: {
|
|
25
|
+
control: 'radio',
|
|
26
|
+
options: Object.values(IndicatorDirection),
|
|
27
|
+
},
|
|
28
|
+
variant: {
|
|
29
|
+
control: 'radio',
|
|
30
|
+
options: Object.values(IndicatorTankAtmosphericVariant),
|
|
31
|
+
},
|
|
32
|
+
value: {
|
|
33
|
+
control: 'radio',
|
|
34
|
+
options: Object.values(IndicatorTankAtmosphericValue),
|
|
35
|
+
},
|
|
36
|
+
level: {control: {type: 'range', min: 0, max: 100}},
|
|
37
|
+
},
|
|
38
|
+
render: (args) => html`
|
|
39
|
+
<obc-indicator-tank-atmospheric
|
|
40
|
+
.direction=${args.direction}
|
|
41
|
+
.variant=${args.variant}
|
|
42
|
+
.value=${args.value}
|
|
43
|
+
.level=${args.level}
|
|
44
|
+
.data=${args.data}
|
|
45
|
+
.hasIcon=${args.hasIcon}
|
|
46
|
+
></obc-indicator-tank-atmospheric>
|
|
47
|
+
`,
|
|
48
|
+
} satisfies Meta<Partial<ObcIndicatorTankAtmospheric>>;
|
|
49
|
+
|
|
50
|
+
export default meta;
|
|
51
|
+
type Story = StoryObj<Partial<ObcIndicatorTankAtmospheric>>;
|
|
52
|
+
|
|
53
|
+
export const Default: Story = {};
|
|
54
|
+
|
|
55
|
+
export const BarHorizontal: Story = {
|
|
56
|
+
args: {direction: IndicatorDirection.horizontal},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const TrendVertical: Story = {
|
|
60
|
+
args: {variant: IndicatorTankAtmosphericVariant.trend},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const TrendHorizontal: Story = {
|
|
64
|
+
args: {
|
|
65
|
+
variant: IndicatorTankAtmosphericVariant.trend,
|
|
66
|
+
direction: IndicatorDirection.horizontal,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const Enhanced: Story = {
|
|
71
|
+
args: {value: IndicatorTankAtmosphericValue.enhanced},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const Medium: Story = {
|
|
75
|
+
args: {value: IndicatorTankAtmosphericValue.medium},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const TrendEnhanced: Story = {
|
|
79
|
+
args: {
|
|
80
|
+
variant: IndicatorTankAtmosphericVariant.trend,
|
|
81
|
+
value: IndicatorTankAtmosphericValue.enhanced,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const TrendMedium: Story = {
|
|
86
|
+
args: {
|
|
87
|
+
variant: IndicatorTankAtmosphericVariant.trend,
|
|
88
|
+
value: IndicatorTankAtmosphericValue.medium,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const WithIcon: Story = {
|
|
93
|
+
args: {hasIcon: true},
|
|
94
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import {LitElement, html, unsafeCSS, nothing} from 'lit';
|
|
2
|
+
import {property} from 'lit/decorators.js';
|
|
3
|
+
import componentStyle from './indicator-tank-atmospheric.css?inline';
|
|
4
|
+
import {customElement} from '../../decorator.js';
|
|
5
|
+
import '../../icons/icon-tank.js';
|
|
6
|
+
import {
|
|
7
|
+
IndicatorDirection,
|
|
8
|
+
LinearIndicatorScheme,
|
|
9
|
+
Rect,
|
|
10
|
+
renderLinearBar,
|
|
11
|
+
renderTrendGraph,
|
|
12
|
+
} from '../indicator-shared/linear-indicator.js';
|
|
13
|
+
|
|
14
|
+
export enum IndicatorTankAtmosphericVariant {
|
|
15
|
+
bar = 'bar',
|
|
16
|
+
trend = 'trend',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export enum IndicatorTankAtmosphericValue {
|
|
20
|
+
regular = 'regular',
|
|
21
|
+
enhanced = 'enhanced',
|
|
22
|
+
medium = 'medium',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const HORIZONTAL_BAR: Rect = {x: 8, y: 11, width: 32, height: 26};
|
|
26
|
+
const VERTICAL_BAR: Rect = {x: 11, y: 8, width: 26, height: 32};
|
|
27
|
+
const HORIZONTAL_TREND: Rect = {x: 8, y: 10, width: 32, height: 28};
|
|
28
|
+
const VERTICAL_TREND: Rect = {x: 10, y: 8, width: 28, height: 32};
|
|
29
|
+
|
|
30
|
+
const FRAME_PATH =
|
|
31
|
+
'M20.5 32.5L4.21259 32.5C3.70663 32.5 3.45364 32.5 3.09178 32.3531C2.84817 32.2542 2.44102 31.9492 2.27758 31.7433C2.0348 31.4373 1.98325 31.2615 1.88017 30.9098C1.29286 28.906 0.5 24.6852 0.5 16.5C0.5 8.31487 1.29286 4.09402 1.88016 2.09025C1.98325 1.73854 2.0348 1.56268 2.27758 1.25676C2.44102 1.0508 2.84817 0.745859 3.09178 0.646944C3.45364 0.500011 3.70662 0.500011 4.21259 0.500011L20.5 0.50001L36.7874 0.500011C37.2934 0.500011 37.5464 0.500011 37.9082 0.646943C38.1518 0.745859 38.559 1.0508 38.7224 1.25675C38.9652 1.56268 39.0167 1.73854 39.1198 2.09025C39.7071 4.09402 40.5 8.31487 40.5 16.5C40.5 24.6851 39.7071 28.906 39.1198 30.9098C39.0167 31.2615 38.9652 31.4373 38.7224 31.7433C38.559 31.9492 38.1518 32.2542 37.9082 32.3531C37.5464 32.5 37.2934 32.5 36.7874 32.5H20.5Z';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Compact 48×48 atmospheric tank level indicator.
|
|
35
|
+
*
|
|
36
|
+
* ## Features / Variants
|
|
37
|
+
* - `variant`: `bar` (bottom-up percentage fill) or `trend` (history graph
|
|
38
|
+
* with a current-value micro-bar)
|
|
39
|
+
* - `direction`: `vertical` or `horizontal` tank outline
|
|
40
|
+
* - `value`: color scheme — `regular`, `enhanced` or `medium`
|
|
41
|
+
* - `level`: current tank level 0–100 (%), drives the fill and micro-bar
|
|
42
|
+
* - `data`: history samples 0–100 (%) drawn as the trend graph
|
|
43
|
+
* - `hasIcon`: centered icon overlay (bar variant only)
|
|
44
|
+
*
|
|
45
|
+
* ## Usage Guidelines
|
|
46
|
+
* Use as an at-a-glance level indicator in dashboards and lists. For a
|
|
47
|
+
* full-featured tank cell with readouts, badges and setpoints, use
|
|
48
|
+
* `obc-automation-tank` instead.
|
|
49
|
+
*
|
|
50
|
+
* ## Best Practices / Constraints
|
|
51
|
+
* - `level` and `data` values are clamped to 0–100; values outside that
|
|
52
|
+
* range are pinned to the nearest bound.
|
|
53
|
+
* - The `trend` variant needs at least two `data` samples to draw a line;
|
|
54
|
+
* with fewer it shows only the current-value micro-bar.
|
|
55
|
+
* - The `icon` slot renders only in the `bar` variant; it is ignored in
|
|
56
|
+
* `trend`.
|
|
57
|
+
* - Display-only: the root SVG is `aria-hidden`; convey tank level to
|
|
58
|
+
* assistive tech through adjacent text if it is not otherwise available.
|
|
59
|
+
*
|
|
60
|
+
* ## Slots
|
|
61
|
+
* | Slot | Condition | Purpose |
|
|
62
|
+
* | ------ | ----------------------------- | ------------------------------ |
|
|
63
|
+
* | `icon` | `hasIcon` and `variant='bar'` | Icon overlay (`obi-*` element) |
|
|
64
|
+
*
|
|
65
|
+
* @slot icon - Centered icon overlay, defaults to `obi-tank`
|
|
66
|
+
*/
|
|
67
|
+
@customElement('obc-indicator-tank-atmospheric')
|
|
68
|
+
export class ObcIndicatorTankAtmospheric extends LitElement {
|
|
69
|
+
@property({type: String, reflect: true}) direction: IndicatorDirection =
|
|
70
|
+
IndicatorDirection.vertical;
|
|
71
|
+
@property({type: String, reflect: true})
|
|
72
|
+
variant: IndicatorTankAtmosphericVariant =
|
|
73
|
+
IndicatorTankAtmosphericVariant.bar;
|
|
74
|
+
@property({type: String, reflect: true})
|
|
75
|
+
value: IndicatorTankAtmosphericValue = IndicatorTankAtmosphericValue.regular;
|
|
76
|
+
/** Current level, 0-100 (%) */
|
|
77
|
+
@property({type: Number}) level = 0;
|
|
78
|
+
/** History samples, 0-100 (%) each, oldest first */
|
|
79
|
+
@property({type: Array}) data: number[] = [];
|
|
80
|
+
@property({type: Boolean}) hasIcon = false;
|
|
81
|
+
|
|
82
|
+
private get scheme(): LinearIndicatorScheme {
|
|
83
|
+
switch (this.value) {
|
|
84
|
+
case IndicatorTankAtmosphericValue.enhanced:
|
|
85
|
+
return LinearIndicatorScheme.enhanced;
|
|
86
|
+
case IndicatorTankAtmosphericValue.medium:
|
|
87
|
+
return LinearIndicatorScheme.categorical;
|
|
88
|
+
default:
|
|
89
|
+
return LinearIndicatorScheme.regular;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
override render() {
|
|
94
|
+
const horizontal = this.direction === IndicatorDirection.horizontal;
|
|
95
|
+
const trend = this.variant === IndicatorTankAtmosphericVariant.trend;
|
|
96
|
+
const barRect = horizontal
|
|
97
|
+
? trend
|
|
98
|
+
? HORIZONTAL_TREND
|
|
99
|
+
: HORIZONTAL_BAR
|
|
100
|
+
: trend
|
|
101
|
+
? VERTICAL_TREND
|
|
102
|
+
: VERTICAL_BAR;
|
|
103
|
+
const frameTransform = horizontal
|
|
104
|
+
? 'translate(3.5 7.5)'
|
|
105
|
+
: 'rotate(90 24 24) translate(3.5 7.5)';
|
|
106
|
+
const showIcon =
|
|
107
|
+
this.hasIcon && this.variant === IndicatorTankAtmosphericVariant.bar;
|
|
108
|
+
return html`
|
|
109
|
+
<svg viewBox="0 0 48 48" aria-hidden="true">
|
|
110
|
+
<g transform=${frameTransform}>
|
|
111
|
+
<path class="frame" d=${FRAME_PATH} />
|
|
112
|
+
</g>
|
|
113
|
+
${this.variant === IndicatorTankAtmosphericVariant.trend
|
|
114
|
+
? renderTrendGraph(
|
|
115
|
+
barRect,
|
|
116
|
+
this.data,
|
|
117
|
+
this.level,
|
|
118
|
+
this.scheme,
|
|
119
|
+
'tank'
|
|
120
|
+
)
|
|
121
|
+
: renderLinearBar(barRect, this.level, this.scheme, 'tank')}
|
|
122
|
+
</svg>
|
|
123
|
+
${showIcon
|
|
124
|
+
? html`<div class="icon-wrapper">
|
|
125
|
+
<slot name="icon"><obi-tank usecsscolor></obi-tank></slot>
|
|
126
|
+
</div>`
|
|
127
|
+
: nothing}
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
static override styles = unsafeCSS(componentStyle);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
declare global {
|
|
135
|
+
interface HTMLElementTagNameMap {
|
|
136
|
+
'obc-indicator-tank-atmospheric': ObcIndicatorTankAtmospheric;
|
|
137
|
+
}
|
|
138
|
+
}
|