Loading src/components/NetworkGraph/GraphNode.vue +2 −2 Original line number Diff line number Diff line Loading @@ -229,7 +229,7 @@ export default { 'SET_NODE_DETAIL', 'UNSET_NODE_DETAIL', 'SELECT_NODE', 'UNSELECT_NODE', 'DESELECT_NODES', 'SET_TOOL_SHOW', 'SET_LINK_HIGHLIGHT', 'CLEAR_LINK_HIGHLIGHT' Loading Loading @@ -257,7 +257,7 @@ export default { // if user held the node without dragging, interpret is as a selection if (this.currentDragLength < 4) { if (this.isUserSelecting) { this.isSelected ? this.UNSELECT_NODE(this.node.id) : this.SELECT_NODE(this.node.id) this.isSelected ? this.DESELECT_NODES([this.node.id]) : this.SELECT_NODE(this.node.id) this.isSelected ? this.showNodeSelection() : this.hideNodeSelectionIfEmpty() this.isUserSelecting = false } Loading src/store/modules/data.js +88 −42 Original line number Diff line number Diff line import Vue from 'vue' import mockData from '@/datasets/data-small.js' import mockData from '@/datasets/data-harry.js' import { cloneDeep, merge, sortBy } from 'lodash' import { clone, validate } from '@babel/types' import { type } from 'os' /** * @file Manages data storage/operations for network visualization module Loading Loading @@ -95,9 +96,10 @@ Object.freeze(AN_AGGREGATION_TYPES) * @param {Object} state * @param {Node[]} state.nodes * @param {number} id of node * @returns {Node} * * @returns {Node} Node instance, or undefined if not found */ function getNodeById(state, id) { function findNodeById(state, id) { return state.nodes.find(n => n.id === id) } Loading @@ -108,6 +110,7 @@ function filterVisibleLinks(links) { /** * Tell if node is visible in graph visualization * @param {Node} node * * @returns {boolean} */ function isNodeVisible(node) { Loading Loading @@ -138,6 +141,7 @@ export function findConnectedLinks(links, id) { /** * Check if visible: not in multilink, not an archived multilink, newTarget != newSource * @param {Link} l * * @returns {boolean} */ function isLinkVisible(l) { Loading @@ -154,6 +158,7 @@ function isLinkVisible(l) { /** * Find first available id by iterating over objects in array * @param {Object[]} arr of objects, each object has attribute id * * @returns {number} id */ function arrGetNewId(arr) { Loading @@ -166,6 +171,7 @@ function arrGetNewId(arr) { /** * Check if link has set vis.newSource and vis.newTarget * @param {Link} link * * @returns {boolean} */ function isLinkTransferred(link) { Loading Loading @@ -198,6 +204,7 @@ function isLinkSimilar(link, linkToMatch) { * @param {Object[]} arr where link or multilink will be created * @param {Link} link to insert * @param {Number} groupId so multilink has always source set to group node * * @return {Object} multilink copy, if created; null otherwise */ function insertUpdatedLink(arr, link, groupId, newId) { Loading Loading @@ -250,6 +257,8 @@ function insertUpdatedLink(arr, link, groupId, newId) { /** * Return link.vis.newSource uif set, otherwise just link.source * @param {Link} link * * @return {number} ID of source */ export function getLinkSource(link) { if (link.vis !== undefined && link.vis.newSource !== undefined) Loading @@ -258,8 +267,10 @@ export function getLinkSource(link) { } /** * Return link.vis.newTarget uif set, otherwise just link.target * Return link.vis.newTarget if set, otherwise just link.target * @param {Link} link * * @return {number} ID of target */ export function getLinkTarget(link) { if (link.vis !== undefined && link.vis.newTarget !== undefined) Loading @@ -277,7 +288,8 @@ export function getLinkTarget(link) { * @param {Link} link to insert; NOT MUTATED * @param {Link[]} arr to be updated with the new link * @param {number} availableId for creating new multilink * @returns multilink, if new one was created during insertion; otherwise null * * @returns {Link} multilink, if new one was created during insertion; otherwise null */ function insertLinkWithMultilinkUpdate(link, arr, availableId) { if (isMultilink(link)) throw Error('Bad usage. Inserting a multilink is not allowed.') Loading Loading @@ -338,7 +350,7 @@ function multilinkUpdateDirection(/* const */ arr, ml) { * @param {Number} id of presentational node */ export function isReplacedBy(node, id) { return !node.vis ? false : (node.vis.replacedBy === id) return !node.vis ? false : node.vis.replacedBy === id } /** Loading @@ -347,33 +359,66 @@ export function isReplacedBy(node, id) { * @param {Number} id of presentational node */ export function isBundledIn(node, id) { return !node.vis ? false : (node.vis.bundledIn === id) return !node.vis ? false : node.vis.bundledIn === id } export function isBundle(n) { return !n.vis ? false : (n.vis.bundledNodes !== undefined && n.vis.bundledNodes.length > 0) } /** * Check if link is in multilink * @param {Link} link * @return {Boolean} * @return {boolean} */ function isInMultilink(link) { if (!link.vis) return false return link.vis.inMultilink !== undefined } /** * Check if link is in specific multilink * @param {Link} link * @param {number} mlId * * @return {boolean} */ function isInMultilinkOfId(link, mlId) { if (!Number.isInteger(mlId)) throw Error('Invalid ID') if (!link.vis) return false return link.vis.inMultilink === mlId } /** * Check for vis.isMultilink attribute * @param {Link} link * * @return {boolean} */ function isMultilink(link) { export function isMultilink(link) { if (!link.vis) return false return link.vis.isMultilink !== undefined } /** * Check for vis.isArchived attribute * @param {Link} multilink * * @return {boolean} */ function isMultilinkArchived(multilink) { if (!isMultilink(multilink)) return false return multilink.vis.isArchived === true } /** * Push new group node to state * @param {Object} state writes to state.nodes * @param {Node} node will be assigned type and new ID * @param {boolean} isUserDefined true to set this vis attr * * @return {number} ID assigned to the new node */ function pushNodeToState(state, node, isUserDefined) { node.id = arrGetNewId(state.nodes) Loading @@ -399,11 +444,11 @@ function setVisAttr(el, attr, val) { /** * Set vis attributes to object, merging vis objects * @param {Node|Link} el * @param {NodeVis|LinkVis} attrObj vis object to merge * @param {NodeVis|LinkVis} visAttrs vis object to merge */ function setVisAttrs(el, attrObj) { function setVisAttrs(el, visAttrs) { el.vis = el.vis || {} Vue.set(el, 'vis', merge(el.vis, attrObj)) Vue.set(el, 'vis', merge(el.vis, visAttrs)) } /** Loading Loading @@ -450,6 +495,7 @@ function createMultilink(newId, source, target) { * Nodes map is in form [{id: replacedById | undefined },..] * @param {Map} nodesMap created with makeNodesMap * @param {number} nodeId on which to start lookup * * @returns {number} node id */ function findReplacementNode(nodesMap, nodeId) { Loading Loading @@ -607,17 +653,13 @@ export const mutations = { const groupId = action === 'bundle' ? groupNode.id : pushNodeToState(state, groupNode, false) if (action === 'bundle') { const n = getNodeById(state, groupId) const n = findNodeById(state, groupId) if (n.vis && n.vis.bundledNodes !== undefined) { const newBundledNodes = n.vis.bundledNodes nodesToGroupIds.forEach(id => newBundledNodes.push(id)) //setVisAttr(n, 'bundledNodes', newBundledNodes) n.vis = n.vis || {} Vue.set(n.vis, 'bundledNodes', newBundledNodes) setVisAttr(n, 'bundledNodes', newBundledNodes) } else { //setVisAttr(n, 'bundledNodes', nodesToGroupIds) n.vis = n.vis || {} Vue.set(n.vis, 'bundledNodes', nodesToGroupIds) setVisAttr(n, 'bundledNodes', nodesToGroupIds) } arraySetVisAttrs(state.nodes.filter(n => nodesToGroupIds.includes(n.id)), { bundledIn: groupId }) } else { Loading Loading @@ -924,10 +966,8 @@ function makeNodesMap(state) { return new Map( state.nodes.map(n => { if (n.vis) { if (n.vis.replacedBy !== undefined) return [n.id, n.vis.replacedBy] if (n.vis.bundledIn !== undefined) return [n.id, n.vis.bundledIn] if (n.vis.replacedBy !== undefined) return [n.id, n.vis.replacedBy] if (n.vis.bundledIn !== undefined) return [n.id, n.vis.bundledIn] } return [n.id, undefined] }) Loading Loading @@ -988,29 +1028,31 @@ export function validateDataset(dataset) { const target = getLinkTarget(link) const similarLinks = val.links.filter(l => isLinkSimilar(l, link) && l.id !== link.id) const multilinks = similarLinks.filter(l => isMultilink(l)) const multilinks = similarLinks.filter(l => isMultilink(l) && !isMultilinkArchived(l)) const singlelinks = similarLinks.filter(l => !isMultilink(l)) debug && console.log("Similar: "+JSON.stringify(similarLinks)) debug && console.log(" ML: "+JSON.stringify(multilinks)) debug && console.log(" other SL: "+JSON.stringify(singlelinks)) // there are similarly connected links // there are similarly connected links except the link itself if (similarLinks.length > 0) { if (source === target) return // if its multilink, check that there exists singlelinks which it replaced if (isMultilink(link)) { if (singlelinks.length === 0) throw Error('Multilink '+JSON.stringify(link)+' was found without it\' singlelinks.') // the similar link is ml, so there should be at least one other singlelink if (!isMultilinkArchived(link) && singlelinks.length === 0) throw Error('Multilink '+JSON.stringify(link)+' was found without singlelinks.') } else { // archived multilink are not in the array if (multilinks.length !== 1) throw Error("Nodes ["+source+","+target+"] have similar links "+JSON.stringify(similarLinks)+" with wrong multilinks set") const ml = multilinks[0] if (!isInMultilink(link) || link.vis.inMultilink !== ml.id) if (link.vis.inMultilink !== ml.id) throw Error("Link "+link.id+" is supposed to be in multilink "+JSON.stringify(ml.id)) } } else { if (isMultilink(link) && link.vis.isArchived !== true) if (isMultilink(link) && !isMultilinkArchived(link)) throw Error("Link "+link.id+" is not supposed to be multilink.") } }) Loading @@ -1037,7 +1079,6 @@ export const actions = { updateNodes({ commit }, nodesDiff) { commit('UPDATE_NODES', nodesDiff) validateDataset(state) }, removeNode({ commit }, nodeId) { Loading @@ -1045,7 +1086,7 @@ export const actions = { validateDataset(state) }, groupNodes({ commit }, nodeIdArr) { groupNodes({ commit, dispatch }, nodeIds) { let groupNode = { name: 'Group of: ', type: 'group', Loading @@ -1054,13 +1095,14 @@ export const actions = { commit('AGGREGATE_NODES', { action: 'group', nodeIds: nodeIdArr, nodeIds: nodeIds, targetGroupNode: groupNode }) validateDataset(state) dispatch('interaction/respondToRemovedNodes', nodeIds, { root: true }) }, aliasNodes({ commit }, nodeIdArr) { aliasNodes({ commit, dispatch }, nodeIds) { let groupNode = { name: 'Alias of: ', type: 'alias', Loading @@ -1069,10 +1111,11 @@ export const actions = { commit('AGGREGATE_NODES', { action: 'alias', nodeIds: nodeIdArr, nodeIds: nodeIds, targetGroupNode: groupNode }) validateDataset(state) dispatch('interaction/respondToRemovedNodes', nodeIds, { root: true }) }, /** Loading @@ -1081,7 +1124,7 @@ export const actions = { * @param {number} payload.representativeNodeId the "bundle" node * @param {number[]} payload.nodeIds to bundle inside the representative node */ bundleNodes({ commit, getters }, payload) { bundleNodes({ commit, getters, dispatch }, payload) { console.log('bundleNodes: ' + JSON.stringify(payload)) commit('AGGREGATE_NODES', { action: 'bundle', Loading @@ -1089,9 +1132,10 @@ export const actions = { targetGroupNode: getters.getNodeById(payload.representativeNodeId) }) validateDataset(state) dispatch('interaction/respondToRemovedNodes', payload.nodeIds, { root: true }) }, ungroupNode({ commit, getters }, nodeId) { ungroupNode({ commit, getters, dispatch }, nodeId) { // unbundle first if bundle if (getters.getNodeBundledNodeCount(nodeId) > 0) { commit('DISAGGREGATE_NODE', { Loading @@ -1105,6 +1149,7 @@ export const actions = { nodeId: nodeId }) validateDataset(state) dispatch('interaction/respondToRemovedNodes', [nodeId], { root: true }) }, unbundleNode({ commit }, nodeId) { Loading @@ -1124,13 +1169,14 @@ export const getters = { getVisibleNodes: state => filterVisibleNodes(state.nodes), getVisibleLinks: state => filterVisibleLinks(state.links), getNodeById: state => id => state.nodes.find(n => n.id === id), getLinkById: state => id => state.links.find(n => n.id === id), getNodeById: state => id => findNodeById(state, id), getLinkById: state => id => state.links.find(l => l.id === id), getMultilinkLinks: state => multilinkId => state.links.filter(l => isInMultilinkOfId(l, multilinkId)), getLinkIdsConnectedToNode: state => id => findConnectedLinks(state.links, id), getNodeBundledNodeCount: (state, getters) => id => { const n = getters.getNodeById(id) if (!n) throw Error('Node does not exist') getNodeBundledNodeCount: state => id => { const n = findNodeById(state, id) if (!n) return 0 return n.vis && n.vis.bundledNodes !== undefined ? n.vis.bundledNodes.length : 0 } } src/store/modules/interaction.js +14 −2 Original line number Diff line number Diff line Loading @@ -46,8 +46,8 @@ export const mutations = { SELECT_NODE(state, id) { state.selectedNodes.push(id) }, UNSELECT_NODE(state, id) { state.selectedNodes = state.selectedNodes.filter(n => n !== id) DESELECT_NODES(state, idArr) { state.selectedNodes = state.selectedNodes.filter(id => !idArr.includes(id)) }, CLEAR_NODE_SELECTION(state) { state.selectedNodes = [] Loading @@ -64,6 +64,7 @@ export const mutations = { }, HIDE_LINK_DETAIL(state) { state.detailedLink = NaN state.shownTools.linkDetail = false }, SET_TOOL_SHOW(state, { tool, bool }) { Loading Loading @@ -104,6 +105,10 @@ export const actions = { if (state.selectedNodes.length !== 0) return commit('SET_TOOL_SHOW', { tool: 'nodeSelection', bool: false }) }, deselectNode({ commit, dispatch }, id) { commit('DESELECT_NODES', [id]) dispatch('hideNodeSelectionIfEmpty') }, showLinkDetail({ commit }, id) { commit('SHOW_LINK_DETAIL', id) Loading @@ -126,6 +131,13 @@ export const actions = { }, deactivateDebug({ commit }) { commit('SET_DEBUG', false) }, respondToRemovedNodes({ commit, state, dispatch }, removedNodes) { commit('DESELECT_NODES', removedNodes) if (removedNodes.includes(state.detailedNode)) dispatch('hideNodeDetail') if (removedNodes.includes(state.hoveredNode)) dispatch('hideNodeDetail') commit('HIDE_LINK_DETAIL') } } Loading Loading
src/components/NetworkGraph/GraphNode.vue +2 −2 Original line number Diff line number Diff line Loading @@ -229,7 +229,7 @@ export default { 'SET_NODE_DETAIL', 'UNSET_NODE_DETAIL', 'SELECT_NODE', 'UNSELECT_NODE', 'DESELECT_NODES', 'SET_TOOL_SHOW', 'SET_LINK_HIGHLIGHT', 'CLEAR_LINK_HIGHLIGHT' Loading Loading @@ -257,7 +257,7 @@ export default { // if user held the node without dragging, interpret is as a selection if (this.currentDragLength < 4) { if (this.isUserSelecting) { this.isSelected ? this.UNSELECT_NODE(this.node.id) : this.SELECT_NODE(this.node.id) this.isSelected ? this.DESELECT_NODES([this.node.id]) : this.SELECT_NODE(this.node.id) this.isSelected ? this.showNodeSelection() : this.hideNodeSelectionIfEmpty() this.isUserSelecting = false } Loading
src/store/modules/data.js +88 −42 Original line number Diff line number Diff line import Vue from 'vue' import mockData from '@/datasets/data-small.js' import mockData from '@/datasets/data-harry.js' import { cloneDeep, merge, sortBy } from 'lodash' import { clone, validate } from '@babel/types' import { type } from 'os' /** * @file Manages data storage/operations for network visualization module Loading Loading @@ -95,9 +96,10 @@ Object.freeze(AN_AGGREGATION_TYPES) * @param {Object} state * @param {Node[]} state.nodes * @param {number} id of node * @returns {Node} * * @returns {Node} Node instance, or undefined if not found */ function getNodeById(state, id) { function findNodeById(state, id) { return state.nodes.find(n => n.id === id) } Loading @@ -108,6 +110,7 @@ function filterVisibleLinks(links) { /** * Tell if node is visible in graph visualization * @param {Node} node * * @returns {boolean} */ function isNodeVisible(node) { Loading Loading @@ -138,6 +141,7 @@ export function findConnectedLinks(links, id) { /** * Check if visible: not in multilink, not an archived multilink, newTarget != newSource * @param {Link} l * * @returns {boolean} */ function isLinkVisible(l) { Loading @@ -154,6 +158,7 @@ function isLinkVisible(l) { /** * Find first available id by iterating over objects in array * @param {Object[]} arr of objects, each object has attribute id * * @returns {number} id */ function arrGetNewId(arr) { Loading @@ -166,6 +171,7 @@ function arrGetNewId(arr) { /** * Check if link has set vis.newSource and vis.newTarget * @param {Link} link * * @returns {boolean} */ function isLinkTransferred(link) { Loading Loading @@ -198,6 +204,7 @@ function isLinkSimilar(link, linkToMatch) { * @param {Object[]} arr where link or multilink will be created * @param {Link} link to insert * @param {Number} groupId so multilink has always source set to group node * * @return {Object} multilink copy, if created; null otherwise */ function insertUpdatedLink(arr, link, groupId, newId) { Loading Loading @@ -250,6 +257,8 @@ function insertUpdatedLink(arr, link, groupId, newId) { /** * Return link.vis.newSource uif set, otherwise just link.source * @param {Link} link * * @return {number} ID of source */ export function getLinkSource(link) { if (link.vis !== undefined && link.vis.newSource !== undefined) Loading @@ -258,8 +267,10 @@ export function getLinkSource(link) { } /** * Return link.vis.newTarget uif set, otherwise just link.target * Return link.vis.newTarget if set, otherwise just link.target * @param {Link} link * * @return {number} ID of target */ export function getLinkTarget(link) { if (link.vis !== undefined && link.vis.newTarget !== undefined) Loading @@ -277,7 +288,8 @@ export function getLinkTarget(link) { * @param {Link} link to insert; NOT MUTATED * @param {Link[]} arr to be updated with the new link * @param {number} availableId for creating new multilink * @returns multilink, if new one was created during insertion; otherwise null * * @returns {Link} multilink, if new one was created during insertion; otherwise null */ function insertLinkWithMultilinkUpdate(link, arr, availableId) { if (isMultilink(link)) throw Error('Bad usage. Inserting a multilink is not allowed.') Loading Loading @@ -338,7 +350,7 @@ function multilinkUpdateDirection(/* const */ arr, ml) { * @param {Number} id of presentational node */ export function isReplacedBy(node, id) { return !node.vis ? false : (node.vis.replacedBy === id) return !node.vis ? false : node.vis.replacedBy === id } /** Loading @@ -347,33 +359,66 @@ export function isReplacedBy(node, id) { * @param {Number} id of presentational node */ export function isBundledIn(node, id) { return !node.vis ? false : (node.vis.bundledIn === id) return !node.vis ? false : node.vis.bundledIn === id } export function isBundle(n) { return !n.vis ? false : (n.vis.bundledNodes !== undefined && n.vis.bundledNodes.length > 0) } /** * Check if link is in multilink * @param {Link} link * @return {Boolean} * @return {boolean} */ function isInMultilink(link) { if (!link.vis) return false return link.vis.inMultilink !== undefined } /** * Check if link is in specific multilink * @param {Link} link * @param {number} mlId * * @return {boolean} */ function isInMultilinkOfId(link, mlId) { if (!Number.isInteger(mlId)) throw Error('Invalid ID') if (!link.vis) return false return link.vis.inMultilink === mlId } /** * Check for vis.isMultilink attribute * @param {Link} link * * @return {boolean} */ function isMultilink(link) { export function isMultilink(link) { if (!link.vis) return false return link.vis.isMultilink !== undefined } /** * Check for vis.isArchived attribute * @param {Link} multilink * * @return {boolean} */ function isMultilinkArchived(multilink) { if (!isMultilink(multilink)) return false return multilink.vis.isArchived === true } /** * Push new group node to state * @param {Object} state writes to state.nodes * @param {Node} node will be assigned type and new ID * @param {boolean} isUserDefined true to set this vis attr * * @return {number} ID assigned to the new node */ function pushNodeToState(state, node, isUserDefined) { node.id = arrGetNewId(state.nodes) Loading @@ -399,11 +444,11 @@ function setVisAttr(el, attr, val) { /** * Set vis attributes to object, merging vis objects * @param {Node|Link} el * @param {NodeVis|LinkVis} attrObj vis object to merge * @param {NodeVis|LinkVis} visAttrs vis object to merge */ function setVisAttrs(el, attrObj) { function setVisAttrs(el, visAttrs) { el.vis = el.vis || {} Vue.set(el, 'vis', merge(el.vis, attrObj)) Vue.set(el, 'vis', merge(el.vis, visAttrs)) } /** Loading Loading @@ -450,6 +495,7 @@ function createMultilink(newId, source, target) { * Nodes map is in form [{id: replacedById | undefined },..] * @param {Map} nodesMap created with makeNodesMap * @param {number} nodeId on which to start lookup * * @returns {number} node id */ function findReplacementNode(nodesMap, nodeId) { Loading Loading @@ -607,17 +653,13 @@ export const mutations = { const groupId = action === 'bundle' ? groupNode.id : pushNodeToState(state, groupNode, false) if (action === 'bundle') { const n = getNodeById(state, groupId) const n = findNodeById(state, groupId) if (n.vis && n.vis.bundledNodes !== undefined) { const newBundledNodes = n.vis.bundledNodes nodesToGroupIds.forEach(id => newBundledNodes.push(id)) //setVisAttr(n, 'bundledNodes', newBundledNodes) n.vis = n.vis || {} Vue.set(n.vis, 'bundledNodes', newBundledNodes) setVisAttr(n, 'bundledNodes', newBundledNodes) } else { //setVisAttr(n, 'bundledNodes', nodesToGroupIds) n.vis = n.vis || {} Vue.set(n.vis, 'bundledNodes', nodesToGroupIds) setVisAttr(n, 'bundledNodes', nodesToGroupIds) } arraySetVisAttrs(state.nodes.filter(n => nodesToGroupIds.includes(n.id)), { bundledIn: groupId }) } else { Loading Loading @@ -924,10 +966,8 @@ function makeNodesMap(state) { return new Map( state.nodes.map(n => { if (n.vis) { if (n.vis.replacedBy !== undefined) return [n.id, n.vis.replacedBy] if (n.vis.bundledIn !== undefined) return [n.id, n.vis.bundledIn] if (n.vis.replacedBy !== undefined) return [n.id, n.vis.replacedBy] if (n.vis.bundledIn !== undefined) return [n.id, n.vis.bundledIn] } return [n.id, undefined] }) Loading Loading @@ -988,29 +1028,31 @@ export function validateDataset(dataset) { const target = getLinkTarget(link) const similarLinks = val.links.filter(l => isLinkSimilar(l, link) && l.id !== link.id) const multilinks = similarLinks.filter(l => isMultilink(l)) const multilinks = similarLinks.filter(l => isMultilink(l) && !isMultilinkArchived(l)) const singlelinks = similarLinks.filter(l => !isMultilink(l)) debug && console.log("Similar: "+JSON.stringify(similarLinks)) debug && console.log(" ML: "+JSON.stringify(multilinks)) debug && console.log(" other SL: "+JSON.stringify(singlelinks)) // there are similarly connected links // there are similarly connected links except the link itself if (similarLinks.length > 0) { if (source === target) return // if its multilink, check that there exists singlelinks which it replaced if (isMultilink(link)) { if (singlelinks.length === 0) throw Error('Multilink '+JSON.stringify(link)+' was found without it\' singlelinks.') // the similar link is ml, so there should be at least one other singlelink if (!isMultilinkArchived(link) && singlelinks.length === 0) throw Error('Multilink '+JSON.stringify(link)+' was found without singlelinks.') } else { // archived multilink are not in the array if (multilinks.length !== 1) throw Error("Nodes ["+source+","+target+"] have similar links "+JSON.stringify(similarLinks)+" with wrong multilinks set") const ml = multilinks[0] if (!isInMultilink(link) || link.vis.inMultilink !== ml.id) if (link.vis.inMultilink !== ml.id) throw Error("Link "+link.id+" is supposed to be in multilink "+JSON.stringify(ml.id)) } } else { if (isMultilink(link) && link.vis.isArchived !== true) if (isMultilink(link) && !isMultilinkArchived(link)) throw Error("Link "+link.id+" is not supposed to be multilink.") } }) Loading @@ -1037,7 +1079,6 @@ export const actions = { updateNodes({ commit }, nodesDiff) { commit('UPDATE_NODES', nodesDiff) validateDataset(state) }, removeNode({ commit }, nodeId) { Loading @@ -1045,7 +1086,7 @@ export const actions = { validateDataset(state) }, groupNodes({ commit }, nodeIdArr) { groupNodes({ commit, dispatch }, nodeIds) { let groupNode = { name: 'Group of: ', type: 'group', Loading @@ -1054,13 +1095,14 @@ export const actions = { commit('AGGREGATE_NODES', { action: 'group', nodeIds: nodeIdArr, nodeIds: nodeIds, targetGroupNode: groupNode }) validateDataset(state) dispatch('interaction/respondToRemovedNodes', nodeIds, { root: true }) }, aliasNodes({ commit }, nodeIdArr) { aliasNodes({ commit, dispatch }, nodeIds) { let groupNode = { name: 'Alias of: ', type: 'alias', Loading @@ -1069,10 +1111,11 @@ export const actions = { commit('AGGREGATE_NODES', { action: 'alias', nodeIds: nodeIdArr, nodeIds: nodeIds, targetGroupNode: groupNode }) validateDataset(state) dispatch('interaction/respondToRemovedNodes', nodeIds, { root: true }) }, /** Loading @@ -1081,7 +1124,7 @@ export const actions = { * @param {number} payload.representativeNodeId the "bundle" node * @param {number[]} payload.nodeIds to bundle inside the representative node */ bundleNodes({ commit, getters }, payload) { bundleNodes({ commit, getters, dispatch }, payload) { console.log('bundleNodes: ' + JSON.stringify(payload)) commit('AGGREGATE_NODES', { action: 'bundle', Loading @@ -1089,9 +1132,10 @@ export const actions = { targetGroupNode: getters.getNodeById(payload.representativeNodeId) }) validateDataset(state) dispatch('interaction/respondToRemovedNodes', payload.nodeIds, { root: true }) }, ungroupNode({ commit, getters }, nodeId) { ungroupNode({ commit, getters, dispatch }, nodeId) { // unbundle first if bundle if (getters.getNodeBundledNodeCount(nodeId) > 0) { commit('DISAGGREGATE_NODE', { Loading @@ -1105,6 +1149,7 @@ export const actions = { nodeId: nodeId }) validateDataset(state) dispatch('interaction/respondToRemovedNodes', [nodeId], { root: true }) }, unbundleNode({ commit }, nodeId) { Loading @@ -1124,13 +1169,14 @@ export const getters = { getVisibleNodes: state => filterVisibleNodes(state.nodes), getVisibleLinks: state => filterVisibleLinks(state.links), getNodeById: state => id => state.nodes.find(n => n.id === id), getLinkById: state => id => state.links.find(n => n.id === id), getNodeById: state => id => findNodeById(state, id), getLinkById: state => id => state.links.find(l => l.id === id), getMultilinkLinks: state => multilinkId => state.links.filter(l => isInMultilinkOfId(l, multilinkId)), getLinkIdsConnectedToNode: state => id => findConnectedLinks(state.links, id), getNodeBundledNodeCount: (state, getters) => id => { const n = getters.getNodeById(id) if (!n) throw Error('Node does not exist') getNodeBundledNodeCount: state => id => { const n = findNodeById(state, id) if (!n) return 0 return n.vis && n.vis.bundledNodes !== undefined ? n.vis.bundledNodes.length : 0 } }
src/store/modules/interaction.js +14 −2 Original line number Diff line number Diff line Loading @@ -46,8 +46,8 @@ export const mutations = { SELECT_NODE(state, id) { state.selectedNodes.push(id) }, UNSELECT_NODE(state, id) { state.selectedNodes = state.selectedNodes.filter(n => n !== id) DESELECT_NODES(state, idArr) { state.selectedNodes = state.selectedNodes.filter(id => !idArr.includes(id)) }, CLEAR_NODE_SELECTION(state) { state.selectedNodes = [] Loading @@ -64,6 +64,7 @@ export const mutations = { }, HIDE_LINK_DETAIL(state) { state.detailedLink = NaN state.shownTools.linkDetail = false }, SET_TOOL_SHOW(state, { tool, bool }) { Loading Loading @@ -104,6 +105,10 @@ export const actions = { if (state.selectedNodes.length !== 0) return commit('SET_TOOL_SHOW', { tool: 'nodeSelection', bool: false }) }, deselectNode({ commit, dispatch }, id) { commit('DESELECT_NODES', [id]) dispatch('hideNodeSelectionIfEmpty') }, showLinkDetail({ commit }, id) { commit('SHOW_LINK_DETAIL', id) Loading @@ -126,6 +131,13 @@ export const actions = { }, deactivateDebug({ commit }) { commit('SET_DEBUG', false) }, respondToRemovedNodes({ commit, state, dispatch }, removedNodes) { commit('DESELECT_NODES', removedNodes) if (removedNodes.includes(state.detailedNode)) dispatch('hideNodeDetail') if (removedNodes.includes(state.hoveredNode)) dispatch('hideNodeDetail') commit('HIDE_LINK_DETAIL') } } Loading