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

Edge slicing #372

Merged
merged 30 commits into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a677b64
Add edge slicing component
gotdairyya Feb 10, 2022
b580ad6
Add edge slicing component
gotdairyya Feb 10, 2022
7d4a2a6
Use instance for edge slice width
gotdairyya Feb 10, 2022
fe0b541
Change cog to chevron
gotdairyya Feb 10, 2022
b5aacaa
Refactor edge attribute list creation
gotdairyya Feb 11, 2022
d26e069
Represent total edges in edge slices
gotdairyya Feb 11, 2022
abe8699
Fix position of tooltip
gotdairyya Feb 11, 2022
485911d
Visualize categorical attributes
gotdairyya Feb 14, 2022
467e52a
Format numeric labels
gotdairyya Feb 14, 2022
f6887cb
Remove edge slices on query
gotdairyya Feb 14, 2022
82f685a
Fix most type issues
JackWilb Feb 14, 2022
c16d769
Fix tooltip error
gotdairyya Feb 17, 2022
58ef38c
Merge 'origin/main' into edge-slicing
gotdairyya Feb 17, 2022
50ecc03
Show labels under slices for numeric
gotdairyya Feb 17, 2022
6323c14
Add padding
gotdairyya Feb 17, 2022
36e697a
Manage clearing form and sliced network
gotdairyya Feb 17, 2022
ea67b17
Reset edge slice number
gotdairyya Feb 17, 2022
01e16c4
Set sliced network to empty on updateNetwork
gotdairyya Feb 17, 2022
83448b8
Fix numeric edge slices
gotdairyya Feb 17, 2022
35ece64
Format edge slicing + fix label display
gotdairyya Feb 17, 2022
cd9cf04
Add isTime to reset
gotdairyya Feb 17, 2022
439163e
Disable buttpns when network not sliced
gotdairyya Feb 18, 2022
b219da0
Remove unnecessary network update
gotdairyya Feb 18, 2022
95ad01a
Remove unused variables
gotdairyya Feb 18, 2022
c303cc5
Remove d3 for edge selection
gotdairyya Feb 18, 2022
4e67144
Check for valid input
gotdairyya Mar 9, 2022
73cae4e
Remove computed ref manipulation
gotdairyya Mar 9, 2022
47e3f9c
Fix categorical slicing
gotdairyya Mar 9, 2022
0fa5f4b
Fix reset clearing removes min and max
gotdairyya Mar 9, 2022
63f104e
Update time selection
gotdairyya Mar 9, 2022
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
9 changes: 8 additions & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { computed } from '@vue/composition-api';
import ProvVis from '@/components/ProvVis.vue';
import Controls from '@/components/Controls.vue';
import MultiMatrix from '@/components/MultiMatrix.vue';
import EdgeSlices from '@/components/EdgeSlices.vue';
import { getUrlVars } from '@/lib/utils';
import store from '@/store';

Expand All @@ -15,6 +16,7 @@ export default {
Controls,
MultiMatrix,
ProvVis,
EdgeSlices,
},

setup() {
Expand All @@ -32,10 +34,13 @@ export default {

const showProvenanceVis = computed(() => store.state.showProvenanceVis);

const slicedNetwork = computed(() => store.state.slicedNetwork.length > 1);

return {
loadError,
network,
showProvenanceVis,
slicedNetwork,
};
},
};
Expand All @@ -45,7 +50,9 @@ export default {
<v-app>
<v-content>
<controls />

<edge-slices
v-if="network !== null && slicedNetwork"
/>
<multi-matrix v-if="network !== null" />

<alert v-if="loadError.message !== ''" />
Expand Down
4 changes: 4 additions & 0 deletions src/components/Controls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import store from '@/store';
import AboutDialog from '@/components/AboutDialog.vue';
import LoginMenu from '@/components/LoginMenu.vue';
import ConnectivityQuery from '@/components/ConnectivityQuery.vue';
import EdgeSlicing from '@/components/EdgeSlicing.vue';
import {
computed, defineComponent, Ref, ref, watch, watchEffect,
} from '@vue/composition-api';
Expand All @@ -16,6 +17,7 @@ export default defineComponent({
AboutDialog,
LoginMenu,
ConnectivityQuery,
EdgeSlicing,
},

setup() {
Expand Down Expand Up @@ -496,6 +498,8 @@ export default defineComponent({
</v-list-item>
</div>
</div>
<!-- Edge Slicing -->
<EdgeSlicing />

<!-- Connectivity Query -->
<connectivity-query />
Expand Down
266 changes: 266 additions & 0 deletions src/components/EdgeSlices.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
<script lang="ts">
import store from '@/store';
import {
computed, defineComponent, getCurrentInstance, ref, watch,
} from '@vue/composition-api';
import { max } from 'd3-array';
import { formatLongDate, formatShortDate } from '@/lib/utils';
import { format } from 'd3-format';
import { scaleLinear } from 'd3-scale';

export default defineComponent({
name: 'EdgeSlices',

setup() {
const slicedNetwork = computed(() => store.state.slicedNetwork);
const isDate = computed(() => store.state.isDate);
const isNumeric = computed(() => (slicedNetwork.value[0].category === ''));
const currentInstance = getCurrentInstance();
const svgWidth = computed(() => (currentInstance !== null ? currentInstance.proxy.$vuetify.breakpoint.width - store.state.controlsWidth : 0));
JackWilb marked this conversation as resolved.
Show resolved Hide resolved
const rectHeight = ref(20);

const currentSlice = computed(() => {
const slices: { timeRanges: {[key: number]: number[] | Date[]} ; current: number ; slices: number; sumEdges: number[]; categories: {[key: number]: string} } = {
timeRanges: {}, current: 0, slices: 0, sumEdges: [], categories: {},
};
slicedNetwork.value.forEach((slice, i) => {
slices.slices = i + 1;
slices.sumEdges[i] = slice.network.edges.length;
if (isNumeric.value) {
slices.timeRanges[i] = slice.time;
} else {
slices.categories[i] = slice.category;
}
});
return slices;
});
const textSpacer = ref(70);

const timeRangesLength = computed(() => currentSlice.value.slices);

// Update sliced view and network
const selectedArray = ref([true]);

watch([timeRangesLength], () => {
selectedArray.value = Array.from(Array(timeRangesLength.value), (_, x: number) => (x === 0));
});

function isSelected(key: number) {
selectedArray.value.fill(false);
selectedArray.value[key] = true;
Copy link
Member

Choose a reason for hiding this comment

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

Changing a value on a computed ref like this is dangerous. You should change the computedref to a ref and a watcher on timeRangesLength just to reset it when it changes, and then you can modify the array without it magically getting updated by the computed component

Copy link
Contributor

Choose a reason for hiding this comment

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

FWIW (might not apply here) you can use a "computed setter" (V2: https://v2.vuejs.org/v2/guide/computed.html#Computed-Setter) or a "writable computed" (V3: https://vuejs.org/guide/essentials/computed.html#writable-computed) to give semantics to "changing a value on a computed ref".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, this has also been fixed!

store.commit.setNetwork(slicedNetwork.value[key].network);
}

// Heightscale for numeric attributes
const heightScale = computed(() => scaleLinear<number, number>().domain([0, max(currentSlice.value.sumEdges) || 0]).range([0, rectHeight.value]));
// Width of rect
const rectWidth = computed(() => (svgWidth.value - (textSpacer.value * 2) - ((timeRangesLength.value - 1) * 4)) / timeRangesLength.value);

// Tooltip
const tooltipMessage = ref('');
const toggleTooltip = ref(false);
const tooltipPosition = ref({ x: 0, y: 0 });
const tooltipStyle = computed(() => `left: ${tooltipPosition.value.x}px; top: ${tooltipPosition.value.y}px; white-space: pre-line;`);
const controlsWidth = computed(() => store.state.controlsWidth);

function showTooltip(key: number, event: MouseEvent) {
tooltipPosition.value = {
x: event.clientX - controlsWidth.value - 20,
y: event.clientY + 20,
};
if (isNumeric.value) {
if (isDate.value) {
tooltipMessage.value = `Slice: ${key}
Range: ${formatLongDate(`${currentSlice.value.timeRanges[key][0]}`)} - ${formatLongDate(`${currentSlice.value.timeRanges[key][1]}`)}
Total Edges: ${currentSlice.value.sumEdges[key]}`;
toggleTooltip.value = true;
} else {
tooltipMessage.value = `Slice: ${key}
Range: ${format('.2s')(currentSlice.value.timeRanges[key][0])} - ${format('.2s')(currentSlice.value.timeRanges[key][1])}
Total Edges: ${currentSlice.value.sumEdges[key]}`;
toggleTooltip.value = true;
}
} else {
tooltipMessage.value = `Category: ${currentSlice.value.categories[key]}
Total Edges: ${currentSlice.value.sumEdges[key]}`;
toggleTooltip.value = true;
}
}

function hideTooltip() {
tooltipMessage.value = '';
toggleTooltip.value = false;
}
JackWilb marked this conversation as resolved.
Show resolved Hide resolved

// Select the first slice on when slices are changed load
watch([slicedNetwork], () => {
isSelected(0);
});

return {
svgWidth,
currentSlice,
showTooltip,
hideTooltip,
tooltipStyle,
toggleTooltip,
tooltipMessage,
textSpacer,
isDate,
formatShortDate,
heightScale,
rectHeight,
rectWidth,
isNumeric,
format,
isSelected,
selectedArray,
};
},
});
</script>

<template>
<div>
<h4 class="pl-2 pt-2">
Edge Slices
</h4>
<svg
:width="svgWidth"
:height="50"
>
<!-- Numeric + Date Visualization -->
<g
v-if="isNumeric"
id="edgeslices"
transform="translate(0,2)"
>
<g
v-for="(slice, key, index) of currentSlice.timeRanges"
:key="`edgeSlice_${key}`"
class="edgeSliceGroup"
@mouseover="showTooltip(key, $event)"
@mouseout="hideTooltip"
@click="isSelected(key)"
>
<rect
:id="`edgeSlice_${key}`"
:class="selectedArray[key] ? 'edgeSliceRectClass selected' : 'edgeSliceRectClass'"
:width="rectWidth"
:height="rectHeight"
y="0"
fill="lightgrey"
:x="textSpacer + (4 * index) + (rectWidth * index)"
/>
<rect
:width="rectWidth"
:height="rectHeight - heightScale(currentSlice.sumEdges[key])"
class="edgeSliceRectClass"
y="0"
fill="white"
:x="textSpacer + (4 * index) + (rectWidth * index)"
/>
<foreignObject
class="sliceLabels"
:width="rectWidth"
:x="textSpacer + (4 * index) + (rectWidth * index)"
y="30"
height="20"
>
{{ isDate ? `${formatShortDate(slice[0])} - ${formatShortDate(slice[1])}` : `${format('.2s')(slice[0])} - ${format('.2s')(slice[1])}` }}
</foreignObject>
</g>
</g>
<!-- Categorical Visualization -->
<g
v-else
id="edgeSlices"
transform="translate(0,2)"
>
<g
v-for="(slice, key, index) of currentSlice.categories"
:key="`edgeSlice_${key}`"
class="edgeSliceGroup"
@mouseover="showTooltip(key, $event)"
@mouseout="hideTooltip"
@click="isSelected(key)"
>
<rect
:id="`edgeSlice_${key}`"
:class="selectedArray[key] ? 'edgeSliceRectClass selected' : 'edgeSliceRectClass'"
:width="rectWidth"
:height="rectHeight"
y="0"
fill="lightgrey"
:x="textSpacer + (4 * index) + (rectWidth * index)"
/>
<rect
:width="rectWidth"
:height="rectHeight - heightScale(currentSlice.sumEdges[key])"
class="edgeSliceRectClass"
y="0"
fill="white"
:x="textSpacer + (4 * index) + (rectWidth * index)"
/>
<foreignObject
class="sliceLabels"
:width="rectWidth"
:x="textSpacer + (4 * index) + (rectWidth * index)"
y="30"
height="20"
>
{{ slice }}
</foreignObject>
</g>
</g>
</svg>

<div
v-if="toggleTooltip"
class="tooltip"
:style="tooltipStyle"
>
{{ tooltipMessage }}
</div>
</div>
</template>

<style scoped>
.tooltip {
position: absolute;
background-color: white;
font-size: 12.5px;
color: #000;
border-radius: 5px;
padding: 5px;
pointer-events: none;
-webkit-box-shadow: 0 4px 8px 0 rgba(0,0,0,.2);
box-shadow: 0 4px 8px 0 rgba(0,0,0,.2);
max-width: 400px
}

.edgeSliceGroup {
cursor: pointer;
}

.edgeSliceRectClass {
stroke: darkgray;
stroke-width: 1px;
position: absolute;
}

.selected {
stroke: black;
fill: #f99f31;
stroke-width: 2px;
}

svg >>> .sliceLabels {
text-overflow: ellipsis;
overflow: hidden;
z-index: 100;
margin: 0;
fill: black !important;
text-align: center;
}
</style>
Loading