Skip to content

Commit

Permalink
Attribute Sorting/Filtering/Graphing (#1280)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* addressing comments

* Fixing upload, changing styling

Co-authored-by: Mary Salvi <[email protected]>
  • Loading branch information
BryonLewis and marySalvi authored Aug 12, 2022
1 parent 845e253 commit d2214cb
Show file tree
Hide file tree
Showing 38 changed files with 2,155 additions and 72 deletions.
1 change: 1 addition & 0 deletions client/dive-common/components/AttributeInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export default defineComponent({
:label="datatype"
:value="value"
:disabled="disabled"
:step="value <= 1 ? .01 : 1"
class="input-box"
type="number"
@change="change"
Expand Down
103 changes: 103 additions & 0 deletions client/dive-common/components/AttributesSideBar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<script lang="ts">
import {
defineComponent, ref, watch, PropType,
} from '@vue/composition-api';
import StackedVirtualSidebarContainer from 'dive-common/components/StackedVirtualSidebarContainer.vue';
import { useReadOnlyMode } from 'vue-media-annotator/provides';
import { usePrompt } from 'dive-common/vue-utilities/prompt-service';
import AttributeFilters from 'vue-media-annotator/components/AttributeFilters.vue';
import AttributeTimeline from 'vue-media-annotator/components/AttributeTimeline.vue';
import TooltipBtn from 'vue-media-annotator/components/TooltipButton.vue';
export default defineComponent({
name: 'AttributesSideBar',
components: {
StackedVirtualSidebarContainer,
AttributeFilters,
AttributeTimeline,
TooltipBtn,
},
props: {
width: {
type: Number,
default: 300,
},
subCategory: {
type: String as PropType<'Timeline' | 'Filtering'>,
required: false,
},
},
setup(props) {
const readOnlyMode = useReadOnlyMode();
const { visible } = usePrompt();
const currentMode = ref(props.subCategory);
const modes = ref(['Filtering', 'Timeline']);
watch(() => props.subCategory, () => {
if (props.subCategory !== undefined) {
currentMode.value = props.subCategory;
}
});
return {
readOnlyMode,
currentMode,
modes,
visible,
};
},
});
</script>


<template>
<StackedVirtualSidebarContainer
:width="width"
:enable-slot="false"
>
<template #default="{ bottomHeight }">
<v-container>
<h3> {{ currentMode }} </h3>
<v-row class="px-3">
<div class="mx-1">
<tooltip-btn
icon="mdi-filter"
tooltip-text="Filter Attributes displayed"
size="large"
:color="currentMode === 'Filtering'? 'primary' : 'default'"
outlined
tile
@click="currentMode = 'Filtering'"
/>
</div>
<div class="mx-1">
<tooltip-btn
icon="mdi-chart-line-variant"
tooltip-text="Chart Numeric Attributes"
size="large"
outlined
:color="currentMode === 'Timeline'? 'primary' : 'default'"

tile
@click="currentMode = 'Timeline'"
/>
</div>
</v-row>
<v-divider />
<attribute-filters
v-if="currentMode === 'Filtering'"
class="flex-grow-0 flex-shrink-0"
:height="bottomHeight"
:hotkeys-disabled="visible() || readOnlyMode"
/>
<attribute-timeline
v-if="currentMode === 'Timeline'"
class="flex-grow-0 flex-shrink-0"
:height="bottomHeight"
/>
</v-container>
</template>
</StackedVirtualSidebarContainer>
</template>
138 changes: 118 additions & 20 deletions client/dive-common/components/AttributesSubsection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ import {
useCameraStore,
useTime,
useReadOnlyMode,
useAttributesFilters,
} from 'vue-media-annotator/provides';
import { Attribute } from 'vue-media-annotator/use/useAttributes';
import type { Attribute, AttributeFilter } from 'vue-media-annotator/use/useAttributes';
import AttributeInput from 'dive-common/components/AttributeInput.vue';
import PanelSubsection from 'dive-common/components/PanelSubsection.vue';
import TooltipBtn from 'vue-media-annotator/components/TooltipButton.vue';
import context from 'dive-common/store/context';
export default defineComponent({
components: {
AttributeInput,
PanelSubsection,
TooltipBtn,
},
props: {
attributes: {
Expand All @@ -39,8 +42,12 @@ export default defineComponent({
const readOnlyMode = useReadOnlyMode();
const { frame: frameRef } = useTime();
const selectedTrackIdRef = useSelectedTrackId();
const { attributeFilters, sortAndFilterAttributes, timelineEnabled } = useAttributesFilters();
const cameraStore = useCameraStore();
const activeSettings = ref(true);
const sortingMethods = ['a-z', '1-0'];
const sortingMethodIcons = ['mdi-sort-alphabetical-ascending', 'mdi-sort-numeric-ascending'];
const sortingMode = ref(0);
const selectedTrack = computed(() => {
if (selectedTrackIdRef.value !== null) {
Expand All @@ -66,9 +73,23 @@ export default defineComponent({
return null;
});
const filteredFullAttributes = computed(() => Object.values(props.attributes).filter(
(attribute: Attribute) => attribute.belongs === props.mode.toLowerCase(),
));
const filteredFullAttributes = computed(() => {
let additionFilters: AttributeFilter[] = [];
let mode: 'track' | 'detection' = 'track';
if (props.mode === 'Track') {
additionFilters = attributeFilters.value.track;
} else {
additionFilters = attributeFilters.value.detection;
mode = 'detection';
}
let attributeVals = {};
if (selectedAttributes.value && selectedAttributes.value.attributes) {
attributeVals = selectedAttributes.value.attributes;
}
return sortAndFilterAttributes(
props.attributes, mode, attributeVals, sortingMode.value, additionFilters,
);
});
const activeAttributesCount = computed(
() => props.attributes.filter(
Expand Down Expand Up @@ -106,6 +127,27 @@ export default defineComponent({
function addAttribute() {
emit('add-attribute', props.mode);
}
function clickSortToggle() {
sortingMode.value = (sortingMode.value + 1) % sortingMethods.length;
}
const filtersActive = computed(() => {
let additionFilters: AttributeFilter[] = [];
if (props.mode === 'Track') {
additionFilters = attributeFilters.value.track;
} else {
additionFilters = attributeFilters.value.detection;
}
return !!additionFilters.find((filter) => filter.filterData.active === true);
});
function openFilter() {
context.openClose('AttributesSideBar', true, 'Filtering');
}
function openTimeline() {
context.openClose('AttributesSideBar', true, 'Timeline');
}
return {
frameRef,
Expand All @@ -120,6 +162,14 @@ export default defineComponent({
editAttribute,
addAttribute,
setEditIndividual,
//Sorting & Filters
sortingMethodIcons,
sortingMode,
clickSortToggle,
openFilter,
openTimeline,
timelineEnabled,
filtersActive,
};
},
});
Expand All @@ -134,25 +184,32 @@ export default defineComponent({
class="align-center"
no-gutters
>
<b>{{ mode }} Attributes:</b>
<v-spacer />
<v-col dense>
<b class="attribute-header">{{ mode }} Attributes</b>
<div
v-if="mode === 'Detection'"
no-gutters
class="text-caption"
>
{{ `Frame: ${frameRef}` }}
</div>
</v-col>
<v-tooltip
open-delay="200"
bottom
max-width="200"
>
<template #activator="{ on }">
<v-btn
outlined
x-small
small
icon
:disabled="readOnlyMode"
v-on="on"
@click="addAttribute"
>
<v-icon small>
mdi-plus
</v-icon>
Attribute
</v-btn>
</template>
<span>Add a new {{ mode }} Attribute</span>
Expand All @@ -178,13 +235,29 @@ export default defineComponent({
</template>
<span>Show/Hide un-used</span>
</v-tooltip>
</v-row>
<v-row
v-if="mode === 'Detection'"
no-gutters
class="text-caption"
>
{{ `Frame: ${frameRef}` }}
<tooltip-btn
:icon="sortingMethodIcons[sortingMode]"
tooltip-text="Sort types by value or alphabetically"
@click="clickSortToggle"
/>
<tooltip-btn
icon="mdi-filter"
:color="filtersActive ? 'primary' : 'default'"
:tooltip-text="filtersActive
? 'Filters are active, click to view': 'No filters are active, click to edit'"
@click="openFilter"
/>
<tooltip-btn
v-if="mode === 'Detection'"
icon="mdi-chart-line-variant"
:color="timelineEnabled ? 'primary' : 'default'"
tooltip-text="Timeline Settings for Attributes"
@click="openTimeline"
/>
<div
v-else
class="blank-spacer"
/>
</v-row>
</template>

Expand All @@ -199,8 +272,8 @@ export default defineComponent({
class="pa-0"
>
<span
v-for="(attribute, i) of filteredFullAttributes"
:key="i"
v-for="(attribute) of filteredFullAttributes"
:key="attribute.name"
>
<v-row
v-if="
Expand All @@ -211,7 +284,14 @@ export default defineComponent({
dense
align="center"
>
<v-col class="attribute-name"> {{ attribute.name }}: </v-col>
<v-col class="attribute-name"> <div
class="type-color-box"
:style="{
backgroundColor: attribute.color,
}"
/><span>{{ attribute.name }}:
</span>
</v-col>
<v-col class="px-1">
<AttributeInput
v-if="activeSettings"
Expand Down Expand Up @@ -277,6 +357,9 @@ export default defineComponent({
</template>

<style scoped lang="scss">
.attribute-header {
font-size: 12px;
}
.attribute-item-value {
max-width: 80%;
margin: 0px;
Expand All @@ -290,4 +373,19 @@ export default defineComponent({
max-width: 50%;
min-width: 50%;
}
.type-color-box {
display: inline-block;
margin-right: 5px;
min-width: 8px;
max-width: 8px;
min-height: 8px;
max-height: 8px;
}
.blank-spacer {
min-width: 28px;
min-height: 28px;
max-width: 28px;
max-height: 28px;
}
</style>
Loading

0 comments on commit d2214cb

Please sign in to comment.