add basic exploration feature

neo4j-4
Josha von Gizycki 5 years ago
parent 4590a345b1
commit d3efb3165e

@ -16,7 +16,7 @@
commons-codec]] commons-codec]]
[ring/ring-json "0.4.0"] [ring/ring-json "0.4.0"]
[hiccup "1.0.5"] [hiccup "1.0.5"]
[joshavg/formulare "0.4.0"] [joshavg/formulare "0.5.0-SNAPSHOT"]
;; neo4j ;; neo4j
[gorillalabs/neo4j-clj "2.0.1" [gorillalabs/neo4j-clj "2.0.1"

@ -1,5 +1,6 @@
@ci-blue: Highlight; @ci-highlight: Highlight;
@error-color: #e00; @error-color: #e00;
@warn-color: #eebc00;
@accent-color: #efefef; @accent-color: #efefef;
@body-background-color: #fcfcfc; @body-background-color: #fcfcfc;
@background-color: #eee; @background-color: #eee;
@ -40,7 +41,7 @@ h1 {
h2 { h2 {
font-size: 1.3rem; font-size: 1.3rem;
border-bottom: 1px solid @ci-blue; border-bottom: 1px solid @ci-highlight;
} }
em { em {
@ -57,7 +58,7 @@ img, svg {
} }
.__icon { .__icon {
color: @ci-blue; color: @ci-highlight;
font-weight: bold; font-weight: bold;
} }
@ -83,7 +84,7 @@ img, svg {
background-position: right; background-position: right;
background-origin: content-box; background-origin: content-box;
background-repeat: no-repeat; background-repeat: no-repeat;
border-bottom: 1px solid @ci-blue; border-bottom: 1px solid @ci-highlight;
.app-title { .app-title {
grid-column: 1; grid-column: 1;
@ -114,7 +115,7 @@ img, svg {
h2::before { h2::before {
content: "▤ "; content: "▤ ";
color: @ci-blue; color: @ci-highlight;
} }
h2 { h2 {
@ -194,37 +195,45 @@ form {
} }
} }
.flash--error { .delete-btn.__on-countdown {
.flash; font-weight: bold;
border-color: @error-color;
.flash__heading--error {
margin: 0;
background-color: @error-color;
color: white;
margin-top: -.7rem;
margin-left: -.7rem;
margin-right: -.7rem;
}
} }
}
.flash { .flash {
grid-column: 1 e("/") 3; grid-column: 1 e("/") 3;
.thin-border; .thin-border;
border-left-width: @accent-border-width; border-left-width: @accent-border-width;
padding: .7rem; padding: .7rem;
.flash__field { .flash__field {
font-style: italic; font-style: italic;
} }
.flash__pred { .flash__pred {
font-family: monospace; font-family: monospace;
} }
}
.flash--error {
.flash;
border-color: @error-color;
.flash__heading--error {
background-color: @error-color;
color: white;
margin: -.7rem -.7rem 0;
} }
}
.delete-btn.__on-countdown { .flash--warn {
font-weight: bold; .flash;
border-color: @warn-color;
.flash__heading--error {
background-color: @warn-color;
color: white;
margin: -.7rem -.7rem 0;
} }
} }
@ -325,7 +334,12 @@ table {
.single-tag { .single-tag {
border: 1px solid @ci-color; border: 1px solid @ci-color;
border-left: .5rem solid @ci-blue; border-left: .5rem solid @ci-highlight;
border-radius: .5rem; border-radius: .5rem;
padding: .1rem .4rem; padding: .1rem .4rem;
} }
.vis-canvas {
border: 1px solid @ci-highlight;
height: 45rem;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,82 @@
document.addEventListener('DOMContentLoaded', function () {
const canvas = document.querySelector('.vis-canvas')
const nodes = new vis.DataSet([])
const edges = new vis.DataSet([])
const network = new vis.Network(canvas, {
nodes: nodes,
edges: edges
}, {
manipulation: true
})
const instanceLabel = (inst, schema) => `${inst.name}\n${schema.name}`
const addNode = (toAdd) => {
try {
nodes.add({
id: toAdd.uuid,
label: instanceLabel(toAdd, toAdd.schema)
})
} catch(e) {
console.debug(e)
}
}
const addOutEdges = (fromId, toAdd) => {
toAdd.forEach((inst) => {
try {
edges.add({
from: fromId,
to: inst.uuid,
label: inst['link-name'],
arrows: {
to: {enabled: true}
}
})
} catch (e) {
console.debug(e)
}
})
}
const addLinkedNodes = (toAdd) => {
toAdd.forEach((inst) => {
try {
console.debug(inst.name)
nodes.add({
id: inst.uuid,
label: instanceLabel(inst, inst.schema)
})
} catch (e) {
console.debug(e)
}
})
}
const addInstance = (instance) => {
addNode(instance)
addLinkedNodes(instance['links-out'])
addOutEdges(instance.uuid, instance['links-out'])
}
fetch(canvas.getAttribute('data-instance-url'))
.then((resp) => resp.json())
.then((json) => {
addInstance(json)
})
const searchForm = document.querySelector('.search')
const searchUrl = searchForm.getAttribute('action')
searchForm.addEventListener('submit', (event) => {
event.preventDefault()
const term = searchForm.querySelector('[name=term]').value
fetch(searchUrl + encodeURIComponent(term))
.then((resp) => resp.json())
.then((json) => {
console.debug(json)
json.forEach((inst) => {
addInstance(inst)
})
})
})
})

@ -1,5 +1,6 @@
(ns wanijo.visualisation.domain (ns wanijo.visualisation.domain
(:require [wanijo.framework.neo4j :as neo4j])) (:require [wanijo.framework.neo4j :as neo4j]
[wanijo.instance.domain :as domain-instance]))
(neo4j/defquery all-instance-connections (neo4j/defquery all-instance-connections
"MATCH "MATCH
@ -8,6 +9,28 @@
(source)<-[:link_from]-(link:link)-[:link_to]->(target:instance), (source)<-[:link_from]-(link:link)-[:link_to]->(target:instance),
(target)-[:of]->(target_schema:schema) (target)-[:of]->(target_schema:schema)
RETURN source, schema, link, target, target_schema") RETURN source, schema, link, target, target_schema")
(defn all-instance-connections! [] (defn all-instance-connections! []
(neo4j/exec-query! all-instance-connections {})) (neo4j/exec-query! all-instance-connections {}))
(neo4j/defquery search
"MATCH (i:instance)
WHERE i.name =~ {term}
RETURN i
LIMIT 10")
(defn enrich-links [instance]
(update instance
:links-out
(fn [links-in]
(map #(assoc
(domain-instance/full-instance-by-uuid!
(-> % :target :uuid))
:link-name
(-> % :label :name))
links-in))))
(defn search! [term]
(->> (neo4j/exec-query! search {:term (str ".*" term ".*")})
(map #(domain-instance/full-instance-by-uuid! (-> % :i :uuid)))
(map enrich-links)))
(defn instance! [uuid]
(enrich-links (domain-instance/full-instance-by-uuid! uuid)))

@ -13,22 +13,38 @@
(defn all-instances [req] (defn all-instances [req]
(view/layout! (view/layout!
:request req :request req
:content :content
[[:h1 "All Instances"] [[:h1 "All Instances"]
(viz/all-instances (vis-domain/all-instance-connections!))])) (viz/all-instances (vis-domain/all-instance-connections!))]))
(defn all-schemas [req] (defn all-schemas [req]
{:content-type "text/json" {:content-type "text/json"
:body (domain-schema/accessible-schemas! (-> req :session :uuid))}) :body (domain-schema/accessible-schemas! (-> req :session :uuid))})
(defn instance [uuid]
{:content-type "text/json"
:body (vis-domain/instance! uuid)})
(defn search [term]
{:content-type "text/json"
:body (map #(merge %
{:urls {:instance (path :vis-get-instance %)}})
(vis-domain/search! term))})
(defroutes routes (defroutes routes
(GET (register! :vis-explore "/vis/explore/:instance-uuid") (GET (register! :vis-explore "/vis/explore/:instance-uuid")
[:as req] [instance-uuid :as req]
(vis-view/index req)) (vis-view/index instance-uuid req))
(GET (register! :vis-all-instances "/vis/all-instances") (GET (register! :vis-all-instances "/vis/all-instances")
[:as req] [:as req]
(all-instances req)) (all-instances req))
(GET (register! :vis-all-schemas "/api/vis/schemas") (GET (register! :vis-all-schemas "/api/vis/schemas")
[:as req] [:as req]
(all-schemas req))) (all-schemas req))
(GET (register! :vis-get-instance "/api/vis/instance/:uuid")
[uuid]
(instance uuid))
(GET (register! :vis-search-instance "/api/vis/search/:term")
[term]
(search term)))

@ -1,10 +1,22 @@
(ns wanijo.visualisation.view (ns wanijo.visualisation.view
(:require [hiccup.page :refer [include-js]] (:require [hiccup.page :refer [include-js include-css]]
[hiccup.form :as hform]
[wanijo.framework.routing :refer [path]]
[wanijo.framework.view :as view])) [wanijo.framework.view :as view]))
(defn index [req] (defn index [instance-uuid req]
(view/layout! (view/layout!
:request req :request req
:head (list (include-css "/css/vis-network.min.css")
(include-js "/js/vis-network.min.js"
"/js/vis.js"))
:content :content
[[:h1 "Visualisation"] [[:h1 "Explore"]
[:div#visualisation]])) [:form.search {:action (path :vis-search-instance {:term ""})}
(hform/label "term" "Search Instance")
(hform/text-field {:autofocus true} "term")
[:input {:style "display: none" :type "submit"}]]
[:div.vis-canvas {:data-instance-url (path :vis-get-instance
{:uuid instance-uuid})}]
[:p.flash--warn
"Changes to the visualisation will not (yet) mirror to the actual database"]]))

Loading…
Cancel
Save