From a171427e10e127a92416fc463b47de739323e606 Mon Sep 17 00:00:00 2001 From: Lukas Winkler Date: Sat, 19 Nov 2022 22:09:15 +0100 Subject: [PATCH] basic graph version --- .gitignore | 1 - graph/__init__.py | 0 graph/apps.py | 6 ++ graph/templates/graph/graph.jinja | 10 ++++ graph/tests.py | 3 + graph/urls.py | 1 + graph/views.py | 38 ++++++++++++ locations/models.py | 4 ++ notes/models.py | 4 ++ package-lock.json | 97 +++++++++++++++++++++++++++++++ package.json | 4 ++ rpg_notes/settings.py | 2 +- rpg_notes/urls.py | 2 +- static/js/graph.ts | 46 +++++++++++++++ static/main.ts | 1 + static/scss/_graph.scss | 7 +++ static/scss/main.scss | 1 + 17 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 graph/__init__.py create mode 100644 graph/apps.py create mode 100644 graph/templates/graph/graph.jinja create mode 100644 graph/tests.py create mode 100644 graph/views.py create mode 100644 static/js/graph.ts create mode 100644 static/scss/_graph.scss 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";