Skip to content
Snippets Groups Projects
Commit 5fbf2323 authored by JeanClement's avatar JeanClement
Browse files

Merge branch 'release/0.5.0'

parents d7a7b8e7 78500d80
No related branches found
Tags 0.5.0
No related merge requests found
Pipeline #212624 failed
......@@ -8,6 +8,15 @@
<button v-on:click="curveLine">
curveLine
</button>
<button v-on:click="removeSelectedNodes">
REMOVE
</button>
<button v-on:click="verticalAlign">
VERTICAL ALIGN
</button>
<button v-on:click="horizontalAlign">
HORIZONTAL ALIGN
</button>
<!-- <button v-on:click="undoFunction.undo">
UNDO
</button>
......@@ -19,9 +28,11 @@
v-on:contextmenu.prevent
:network="network"
:graphStyleProperties="networkStyle"
@node-left-click-event="selectNode"
@node-right-click-event="rightClickFunction"
@mouse-leave-node="leaveNode"
@mouse-over-node="overNode"
@dblclick.prevent="unselectNodes"
></NetworkComponent>
</template>
......@@ -37,24 +48,24 @@
import { ref, reactive, onMounted, watch } from "vue";
// Types ----------------
import type { Network } from "./types/Network";
import { GraphStyleProperties } from "./types/GraphStyleProperties";
import type { NodeStyle } from "./types/NodeStyle";
import type { GraphStyleProperties } from "./types/GraphStyleProperties";
// Composables ----------
import { createStaticForceLayout } from './composables/UseCreateForceLayout';
import { initZoom, rescale } from "./composables/UseZoomSvg";
import { importNetworkFromFile, importNetworkFromURL } from "./composables/UseImportNetwork";
import { switchLineStyle } from "./composables/UseManageNetworkData";
import { switchLineStyle, removeAllNodesByAttribut } from "./composables/UseManageNetworkData";
// import { removeThisNode, duplicateThisNode } from './composables/UseManageNetworkData';
import { saveNetworkAsJSON } from './composables/UseSaveNetwork';
// import { addMappingStyleOnNode, nodeBorderColorByAttribut } from "./composables/UseStyleManager";
// import { createUndoFunction } from "./composables/UseUndo";
// import { removeAllSelectedNodes } from './composables/UseManageNetworkData';
import { defineBrush, unselectAll, nodeSelection, verticalNodesAlign, horizontalNodesAlign } from './composables/UseGraphManager';
// Components -----------
import NetworkComponent from "./components/NetworkComponent.vue";
import { Node } from "./types/Node";
import { addLinkStyle, removeLinkStyle } from "./composables/UseStyleManager";
import { Link } from "./types/Link";
import { LinkStyle } from "./types/LinkStyle";
import { removeIsolatedNodes } from "./composables/UseManageNetworkData";
// import { useRandomNetwork } from './composables/CreateRandomGraph';
......@@ -78,6 +89,44 @@ function curveLine() {
switchLineStyle(networkStyle.value);
}
function unselectNodes() {
unselectAll(network.value);
}
function selectNode(_Event: MouseEvent, node: Node) {
nodeSelection(node);
}
function removeSelectedNodes() {
removeAllNodesByAttribut(network.value, 'selected');
}
function verticalAlign() {
const nodes = [] as Array<Node>;
let xTot = 0;
Object.keys(network.value.nodes).forEach((nodeID: string) => {
if (network.value.nodes[nodeID].metadata!.selected) {
nodes.push(network.value.nodes[nodeID]);
xTot += network.value.nodes[nodeID].x;
}
});
verticalNodesAlign(network.value, nodes, xTot);
}
function horizontalAlign() {
const nodes = [] as Array<Node>;
let yTot = 0;
Object.keys(network.value.nodes).forEach((nodeID: string) => {
if (network.value.nodes[nodeID].metadata!.selected) {
nodes.push(network.value.nodes[nodeID]);
yTot += network.value.nodes[nodeID].y;
}
});
horizontalNodesAlign(network.value, nodes, yTot);
}
function rightClickFunction(_Event: MouseEvent, node: Node) {
console.log(node);
// if (rightClicked.nodes.includes(node)) {
......@@ -170,9 +219,10 @@ function loadFile(event: Event) {
// }
async function callbackFunction() {
removeIsolatedNodes(network.value);
createStaticForceLayout(network.value, true);
// removeIsolatedNodes(network.value);
// createStaticForceLayout(network.value, true);
rescale(svgProperties);
defineBrush(network.value, networkStyle.value.nodeStyles as {[key: string]: NodeStyle});
}
onMounted(() => {
......@@ -187,7 +237,7 @@ watch(() => network.value.rescale, (val) => {
if (val) {
rescale(svgProperties);
}
})
});
</script>
......
<template>
<circle
@contextmenu.prevent="rightClickEvent"
ref="nodeElement"
:id="props.node.id"
:cx="circleX"
......@@ -63,13 +62,6 @@ const circleX = computed(() => {
}
});
const emit = defineEmits([
'rightClickEvent',
'drag',
'dragend',
'dragstart'
])
/**
* Return y position of the circle
*/
......@@ -81,10 +73,6 @@ const circleY = computed(() => {
}
});
function rightClickEvent(Event: MouseEvent): void {
emit('rightClickEvent', Event, props.node);
}
</script>
<style scoped>
......
<template>
<div class="global" id="globalViz">
<svg id="d3Viz" class="viz">
<g id="brush" class="brush"></g>
<g id="graphComponent">
<LinkData
v-for="link in props.network.links"
......@@ -18,6 +19,7 @@
@drag="nodeMove"
@dragend="dragStop"
@dragstart="dragStart"
@leftClickEvent="leftClickEvent"
@rightClickEvent="rightClickEvent"
@mouseOverEvent="mouseOverNode"
@mouseLeaveEvent="mouseLeaveNode"
......@@ -53,9 +55,10 @@ const props = defineProps({
});
// Emits ------------
const emits = defineEmits([
const emit = defineEmits([
'dragStart',
'dragEnd',
'nodeLeftClickEvent',
'nodeRightClickEvent',
'mouseLeaveNode',
'mouseOverNode'
......@@ -75,23 +78,27 @@ function nodeMove(id: string, x: number, y: number): void {
}
function dragStop(): void {
emits('dragEnd');
emit('dragEnd');
}
function dragStart(): void {
emits('dragStart');
emit('dragStart');
}
function leftClickEvent(Event: MouseEvent, node: Node): void {
emit('nodeLeftClickEvent', Event, node);
}
function rightClickEvent(Event: MouseEvent, node: Node): void {
emits('nodeRightClickEvent', Event, node);
emit('nodeRightClickEvent', Event, node);
}
function mouseOverNode(Event: MouseEvent, node: Node): void {
emits('mouseOverNode', Event, node);
emit('mouseOverNode', Event, node);
}
function mouseLeaveNode(Event: MouseEvent, node: Node): void {
emits('mouseLeaveNode', Event, node);
emit('mouseLeaveNode', Event, node);
}
</script>
......
......@@ -7,10 +7,19 @@
@drag="drag"
@dragend="dragEnd"
@dragstart="dragStart"
@rightClickEvent="rightClickEvent"
@leftClickNode="leftClickNode"
@contextmenu.prevent="rightClickEvent"
@mouseover="mouseOverEvent"
@mouseleave="mouseLeaveEvent"
/>
<component
v-if="isSelected"
class="disablePointerEvent"
:is="nodeShape.shape"
:node="props.node"
:style="selectedStyle"
:shape="nodeShape"
/>
<text
v-if="nodeStyle.displayLabel"
class="disablePointerEvent"
......@@ -33,7 +42,7 @@ import type { PropType } from "vue";
import type { Node } from "../types/Node";
// Vue ------------
import { computed } from "vue";
import { computed, reactive } from "vue";
// Utils ----------
import { getNodeStyle } from "../utils/NodeStyles";
......@@ -58,6 +67,7 @@ const emit = defineEmits([
'drag',
'dragend',
'dragstart',
'leftClickEvent',
'rightClickEvent',
'mouseOverEvent',
'mouseLeaveEvent'
......@@ -76,8 +86,12 @@ function dragEnd(): void {
emit('dragend');
}
function rightClickEvent(Event: MouseEvent, node: Node): void {
emit('rightClickEvent', Event, node);
function leftClickNode(Event: MouseEvent): void {
emit('leftClickEvent', Event, props.node);
}
function rightClickEvent(Event: MouseEvent): void {
emit('rightClickEvent', Event, props.node);
}
function mouseOverEvent(Event: MouseEvent): void {
......@@ -89,6 +103,13 @@ function mouseLeaveEvent(Event: MouseEvent): void {
}
// Computed ---------
const isSelected = computed(() => {
if (props.node.metadata!.selected) {
return props.node.metadata!.selected;
} else {
return false;
}
});
/**
* Return node style: default style + specific style according to node classe(s)
*/
......@@ -129,6 +150,15 @@ const labelY = computed(() => {
}
});
// Variables --------
const selectedStyle = reactive({
height: nodeStyle.value.height,
width: nodeStyle.value.width,
fill: '#000000',
opacity: 0.4,
shape: nodeShape
});
</script>
<style scoped>
......
<template>
<polygon
@contextmenu.prevent="rightClickEvent"
ref="nodeElement"
:id="props.node.id"
:points="shape.points"
......@@ -46,17 +45,6 @@ const props = defineProps({
const { nodeElement, cursor } = useDragAndDrop(props);
const emit = defineEmits([
'rightClickEvent',
'drag',
'dragend',
'dragstart'
]);
function rightClickEvent(Event: MouseEvent): void {
emit('rightClickEvent', Event, props.node);
}
</script>
<style scoped>
......
......@@ -22,6 +22,8 @@ export function useDragAndDrop(props: { node: Node }): {cursor: ComputedRef<stri
const nodeElement = ref<SVGElement>();
const dragOffsetX = ref(0);
const dragOffsetY = ref(0);
const initialX = ref(0);
const initialY = ref(0);
const x = ref<number>(0);
const y = ref<number>(0);
......@@ -65,12 +67,20 @@ export function useDragAndDrop(props: { node: Node }): {cursor: ComputedRef<stri
const onDragStart = (event: MouseEvent) => {
dragOffsetX.value = x.value - event.x;
dragOffsetY.value = y.value - event.y;
initialX.value = x.value;
initialY.value = y.value;
instance.emit('dragstart');
}
const onDragEnd = () => {
if (initialX.value === x.value && initialY.value === y.value) {
instance.emit('leftClickNode');
} else {
instance.emit('dragend');
}
dragOffsetX.value = dragOffsetY.value = 0;
instance.emit('dragend');
}
const onDrag = (event: MouseEvent) => {
......
import type { Network } from "../types/Network";
import type { Node } from "../types/Node";
import type { NodeStyle } from "../types/NodeStyle";
import { screenSize } from "../utils/GraphUtils";
import { getNodeStyle } from "../utils/NodeStyles";
import * as d3 from 'd3';
/**
* Change a boolean to switch between two modes. For example between selection and zoom for graph panel
* @param mode Current state
* @returns The opposite of current state
*/
export function switchGraphMode(mode: boolean): boolean {
return !(mode);
}
/**
* Select or unselect a node
* @param node Node object clicked
*/
export function nodeSelection(node: Node) {
if (node.metadata) {
if (node.metadata.selected) {
node.metadata.selected = false;
} else {
node.metadata.selected = true;
}
} else {
node['metadata'] = {
selected: true
}
}
}
export function defineBrush(network: Network, styles: {[key: string]: NodeStyle}): void {
const svgSize = screenSize();
const svgWidth = svgSize[0] as number;
const svgHeight = svgSize[1] as number;
function startBrush() {
const brushLayer = document.getElementById('brush');
const graphLayer = document.getElementById('graphComponent');
graphLayer?.parentNode?.insertBefore(graphLayer, brushLayer);
}
function endBrush(e: any) {
if (!e.selection) return;
var extent = e.selection;
if (extent[1][0] - extent[0][0] > 20 || extent[1][1] - extent[0][1] > 20) {
var transform = d3.zoomTransform(d3.select("#globalViz").select("#d3Viz").select('#graphComponent').node() as any);
Object.keys(network.nodes).forEach((nodeID: string) => {
const node = network.nodes[nodeID];
const nodeStyle = getNodeStyle(node, styles);
const xCenter = node.x + (nodeStyle.width as number / 2);
const yCenter = node.y + (nodeStyle.height as number / 2);
if ((transform.invertX(extent[0][0]) <= xCenter && xCenter < transform.invertX(extent[1][0]))
&& (transform.invertY(extent[0][1]) <= yCenter && yCenter < transform.invertY(extent[1][1]))) {
if (network.nodes[nodeID].metadata) {
if (network.nodes[nodeID].metadata?.selected) {
network.nodes[nodeID].metadata!.selected = true;
} else {
network.nodes[nodeID].metadata!['selected'] = true;
}
} else {
network.nodes[nodeID]['metadata'] = {
selected: true
}
}
}
});
}
d3.select('.brush').call(brushEvent.move as any, null);
const brushLayer = document.getElementById('brush');
const graphLayer = document.getElementById('graphComponent');
brushLayer?.parentNode?.insertBefore(brushLayer, graphLayer);
}
const brushEvent = d3.brush()
.filter((event: MouseEvent) => {
return event.button === 2
})
.on('start', startBrush)
.on('end', endBrush)
.extent([[0, 0], [svgWidth, svgHeight]]);
d3.select<SVGGElement, unknown>('#globalViz')
.select<SVGElement>('#d3Viz')
.select<SVGGElement>('#brush')
.call(brushEvent);
}
export function stopBrush() {
d3.selectAll('#brush').remove();
}
export function verticalNodesAlign(network: Network, nodes: Array<Node>, xTot: number) {
nodes.forEach((node: Node) => {
const nodeID = node.id as string;
network.nodes[nodeID].x = xTot / nodes.length;
});
}
export function horizontalNodesAlign(network: Network, nodes: Array<Node>, yTot: number) {
nodes.forEach((node: Node) => {
const nodeID = node.id as string;
network.nodes[nodeID].y = yTot / nodes.length;
});
}
export function unselectAll(network: Network) {
Object.keys(network.nodes).forEach((nodeID: string) => {
if (network.nodes[nodeID].metadata!.selected) {
network.nodes[nodeID].metadata!.selected = false;
}
});
}
......@@ -9,26 +9,24 @@ export function initZoom(): any {
const panel = d3.select<SVGGElement, unknown>('#globalViz')
.select<SVGElement>('#d3Viz')
.select<SVGGElement>('#graphComponent');
panel.attr('transform', e.transform);
};
const zoom: d3.ZoomBehavior<Element, unknown> = d3.zoom()
.on('zoom', handleZoom);
d3.select<Element, unknown>('#globalViz').call(zoom);
d3.select<Element, unknown>('#globalViz').call(zoom).on('dblclick.zoom', null);
return zoom;
}
/**
* Remove d3.zoom() event from graph svg
* @returns {null} Null value
* @returns {} Empty object
*/
export function stopZoom() {
d3.select('#globalViz').call(d3.zoom().on("zoom", null) as any);
return null;
d3.select('#globalViz').on(".zoom", null);
}
/**
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment