From d606a2882d42dfe462d8fea1a93419d03c1905fc Mon Sep 17 00:00:00 2001 From: derya Date: Mon, 5 Jul 2021 15:35:52 -0600 Subject: [PATCH 01/20] Set up basic intermediary node component No logic hooked up for toggling Nothing visualized atm --- src/components/IntermediaryNodes.vue | 70 ++++++++++++++++++++++++++++ src/components/MultiMatrix.vue | 7 +++ src/store/index.ts | 5 ++ src/types.ts | 1 + 4 files changed, 83 insertions(+) create mode 100644 src/components/IntermediaryNodes.vue diff --git a/src/components/IntermediaryNodes.vue b/src/components/IntermediaryNodes.vue new file mode 100644 index 00000000..eec78b39 --- /dev/null +++ b/src/components/IntermediaryNodes.vue @@ -0,0 +1,70 @@ + + + diff --git a/src/components/MultiMatrix.vue b/src/components/MultiMatrix.vue index 391f7b81..daefad48 100644 --- a/src/components/MultiMatrix.vue +++ b/src/components/MultiMatrix.vue @@ -24,6 +24,7 @@ import { select, selectAll } from 'd3-selection'; import { transition } from 'd3-transition'; import store from '@/store'; import LineUp from '@/components/LineUp.vue'; +import IntermediaryNodes from '@/components/IntermediaryNodes.vue'; import 'science'; import 'reorder.js'; @@ -33,6 +34,7 @@ declare const reorder: any; export default Vue.extend({ components: { LineUp, + IntermediaryNodes, }, data(): { @@ -186,6 +188,10 @@ export default Vue.extend({ childColorScale() { return store.getters.childColorScale; }, + + showInterMediateNodes() { + return store.state.showIntNodeVis; + }, }, watch: { @@ -1125,6 +1131,7 @@ export default Vue.extend({ /> +
Date: Mon, 5 Jul 2021 15:47:03 -0600 Subject: [PATCH 02/20] Store paths from aql query --- src/components/ConnectivityQuery.vue | 1 + src/store/index.ts | 4 ++++ src/types.ts | 1 + 3 files changed, 6 insertions(+) diff --git a/src/components/ConnectivityQuery.vue b/src/components/ConnectivityQuery.vue index bf9168af..b684418e 100644 --- a/src/components/ConnectivityQuery.vue +++ b/src/components/ConnectivityQuery.vue @@ -157,6 +157,7 @@ export default { newAQLNetwork.then((promise) => { const aqlResults = promise[0]; if (aqlResults.paths.length !== 0) { + store.commit.setConnectivityMatrixPaths(aqlResults.paths); // some data manipulation to show only start + end nodes const newNetwork: Network = { nodes: [], edges: [] }; const nodesSet = new Set(); diff --git a/src/store/index.ts b/src/store/index.ts index 154b676f..8dc29467 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -61,6 +61,7 @@ const { nodeAttributes: {}, edgeAttributes: {}, showIntNodeVis: true, + connectivityMatrixPaths: {}, } as State, getters: { @@ -265,6 +266,9 @@ const { toggleShowIntNodeVis(state, showIntNodeVis: boolean) { state.showIntNodeVis = showIntNodeVis; }, + setConnectivityMatrixPaths(state, connectivityMatrixPaths) { + state.connectivityMatrixPaths = connectivityMatrixPaths; + }, }, actions: { async fetchNetwork(context, { workspaceName, networkName }) { diff --git a/src/types.ts b/src/types.ts index 16edb21b..7065aff4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -73,6 +73,7 @@ export interface State { nodeAttributes: ArangoAttributes; edgeAttributes: ArangoAttributes; showIntNodeVis: boolean; + connectivityMatrixPaths: ArangoAttributes; } export type ProvenanceEventTypes = From 07527827bf4f701e96fb70017cbed6e4663e9da5 Mon Sep 17 00:00:00 2001 From: derya Date: Mon, 5 Jul 2021 20:04:31 -0600 Subject: [PATCH 03/20] Update paths type --- src/components/ConnectivityQuery.vue | 18 ++++++++++++------ src/store/index.ts | 8 +++++--- src/types.ts | 11 ++++++++++- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/components/ConnectivityQuery.vue b/src/components/ConnectivityQuery.vue index b684418e..f95917d4 100644 --- a/src/components/ConnectivityQuery.vue +++ b/src/components/ConnectivityQuery.vue @@ -157,10 +157,11 @@ export default { newAQLNetwork.then((promise) => { const aqlResults = promise[0]; if (aqlResults.paths.length !== 0) { - store.commit.setConnectivityMatrixPaths(aqlResults.paths); // some data manipulation to show only start + end nodes const newNetwork: Network = { nodes: [], edges: [] }; - const nodesSet = new Set(); + const endsNodesSet = new Set(); + const middleNodesSet = new Set(); + const middleNodesList: Node[] = []; aqlResults.paths.forEach((path: { edges: Edge[]; vertices: Node[] }, val: number) => { const newPath: Edge = { @@ -170,14 +171,16 @@ export default { for (let i = 0; i < selectedHops.value + 1; i += 1) { if (i === 0) { newPath._from = path.vertices[i]._id; - if (!nodesSet.has(path.vertices[i]._id)) { newNetwork.nodes.push(path.vertices[i]); } - nodesSet.add(path.vertices[i]._id); + if (!endsNodesSet.has(path.vertices[i]._id)) { newNetwork.nodes.push(path.vertices[i]); } + endsNodesSet.add(path.vertices[i]._id); } if (i === (selectedHops.value)) { newPath._to = path.vertices[i]._id; - if (!nodesSet.has(path.vertices[i]._id)) { newNetwork.nodes.push(path.vertices[i]); } - nodesSet.add(path.vertices[i]._id); + if (!endsNodesSet.has(path.vertices[i]._id)) { newNetwork.nodes.push(path.vertices[i]); } + endsNodesSet.add(path.vertices[i]._id); } + if (!middleNodesSet.has(path.vertices[i]._id)) { middleNodesList.push(path.vertices[i]); } + middleNodesSet.add(path.vertices[i]._id); } // generate _key and _id @@ -186,6 +189,9 @@ export default { newNetwork.edges.push(newPath); }); + // Update state for use in intermediate node vis + store.commit.setConnectivityMatrixPaths({ nodes: middleNodesList, paths: aqlResults.paths }); + // Update state with new network store.dispatch.updateNetwork({ network: newNetwork }); } diff --git a/src/store/index.ts b/src/store/index.ts index 8dc29467..0e8fb934 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -61,7 +61,7 @@ const { nodeAttributes: {}, edgeAttributes: {}, showIntNodeVis: true, - connectivityMatrixPaths: {}, + connectivityMatrixPaths: { nodes: [], paths: [] }, } as State, getters: { @@ -266,8 +266,10 @@ const { toggleShowIntNodeVis(state, showIntNodeVis: boolean) { state.showIntNodeVis = showIntNodeVis; }, - setConnectivityMatrixPaths(state, connectivityMatrixPaths) { - state.connectivityMatrixPaths = connectivityMatrixPaths; + + setConnectivityMatrixPaths(state, payload: { nodes: Node[]; paths: unknown[]}) { + state.connectivityMatrixPaths.nodes = payload.nodes; + state.connectivityMatrixPaths.paths = payload.paths; }, }, actions: { diff --git a/src/types.ts b/src/types.ts index 7065aff4..bedd1a7f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -35,6 +35,15 @@ export interface Cell { correspondingCell: string; } +export interface ConnectivityCell { + x: number; + y: number; + z: number; + startingNode: string; + endingNode: string; + cellName: string; +} + export interface LoadError { message: string; href: string; @@ -73,7 +82,7 @@ export interface State { nodeAttributes: ArangoAttributes; edgeAttributes: ArangoAttributes; showIntNodeVis: boolean; - connectivityMatrixPaths: ArangoAttributes; + connectivityMatrixPaths: {nodes: Node[]; paths: unknown[]}; } export type ProvenanceEventTypes = From 4db6c5790290e63df976452fbdd68b3341a90a35 Mon Sep 17 00:00:00 2001 From: derya Date: Tue, 6 Jul 2021 17:04:35 -0600 Subject: [PATCH 04/20] Update processData Fix matrix calculation Now in correct format --- src/components/ConnectivityQuery.vue | 8 +- src/components/IntermediaryNodes.vue | 106 ++++++++++++++++++++++----- src/store/index.ts | 3 +- src/types.ts | 9 ++- 4 files changed, 103 insertions(+), 23 deletions(-) diff --git a/src/components/ConnectivityQuery.vue b/src/components/ConnectivityQuery.vue index f95917d4..37462d8c 100644 --- a/src/components/ConnectivityQuery.vue +++ b/src/components/ConnectivityQuery.vue @@ -173,14 +173,14 @@ export default { newPath._from = path.vertices[i]._id; if (!endsNodesSet.has(path.vertices[i]._id)) { newNetwork.nodes.push(path.vertices[i]); } endsNodesSet.add(path.vertices[i]._id); - } - if (i === (selectedHops.value)) { + } else if (i > 0 && i < selectedHops.value) { + if (!middleNodesSet.has(path.vertices[i]._id)) { middleNodesList.push(path.vertices[i]); } + middleNodesSet.add(path.vertices[i]._id); + } else { newPath._to = path.vertices[i]._id; if (!endsNodesSet.has(path.vertices[i]._id)) { newNetwork.nodes.push(path.vertices[i]); } endsNodesSet.add(path.vertices[i]._id); } - if (!middleNodesSet.has(path.vertices[i]._id)) { middleNodesList.push(path.vertices[i]); } - middleNodesSet.add(path.vertices[i]._id); } // generate _key and _id diff --git a/src/components/IntermediaryNodes.vue b/src/components/IntermediaryNodes.vue index eec78b39..7188aced 100644 --- a/src/components/IntermediaryNodes.vue +++ b/src/components/IntermediaryNodes.vue @@ -2,17 +2,36 @@ import { computed, onMounted, SetupContext, watch, } from '@vue/composition-api'; -import { select } from 'd3-selection'; +import { + ScaleBand, + scaleBand, +} from 'd3-scale'; +import { select, selectAll } from 'd3-selection'; +import { transition } from 'd3-transition'; import store from '@/store'; +import { ArangoPath, ConnectivityCell } from '@/types'; export default { name: 'IntermediaryNodes', setup(props: unknown, context: SetupContext) { + const intNodeSVG = document.getElementById('intNode'); const network = computed(() => store.state.network); - // const selectedNodes = computed(() => store.state.selectedNodes); - // const hoveredNodes = computed(() => store.state.hoveredNodes); - + const connectivityPaths = computed(() => store.state.connectivityMatrixPaths); + const matrix: ConnectivityCell[][] = []; + const cellSize = computed(() => store.state.cellSize); + const sortOrder = computed(() => store.state.connectivityMatrixPaths.nodes.map((node: Node) => node._id).sort()); + const margin = { + top: 70, + right: 0, + bottom: 0, + left: 70, + }; + const orderingScale: ScaleBand = scaleBand() + .domain(sortOrder.value) + .range([0, sortOrder.value.length * cellSize.value]); + const matrixWidth = computed(() => connectivityPaths.value.paths[0].edges.length * cellSize.value + margin.left + margin.right); + const matrixHeight = computed(() => connectivityPaths.value.nodes.length * cellSize.value + margin.top + margin.bottom); const intNodeWidth = computed(() => { const controlsElement = select('.app-sidebar').node(); const matrixElement = select('#matrix').node(); @@ -26,37 +45,83 @@ export default { }); function removeIntView() { - const intNodeDiv = document.getElementById('intNodeDiv'); - const matrix = []; - - if (intNodeDiv !== null) { - intNodeDiv.innerHTML = ''; + if (intNodeSVG !== null) { + intNodeSVG.innerHTML = ''; } + } + + function processData() { + if (network.value !== null && intNodeSVG !== null && connectivityPaths.value.nodes.length > 0) { + const hops: number = connectivityPaths.value.paths[0].edges.length - 1; - // lineup.value = null; - // builder.value = null; + // Set up matrix intermediate nodes x # of hops + connectivityPaths.value.nodes.forEach((rowNode: Node, i: number) => { + matrix[i] = [...Array(hops).keys()].slice(0).map((j: number) => ({ + cellName: `${rowNode._id}`, + nodePosition: j + 1, + startingNode: '', + endingNode: '', + x: j, + y: i, + z: 0, + paths: [], + })); + }); + + // Set up number of connections based on paths + // Record associated paths + connectivityPaths.value.paths.forEach((path: ArangoPath, i: number) => { + matrix.forEach((matrixRow) => { + [...Array(hops).keys()].slice(0).forEach((j) => { + if (path.vertices[j + 1]._id === matrixRow[j].cellName) { + // eslint-disable-next-line no-param-reassign + matrixRow[j].z += 1; + matrixRow[j].paths.push(i); + } + }); + }); + }); + console.log(matrix); + } + console.log(connectivityPaths.value); } function buildIntView() { - const intNodeDiv = document.getElementById('intNodeDiv'); + if (matrix.length > 0) { + // set the radius for cells + const cellRadius = 3; + + // set the matrix highlight + const matrixHighlightLength = matrix.length * cellSize.value; + + const svg = select('#intNode').append('g').attr('transform', `translate(${margin.left},${margin.top})`); + + // constant for starting the column label container + const columnLabelContainerStart = 20; + const labelContainerHeight = 25; + const rowLabelContainerStart = 75; + const labelContainerWidth = rowLabelContainerStart; - if (network.value !== null && intNodeDiv !== null) { - // Create matrix from the paths - console.log('Hello!'); + const verticalOffset = 187.5; + const horizontalOffset = (orderingScale.bandwidth() / 2 - 4.5) / 0.075; } } onMounted(() => { + processData(); buildIntView(); }); watch(network, () => { removeIntView(); + processData(); buildIntView(); }); return { intNodeWidth, + matrixWidth, + matrixHeight, }; }, }; @@ -64,7 +129,14 @@ export default { diff --git a/src/store/index.ts b/src/store/index.ts index 0e8fb934..1e79cb9c 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -12,6 +12,7 @@ import { } from 'multinet'; import { ArangoAttributes, + ArangoPath, Cell, Edge, LoadError, Network, Node, ProvenanceEventTypes, State, } from '@/types'; @@ -267,7 +268,7 @@ const { state.showIntNodeVis = showIntNodeVis; }, - setConnectivityMatrixPaths(state, payload: { nodes: Node[]; paths: unknown[]}) { + setConnectivityMatrixPaths(state, payload: { nodes: Node[]; paths: ArangoPath[]}) { state.connectivityMatrixPaths.nodes = payload.nodes; state.connectivityMatrixPaths.paths = payload.paths; }, diff --git a/src/types.ts b/src/types.ts index bedd1a7f..19cb431a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -42,6 +42,8 @@ export interface ConnectivityCell { startingNode: string; endingNode: string; cellName: string; + nodePosition: number; + paths: number[]; } export interface LoadError { @@ -53,6 +55,11 @@ export interface ArangoAttributes { [key: string]: unknown[]; } +export interface ArangoPath { + vertices: Node[]; + edges: Edge[]; +} + export interface State { workspaceName: string | null; networkName: string | null; @@ -82,7 +89,7 @@ export interface State { nodeAttributes: ArangoAttributes; edgeAttributes: ArangoAttributes; showIntNodeVis: boolean; - connectivityMatrixPaths: {nodes: Node[]; paths: unknown[]}; + connectivityMatrixPaths: {nodes: Node[]; paths: ArangoPath[]}; } export type ProvenanceEventTypes = From 34bcd29f418d29a651d307372e592741d36c4319 Mon Sep 17 00:00:00 2001 From: derya Date: Wed, 7 Jul 2021 12:29:04 -0600 Subject: [PATCH 05/20] Create basic matrix Good amount broken still --- src/components/IntermediaryNodes.vue | 109 +++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 16 deletions(-) diff --git a/src/components/IntermediaryNodes.vue b/src/components/IntermediaryNodes.vue index 7188aced..5e31cdc3 100644 --- a/src/components/IntermediaryNodes.vue +++ b/src/components/IntermediaryNodes.vue @@ -5,9 +5,9 @@ import { import { ScaleBand, scaleBand, + scaleLinear, } from 'd3-scale'; import { select, selectAll } from 'd3-selection'; -import { transition } from 'd3-transition'; import store from '@/store'; import { ArangoPath, ConnectivityCell } from '@/types'; @@ -27,11 +27,9 @@ export default { bottom: 0, left: 70, }; - const orderingScale: ScaleBand = scaleBand() - .domain(sortOrder.value) - .range([0, sortOrder.value.length * cellSize.value]); const matrixWidth = computed(() => connectivityPaths.value.paths[0].edges.length * cellSize.value + margin.left + margin.right); const matrixHeight = computed(() => connectivityPaths.value.nodes.length * cellSize.value + margin.top + margin.bottom); + const intNodeWidth = computed(() => { const controlsElement = select('.app-sidebar').node(); const matrixElement = select('#matrix').node(); @@ -44,6 +42,14 @@ export default { return 330; }); + const orderingScale: ScaleBand = scaleBand() + .domain(sortOrder.value) + .range([0, sortOrder.value.length * cellSize.value]); + + const xScale = scaleBand().domain([0, sortOrder.value.length]).rangeRound([0, matrixWidth.value]).paddingInner(0.1) + .align(0); + const opacity = scaleLinear().domain([0, 0]).range([0.25, 1]).clamp(true); + function removeIntView() { if (intNodeSVG !== null) { intNodeSVG.innerHTML = ''; @@ -84,26 +90,96 @@ export default { console.log(matrix); } console.log(connectivityPaths.value); + // Update opacity + const allPaths = matrix.map((row: ConnectivityCell[]) => row.map((cell: ConnectivityCell) => cell.z)).flat(); + const maxPath = allPaths.reduce((a, b) => Math.max(a, b)); + opacity.domain([ + 0, + maxPath, + ]); + } + + function makeRow(rowData: ConnectivityCell) { + const row = selectAll('g.row'); + const column = selectAll('g.column'); + console.log(rowData); + const cell = select(this) + .selectAll('rect') + .data(rowData) + .enter() + .append('rect') + .attr('x', (d) => xScale(d.x)) + .attr('width', xScale.bandwidth()) + .attr('height', xScale.bandwidth()) + .style('fill-opacity', (d) => opacity(d.z)) + .style('fill', 'blue') + .on('mouseover', (d) => { + row + .filter((_, i) => d.x === i) + .selectAll('text') + .style('fill', '#d62333') + .style('font-weight', 'bold'); + column + .filter((_, j) => d.y === j) + .style('fill', '#d62333') + .style('font-weight', 'bold'); + }) + .on('mouseout', () => { + row.selectAll('text').style('fill', null).style('font-weight', null); + column.style('fill', null).style('font-weight', null); + }); + cell.append('title').text((d) => ` + ${d.cellName} + in ${d.z} paths`); } function buildIntView() { if (matrix.length > 0) { // set the radius for cells - const cellRadius = 3; - - // set the matrix highlight - const matrixHighlightLength = matrix.length * cellSize.value; + // const cellRadius = 3; const svg = select('#intNode').append('g').attr('transform', `translate(${margin.left},${margin.top})`); - + // Calculate opacity domain // constant for starting the column label container - const columnLabelContainerStart = 20; - const labelContainerHeight = 25; - const rowLabelContainerStart = 75; - const labelContainerWidth = rowLabelContainerStart; - - const verticalOffset = 187.5; - const horizontalOffset = (orderingScale.bandwidth() / 2 - 4.5) / 0.075; + // const columnLabelContainerStart = 20; + // const labelContainerHeight = 25; + // const rowLabelContainerStart = 75; + // const labelContainerWidth = rowLabelContainerStart; + + // const verticalOffset = 187.5; + // const horizontalOffset = (orderingScale.bandwidth() / 2 - 4.5) / 0.075; + + // Draw rows + const row = svg + .selectAll('g.row') + .data(matrix) + .enter() + .append('g') + .attr('class', 'row') + .attr('transform', (_, i) => `translate(0,${xScale(i)})`) + .each(makeRow); + row + .append('text') + .attr('class', 'label') + .attr('x', -4) + .attr('y', xScale.bandwidth() / 2) + .attr('dy', '0.32em') + .text((d: ConnectivityCell) => `${d.cellName}: in ${d.z} paths`); + + // Draw columns + const column = svg + .selectAll('g.column') + .data(matrix) + .enter() + .append('g') + .attr('class', 'column') + .attr('transform', (_, i) => `translate(${xScale(i)}, 0)rotate(-90)`) + .append('text') + .attr('class', 'label') + .attr('x', 4) + .attr('y', xScale.bandwidth() / 2) + .attr('dy', '0.32em') + .text((d: ConnectivityCell) => `${d.cellName}: in ${d.z} paths`); } } @@ -129,6 +205,7 @@ export default {