1
0
mirror of https://github.com/robonen/eulerian-cycle.git synced 2026-03-20 02:44:47 +00:00

Fixed indexing error, changed mouse events

This commit is contained in:
2021-12-10 15:41:53 +07:00
parent c0aa8d437b
commit cb14d1ed7b
5 changed files with 114 additions and 74 deletions

View File

@@ -3,7 +3,7 @@
class="render-area" class="render-area"
ref="renderer" ref="renderer"
@dblclick="createNode" @dblclick="createNode"
@click="deactivateAllNodes" @click.left.stop="unselectAllNodes"
> >
<g> <g>
<line <line
@@ -15,23 +15,23 @@
:x2="nodes[link.target].x" :x2="nodes[link.target].x"
:y2="nodes[link.target].y" :y2="nodes[link.target].y"
:class="{ 'line-active': link.selected }" :class="{ 'line-active': link.selected }"
/> @click.right.stop.prevent="removeLink(idx)"
></line>
</g> </g>
<g> <g>
<circle <circle
class="node-default" class="node-default"
r="25" :r="RADIUS"
v-for="(node, idx) in nodes" v-for="(node, idx) in nodes"
:key="idx" :key="idx"
:cx="node.x" :cx="node.x"
:cy="node.y" :cy="node.y"
:class="{ 'node-active': node.selected }" :class="{ 'node-active': node.selected }"
@dblclick.prevent="createNode" @click.right.stop.prevent="removeNode(idx)"
@click.right.prevent="removeNode(idx)"
@click.left.stop="selectNode(idx)" @click.left.stop="selectNode(idx)"
@mousedown="dragNode($event, idx)" @mousedown="dragNode($event, idx)"
@mouseup="dropNode()" @mouseup="dropNode"
/> ></circle>
</g> </g>
<g> <g>
<text <text
@@ -45,10 +45,13 @@
</text> </text>
</g> </g>
</svg> </svg>
<!-- <path d="M591,451 C291,300 891,300 591,451" stroke="black" fill="transparent" style="
stroke-width: 4px;
"></path> -->
</template> </template>
<script> <script>
import { ref, watch } from "vue"; import { ref } from "vue";
import Linker from "./core/linker"; import Linker from "./core/linker";
import EulerCycle from "./core/euler"; import EulerCycle from "./core/euler";
@@ -80,27 +83,27 @@ export default {
const euler = new EulerCycle(); const euler = new EulerCycle();
// Watchers // Watchers
watch( // watch(
() => props.stepData, // () => props.stepData,
(sd) => { // (sd) => {
nodes.value.forEach((e) => { // nodes.value.forEach((e) => {
e.selected = false; // e.selected = false;
}); // });
links.value.forEach((e) => { // links.value.forEach((e) => {
e.selected = false; // e.selected = false;
}); // });
if (Object.keys(sd).length !== 0) { // if (Object.keys(sd).length !== 0) {
nodes.value[sd.source].selected = true; // nodes.value[sd.source].selected = true;
nodes.value[sd.target].selected = true; // nodes.value[sd.target].selected = true;
links.value.forEach((e) => { // links.value.forEach((e) => {
if (e.source === sd.source && e.target === sd.target) // if (e.source === sd.source && e.target === sd.target)
e.selected = true; // e.selected = true;
}); // });
} // }
} // }
); // );
// Methods // Methods
const hasntIntersections = (node) => { const hasntIntersections = (node) => {
@@ -112,12 +115,6 @@ export default {
}); });
}; };
const deactivateAllNodes = () =>
nodes.value.forEach((e) => {
e.selected = false;
linker.reset();
});
const activateNodes = (ids) => const activateNodes = (ids) =>
ids.forEach((e) => (nodes.value[e].selected = true)); ids.forEach((e) => (nodes.value[e].selected = true));
@@ -138,23 +135,32 @@ export default {
}; };
const removeNode = (id) => { const removeNode = (id) => {
links.value = links.value.filter( links.value = links.value
(e) => e.source !== id && e.target !== id .filter((e) => e.source !== id && e.target !== id)
); .map((current) => {
nodes.value = nodes.value.filter((el, idx) => idx !== id); current.source =
current.source > id ? current.source - 1 : current.source;
current.target =
current.target > id ? current.target - 1 : current.target;
return current;
});
nodes.value = nodes.value.filter((_, idx) => idx !== id);
emit("isEuler", []); emit("isEuler", []);
}; };
const selectNode = (id) => { const selectNode = (id) => {
if (linker.sourceEmpty()) { if (linker.sourceEmpty()) linker.setSource(id);
linker.source = id; else linker.addTarget(id);
activateNodes([id]);
} else { activateNodes([id]);
linker.target = id; };
deactivateNodes([linker.source, linker.target]);
createLink(linker.load()); const unselectAllNodes = () => {
linker.reset(); deactivateNodes(linker.expandIds());
}
linker.reset();
}; };
// Node drag and drop // Node drag and drop
@@ -186,23 +192,33 @@ export default {
}; };
// Links // Links
const createLink = (link) => { linker.onLink((source, target) => {
links.value.push(Object.assign({ selected: false }, link)); links.value.push({
selected: false,
source,
target,
});
euler.loadLinks([...links.value]); euler.loadLinks([...links.value]);
if (euler.check()) emit("isEuler", euler.find()); if (euler.check()) emit("isEuler", euler.find());
else emit("isEuler", []); else emit("isEuler", []);
});
const removeLink = (id) => {
links.value = links.value.filter((_, idx) => idx !== id);
}; };
return { return {
RADIUS,
nodes, nodes,
links, links,
renderer, renderer,
createNode, createNode,
selectNode, selectNode,
unselectAllNodes,
removeNode, removeNode,
deactivateAllNodes, removeLink,
dragNode, dragNode,
dropNode, dropNode,
}; };
@@ -234,6 +250,7 @@ line {
-webkit-user-select: none; -webkit-user-select: none;
-ms-user-select: none; -ms-user-select: none;
fill: #fff; fill: #fff;
pointer-events: none;
} }
.node-default { .node-default {

View File

@@ -1,44 +1,55 @@
class Linker { class Linker {
#source = null; #source = null;
#target = null; #targets = null;
#callback = () => {};
constructor() {} constructor() {
this.reset();
}
get source() { getSource() {
return this.#source; return this.#source;
} }
set source(src) { setSource(src) {
if (this.#source === null) this.#source = src; if (this.#source === null) this.#source = src;
} }
get target() { getTargets() {
return this.#target; return this.#targets;
} }
set target(trg) { setTargets(trg) {
if (trg === this.#source) return; this.#targets = trg;
if (this.#target === null) this.#target = trg; }
addTarget(trg) {
this.#targets.push(trg);
this.#callback(this.#source, trg);
} }
sourceEmpty() { sourceEmpty() {
return this.#source === null; return this.#source === null;
} }
targetEmpty() { targetsEmpty() {
return this.#target === null; return this.#targets.length === 0;
} }
load() { expandIds() {
return { if (this.sourceEmpty()) {
source: this.#source, return [...this.#targets];
target: this.#target, }
}; return [this.#source, ...this.#targets];
}
onLink(fn) {
this.#callback = fn;
} }
reset() { reset() {
this.#source = null; this.#source = null;
this.#target = null; this.#targets = [];
} }
} }

View File

@@ -2,4 +2,20 @@ import { createApp } from "vue";
import router from "./router"; import router from "./router";
import App from "./App.vue"; import App from "./App.vue";
createApp(App).use(router).mount("#app"); const app = createApp(App).use(router);
app.directive("click-outside", {
beforeMount(el, binding) {
el.clickOutsideEvent = function (event) {
if (!(el === event.target || el.contains(event.target))) {
binding.value(event, el);
}
};
document.body.addEventListener("click", el.clickOutsideEvent);
},
unmounted(el) {
document.body.removeEventListener("click", el.clickOutsideEvent);
},
});
app.mount("#app");

View File

@@ -1,17 +1,12 @@
import { createRouter, createWebHistory } from "vue-router"; import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue"; import View from "@/views/View.vue";
const routes = [ const routes = [
{ {
path: "/", path: "/",
name: "Home",
component: Home,
},
{
path: "/view",
name: "view", name: "view",
component: () => import(/* webpackChunkName: "view" */ "../views/View.vue"), component: View,
}, },
]; ];

View File

@@ -141,5 +141,6 @@ export default {
</script> </script>
<style> <style>
@import "~@/assets/css/creation.css";
@import "~@/assets/css/graph.css"; @import "~@/assets/css/graph.css";
</style> </style>