diff --git a/.gitignore b/.gitignore
index a6a2c44..b6aca77 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,5 +5,4 @@ __pycache__/
static/css
static/build
node_modules
-graph
build_static/
diff --git a/graph/__init__.py b/graph/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/graph/apps.py b/graph/apps.py
new file mode 100644
index 0000000..7012829
--- /dev/null
+++ b/graph/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class GraphConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'graph'
diff --git a/graph/templates/graph/graph.jinja b/graph/templates/graph/graph.jinja
new file mode 100644
index 0000000..0391020
--- /dev/null
+++ b/graph/templates/graph/graph.jinja
@@ -0,0 +1,10 @@
+{% extends "tenantbase.jinja" %}
+
+{% block content %}
+
Graph
+
+{% endblock %}
+
+{% block extra_js %}
+ {{ super() }}
+{% endblock %}
diff --git a/graph/tests.py b/graph/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/graph/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/graph/urls.py b/graph/urls.py
index f9667a8..b66f476 100644
--- a/graph/urls.py
+++ b/graph/urls.py
@@ -4,4 +4,5 @@ from graph import views
urlpatterns = [
path("", views.GraphView.as_view(), name="graph"),
+ path("graph", views.get_graph, name="api")
]
diff --git a/graph/views.py b/graph/views.py
new file mode 100644
index 0000000..49c088e
--- /dev/null
+++ b/graph/views.py
@@ -0,0 +1,38 @@
+from django.http import JsonResponse, HttpRequest, HttpResponse
+from django.views.generic import TemplateView
+
+from locations.models import Location
+from notes.models import Note
+
+
+# @dataclass
+# class Node:
+#
+#
+# class Graph:
+# def __init__(self):
+# self.nodes=
+
+class GraphView(TemplateView):
+ template_name = "graph/graph.jinja"
+
+
+def get_graph(request: HttpRequest) -> HttpResponse:
+ nodes = []
+ edges = []
+ for loc in list(Location.objects.all())+list(Note.objects.all()):
+ nodes.append({
+ "key": loc.graphkey,
+ "attributes": {"label": loc.name}
+ })
+ if loc.parent:
+ edges.append({
+ "source": loc.graphkey,
+ "target": loc.parent.graphkey
+ })
+
+ return JsonResponse({
+ "attributes": {},
+ "nodes": nodes,
+ "edges": edges
+ })
diff --git a/locations/models.py b/locations/models.py
index 68ffc9c..cfbb979 100644
--- a/locations/models.py
+++ b/locations/models.py
@@ -31,3 +31,7 @@ class Location(TreeNode, NameSlugModel, DescriptionModel, AliasModel, HistoryMod
def get_absolute_url(self):
return reverse('locationdetail', args=[self.slug])
+
+ @property
+ def graphkey(self):
+ return f"loc{self.pk}"
diff --git a/notes/models.py b/notes/models.py
index 4101522..3ec0c02 100644
--- a/notes/models.py
+++ b/notes/models.py
@@ -31,3 +31,7 @@ class Note(TreeNode, NameSlugModel, DescriptionModel, AliasModel, HistoryModel):
def get_absolute_url(self):
return reverse('notedetail', args=[self.slug])
+
+ @property
+ def graphkey(self):
+ return f"not{self.pk}"
diff --git a/package-lock.json b/package-lock.json
index 69ac7a9..4627127 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,7 +19,11 @@
"bootstrap": "^5.2.0-beta1",
"codemirror": "^6.0.1",
"dts-gen": "^0.6.0",
+ "graphology": "^0.25.1",
+ "graphology-layout": "^0.6.1",
+ "graphology-layout-force": "^0.2.4",
"luminous-lightbox": "^2.3.5",
+ "sigma": "^2.4.0",
"typescript": "^4.7.4",
"vite": "^3.0.2",
"y-codemirror.next": "^0.3.2",
@@ -361,6 +365,11 @@
"@popperjs/core": "^2.9.2"
}
},
+ "node_modules/@yomguithereal/helpers": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@yomguithereal/helpers/-/helpers-1.1.1.tgz",
+ "integrity": "sha512-UYvAq/XCA7xoh1juWDYsq3W0WywOB+pz8cgVnE1b45ZfdMhBvHDrgmSFG3jXeZSr2tMTYLGHFHON+ekG05Jebg=="
+ },
"node_modules/ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
@@ -999,6 +1008,14 @@
"node": ">=6"
}
},
+ "node_modules/events": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+ "engines": {
+ "node": ">=0.8.x"
+ }
+ },
"node_modules/extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
@@ -1100,6 +1117,55 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
},
+ "node_modules/graphology": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/graphology/-/graphology-0.25.1.tgz",
+ "integrity": "sha512-yYA7BJCcXN2DrKNQQ9Qf22zBHm/yTbyBR71T1MYBbGtywNHsv0QZtk8zaR6zxNcp2hCCZayUkHp9DyMSZCpoxQ==",
+ "dependencies": {
+ "events": "^3.3.0",
+ "obliterator": "^2.0.2"
+ },
+ "peerDependencies": {
+ "graphology-types": ">=0.24.0"
+ }
+ },
+ "node_modules/graphology-layout": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/graphology-layout/-/graphology-layout-0.6.1.tgz",
+ "integrity": "sha512-m9aMvbd0uDPffUCFPng5ibRkb2pmfNvdKjQWeZrf71RS1aOoat5874+DcyNfMeCT4aQguKC7Lj9eCbqZj/h8Ag==",
+ "dependencies": {
+ "graphology-utils": "^2.3.0",
+ "pandemonium": "^2.4.0"
+ },
+ "peerDependencies": {
+ "graphology-types": ">=0.19.0"
+ }
+ },
+ "node_modules/graphology-layout-force": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/graphology-layout-force/-/graphology-layout-force-0.2.4.tgz",
+ "integrity": "sha512-NYZz0YAnDkn5pkm30cvB0IScFoWGtbzJMrqaiH070dYlYJiag12Oc89dbVfaMaVR/w8DMIKxn/ix9Bqj+Umm9Q==",
+ "dependencies": {
+ "graphology-utils": "^2.4.2"
+ },
+ "peerDependencies": {
+ "graphology-types": ">=0.19.0"
+ }
+ },
+ "node_modules/graphology-types": {
+ "version": "0.24.5",
+ "resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.5.tgz",
+ "integrity": "sha512-m8FVoj9b6MwIaTN+/AvoxXhcK5n0uSe7ZnhbQNTcjh94vzN6m5hU501LihtCfRjF35QEMVrXYOrTNO0wAR1Gxw==",
+ "peer": true
+ },
+ "node_modules/graphology-utils": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/graphology-utils/-/graphology-utils-2.5.2.tgz",
+ "integrity": "sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==",
+ "peerDependencies": {
+ "graphology-types": ">=0.23.0"
+ }
+ },
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -1339,6 +1405,14 @@
"resolved": "https://registry.npmjs.org/luminous-lightbox/-/luminous-lightbox-2.4.0.tgz",
"integrity": "sha512-qu1IE2/+sTyAzUgEGffHPnBNenJ1CDDieSDy6yLgmJUsMVoTLCbTeDJuQvje4hpl+M3xZvq87TIP8hu3BsgaEQ=="
},
+ "node_modules/mnemonist": {
+ "version": "0.39.5",
+ "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.5.tgz",
+ "integrity": "sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==",
+ "dependencies": {
+ "obliterator": "^2.0.1"
+ }
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -1383,6 +1457,11 @@
"node": ">=0.10.0"
}
},
+ "node_modules/obliterator": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz",
+ "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ=="
+ },
"node_modules/open": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz",
@@ -1411,6 +1490,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/pandemonium": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/pandemonium/-/pandemonium-2.4.1.tgz",
+ "integrity": "sha512-wRqjisUyiUfXowgm7MFH2rwJzKIr20rca5FsHXCMNm1W5YPP1hCtrZfgmQ62kP7OZ7Xt+cR858aB28lu5NX55g==",
+ "dependencies": {
+ "mnemonist": "^0.39.2"
+ }
+ },
"node_modules/parse-git-config": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/parse-git-config/-/parse-git-config-1.1.1.tgz",
@@ -1854,6 +1941,16 @@
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
+ "node_modules/sigma": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/sigma/-/sigma-2.4.0.tgz",
+ "integrity": "sha512-spi4C+c3cjlhCklT+RvAxJJcarMmjRpF6RPNvBIBYDduALq8iSNm7FwSpijQNGtI+ryeZ2EfvyBNLp36OFaZiw==",
+ "dependencies": {
+ "@yomguithereal/helpers": "^1.1.1",
+ "events": "^3.3.0",
+ "graphology-utils": "^2.5.0"
+ }
+ },
"node_modules/simple-peer": {
"version": "9.11.1",
"resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.11.1.tgz",
diff --git a/package.json b/package.json
index e26c55a..be12218 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,11 @@
"bootstrap": "^5.2.0-beta1",
"codemirror": "^6.0.1",
"dts-gen": "^0.6.0",
+ "graphology": "^0.25.1",
+ "graphology-layout": "^0.6.1",
+ "graphology-layout-force": "^0.2.4",
"luminous-lightbox": "^2.3.5",
+ "sigma": "^2.4.0",
"typescript": "^4.7.4",
"vite": "^3.0.2",
"y-codemirror.next": "^0.3.2",
diff --git a/rpg_notes/settings.py b/rpg_notes/settings.py
index b2de56a..d73a500 100644
--- a/rpg_notes/settings.py
+++ b/rpg_notes/settings.py
@@ -73,7 +73,7 @@ TENANT_APPS = (
'factions',
'notes',
'search',
- # 'graph',
+ 'graph',
'common',
'simple_history',
diff --git a/rpg_notes/urls.py b/rpg_notes/urls.py
index 26fd295..4887603 100644
--- a/rpg_notes/urls.py
+++ b/rpg_notes/urls.py
@@ -19,7 +19,7 @@ urlpatterns = [
path('note/', include("notes.urls")),
path('loot/', include("loot.urls")),
path('search/', include("search.urls")),
- # path('graph/', include("graph.urls")),
+ path('graph/', include("graph.urls")),
path('', include("common.urls")),
path('', include("campaigns.urls"))
]
diff --git a/static/js/graph.ts b/static/js/graph.ts
new file mode 100644
index 0000000..639a156
--- /dev/null
+++ b/static/js/graph.ts
@@ -0,0 +1,46 @@
+import Sigma from "sigma";
+import ForceSupervisor from "graphology-layout-force/worker";
+import Graph from "graphology";
+import random from 'graphology-layout/random';
+
+const container = document.getElementById("graph")
+console.log("graph")
+if (container) {
+ const graph = new Graph({
+ type: "directed",
+ });
+ //
+ // graph.addNode("John", {x: 0, y: 10, size: 50, label: "John", color: "blue"});
+ // graph.addNode("Mary", {x: 10, y: 0, size: 30, label: "Mary", color: "red"});
+ // graph.addNode("test", {x: 10, y: 10, size: 30, label: "Mary", color: "red"});
+ // graph.addNode("test2", {x: 10, y: 100, size: 30, label: "Mary", color: "red"});
+ //
+ // graph.addEdge("John", "Mary");
+ // graph.addEdge("John", "test");
+ // graph.addEdge("Mary", "test");
+ // graph.addEdge("Mary", "test2");
+
+ fetch('/graph/graph')
+ .then((response) => response.json())
+ .then((data) => {
+ console.log(data)
+ graph.import(data)
+ random.assign(graph);
+ });
+
+
+ // Create the spring layout and start it
+ const layout = new ForceSupervisor(graph, {isNodeFixed: (_, attr) => attr.highlighted});
+ layout.start();
+
+ console.log(graph.export())
+
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const renderer = new Sigma(graph, container, {
+ labelSize:20,
+ edgeLabelSize:200
+ });
+}
+
+
diff --git a/static/main.ts b/static/main.ts
index 99ae43d..e5724e8 100644
--- a/static/main.ts
+++ b/static/main.ts
@@ -10,6 +10,7 @@ import {default as Collapse} from 'bootstrap/js/src/collapse'
import "./js/autocomplete"
import "./js/popover"
import "./js/gallery"
+import "./js/graph"
const bootstrapModules = [Collapse, Dropdown]
diff --git a/static/scss/_graph.scss b/static/scss/_graph.scss
new file mode 100644
index 0000000..981b9f1
--- /dev/null
+++ b/static/scss/_graph.scss
@@ -0,0 +1,7 @@
+#graph {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+}
diff --git a/static/scss/main.scss b/static/scss/main.scss
index 4cba109..ace3129 100644
--- a/static/scss/main.scss
+++ b/static/scss/main.scss
@@ -4,6 +4,7 @@
//@import "node_modules/easymde/dist/easymde.min";
@import "node_modules/luminous-lightbox/dist/luminous-basic";
@import "autocomple";
+@import "graph";
@import "misc";
@import "avatar";