Skip to content

Commit 3b1cea4

Browse files
committed
feat: improvements to category tree visualization (collapsible, show percent)
1 parent f4c0181 commit 3b1cea4

1 file changed

Lines changed: 80 additions & 22 deletions

File tree

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,72 @@
11
<template lang="pug">
2-
div
3-
div(v-for="cat in category_hierarchy", style="padding: 0.1em 0 0.1em 0")
4-
span(:style="'padding-left: ' + (1.4 * cat.depth - 1) + 'em'")
5-
span(v-if="cat.depth > 0", style="opacity: 0.7") #[icon.mr-1(name="caret-right")]
2+
div(style="font-size: 0.9em")
3+
div(v-for="cat in category_hierarchy", style="padding: 0.1em 0 0.1em 0" @click="toggle(cat)" v-if="parents_expanded(cat)")
4+
span(:style="'padding-left: ' + (1.4 * cat.depth) + 'em'")
5+
6+
// icon
7+
span(v-if="cat.children.length > 0", style="opacity: 0.8")
8+
b(v-if="!expanded.has(cat.name_pretty)")
9+
icon.mr-1(name="regular/plus-square", scale="0.8")
10+
b(v-else)
11+
icon.mr-1(name="regular/minus-square", scale="0.8")
12+
span(v-else, style="opacity: 0.6")
13+
icon(name="circle", scale="0.4", style="margin-left: 1em; margin-right: 1.22em;")
14+
15+
// title
616
| {{cat.subname}}
17+
18+
// time
719
span(style="float: right")
8-
| {{cat.duration | friendlyduration}}
20+
span(v-if="show_perc")
21+
| {{Math.round(100 * cat.duration / total_duration, 1)}}%
22+
span(v-else)
23+
| {{cat.duration | friendlyduration}}
24+
hr
25+
// TODO: Make configurable in a cleaner way (figure out a way to configure visualizations generally)
26+
b-checkbox(v-model="show_perc" size="sm") Show percent
927
</template>
1028

1129
<script>
12-
import 'vue-awesome/icons/caret-right';
30+
import 'vue-awesome/icons/circle';
31+
import 'vue-awesome/icons/regular/plus-square';
32+
import 'vue-awesome/icons/regular/minus-square';
1333
const _ = require('lodash');
1434
const classes = require('~/util/classes.ts');
1535
36+
function _get_child_cats(cat, all_cats) {
37+
return _.filter(all_cats, c => _.isEqual(c.parent, cat.name));
38+
}
39+
40+
function _assign_children(parent, all_cats) {
41+
const child_cats = _get_child_cats(parent, all_cats);
42+
// Recurse
43+
_.map(child_cats, c => _assign_children(c, all_cats));
44+
parent.children = _.sortBy(child_cats, cc => -cc.duration);
45+
}
46+
47+
// Flattens the category hierarchy
48+
function _flatten_hierarchy(c) {
49+
if (!c.children) return [];
50+
return _.flattenDeep([c, _.map(c.children, cc => _flatten_hierarchy(cc))]);
51+
}
52+
1653
export default {
1754
name: 'aw-categorytree',
1855
props: {
1956
events: { type: Array },
2057
},
58+
data: function () {
59+
return {
60+
expanded: new Set(),
61+
show_perc: false,
62+
};
63+
},
2164
computed: {
65+
total_duration: function () {
66+
// sum top-level categories
67+
const top_c = _.filter(this.category_hierarchy, c => c.depth == 0);
68+
return _.sumBy(top_c, c => c.duration);
69+
},
2270
category_hierarchy: function () {
2371
const events = JSON.parse(JSON.stringify(this.events));
2472
@@ -39,33 +87,43 @@ export default {
3987
return c;
4088
});
4189
42-
function _get_child_cats(cat, all_cats) {
43-
return _.filter(all_cats, c => _.isEqual(c.parent, cat.name));
44-
}
45-
46-
function _assign_children(parent, all_cats) {
47-
const child_cats = _get_child_cats(parent, all_cats);
48-
// Recurse
49-
_.map(child_cats, c => _assign_children(c, all_cats));
50-
parent.children = _.sortBy(child_cats, cc => -cc.duration);
51-
}
52-
5390
const cats_with_depth0 = _.sortBy(
5491
_.filter(cats, c => c.depth == 0),
5592
c => -c.duration
5693
);
5794
_.map(cats_with_depth0, c => _assign_children(c, cats));
5895
59-
// Flattens the category hierarchy
60-
function _flatten_hierarchy(c) {
61-
if (!c.children) return [];
62-
return _.flattenDeep([c, _.map(c.children, cc => _flatten_hierarchy(cc))]);
63-
}
6496
cats = _.flatten(_.map(cats_with_depth0, c => _flatten_hierarchy(c)));
6597
//console.log(cats);
6698
// 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.
6799
return cats;
68100
},
69101
},
102+
methods: {
103+
get_category: function (cat_arr) {
104+
return _.find(this.category_hierarchy, c => _.isEqual(c.name, cat_arr));
105+
},
106+
toggle: function (cat) {
107+
if (this.expanded.has(cat.name_pretty)) {
108+
this.expanded.delete(cat.name_pretty);
109+
} else {
110+
this.expanded.add(cat.name_pretty);
111+
}
112+
// needed to trigger update, since Set isn't reactive in Vue 2
113+
this.expanded = new Set(this.expanded);
114+
},
115+
parents_expanded: function (cat) {
116+
if (cat === undefined || !cat.parent) {
117+
// top-level category
118+
return true;
119+
}
120+
return (
121+
// Check grandparents recursively
122+
this.parents_expanded(this.get_category(cat.parent)) &&
123+
// Check parent
124+
this.expanded.has(cat.parent.join('>'))
125+
);
126+
},
127+
},
70128
};
71129
</script>

0 commit comments

Comments
 (0)