Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attribute Sorting/Filtering/Graphing #1280

Merged
merged 15 commits into from
Aug 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect to be able to toggle ascending/descending for both of these sorts not switch between sort types. I think this is fine for now because space is an issue but we might want to log an issue for the future.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied the behavior in the type list. It has the same sort of sorting. Alphabetically or Numerically by count. I wanted to stick with the same paradigm for this.

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