Skip to content

Commit bf7256b

Browse files
authored
Merge pull request #19 from OpenGeoscience/tabular-chart-updates
Tabular Chart Updates
2 parents 5910c50 + a08ca3f commit bf7256b

22 files changed

Lines changed: 1398 additions & 178 deletions

client/src/MapStore.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
NetCDFData,
1010
NetCDFLayer,
1111
RasterMapLayer,
12+
VectorFeatureTableGraph,
1213
VectorMapLayer,
1314
} from './types';
1415
import UVdatApi from './api/UVDATApi';
@@ -99,6 +100,31 @@ export default class MapStore {
99100
MapStore.mapLayersByDataset[datasetId] = await UVdatApi.getDatasetLayers(datasetId);
100101
}
101102

103+
public static mapLayerFeatureGraphs = computed(() => {
104+
const foundMapLayerFeatureGraphs: { name: string, id: number; graphs: VectorFeatureTableGraph[] }[] = [];
105+
MapStore.selectedVectorMapLayers.value.forEach((item) => {
106+
if (item.default_style.mapLayerFeatureTableGraphs && item.default_style.mapLayerFeatureTableGraphs.length) {
107+
foundMapLayerFeatureGraphs.push({
108+
name: item.name,
109+
id: item.id,
110+
graphs: item.default_style.mapLayerFeatureTableGraphs,
111+
});
112+
}
113+
});
114+
return foundMapLayerFeatureGraphs;
115+
});
116+
117+
public static mapLayerFeatureGraphsVisible = ref(false);
118+
119+
// Graph color mapping implementation
120+
public static enabledMapLayerFeatureColorMapping = ref(false);
121+
122+
public static mapLayerFeatureColorMapping: Ref<Record<number, string>> = ref({});
123+
124+
public static clearMapLayerFeatureColorMapping = () => {
125+
MapStore.mapLayerFeatureColorMapping.value = {};
126+
};
127+
102128
// ToolTips
103129
public static toolTipMenuOpen = ref(false);
104130

@@ -131,6 +157,27 @@ export default class MapStore {
131157

132158
public static selectedIds = computed(() => MapStore.selectedFeatures.value.map((item) => item.properties.vectorfeatureid));
133159

160+
public static hoveredFeatures: Ref<number[]> = ref([]);
161+
162+
public static setHoveredFeature = (feature: number) => {
163+
MapStore.hoveredFeatures.value = [feature];
164+
};
165+
166+
public static removeHoveredFeature = (feature: number) => {
167+
const foundIndex = MapStore.hoveredFeatures.value.findIndex((item) => item === feature);
168+
if (foundIndex !== -1) {
169+
MapStore.hoveredFeatures.value.splice(foundIndex, 1);
170+
}
171+
};
172+
173+
public static removeHoveredFeatures = (features: number[]) => {
174+
MapStore.hoveredFeatures.value = MapStore.hoveredFeatures.value.filter((item) => !features.includes(item));
175+
};
176+
177+
public static clearHoveredFeatures = () => {
178+
MapStore.hoveredFeatures.value = [];
179+
};
180+
134181
public static pollForVectorBasemap = (pollInterval: number = 5000) => {
135182
let cancelled = false;
136183

client/src/api/UVDATApi.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -506,17 +506,34 @@ export default class UVdatApi {
506506
vectorFeatureId: number,
507507
xAxis: string = 'index',
508508
yAxis: string = 'mean_va',
509-
filter?: string,
510-
filterVal?: string,
511509
): Promise<FeatureGraphData> {
512510
const response = await UVdatApi.apiClient.get('/vectorfeature/tabledata/feature-graph/', {
513511
params: {
514512
tableType,
515513
vectorFeatureId,
516514
xAxis,
517515
yAxis,
518-
filter,
519-
filterVal,
516+
},
517+
});
518+
return response.data;
519+
}
520+
521+
public static async getMapLayerFeatureGraphData(
522+
tableType: string,
523+
mapLayerId: number,
524+
xAxis: string = 'index',
525+
yAxis: string = 'mean_va',
526+
indexer: string = 'vectorFeatureId',
527+
bbox?: string,
528+
): Promise<FeatureGraphData> {
529+
const response = await UVdatApi.apiClient.get('/vectorfeature/tabledata/map-layer-feature-graph/', {
530+
params: {
531+
tableType,
532+
mapLayerId,
533+
xAxis,
534+
yAxis,
535+
indexer,
536+
bbox,
520537
},
521538
});
522539
return response.data;

client/src/components/DataSelection/Datasets.vue

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,17 +175,18 @@ export default defineComponent({
175175
</template>
176176
</v-tooltip>
177177
</div>
178-
<v-list>
178+
<v-list class="pa-0">
179179
<v-list-group
180180
v-for="dataset in datasets"
181181
:key="`${dataset.id}_${dataset.modified}`"
182182
:value="`dataset:${dataset.id}`"
183-
class="list-group"
183+
class="list-group pa-0"
184184
>
185185
<template #activator="{ props, isOpen }">
186186
<v-list-item
187187
v-bind="props"
188188
:title="dataset.name"
189+
class="pa-0"
189190
@click="!isOpen && loadDataset(dataset)"
190191
>
191192
<v-tooltip
@@ -242,6 +243,7 @@ export default defineComponent({
242243
<v-list-item
243244
v-for="layer in layersByDataset[dataset.id]"
244245
:key="layer.id"
246+
class="pa-0"
245247
>
246248
<DatasetItem :layer="layer" @netcdf-deleted="updateNetCDFLayer(dataset.id)" />
247249
</v-list-item>
@@ -291,7 +293,7 @@ export default defineComponent({
291293
.list-group {
292294
/* reduce padding of nested groups */
293295
--list-indent-size: 0px;
294-
--prepend-width: 24px;
296+
--prepend-width: 8px;
295297
}
296298
297299
.layer-selection {

client/src/components/DataSelection/NetCDFDataConfigurator.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ export default defineComponent({
377377

378378
<template>
379379
<v-list dense>
380-
<v-row dense align="center" justify="center" class="mx-1">
380+
<v-row dense align="center" justify="center" class="mx-1 pa-0">
381381
<v-icon v-tooltip="'NetCDF Layer'">
382382
mdi-grid
383383
</v-icon>
@@ -764,6 +764,7 @@ export default defineComponent({
764764
white-space: nowrap;
765765
overflow: hidden;
766766
text-overflow: ellipsis;
767+
max-width:210px;
767768
}
768769
.select-variable {
769770
border: 1px solid gray;

client/src/components/FeatureSelection/SelectedFeature.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,9 @@ export default defineComponent({
112112
</v-row>
113113
</v-card-title>
114114
<v-card-text>
115-
<v-expansion-panels :model-value="selectedFeatureExpanded" @click="toggleFeatureExpaned()">
115+
<v-expansion-panels :model-value="selectedFeatureExpanded">
116116
<v-expansion-panel value="expanded">
117-
<v-expansion-panel-title>
117+
<v-expansion-panel-title @click="toggleFeatureExpaned()">
118118
<v-icon class="pr-2">
119119
mdi-list-box-outline
120120
</v-icon> Data
@@ -163,7 +163,7 @@ export default defineComponent({
163163
</v-icon> {{ vectorGraph.name }}
164164
</v-expansion-panel-title>
165165
<v-expansion-panel-text class="ma-0">
166-
<vector-feature-chart :graph-info="vectorGraph" :vector-feature-id="featureId" />
166+
<vector-feature-chart :graph-info="vectorGraph" :vector-feature-id="featureId" :map-layer-id="mapLayerId" />
167167
</v-expansion-panel-text>
168168
</v-expansion-panel>
169169
</v-expansion-panels>

client/src/components/FeatureSelection/VectorFeatureChart.vue

Lines changed: 59 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
import {
33
PropType, defineComponent, nextTick, ref, watch,
44
} from 'vue';
5-
import * as d3 from 'd3';
65
import UVdatApi from '../../api/UVDATApi';
76
import { FeatureGraphData, VectorFeatureTableGraph } from '../../types';
7+
import { renderVectorFeatureGraph } from './vectorFeatureGraphUtils';
88
99
export default defineComponent({
1010
name: 'FeatureGraph',
@@ -17,91 +17,48 @@ export default defineComponent({
1717
type: Number as PropType<number>,
1818
required: true,
1919
},
20+
mapLayerId: {
21+
type: Number,
22+
required: true,
23+
},
2024
},
2125
setup(props) {
2226
const graphContainer = ref<SVGSVGElement | null>(null);
2327
const graphDialogContainer = ref<SVGSVGElement | null>(null);
2428
const graphData = ref<FeatureGraphData | null>(null);
2529
const dialogVisible = ref(false);
26-
27-
// Render graph using D3
28-
const renderGraph = (data: FeatureGraphData, container = 'graphContainer') => {
29-
const localContainer = container === 'graphContainer' ? graphContainer : graphDialogContainer;
30-
if (!localContainer.value || !data) return;
31-
32-
const svg = d3.select(localContainer.value);
33-
svg.selectAll('*').remove(); // Clear any existing content in the SVG
34-
35-
const margin = {
36-
top: 20, right: container === 'graphContainer' ? 0 : 20, bottom: 40, left: 40,
37-
};
38-
39-
// Set the maximum width to 250px
40-
const width = localContainer.value?.clientWidth || 250 - margin.left - margin.right;
41-
const height = 400 - margin.top - margin.bottom;
42-
43-
const x = d3.scaleLinear().range([0, width]);
44-
const y = d3.scaleLinear().range([height, 0]);
45-
46-
const line = d3.line()
47-
.x((d: [number, number]) => x(d[0]))
48-
.y((d: [number, number]) => y(d[1]));
49-
50-
let dataForGraph: { data: [number, number][], filterVal?: string } | undefined;
51-
52-
// Check for default data or apply filter if necessary
53-
if (data.graphs[props.vectorFeatureId]) {
54-
dataForGraph = data.graphs[props.vectorFeatureId];
55-
}
56-
57-
if (!dataForGraph) {
58-
return; // Return if no data is available
59-
}
60-
61-
// Set the domain for the axes, ensuring we handle empty arrays correctly
62-
const xExtent = d3.extent(dataForGraph.data.map((item) => item[0]));
63-
const yExtent = d3.extent(dataForGraph.data.map((item) => item[1]));
64-
65-
// Fallback to zero if data is empty
66-
x.domain(xExtent[0] !== undefined ? xExtent : [0, 1]);
67-
y.domain(yExtent[0] !== undefined ? yExtent : [0, 1]);
68-
69-
// Create the graph container
70-
const g = svg.append('g')
71-
.attr('transform', `translate(${margin.left},${margin.top})`);
72-
73-
// Add the line path to the graph
74-
svg.append('path')
75-
.attr('fill', 'none')
76-
.attr('stroke', 'steelblue')
77-
.attr('stroke-width', 1.5)
78-
.attr('d', line(dataForGraph.data.sort((a, b) => a[0] - b[0])))
79-
.attr('transform', `translate(${margin.left},${margin.top})`);
80-
81-
// Add the X-axis
82-
g.append('g')
83-
.attr('class', 'x axis')
84-
.attr('transform', `translate(0,${height})`)
85-
.call(d3.axisBottom(x).ticks(5));
86-
87-
// Add the Y-axis
88-
g.append('g')
89-
.attr('class', 'y axis')
90-
.call(d3.axisLeft(y));
91-
};
30+
const noGraphData = ref(false);
9231
9332
// Fetch feature graph data when component is mounted or props change
9433
const fetchFeatureGraphData = async () => {
34+
noGraphData.value = false;
9535
try {
9636
const data = await UVdatApi.getFeatureGraphData(
9737
props.graphInfo.type, // Use graphInfo.type (tableType) instead of mapLayerId
9838
props.vectorFeatureId,
9939
props.graphInfo.xAxis,
10040
props.graphInfo.yAxis,
101-
props.graphInfo.indexer,
10241
);
42+
if (data.graphs && Object.keys(data.graphs).length === 0) {
43+
noGraphData.value = true;
44+
return;
45+
}
10346
graphData.value = data;
104-
renderGraph(data);
47+
if (graphContainer.value) {
48+
nextTick(() => {
49+
if (graphContainer.value) {
50+
renderVectorFeatureGraph(
51+
data,
52+
graphContainer.value,
53+
{
54+
specificGraphKey: props.vectorFeatureId,
55+
xAxisIsTime: props.graphInfo.xAxis === 'unix_time',
56+
xAxisVerticalLabels: true,
57+
},
58+
);
59+
}
60+
});
61+
}
10562
} catch (error) {
10663
// eslint-disable-next-line no-console
10764
console.error('Error fetching feature graph data:', error);
@@ -112,15 +69,27 @@ export default defineComponent({
11269
watch(
11370
() => [props.graphInfo, props.vectorFeatureId],
11471
() => fetchFeatureGraphData(),
115-
{ immediate: true },
11672
);
73+
watch(graphContainer, () => {
74+
fetchFeatureGraphData();
75+
});
11776
11877
// Open the dialog to display a larger graph
11978
const openDialog = () => {
12079
dialogVisible.value = true;
12180
nextTick(() => {
122-
if (graphData.value) {
123-
renderGraph(graphData.value, 'graphDialogContainer');
81+
if (graphData.value && graphDialogContainer.value) {
82+
renderVectorFeatureGraph(
83+
graphData.value,
84+
graphDialogContainer.value,
85+
{
86+
specificGraphKey: props.vectorFeatureId,
87+
xAxisIsTime: props.graphInfo.xAxis === 'unix_time',
88+
showXYValuesOnHover: true,
89+
xAxisLabel: props.graphInfo.xAxisLabel,
90+
yAxisLabel: props.graphInfo.yAxisLabel,
91+
},
92+
);
12493
}
12594
});
12695
};
@@ -131,29 +100,36 @@ export default defineComponent({
131100
graphData,
132101
dialogVisible,
133102
openDialog,
103+
noGraphData,
134104
};
135105
},
136106
});
137107
</script>
138108

139109
<template>
140110
<div>
141-
<!-- Button to open dialog -->
142-
<v-btn color="primary" @click="openDialog">
143-
View Larger Graph
144-
</v-btn>
145-
146-
<!-- Graph container -->
147-
<svg ref="graphContainer" width="100%" height="400" class="selectedFeatureSVG" />
148-
149-
<!-- Dialog for larger chart -->
111+
<div v-if="noGraphData">
112+
<v-alert type="warning">
113+
No Data to Graph
114+
</v-alert>
115+
</div>
116+
<div v-if="graphData">
117+
<v-btn color="primary" size="x-small" @click="openDialog">
118+
View Larger Graph
119+
</v-btn>
120+
</div>
121+
<svg ref="graphContainer" width="100%" :height="graphData ? 400 : 0" class="selectedFeatureSVG" />
150122
<v-dialog v-model="dialogVisible" max-width="800px">
151123
<v-card>
152124
<v-card-title>
153-
<span class="headline">Feature Graph</span>
125+
<v-row>
126+
<v-spacer />
127+
<span class="headline">{{ graphInfo.name }}</span>
128+
<v-spacer />
129+
</v-row>
154130
</v-card-title>
155131
<v-card-text>
156-
<svg ref="graphDialogContainer" width="100%" height="500" />
132+
<svg ref="graphDialogContainer" width="100%" height="400" />
157133
</v-card-text>
158134
<v-card-actions>
159135
<v-btn @click="dialogVisible = false">

0 commit comments

Comments
 (0)