Skip to content

Commit d2214cb

Browse files
Attribute Sorting/Filtering/Graphing (#1280)
* beginning of attribute filtering * temporary stuff * adding in custom filters * moving filtering into useAttributes * adding key filters and improving filtering * Linter fixes * adding basic timeline graphing for detecitons * fixing issues and upating documentation * Adding documentation * fix value issues * Update docs/UI-AttributeDetails.md Co-authored-by: Mary Salvi <40494088+marySalvi@users.noreply.github.com> * addressing comments * Fixing upload, changing styling Co-authored-by: Mary Salvi <40494088+marySalvi@users.noreply.github.com>
1 parent 845e253 commit d2214cb

38 files changed

Lines changed: 2155 additions & 72 deletions

client/dive-common/components/AttributeInput.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ export default defineComponent({
137137
:label="datatype"
138138
:value="value"
139139
:disabled="disabled"
140+
:step="value <= 1 ? .01 : 1"
140141
class="input-box"
141142
type="number"
142143
@change="change"
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<script lang="ts">
2+
import {
3+
defineComponent, ref, watch, PropType,
4+
} from '@vue/composition-api';
5+
6+
import StackedVirtualSidebarContainer from 'dive-common/components/StackedVirtualSidebarContainer.vue';
7+
import { useReadOnlyMode } from 'vue-media-annotator/provides';
8+
import { usePrompt } from 'dive-common/vue-utilities/prompt-service';
9+
import AttributeFilters from 'vue-media-annotator/components/AttributeFilters.vue';
10+
import AttributeTimeline from 'vue-media-annotator/components/AttributeTimeline.vue';
11+
import TooltipBtn from 'vue-media-annotator/components/TooltipButton.vue';
12+
13+
export default defineComponent({
14+
name: 'AttributesSideBar',
15+
16+
components: {
17+
StackedVirtualSidebarContainer,
18+
AttributeFilters,
19+
AttributeTimeline,
20+
TooltipBtn,
21+
},
22+
23+
props: {
24+
width: {
25+
type: Number,
26+
default: 300,
27+
},
28+
subCategory: {
29+
type: String as PropType<'Timeline' | 'Filtering'>,
30+
required: false,
31+
},
32+
},
33+
34+
setup(props) {
35+
const readOnlyMode = useReadOnlyMode();
36+
const { visible } = usePrompt();
37+
const currentMode = ref(props.subCategory);
38+
const modes = ref(['Filtering', 'Timeline']);
39+
watch(() => props.subCategory, () => {
40+
if (props.subCategory !== undefined) {
41+
currentMode.value = props.subCategory;
42+
}
43+
});
44+
return {
45+
readOnlyMode,
46+
currentMode,
47+
modes,
48+
visible,
49+
};
50+
},
51+
});
52+
</script>
53+
54+
55+
<template>
56+
<StackedVirtualSidebarContainer
57+
:width="width"
58+
:enable-slot="false"
59+
>
60+
<template #default="{ bottomHeight }">
61+
<v-container>
62+
<h3> {{ currentMode }} </h3>
63+
<v-row class="px-3">
64+
<div class="mx-1">
65+
<tooltip-btn
66+
icon="mdi-filter"
67+
tooltip-text="Filter Attributes displayed"
68+
size="large"
69+
:color="currentMode === 'Filtering'? 'primary' : 'default'"
70+
outlined
71+
tile
72+
@click="currentMode = 'Filtering'"
73+
/>
74+
</div>
75+
<div class="mx-1">
76+
<tooltip-btn
77+
icon="mdi-chart-line-variant"
78+
tooltip-text="Chart Numeric Attributes"
79+
size="large"
80+
outlined
81+
:color="currentMode === 'Timeline'? 'primary' : 'default'"
82+
83+
tile
84+
@click="currentMode = 'Timeline'"
85+
/>
86+
</div>
87+
</v-row>
88+
<v-divider />
89+
<attribute-filters
90+
v-if="currentMode === 'Filtering'"
91+
class="flex-grow-0 flex-shrink-0"
92+
:height="bottomHeight"
93+
:hotkeys-disabled="visible() || readOnlyMode"
94+
/>
95+
<attribute-timeline
96+
v-if="currentMode === 'Timeline'"
97+
class="flex-grow-0 flex-shrink-0"
98+
:height="bottomHeight"
99+
/>
100+
</v-container>
101+
</template>
102+
</StackedVirtualSidebarContainer>
103+
</template>

client/dive-common/components/AttributesSubsection.vue

Lines changed: 118 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,19 @@ import {
1010
useCameraStore,
1111
useTime,
1212
useReadOnlyMode,
13+
useAttributesFilters,
1314
} from 'vue-media-annotator/provides';
14-
import { Attribute } from 'vue-media-annotator/use/useAttributes';
15+
import type { Attribute, AttributeFilter } from 'vue-media-annotator/use/useAttributes';
1516
import AttributeInput from 'dive-common/components/AttributeInput.vue';
1617
import PanelSubsection from 'dive-common/components/PanelSubsection.vue';
17-
18+
import TooltipBtn from 'vue-media-annotator/components/TooltipButton.vue';
19+
import context from 'dive-common/store/context';
1820
1921
export default defineComponent({
2022
components: {
2123
AttributeInput,
2224
PanelSubsection,
25+
TooltipBtn,
2326
},
2427
props: {
2528
attributes: {
@@ -39,8 +42,12 @@ export default defineComponent({
3942
const readOnlyMode = useReadOnlyMode();
4043
const { frame: frameRef } = useTime();
4144
const selectedTrackIdRef = useSelectedTrackId();
45+
const { attributeFilters, sortAndFilterAttributes, timelineEnabled } = useAttributesFilters();
4246
const cameraStore = useCameraStore();
4347
const activeSettings = ref(true);
48+
const sortingMethods = ['a-z', '1-0'];
49+
const sortingMethodIcons = ['mdi-sort-alphabetical-ascending', 'mdi-sort-numeric-ascending'];
50+
const sortingMode = ref(0);
4451
4552
const selectedTrack = computed(() => {
4653
if (selectedTrackIdRef.value !== null) {
@@ -66,9 +73,23 @@ export default defineComponent({
6673
return null;
6774
});
6875
69-
const filteredFullAttributes = computed(() => Object.values(props.attributes).filter(
70-
(attribute: Attribute) => attribute.belongs === props.mode.toLowerCase(),
71-
));
76+
const filteredFullAttributes = computed(() => {
77+
let additionFilters: AttributeFilter[] = [];
78+
let mode: 'track' | 'detection' = 'track';
79+
if (props.mode === 'Track') {
80+
additionFilters = attributeFilters.value.track;
81+
} else {
82+
additionFilters = attributeFilters.value.detection;
83+
mode = 'detection';
84+
}
85+
let attributeVals = {};
86+
if (selectedAttributes.value && selectedAttributes.value.attributes) {
87+
attributeVals = selectedAttributes.value.attributes;
88+
}
89+
return sortAndFilterAttributes(
90+
props.attributes, mode, attributeVals, sortingMode.value, additionFilters,
91+
);
92+
});
7293
7394
const activeAttributesCount = computed(
7495
() => props.attributes.filter(
@@ -106,6 +127,27 @@ export default defineComponent({
106127
function addAttribute() {
107128
emit('add-attribute', props.mode);
108129
}
130+
function clickSortToggle() {
131+
sortingMode.value = (sortingMode.value + 1) % sortingMethods.length;
132+
}
133+
134+
const filtersActive = computed(() => {
135+
let additionFilters: AttributeFilter[] = [];
136+
if (props.mode === 'Track') {
137+
additionFilters = attributeFilters.value.track;
138+
} else {
139+
additionFilters = attributeFilters.value.detection;
140+
}
141+
return !!additionFilters.find((filter) => filter.filterData.active === true);
142+
});
143+
144+
function openFilter() {
145+
context.openClose('AttributesSideBar', true, 'Filtering');
146+
}
147+
function openTimeline() {
148+
context.openClose('AttributesSideBar', true, 'Timeline');
149+
}
150+
109151
110152
return {
111153
frameRef,
@@ -120,6 +162,14 @@ export default defineComponent({
120162
editAttribute,
121163
addAttribute,
122164
setEditIndividual,
165+
//Sorting & Filters
166+
sortingMethodIcons,
167+
sortingMode,
168+
clickSortToggle,
169+
openFilter,
170+
openTimeline,
171+
timelineEnabled,
172+
filtersActive,
123173
};
124174
},
125175
});
@@ -134,25 +184,32 @@ export default defineComponent({
134184
class="align-center"
135185
no-gutters
136186
>
137-
<b>{{ mode }} Attributes:</b>
138-
<v-spacer />
187+
<v-col dense>
188+
<b class="attribute-header">{{ mode }} Attributes</b>
189+
<div
190+
v-if="mode === 'Detection'"
191+
no-gutters
192+
class="text-caption"
193+
>
194+
{{ `Frame: ${frameRef}` }}
195+
</div>
196+
</v-col>
139197
<v-tooltip
140198
open-delay="200"
141199
bottom
142200
max-width="200"
143201
>
144202
<template #activator="{ on }">
145203
<v-btn
146-
outlined
147-
x-small
204+
small
205+
icon
148206
:disabled="readOnlyMode"
149207
v-on="on"
150208
@click="addAttribute"
151209
>
152210
<v-icon small>
153211
mdi-plus
154212
</v-icon>
155-
Attribute
156213
</v-btn>
157214
</template>
158215
<span>Add a new {{ mode }} Attribute</span>
@@ -178,13 +235,29 @@ export default defineComponent({
178235
</template>
179236
<span>Show/Hide un-used</span>
180237
</v-tooltip>
181-
</v-row>
182-
<v-row
183-
v-if="mode === 'Detection'"
184-
no-gutters
185-
class="text-caption"
186-
>
187-
{{ `Frame: ${frameRef}` }}
238+
<tooltip-btn
239+
:icon="sortingMethodIcons[sortingMode]"
240+
tooltip-text="Sort types by value or alphabetically"
241+
@click="clickSortToggle"
242+
/>
243+
<tooltip-btn
244+
icon="mdi-filter"
245+
:color="filtersActive ? 'primary' : 'default'"
246+
:tooltip-text="filtersActive
247+
? 'Filters are active, click to view': 'No filters are active, click to edit'"
248+
@click="openFilter"
249+
/>
250+
<tooltip-btn
251+
v-if="mode === 'Detection'"
252+
icon="mdi-chart-line-variant"
253+
:color="timelineEnabled ? 'primary' : 'default'"
254+
tooltip-text="Timeline Settings for Attributes"
255+
@click="openTimeline"
256+
/>
257+
<div
258+
v-else
259+
class="blank-spacer"
260+
/>
188261
</v-row>
189262
</template>
190263

@@ -199,8 +272,8 @@ export default defineComponent({
199272
class="pa-0"
200273
>
201274
<span
202-
v-for="(attribute, i) of filteredFullAttributes"
203-
:key="i"
275+
v-for="(attribute) of filteredFullAttributes"
276+
:key="attribute.name"
204277
>
205278
<v-row
206279
v-if="
@@ -211,7 +284,14 @@ export default defineComponent({
211284
dense
212285
align="center"
213286
>
214-
<v-col class="attribute-name"> {{ attribute.name }}: </v-col>
287+
<v-col class="attribute-name"> <div
288+
class="type-color-box"
289+
:style="{
290+
backgroundColor: attribute.color,
291+
}"
292+
/><span>{{ attribute.name }}:
293+
</span>
294+
</v-col>
215295
<v-col class="px-1">
216296
<AttributeInput
217297
v-if="activeSettings"
@@ -277,6 +357,9 @@ export default defineComponent({
277357
</template>
278358

279359
<style scoped lang="scss">
360+
.attribute-header {
361+
font-size: 12px;
362+
}
280363
.attribute-item-value {
281364
max-width: 80%;
282365
margin: 0px;
@@ -290,4 +373,19 @@ export default defineComponent({
290373
max-width: 50%;
291374
min-width: 50%;
292375
}
376+
.type-color-box {
377+
display: inline-block;
378+
margin-right: 5px;
379+
min-width: 8px;
380+
max-width: 8px;
381+
min-height: 8px;
382+
max-height: 8px;
383+
}
384+
.blank-spacer {
385+
min-width: 28px;
386+
min-height: 28px;
387+
max-width: 28px;
388+
max-height: 28px;
389+
}
390+
293391
</style>

0 commit comments

Comments
 (0)