Skip to content

Commit

Permalink
feat: improvements to category tree visualization (collapsible, show …
Browse files Browse the repository at this point in the history
…percent)
  • Loading branch information
ErikBjare committed Jan 2, 2021
1 parent f4c0181 commit 3b1cea4
Showing 1 changed file with 80 additions and 22 deletions.
102 changes: 80 additions & 22 deletions src/visualizations/CategoryTree.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,72 @@
<template lang="pug">
div
div(v-for="cat in category_hierarchy", style="padding: 0.1em 0 0.1em 0")
span(:style="'padding-left: ' + (1.4 * cat.depth - 1) + 'em'")
span(v-if="cat.depth > 0", style="opacity: 0.7") #[icon.mr-1(name="caret-right")]
div(style="font-size: 0.9em")
div(v-for="cat in category_hierarchy", style="padding: 0.1em 0 0.1em 0" @click="toggle(cat)" v-if="parents_expanded(cat)")
span(:style="'padding-left: ' + (1.4 * cat.depth) + 'em'")

// icon
span(v-if="cat.children.length > 0", style="opacity: 0.8")
b(v-if="!expanded.has(cat.name_pretty)")
icon.mr-1(name="regular/plus-square", scale="0.8")
b(v-else)
icon.mr-1(name="regular/minus-square", scale="0.8")
span(v-else, style="opacity: 0.6")
icon(name="circle", scale="0.4", style="margin-left: 1em; margin-right: 1.22em;")

// title
| {{cat.subname}}

// time
span(style="float: right")
| {{cat.duration | friendlyduration}}
span(v-if="show_perc")
| {{Math.round(100 * cat.duration / total_duration, 1)}}%
span(v-else)
| {{cat.duration | friendlyduration}}
hr
// TODO: Make configurable in a cleaner way (figure out a way to configure visualizations generally)
b-checkbox(v-model="show_perc" size="sm") Show percent
</template>

<script>
import 'vue-awesome/icons/caret-right';
import 'vue-awesome/icons/circle';
import 'vue-awesome/icons/regular/plus-square';
import 'vue-awesome/icons/regular/minus-square';
const _ = require('lodash');
const classes = require('~/util/classes.ts');
function _get_child_cats(cat, all_cats) {
return _.filter(all_cats, c => _.isEqual(c.parent, cat.name));
}
function _assign_children(parent, all_cats) {
const child_cats = _get_child_cats(parent, all_cats);
// Recurse
_.map(child_cats, c => _assign_children(c, all_cats));
parent.children = _.sortBy(child_cats, cc => -cc.duration);
}
// Flattens the category hierarchy
function _flatten_hierarchy(c) {
if (!c.children) return [];
return _.flattenDeep([c, _.map(c.children, cc => _flatten_hierarchy(cc))]);
}
export default {
name: 'aw-categorytree',
props: {
events: { type: Array },
},
data: function () {
return {
expanded: new Set(),
show_perc: false,
};
},
computed: {
total_duration: function () {
// sum top-level categories
const top_c = _.filter(this.category_hierarchy, c => c.depth == 0);
return _.sumBy(top_c, c => c.duration);
},
category_hierarchy: function () {
const events = JSON.parse(JSON.stringify(this.events));
Expand All @@ -39,33 +87,43 @@ export default {
return c;
});
function _get_child_cats(cat, all_cats) {
return _.filter(all_cats, c => _.isEqual(c.parent, cat.name));
}
function _assign_children(parent, all_cats) {
const child_cats = _get_child_cats(parent, all_cats);
// Recurse
_.map(child_cats, c => _assign_children(c, all_cats));
parent.children = _.sortBy(child_cats, cc => -cc.duration);
}
const cats_with_depth0 = _.sortBy(
_.filter(cats, c => c.depth == 0),
c => -c.duration
);
_.map(cats_with_depth0, c => _assign_children(c, cats));
// Flattens the category hierarchy
function _flatten_hierarchy(c) {
if (!c.children) return [];
return _.flattenDeep([c, _.map(c.children, cc => _flatten_hierarchy(cc))]);
}
cats = _.flatten(_.map(cats_with_depth0, c => _flatten_hierarchy(c)));
//console.log(cats);
// TODO: If a category has children, but also activity attributed directly to the parent that does not belong to a child, then create a "Other" child containing the activity.
return cats;
},
},
methods: {
get_category: function (cat_arr) {
return _.find(this.category_hierarchy, c => _.isEqual(c.name, cat_arr));
},
toggle: function (cat) {
if (this.expanded.has(cat.name_pretty)) {
this.expanded.delete(cat.name_pretty);
} else {
this.expanded.add(cat.name_pretty);
}
// needed to trigger update, since Set isn't reactive in Vue 2
this.expanded = new Set(this.expanded);
},
parents_expanded: function (cat) {
if (cat === undefined || !cat.parent) {
// top-level category
return true;
}
return (
// Check grandparents recursively
this.parents_expanded(this.get_category(cat.parent)) &&
// Check parent
this.expanded.has(cat.parent.join('>'))
);
},
},
};
</script>

0 comments on commit 3b1cea4

Please sign in to comment.