Skip to content

Commit f1658a8

Browse files
committed
[doc] much better graph vis of change history
SQUASHED: AUTO-COMMIT-doc-files-changesgraph.md,doc-much-better-graph-vis-of-change-history,
1 parent 54b125d commit f1658a8

File tree

4 files changed

+192
-84
lines changed

4 files changed

+192
-84
lines changed

demos/markdown/scrollingpane.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Scrolling Pane
2+
3+
Hold CTRL+Drag to scroll
4+
5+
<div id="pane" style="position: relative; background-color: lightgray; width: 200px; height: 200px; overflow: hidden">
6+
<div class="lively-content" style="background-color: blue; border: 1px solid gray; position: absolute; width: 245px; height: 193.182px; left: 68.8707px; top: 80.7599px;">
7+
</div>
8+
</div>
9+
10+
Insight: the scrolling pane has to be positioned absolute or relative to make it scrolling!
11+
12+
<script>
13+
14+
var pane = lively.query(this.parentElement, "#pane")
15+
16+
lively.showElement(pane)
17+
var scroll = pane
18+
pane.style.overflow = "auto"
19+
20+
21+
var lastMove
22+
function onPanningMove(evt) {
23+
var pos = lively.getPosition(evt)
24+
var delta = pos.subPt(lastMove)
25+
scroll.scrollTop -= delta.y
26+
scroll.scrollLeft -= delta.x
27+
28+
lively.showEvent(evt)
29+
lastMove = pos
30+
31+
}
32+
33+
lively.removeEventListener("dev", pane)
34+
lively.addEventListener("dev", pane, "pointerdown", evt => {
35+
if (evt.ctrlKey) {
36+
lastMove = lively.getPosition(evt)
37+
lively.addEventListener("changegraph", document.body.parentElement, "pointermove", evt => onPanningMove(evt))
38+
lively.addEventListener("changegraph", document.body.parentElement, "pointerup", evt => {
39+
lively.removeEventListener("changegraph", document.body.parentElement)
40+
})
41+
lively.showEvent(evt)
42+
evt.stopPropagation()
43+
evt.preventDefault()
44+
}
45+
}, true)
46+
47+
48+
49+
50+
51+
</script>

doc/files/changesgraph.md

Lines changed: 137 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
1-
# Changes Graph
2-
3-
<lively-import src="_navigation.html"></lively-import>
4-
51
<div>
6-
url <input style="width:500px" id="url" value=""><br>
7-
limit <input id="limit">
2+
limit <input id="limit"> url <input style="width:500px" id="url" value=""><br>
83
</div>
94

105
<script>
116
import Paths from "src/client/paths.js"
127
import moment from "src/external/moment.js";
138
import diff from 'src/external/diff-match-patch.js';
149
import AnsiColorFilter from "src/external/ansi-to-html.js"
15-
10+
import ViewNav from 'src/client/viewnav.js'
1611

1712

1813
class ChangesGraph {
@@ -32,7 +27,8 @@ limit <input id="limit">
3227
this.ctx = ctx
3328
var dmp = new diff.diff_match_patch();
3429
var baseUrl = lively4url + "/"
35-
var url = "https://lively-kernel.org/lively4/lively4-jens/src/client/auth-dropbox.js"
30+
// var url = "https://lively-kernel.org/lively4/lively4-jens/src/client/auth-dropbox.js"
31+
var url = "https://lively-kernel.org/lively4/lively4-jens/src/client/"
3632

3733
this.query("input#url").value = url
3834
var limitElement = this.query("input#limit")
@@ -65,6 +61,68 @@ limit <input id="limit">
6561
var baseDataChildrenMap
6662

6763
var changes
64+
65+
let edges, nodes, selectedChange, selectedNode, fullNodes, parents
66+
let pane, svgNodes
67+
68+
const DashedEdgeStyle = `[color="gray" style="dashed" arrowhead="open" arrowsize=.7]`
69+
70+
function key(id) {
71+
return "_" + id.replace(/[^a-z0-9A-Z_]/g,"")
72+
}
73+
74+
function addEdge(a , b, style="") {
75+
edges.add(key(a) + " -> " + key(b) + style)
76+
}
77+
78+
function findConnectingPath(version, path, depth=0, visited=new Set()) {
79+
if (!version) throw new Error("version missing")
80+
if (visited.has(version)) return
81+
visited.add(version)
82+
if (depth > 10000) {
83+
// addEdges(path)
84+
// console.log("stop search at depth " + depth + " path: ", path)
85+
return null
86+
}
87+
if (!path) path = [version]
88+
// console.log("findConnectionPath ", version, path)
89+
var change = baseDataMap.get(version)
90+
if (!change) {
91+
debugger
92+
return // nothing found? should this happen
93+
}
94+
var parents = change.parents.split(" ")
95+
for(var eaParentVersion of parents) {
96+
if (fullNodes.has(eaParentVersion) ) {
97+
return path.concat([eaParentVersion]) // found something!
98+
} else {
99+
// depth first search
100+
var found = eaParentVersion && findConnectingPath(eaParentVersion, path.concat([eaParentVersion]), depth + 1, visited)
101+
if (found) {
102+
// console.log("found ... " + found)
103+
return found
104+
}
105+
}
106+
}
107+
return null
108+
}
109+
110+
function addEdges(path) {
111+
var lastVersion
112+
path.forEach(ea => {
113+
if (ea && lastVersion) {
114+
addEdge(lastVersion, ea)
115+
}
116+
lastVersion = ea
117+
})
118+
}
119+
120+
function addShortPath(path) {
121+
addEdge(path.first, path.last, DashedEdgeStyle)
122+
// var shortCut = ""+path.first + "_TO_" + path.last
123+
// addEdge(path.first, shortCut)
124+
// addEdge(shortCut, path.last)
125+
}
68126

69127
var updateTable = async () => {
70128

@@ -97,24 +155,13 @@ limit <input id="limit">
97155
changes = new Map()
98156

99157

100-
var fullNodes = new Set()
101-
var parents = new Set()
102-
103-
var DashedEdgeStyle = `[color="gray" style="dashed" arrowhead="open" arrowsize=.7]`
158+
fullNodes = new Set()
159+
parents = new Set()
104160

105161

106-
var edges = new Set()
107-
var nodes = []
108-
var selectedChange
109-
var selectedNode
110-
111-
function key(id) {
112-
return "_" + id.replace(/[^a-z0-9A-Z_]/g,"")
113-
}
114-
115-
function addEdge(a , b, style="") {
116-
edges.add(key(a) + " -> " + key(b) + style)
117-
}
162+
edges = new Set()
163+
nodes = []
164+
118165

119166
data.forEach(ea => {
120167
var version = ea.version
@@ -158,54 +205,7 @@ limit <input id="limit">
158205
}
159206
})
160207

161-
function findConnectingPath(version, path, depth=0, visited=new Set()) {
162-
if (!version) throw new Error("version missing")
163-
if (visited.has(version)) return
164-
visited.add(version)
165-
if (depth > 10000) {
166-
// addEdges(path)
167-
// console.log("stop search at depth " + depth + " path: ", path)
168-
return null
169-
}
170-
if (!path) path = [version]
171-
// console.log("findConnectionPath ", version, path)
172-
var change = baseDataMap.get(version)
173-
if (!change) {
174-
debugger
175-
return // nothing found? should this happen
176-
}
177-
var parents = change.parents.split(" ")
178-
for(var eaParentVersion of parents) {
179-
if (fullNodes.has(eaParentVersion) ) {
180-
return path.concat([eaParentVersion]) // found something!
181-
} else {
182-
// depth first search
183-
var found = eaParentVersion && findConnectingPath(eaParentVersion, path.concat([eaParentVersion]), depth + 1, visited)
184-
if (found) {
185-
// console.log("found ... " + found)
186-
return found
187-
}
188-
}
189-
}
190-
return null
191-
}
192-
193-
function addEdges(path) {
194-
var lastVersion
195-
path.forEach(ea => {
196-
if (ea && lastVersion) {
197-
addEdge(lastVersion, ea)
198-
}
199-
lastVersion = ea
200-
})
201-
}
202-
203-
function addShortPath(path) {
204-
addEdge(path.first, path.last, DashedEdgeStyle)
205-
// var shortCut = ""+path.first + "_TO_" + path.last
206-
// addEdge(path.first, shortCut)
207-
// addEdge(shortCut, path.last)
208-
}
208+
209209

210210

211211
graphviz.innerHTML = `<` +`script type="graphviz">digraph {
@@ -214,7 +214,11 @@ limit <input id="limit">
214214
}<` + `/script>}`
215215
await graphviz.updateViz()
216216

217-
graphviz.shadowRoot.querySelectorAll("g.node").forEach(ea => {
217+
var scrollToData = tanglingParents.first
218+
219+
svgNodes = graphviz.shadowRoot.querySelectorAll("g.node")
220+
221+
svgNodes.forEach(ea => {
218222
ea.addEventListener("click", async (evt) => {
219223
var key = ea.querySelector('title').textContent
220224
var change = changes.get(key)
@@ -224,10 +228,18 @@ limit <input id="limit">
224228
lively.openInspector({baseDataMap, baseDataChildrenMap, change})
225229
return
226230
}
227-
231+
// hide previous selected node
228232
if (selectedNode) {
229-
selectedNode.querySelector("polygon").setAttribute("fill", "none")
233+
selectedNode.querySelector("polygon").setAttribute("fill", "none")
234+
}
235+
// toggle details by clicking it
236+
if(selectedNode == ea) {
237+
selectedNode = null
238+
details.innerHTML = ""
239+
lively.setGlobalPosition(details, lively.pt(0,0)) // move out of the way
240+
return
230241
}
242+
231243
selectedNode = ea
232244
selectedNode.querySelector("polygon").setAttribute("fill", "lightgray")
233245
selectedChange = change
@@ -242,6 +254,20 @@ limit <input id="limit">
242254
lively.setGlobalPosition(details, lively.getGlobalBounds(selectedNode).topRight().addPt(lively.pt(10,0)))
243255
})
244256
})
257+
258+
lively.sleep(0).then(() => {
259+
if (pane) {
260+
let pos = lively.getGlobalPosition(_.first(svgNodes))
261+
let panePos = lively.getGlobalPosition(pane)
262+
let delta = pos.subPt(panePos)
263+
pane.scrollLeft = delta.x - lively.getExtent(pane).y / 2
264+
pane.scrollTop = delta.y - 100
265+
lively.notify("scroll to: " + delta )
266+
267+
} else {
268+
lively.notify("no pane to scroll into...")
269+
}
270+
})
245271
}
246272

247273
var details = <div id="details"></div>
@@ -267,13 +293,43 @@ limit <input id="limit">
267293
padding: 5px;
268294
}
269295
`
296+
297+
298+
graphviz.style.display = "inline-block" // so it takes the width of children and not parent
299+
300+
pane = <div id="root" style="z-index: -1; position: absolute; top: 0px; left: 0px; overflow-x: auto; overflow-y: scroll; width: calc(100% - 0px); height: calc(100% - 0px);">
301+
{style}
302+
<div style="height: 20px"></div>
303+
<h2>Change Graph</h2>
304+
{graphviz}
305+
{details}
306+
</div>
307+
308+
309+
var lastMove
310+
function onPanningMove(evt) {
311+
var pos = lively.getPosition(evt)
312+
var delta = pos.subPt(lastMove)
313+
pane.scrollTop -= delta.y
314+
pane.scrollLeft -= delta.x
315+
lastMove = pos
270316

271-
var div = document.createElement("div")
272-
div.id = "root"
273-
div.appendChild(style)
274-
div.appendChild(graphviz)
275-
div.appendChild(details)
276-
return div
317+
}
318+
319+
pane.addEventListener("pointerdown", evt => {
320+
if (evt.ctrlKey) {
321+
lastMove = lively.getPosition(evt)
322+
lively.addEventListener("changegraph", document.body.parentElement, "pointermove", evt => onPanningMove(evt))
323+
lively.addEventListener("changegraph", document.body.parentElement, "pointerup", evt => {
324+
lively.removeEventListener("changegraph", document.body.parentElement)
325+
})
326+
evt.stopPropagation()
327+
evt.preventDefault()
328+
}
329+
}, true)
330+
331+
332+
return pane
277333
}
278334
}
279335
ChangesGraph.create(this)

src/client/viewnav.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ export default class ViewNav {
5959
onPointerDown(evt) {
6060
if (!evt.ctrlKey || evt.button != 0)
6161
return;
62-
62+
63+
64+
debugger
6365
this.targetContainer = evt.composedPath().find(ea => {
6466
return ea.tagName == "LIVELY-CONTAINER"
6567
})

src/components/d3/graphviz-dot.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
</style>
1616
<div id="container">
17-
<div id="graph">
18-
</div>
17+
<div id="graph"></div>
1918
</div>
2019
</template>

0 commit comments

Comments
 (0)