@geode/opengeodeweb-front 10.38.0 → 10.39.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.
Files changed (54) hide show
  1. package/app/components/ThresholdFilter.vue +33 -5
  2. package/app/components/Viewer/EdgedCurve/SpecificEdgesOptions.vue +28 -0
  3. package/app/components/Viewer/Generic/Mesh/CellsOptions.vue +28 -0
  4. package/app/components/Viewer/Generic/Mesh/EdgesOptions.vue +28 -0
  5. package/app/components/Viewer/Generic/Mesh/PointsOptions.vue +14 -0
  6. package/app/components/Viewer/Generic/Mesh/PolygonsOptions.vue +28 -0
  7. package/app/components/Viewer/Generic/Mesh/PolyhedraOptions.vue +30 -0
  8. package/app/components/Viewer/Generic/Model/BlocksOptions.vue +53 -0
  9. package/app/components/Viewer/Generic/Model/CornersOptions.vue +24 -0
  10. package/app/components/Viewer/Generic/Model/LinesOptions.vue +48 -0
  11. package/app/components/Viewer/Generic/Model/SurfacesOptions.vue +58 -0
  12. package/app/components/Viewer/Options/AttributeSelector.vue +25 -0
  13. package/app/components/Viewer/Options/ColoringTypeSelector.vue +10 -0
  14. package/app/components/Viewer/Options/ColormapQuickPicker.vue +17 -2
  15. package/app/components/Viewer/Options/TimeStepSlider.vue +120 -0
  16. package/app/components/Viewer/PointSet/SpecificPointsOptions.vue +14 -0
  17. package/app/components/Viewer/Solid/SpecificPolyhedraOptions.vue +30 -0
  18. package/app/components/Viewer/Surface/PolygonsOptions.vue +28 -0
  19. package/app/composables/global_attribute_style.ts +94 -0
  20. package/app/stores/data_style.ts +3 -0
  21. package/app/utils/attributes.ts +46 -9
  22. package/internal/stores/data_style/mesh/cells/cell.ts +20 -1
  23. package/internal/stores/data_style/mesh/cells/index.ts +4 -2
  24. package/internal/stores/data_style/mesh/cells/vertex.ts +23 -1
  25. package/internal/stores/data_style/mesh/edges/edge.ts +20 -1
  26. package/internal/stores/data_style/mesh/edges/index.ts +4 -2
  27. package/internal/stores/data_style/mesh/edges/vertex.ts +23 -1
  28. package/internal/stores/data_style/mesh/index.ts +68 -0
  29. package/internal/stores/data_style/mesh/points/index.ts +2 -1
  30. package/internal/stores/data_style/mesh/points/vertex.ts +23 -1
  31. package/internal/stores/data_style/mesh/polygons/index.ts +11 -2
  32. package/internal/stores/data_style/mesh/polygons/polygon.ts +23 -1
  33. package/internal/stores/data_style/mesh/polygons/vertex.ts +23 -1
  34. package/internal/stores/data_style/mesh/polyhedra/index.ts +11 -2
  35. package/internal/stores/data_style/mesh/polyhedra/polyhedron.ts +27 -1
  36. package/internal/stores/data_style/mesh/polyhedra/vertex.ts +23 -1
  37. package/internal/stores/data_style/model/blocks/color.ts +11 -2
  38. package/internal/stores/data_style/model/blocks/index.ts +36 -6
  39. package/internal/stores/data_style/model/blocks/polyhedron.ts +61 -7
  40. package/internal/stores/data_style/model/blocks/vertex.ts +61 -7
  41. package/internal/stores/data_style/model/corners/color.ts +5 -1
  42. package/internal/stores/data_style/model/corners/index.ts +18 -3
  43. package/internal/stores/data_style/model/corners/vertex.ts +68 -8
  44. package/internal/stores/data_style/model/lines/color.ts +10 -2
  45. package/internal/stores/data_style/model/lines/edge.ts +58 -7
  46. package/internal/stores/data_style/model/lines/index.ts +35 -6
  47. package/internal/stores/data_style/model/lines/vertex.ts +58 -7
  48. package/internal/stores/data_style/model/surfaces/color.ts +10 -2
  49. package/internal/stores/data_style/model/surfaces/index.ts +53 -6
  50. package/internal/stores/data_style/model/surfaces/polygon.ts +76 -15
  51. package/internal/stores/data_style/model/surfaces/vertex.ts +70 -12
  52. package/internal/stores/data_style/time_step.ts +75 -0
  53. package/internal/stores/data_style/types.ts +1 -0
  54. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ // oxlint-disable eslint/max-lines
2
3
  import type { CollectionComponent } from "@ogw_front/stores/data_helpers/collections";
3
4
  import OptionsSection from "@ogw_front/components/Viewer/Options/OptionsSection.vue";
4
5
  import type { RGBAColor } from "@ogw_front/utils/default_styles/constants";
@@ -123,6 +124,21 @@ const surfacesVertexAttributeName = computed<string | undefined>({
123
124
  },
124
125
  });
125
126
 
127
+ const surfacesVertexAttributeTimeStep = computed<number | undefined>({
128
+ get: () => dataStyleStore.modelSurfacesVertexAttributeTimeStep(modelId, referenceSurfaceId.value),
129
+ set: async (newValue) => {
130
+ if (newValue === undefined) {
131
+ return;
132
+ }
133
+ await dataStyleStore.setModelSurfacesVertexAttributeTimeStep(
134
+ modelId,
135
+ targetSurfaceIds,
136
+ newValue,
137
+ );
138
+ hybridViewerStore.remoteRender();
139
+ },
140
+ });
141
+
126
142
  const surfacesVertexAttributeItem = computed<string | undefined>({
127
143
  get: () => dataStyleStore.modelSurfacesVertexAttributeItem(modelId, referenceSurfaceId.value),
128
144
  set: async (newValue) => {
@@ -184,6 +200,22 @@ const surfacesPolygonAttributeName = computed<string | undefined>({
184
200
  },
185
201
  });
186
202
 
203
+ const surfacesPolygonAttributeTimeStep = computed<number | undefined>({
204
+ get: () =>
205
+ dataStyleStore.modelSurfacesPolygonAttributeTimeStep(modelId, referenceSurfaceId.value),
206
+ set: async (newValue) => {
207
+ if (newValue === undefined) {
208
+ return;
209
+ }
210
+ await dataStyleStore.setModelSurfacesPolygonAttributeTimeStep(
211
+ modelId,
212
+ targetSurfaceIds,
213
+ newValue,
214
+ );
215
+ hybridViewerStore.remoteRender();
216
+ },
217
+ });
218
+
187
219
  const surfacesPolygonAttributeItem = computed<string | undefined>({
188
220
  get: () => dataStyleStore.modelSurfacesPolygonAttributeItem(modelId, referenceSurfaceId.value),
189
221
  set: async (newValue) => {
@@ -247,6 +279,17 @@ const vertexAttributeName = computed<string | undefined>({
247
279
  },
248
280
  });
249
281
 
282
+ const vertexAttributeTimeStep = computed<number | undefined>({
283
+ get: () => dataStyleStore.modelSurfacesVertexAttributeTimeStep(modelId, surfaceId),
284
+ set: async (newValue) => {
285
+ if (surfaceId === undefined || newValue === undefined) {
286
+ return;
287
+ }
288
+ await dataStyleStore.setModelSurfacesVertexAttributeTimeStep(modelId, [surfaceId], newValue);
289
+ hybridViewerStore.remoteRender();
290
+ },
291
+ });
292
+
250
293
  const vertexAttributeItem = computed<string | undefined>({
251
294
  get: () => dataStyleStore.modelSurfacesVertexAttributeItem(modelId, surfaceId),
252
295
  set: async (newValue) => {
@@ -308,6 +351,17 @@ const polygonAttributeName = computed<string | undefined>({
308
351
  },
309
352
  });
310
353
 
354
+ const polygonAttributeTimeStep = computed<number | undefined>({
355
+ get: () => dataStyleStore.modelSurfacesPolygonAttributeTimeStep(modelId, surfaceId),
356
+ set: async (newValue) => {
357
+ if (surfaceId === undefined || newValue === undefined) {
358
+ return;
359
+ }
360
+ await dataStyleStore.setModelSurfacesPolygonAttributeTimeStep(modelId, [surfaceId], newValue);
361
+ hybridViewerStore.remoteRender();
362
+ },
363
+ });
364
+
311
365
  const polygonAttributeItem = computed<string | undefined>({
312
366
  get: () => dataStyleStore.modelSurfacesPolygonAttributeItem(modelId, surfaceId),
313
367
  set: async (newValue) => {
@@ -389,11 +443,13 @@ const polygonSchema = back_schemas.opengeodeweb_back.model_component_polygon_att
389
443
  v-model:vertex_attribute_range="surfacesVertexAttributeRange"
390
444
  v-model:vertex_attribute_color_map="surfacesVertexAttributeColorMap"
391
445
  v-model:vertex_attribute_no_data_color="surfacesVertexAttributeNoDataColor"
446
+ v-model:vertex_attribute_time_step="surfacesVertexAttributeTimeStep"
392
447
  v-model:polygon_attribute_name="surfacesPolygonAttributeName"
393
448
  v-model:polygon_attribute_item="surfacesPolygonAttributeItem"
394
449
  v-model:polygon_attribute_range="surfacesPolygonAttributeRange"
395
450
  v-model:polygon_attribute_color_map="surfacesPolygonAttributeColorMap"
396
451
  v-model:polygon_attribute_no_data_color="surfacesPolygonAttributeNoDataColor"
452
+ v-model:polygon_attribute_time_step="surfacesPolygonAttributeTimeStep"
397
453
  :capabilities="capabilities"
398
454
  :schemas="{ vertex: vertexSchema, polygon: polygonSchema }"
399
455
  :allowRandom="true"
@@ -417,11 +473,13 @@ const polygonSchema = back_schemas.opengeodeweb_back.model_component_polygon_att
417
473
  v-model:vertex_attribute_range="vertexAttributeRange"
418
474
  v-model:vertex_attribute_color_map="vertexAttributeColorMap"
419
475
  v-model:vertex_attribute_no_data_color="vertexAttributeNoDataColor"
476
+ v-model:vertex_attribute_time_step="vertexAttributeTimeStep"
420
477
  v-model:polygon_attribute_name="polygonAttributeName"
421
478
  v-model:polygon_attribute_item="polygonAttributeItem"
422
479
  v-model:polygon_attribute_range="polygonAttributeRange"
423
480
  v-model:polygon_attribute_color_map="polygonAttributeColorMap"
424
481
  v-model:polygon_attribute_no_data_color="polygonAttributeNoDataColor"
482
+ v-model:polygon_attribute_time_step="polygonAttributeTimeStep"
425
483
  :capabilities="capabilities"
426
484
  :schemas="{ vertex: vertexSchema, polygon: polygonSchema }"
427
485
  :allowRandom="true"
@@ -9,6 +9,7 @@ import { DEFAULT_NO_DATA_COLOR } from "@ogw_front/utils/default_styles/constants
9
9
  import type { JsonRpcSchema } from "@ogw_shared/utils/types.js";
10
10
  import ViewerOptionsAttributeColorBar from "@ogw_front/components/Viewer/Options/AttributeColorBar.vue";
11
11
  import ViewerOptionsColorPicker from "@ogw_front/components/Viewer/Options/ColorPicker.vue";
12
+ import ViewerOptionsTimeStepSlider from "@ogw_front/components/Viewer/Options/TimeStepSlider.vue";
12
13
  import { requestForTargets } from "@ogw_front/utils/request_for_targets";
13
14
  import { useBackStore } from "@ogw_front/stores/back";
14
15
 
@@ -21,6 +22,7 @@ const attributeRange = defineModel<(number | undefined)[]>("attributeRange", {
21
22
  });
22
23
  const attributeColorMap = defineModel<string>("attributeColorMap");
23
24
  const attributeNoDataColor = defineModel<typeof DEFAULT_NO_DATA_COLOR>("attributeNoDataColor");
25
+ const attributeTimeStep = defineModel<number>("attributeTimeStep");
24
26
 
25
27
  interface Props {
26
28
  id: string;
@@ -32,6 +34,7 @@ const { id, componentIds = undefined, schema } = defineProps<Props>();
32
34
 
33
35
  interface Emits {
34
36
  "update:attributeColorMap": [colorMap: string];
37
+ "update:attributeTimeStep": [timeStep: number];
35
38
  ranges_per_data: [ranges: RangesPerData];
36
39
  }
37
40
 
@@ -41,6 +44,7 @@ interface AttributeInfo {
41
44
  attribute_name: string;
42
45
  nb_items: number;
43
46
  no_data?: boolean;
47
+ time_steps?: number[];
44
48
  [key: string]: unknown;
45
49
  }
46
50
 
@@ -52,6 +56,25 @@ const groupTargetIds = useBatchGroup(() => id);
52
56
  const currentAttribute = computed<AttributeInfo | undefined>(() =>
53
57
  attributes.value.find((attr) => attr.attribute_name === attributeName.value),
54
58
  );
59
+ const timeSteps = computed<number[]>(() => currentAttribute.value?.time_steps ?? []);
60
+
61
+ let committedSeries: string | undefined = undefined;
62
+
63
+ function commitSeriesTimeStep(): void {
64
+ if (timeSteps.value.length === 0) {
65
+ committedSeries = undefined;
66
+ return;
67
+ }
68
+ const name = currentAttribute.value?.attribute_name;
69
+ if (name === committedSeries) {
70
+ return;
71
+ }
72
+ committedSeries = name;
73
+ const current = attributeTimeStep.value;
74
+ const valid = current !== undefined && current >= 0 && current < timeSteps.value.length;
75
+ emit("update:attributeTimeStep", valid ? current : 0);
76
+ }
77
+
55
78
  const cssNoDataColor = computed<string>(() => {
56
79
  const { red, green, blue, alpha } = attributeNoDataColor.value ?? DEFAULT_NO_DATA_COLOR;
57
80
  return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
@@ -183,6 +206,7 @@ watch([attributeName, attributeItem], ([name, item]) => {
183
206
  });
184
207
 
185
208
  watch([attributeName, attributeItem, currentAttribute], () => {
209
+ commitSeriesTimeStep();
186
210
  if (groupTargetIds.value) {
187
211
  return;
188
212
  }
@@ -221,6 +245,7 @@ watch([attributeName, attributeItem, currentAttribute], () => {
221
245
  class="mt-3"
222
246
  hide-details
223
247
  />
248
+ <ViewerOptionsTimeStepSlider v-model="attributeTimeStep" :time-steps="timeSteps" />
224
249
  <div
225
250
  v-if="currentAttribute && currentAttribute.no_data"
226
251
  class="text-caption text-high-emphasis mt-1 d-flex align-center ga-1"
@@ -45,24 +45,28 @@ const vertex_attribute_item = defineModel<number>("vertex_attribute_item");
45
45
  const vertex_attribute_range = defineModel<(number | undefined)[]>("vertex_attribute_range");
46
46
  const vertex_attribute_color_map = defineModel<string>("vertex_attribute_color_map");
47
47
  const vertex_attribute_no_data_color = defineModel<RGBAColor>("vertex_attribute_no_data_color");
48
+ const vertex_attribute_time_step = defineModel<number>("vertex_attribute_time_step");
48
49
 
49
50
  const edge_attribute_name = defineModel<string>("edge_attribute_name");
50
51
  const edge_attribute_item = defineModel<number>("edge_attribute_item");
51
52
  const edge_attribute_range = defineModel<(number | undefined)[]>("edge_attribute_range");
52
53
  const edge_attribute_color_map = defineModel<string>("edge_attribute_color_map");
53
54
  const edge_attribute_no_data_color = defineModel<RGBAColor>("edge_attribute_no_data_color");
55
+ const edge_attribute_time_step = defineModel<number>("edge_attribute_time_step");
54
56
 
55
57
  const cell_attribute_name = defineModel<string>("cell_attribute_name");
56
58
  const cell_attribute_item = defineModel<number>("cell_attribute_item");
57
59
  const cell_attribute_range = defineModel<(number | undefined)[]>("cell_attribute_range");
58
60
  const cell_attribute_color_map = defineModel<string>("cell_attribute_color_map");
59
61
  const cell_attribute_no_data_color = defineModel<RGBAColor>("cell_attribute_no_data_color");
62
+ const cell_attribute_time_step = defineModel<number>("cell_attribute_time_step");
60
63
 
61
64
  const polygon_attribute_name = defineModel<string>("polygon_attribute_name");
62
65
  const polygon_attribute_item = defineModel<number>("polygon_attribute_item");
63
66
  const polygon_attribute_range = defineModel<(number | undefined)[]>("polygon_attribute_range");
64
67
  const polygon_attribute_color_map = defineModel<string>("polygon_attribute_color_map");
65
68
  const polygon_attribute_no_data_color = defineModel<RGBAColor>("polygon_attribute_no_data_color");
69
+ const polygon_attribute_time_step = defineModel<number>("polygon_attribute_time_step");
66
70
 
67
71
  const polyhedron_attribute_name = defineModel<string>("polyhedron_attribute_name");
68
72
  const polyhedron_attribute_item = defineModel<number>("polyhedron_attribute_item");
@@ -73,6 +77,7 @@ const polyhedron_attribute_color_map = defineModel<string>("polyhedron_attribute
73
77
  const polyhedron_attribute_no_data_color = defineModel<RGBAColor>(
74
78
  "polyhedron_attribute_no_data_color",
75
79
  );
80
+ const polyhedron_attribute_time_step = defineModel<number>("polyhedron_attribute_time_step");
76
81
 
77
82
  interface Props {
78
83
  id: string;
@@ -257,6 +262,7 @@ watch(
257
262
  v-model:attributeRange="vertex_attribute_range"
258
263
  v-model:attributeColorMap="vertex_attribute_color_map"
259
264
  v-model:attributeNoDataColor="vertex_attribute_no_data_color"
265
+ v-model:attributeTimeStep="vertex_attribute_time_step"
260
266
  @ranges_per_data="emit('vertex_attribute_ranges_per_data', $event)"
261
267
  :id="id"
262
268
  :componentIds="componentIds"
@@ -270,6 +276,7 @@ watch(
270
276
  v-model:attributeRange="edge_attribute_range"
271
277
  v-model:attributeColorMap="edge_attribute_color_map"
272
278
  v-model:attributeNoDataColor="edge_attribute_no_data_color"
279
+ v-model:attributeTimeStep="edge_attribute_time_step"
273
280
  @ranges_per_data="emit('edge_attribute_ranges_per_data', $event)"
274
281
  :id="id"
275
282
  :componentIds="componentIds"
@@ -283,6 +290,7 @@ watch(
283
290
  v-model:attributeRange="cell_attribute_range"
284
291
  v-model:attributeColorMap="cell_attribute_color_map"
285
292
  v-model:attributeNoDataColor="cell_attribute_no_data_color"
293
+ v-model:attributeTimeStep="cell_attribute_time_step"
286
294
  @ranges_per_data="emit('cell_attribute_ranges_per_data', $event)"
287
295
  :id="id"
288
296
  :componentIds="componentIds"
@@ -296,6 +304,7 @@ watch(
296
304
  v-model:attributeRange="polygon_attribute_range"
297
305
  v-model:attributeColorMap="polygon_attribute_color_map"
298
306
  v-model:attributeNoDataColor="polygon_attribute_no_data_color"
307
+ v-model:attributeTimeStep="polygon_attribute_time_step"
299
308
  @ranges_per_data="emit('polygon_attribute_ranges_per_data', $event)"
300
309
  :id="id"
301
310
  :componentIds="componentIds"
@@ -309,6 +318,7 @@ watch(
309
318
  v-model:attributeRange="polyhedron_attribute_range"
310
319
  v-model:attributeColorMap="polyhedron_attribute_color_map"
311
320
  v-model:attributeNoDataColor="polyhedron_attribute_no_data_color"
321
+ v-model:attributeTimeStep="polyhedron_attribute_time_step"
312
322
  @ranges_per_data="emit('polyhedron_attribute_ranges_per_data', $event)"
313
323
  :id="id"
314
324
  :componentIds="componentIds"
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import AttributeRangeSelector from "@ogw_front/components/Viewer/Options/AttributeRangeSelector.vue";
3
3
  import ColorMapList from "@ogw_front/components/Viewer/Options/ColorMapList.vue";
4
+ import TimeStepSlider from "@ogw_front/components/Viewer/Options/TimeStepSlider.vue";
4
5
 
5
6
  import { getPresetsWithCurrentAtTop } from "@ogw_front/utils/colormap";
6
7
  import { useGlobalAttributeStyle } from "@ogw_front/composables/global_attribute_style";
@@ -16,8 +17,21 @@ const { dataId = undefined, x, y } = defineProps<Props>();
16
17
  const show = defineModel<boolean>("show", { default: false });
17
18
 
18
19
  const dataIdRef = computed<string>(() => dataId);
19
- const { currentColormap, currentRange, applyGlobalColormap, resetGlobalRange } =
20
- useGlobalAttributeStyle(dataIdRef);
20
+ const {
21
+ currentColormap,
22
+ currentRange,
23
+ applyGlobalColormap,
24
+ resetGlobalRange,
25
+ currentTimeSteps,
26
+ currentTimeStep,
27
+ loadTimeSteps,
28
+ } = useGlobalAttributeStyle(dataIdRef);
29
+
30
+ watch(show, async (isShown) => {
31
+ if (isShown) {
32
+ await loadTimeSteps();
33
+ }
34
+ });
21
35
 
22
36
  const minimum = computed<number>({
23
37
  get: () => currentRange.value[0],
@@ -61,6 +75,7 @@ async function onQuickColormapSelect(preset: { Name: string }): Promise<void> {
61
75
  v-model:maximum="maximum"
62
76
  @reset="resetGlobalRange"
63
77
  />
78
+ <TimeStepSlider :time-steps="currentTimeSteps" v-model="currentTimeStep" />
64
79
  </ColorMapList>
65
80
  </v-menu>
66
81
  </template>
@@ -0,0 +1,120 @@
1
+ <script setup lang="ts">
2
+ const timeStep = defineModel<number>();
3
+
4
+ interface Props {
5
+ timeSteps: number[];
6
+ }
7
+
8
+ const { timeSteps } = defineProps<Props>();
9
+
10
+ const sliderTimeStep = ref<number>(0);
11
+
12
+ watch(
13
+ timeStep,
14
+ (value) => {
15
+ sliderTimeStep.value = value ?? 0;
16
+ },
17
+ { immediate: true },
18
+ );
19
+
20
+ function commitTimeStep(value: number): void {
21
+ timeStep.value = value;
22
+ }
23
+
24
+ function formatTime(index: number): string {
25
+ const time = timeSteps[index];
26
+ return time === undefined ? "" : `t = ${time}`;
27
+ }
28
+ </script>
29
+
30
+ <template>
31
+ <v-slider
32
+ v-if="timeSteps.length > 1"
33
+ data-testid="timeStepSlider"
34
+ v-model="sliderTimeStep"
35
+ :min="0"
36
+ :max="timeSteps.length - 1"
37
+ :step="1"
38
+ color="primary"
39
+ density="compact"
40
+ class="time-step-slider mt-3"
41
+ hide-details
42
+ show-ticks="always"
43
+ :tick-size="10"
44
+ thumb-label="hover"
45
+ @end="commitTimeStep"
46
+ @keyup="commitTimeStep(sliderTimeStep)"
47
+ >
48
+ <template #prepend>
49
+ <span data-testid="timeStepValue" class="time-step-value">
50
+ {{ formatTime(sliderTimeStep) }}
51
+ </span>
52
+ </template>
53
+ <template #thumb-label="{ modelValue }">{{ formatTime(modelValue) }}</template>
54
+ <template #tick-label="{ index }">
55
+ <span class="time-step-tick">
56
+ <span class="v-slider-thumb__label time-step-tick-label">
57
+ <span class="v-slider-thumb__label-wedge" />
58
+ {{ formatTime(index) }}
59
+ </span>
60
+ </span>
61
+ </template>
62
+ </v-slider>
63
+ </template>
64
+
65
+ <style scoped>
66
+ .time-step-value {
67
+ min-width: 32px;
68
+ white-space: nowrap;
69
+ font-size: 0.8125rem;
70
+ }
71
+
72
+ .time-step-slider.v-slider.v-input--horizontal :deep(.v-input__prepend) {
73
+ margin-inline-end: 4px;
74
+ }
75
+
76
+ .time-step-slider :deep(.v-slider-thumb__label) {
77
+ width: auto;
78
+ white-space: nowrap;
79
+ padding: 0 6px;
80
+ }
81
+
82
+ /* Since v-tooltip is a bit different from the slider tick label */
83
+ .time-step-slider :deep(.v-slider-track__tick-label) {
84
+ inset: 0;
85
+ transform: none !important;
86
+ margin: 0 !important;
87
+ }
88
+
89
+ .time-step-tick {
90
+ position: relative;
91
+ display: block;
92
+ width: 100%;
93
+ height: 100%;
94
+ }
95
+
96
+ .time-step-slider .time-step-tick-label {
97
+ display: none;
98
+ bottom: calc(100% + 8px) !important;
99
+ left: 50%;
100
+ pointer-events: none;
101
+ }
102
+
103
+ .time-step-tick:hover .time-step-tick-label {
104
+ display: flex;
105
+ }
106
+
107
+ .time-step-slider :deep(.v-slider-track__tick) {
108
+ border-radius: 50%;
109
+ background-color: rgb(var(--v-theme-primary));
110
+ }
111
+
112
+ /* Vuetify insets the first and last ticks, which only suits tiny ticks */
113
+ .time-step-slider.v-slider.v-input--horizontal :deep(.v-slider-track__tick--first) {
114
+ margin-inline-start: 0;
115
+ }
116
+
117
+ .time-step-slider.v-slider.v-input--horizontal :deep(.v-slider-track__tick--last) {
118
+ margin-inline-start: 100%;
119
+ }
120
+ </style>
@@ -84,6 +84,19 @@ const vertex_attribute_name = computed({
84
84
  hybridViewerStore.remoteRender();
85
85
  },
86
86
  });
87
+
88
+ const vertex_attribute_time_step = computed({
89
+ get: () => dataStyleStore.meshPointsVertexAttributeTimeStep(id.value),
90
+ set: async (newValue) => {
91
+ if (newValue === undefined) {
92
+ return;
93
+ }
94
+ await applyBatchStyle(id.value, (targetId: string) =>
95
+ Promise.resolve(dataStyleStore.setMeshPointsVertexAttributeTimeStep(targetId, newValue)),
96
+ );
97
+ hybridViewerStore.remoteRender();
98
+ },
99
+ });
87
100
  const vertex_attribute_item = computed({
88
101
  get: () => dataStyleStore.meshPointsVertexAttributeItem(id.value),
89
102
  set: async (newValue) => {
@@ -158,6 +171,7 @@ async function setVertexAttributeRangesPerData(ranges: RangesPerData): Promise<v
158
171
  @vertex_attribute_ranges_per_data="setVertexAttributeRangesPerData"
159
172
  v-model:vertex_attribute_color_map="vertex_attribute_color_map"
160
173
  v-model:vertex_attribute_no_data_color="vertex_attribute_no_data_color"
174
+ v-model:vertex_attribute_time_step="vertex_attribute_time_step"
161
175
  :vertex_has_colormap="true"
162
176
  />
163
177
  </template>
@@ -69,6 +69,19 @@ const vertex_attribute_name = computed<string | undefined>({
69
69
  hybridViewerStore.remoteRender();
70
70
  },
71
71
  });
72
+
73
+ const vertex_attribute_time_step = computed<number | undefined>({
74
+ get: () => dataStyleStore.meshPolyhedraVertexAttributeTimeStep(id.value),
75
+ set: async (newValue) => {
76
+ if (newValue === undefined) {
77
+ return;
78
+ }
79
+ await applyBatchStyle(id.value, (targetId: string) =>
80
+ Promise.resolve(dataStyleStore.setMeshPolyhedraVertexAttributeTimeStep(targetId, newValue)),
81
+ );
82
+ hybridViewerStore.remoteRender();
83
+ },
84
+ });
72
85
  const vertex_attribute_item = computed<string | undefined>({
73
86
  get: () => dataStyleStore.meshPolyhedraVertexAttributeItem(id.value),
74
87
  set: async (newValue) => {
@@ -123,6 +136,21 @@ const polyhedron_attribute_name = computed<string | undefined>({
123
136
  hybridViewerStore.remoteRender();
124
137
  },
125
138
  });
139
+
140
+ const polyhedron_attribute_time_step = computed<number | undefined>({
141
+ get: () => dataStyleStore.meshPolyhedraPolyhedronAttributeTimeStep(id.value),
142
+ set: async (newValue) => {
143
+ if (newValue === undefined) {
144
+ return;
145
+ }
146
+ await applyBatchStyle(id.value, (targetId: string) =>
147
+ Promise.resolve(
148
+ dataStyleStore.setMeshPolyhedraPolyhedronAttributeTimeStep(targetId, newValue),
149
+ ),
150
+ );
151
+ hybridViewerStore.remoteRender();
152
+ },
153
+ });
126
154
  const polyhedron_attribute_item = computed<string | undefined>({
127
155
  get: () => dataStyleStore.meshPolyhedraPolyhedronAttributeItem(id.value),
128
156
  set: async (newValue) => {
@@ -206,12 +234,14 @@ async function setPolyhedronAttributeRangesPerData(ranges: RangesPerData): Promi
206
234
  @vertex_attribute_ranges_per_data="setVertexAttributeRangesPerData"
207
235
  v-model:vertex_attribute_color_map="vertex_attribute_color_map"
208
236
  v-model:vertex_attribute_no_data_color="vertex_attribute_no_data_color"
237
+ v-model:vertex_attribute_time_step="vertex_attribute_time_step"
209
238
  v-model:polyhedron_attribute_name="polyhedron_attribute_name"
210
239
  v-model:polyhedron_attribute_item="polyhedron_attribute_item"
211
240
  v-model:polyhedron_attribute_range="polyhedron_attribute_range"
212
241
  @polyhedron_attribute_ranges_per_data="setPolyhedronAttributeRangesPerData"
213
242
  v-model:polyhedron_attribute_color_map="polyhedron_attribute_color_map"
214
243
  v-model:polyhedron_attribute_no_data_color="polyhedron_attribute_no_data_color"
244
+ v-model:polyhedron_attribute_time_step="polyhedron_attribute_time_step"
215
245
  />
216
246
  </template>
217
247
  </template>
@@ -86,6 +86,19 @@ const vertex_attribute_name = computed<string | undefined>({
86
86
  hybridViewerStore.remoteRender();
87
87
  },
88
88
  });
89
+
90
+ const vertex_attribute_time_step = computed<number | undefined>({
91
+ get: () => dataStyleStore.meshPolygonsVertexAttributeTimeStep(id.value),
92
+ set: async (newValue) => {
93
+ if (newValue === undefined) {
94
+ return;
95
+ }
96
+ await applyBatchStyle(id.value, (targetId: string) =>
97
+ Promise.resolve(dataStyleStore.setMeshPolygonsVertexAttributeTimeStep(targetId, newValue)),
98
+ );
99
+ hybridViewerStore.remoteRender();
100
+ },
101
+ });
89
102
  const vertex_attribute_item = computed<string | undefined>({
90
103
  get: () => dataStyleStore.meshPolygonsVertexAttributeItem(id.value),
91
104
  set: async (newValue) => {
@@ -140,6 +153,19 @@ const polygon_attribute_name = computed<string | undefined>({
140
153
  hybridViewerStore.remoteRender();
141
154
  },
142
155
  });
156
+
157
+ const polygon_attribute_time_step = computed<number | undefined>({
158
+ get: () => dataStyleStore.meshPolygonsPolygonAttributeTimeStep(id.value),
159
+ set: async (newValue) => {
160
+ if (newValue === undefined) {
161
+ return;
162
+ }
163
+ await applyBatchStyle(id.value, (targetId: string) =>
164
+ Promise.resolve(dataStyleStore.setMeshPolygonsPolygonAttributeTimeStep(targetId, newValue)),
165
+ );
166
+ hybridViewerStore.remoteRender();
167
+ },
168
+ });
143
169
  const polygon_attribute_item = computed<string | undefined>({
144
170
  get: () => dataStyleStore.meshPolygonsPolygonAttributeItem(id.value),
145
171
  set: async (newValue) => {
@@ -223,12 +249,14 @@ async function setPolygonAttributeRangesPerData(ranges: RangesPerData): Promise<
223
249
  @vertex_attribute_ranges_per_data="setVertexAttributeRangesPerData"
224
250
  v-model:vertex_attribute_color_map="vertex_attribute_color_map"
225
251
  v-model:vertex_attribute_no_data_color="vertex_attribute_no_data_color"
252
+ v-model:vertex_attribute_time_step="vertex_attribute_time_step"
226
253
  v-model:polygon_attribute_name="polygon_attribute_name"
227
254
  v-model:polygon_attribute_item="polygon_attribute_item"
228
255
  v-model:polygon_attribute_range="polygon_attribute_range"
229
256
  @polygon_attribute_ranges_per_data="setPolygonAttributeRangesPerData"
230
257
  v-model:polygon_attribute_color_map="polygon_attribute_color_map"
231
258
  v-model:polygon_attribute_no_data_color="polygon_attribute_no_data_color"
259
+ v-model:polygon_attribute_time_step="polygon_attribute_time_step"
232
260
  />
233
261
  </template>
234
262
  </template>
@@ -39,6 +39,7 @@ interface AttributeResponse {
39
39
  readonly max_values?: readonly number[];
40
40
  readonly min_value?: number;
41
41
  readonly max_value?: number;
42
+ readonly time_steps?: readonly number[];
42
43
  }[];
43
44
  }
44
45
 
@@ -75,6 +76,9 @@ export function useGlobalAttributeStyle(dataIdRef: Ref<string | undefined>): {
75
76
  currentRange: typeof currentRange;
76
77
  applyGlobalColormap: typeof applyGlobalColormap;
77
78
  resetGlobalRange: typeof resetGlobalRange;
79
+ currentTimeSteps: typeof currentTimeSteps;
80
+ currentTimeStep: typeof currentTimeStep;
81
+ loadTimeSteps: typeof loadTimeSteps;
78
82
  } {
79
83
  const dataStyleStore = useDataStyleStore();
80
84
  const hybridViewerStore = useHybridViewerStore();
@@ -294,10 +298,100 @@ export function useGlobalAttributeStyle(dataIdRef: Ref<string | undefined>): {
294
298
  await Promise.all(requestPromises);
295
299
  }
296
300
 
301
+ const currentTimeSteps = ref<number[]>([]);
302
+ const temporalComponent = shallowRef<ActiveComponent | undefined>(undefined);
303
+
304
+ async function requestTimeSteps(targetId: string, comp: ActiveComponent): Promise<number[]> {
305
+ const { activeColoring, attributeType, getterKey } = comp;
306
+ const nameGetter = getDynamicStoreMethod(dataStyleStore, `${getterKey}${attributeType}Name`);
307
+ const rawAttrName = nameGetter?.(targetId);
308
+ const schema = (back_schemas.opengeodeweb_back as Record<string, unknown>)[
309
+ `${activeColoring}_attribute_names`
310
+ ];
311
+ if (
312
+ !isStringOrUndefined(rawAttrName) ||
313
+ rawAttrName === undefined ||
314
+ rawAttrName === "" ||
315
+ !isJsonRpcSchema(schema)
316
+ ) {
317
+ return [];
318
+ }
319
+ let timeSteps: number[] = [];
320
+ await backStore.request(
321
+ { schema, params: { id: targetId } },
322
+ {
323
+ response_function: (response: unknown): void => {
324
+ if (!isAttributeResponse(response)) {
325
+ return;
326
+ }
327
+ const found = response.attributes?.find((attr) => attr.attribute_name === rawAttrName);
328
+ timeSteps = [...(found?.time_steps ?? [])];
329
+ },
330
+ },
331
+ );
332
+ return timeSteps;
333
+ }
334
+
335
+ async function loadTimeSteps(): Promise<void> {
336
+ const targetId = dataIdRef.value;
337
+ temporalComponent.value = undefined;
338
+ currentTimeSteps.value = [];
339
+ if (targetId === undefined || targetId === "") {
340
+ return;
341
+ }
342
+ for (const comp of getActiveComponents(targetId)) {
343
+ // oxlint-disable-next-line no-await-in-loop -- first temporal component wins, in order.
344
+ const timeSteps = await requestTimeSteps(targetId, comp);
345
+ if (timeSteps.length > 0) {
346
+ temporalComponent.value = comp;
347
+ currentTimeSteps.value = timeSteps;
348
+ return;
349
+ }
350
+ }
351
+ }
352
+
353
+ watch(dataIdRef, loadTimeSteps, { immediate: true });
354
+
355
+ async function applyCurrentTimeStep(step: number): Promise<void> {
356
+ const targetId = dataIdRef.value;
357
+ const comp = temporalComponent.value;
358
+ if (targetId === undefined || targetId === "" || !comp) {
359
+ return;
360
+ }
361
+ const { attributeType, setterKey } = comp;
362
+ const setter = getDynamicStoreMethod(dataStyleStore, `set${setterKey}${attributeType}TimeStep`);
363
+ if (setter) {
364
+ await setter(targetId, step);
365
+ await hybridViewerStore.remoteRender();
366
+ }
367
+ }
368
+
369
+ const currentTimeStep = computed<number>({
370
+ get(): number {
371
+ const targetId = dataIdRef.value;
372
+ const comp = temporalComponent.value;
373
+ if (targetId === undefined || targetId === "" || !comp) {
374
+ return 0;
375
+ }
376
+ const { attributeType, getterKey } = comp;
377
+ const getter = getDynamicStoreMethod(dataStyleStore, `${getterKey}${attributeType}TimeStep`);
378
+ const rawStep = getter?.(targetId);
379
+ return typeof rawStep === "number" ? rawStep : 0;
380
+ },
381
+ // Setter cannot be async (see `currentRange`), so the work is delegated to a helper.
382
+ set: (step: number): void => {
383
+ /* oxlint-disable-next-line promise/prefer-await-to-then -- setter cannot be async; see comment above. */
384
+ applyCurrentTimeStep(step).catch(() => undefined);
385
+ },
386
+ });
387
+
297
388
  return {
298
389
  currentColormap,
299
390
  currentRange,
300
391
  applyGlobalColormap,
301
392
  resetGlobalRange,
393
+ currentTimeSteps,
394
+ currentTimeStep,
395
+ loadTimeSteps,
302
396
  };
303
397
  }