-
Notifications
You must be signed in to change notification settings - Fork 0
/
graph.js
156 lines (131 loc) · 4.52 KB
/
graph.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
'use strict'
const d3 = require('d3')
const CIRCLE_RADIUS = 30
const H_PADDING = 20
const V_PADDING = 60
const CIRCLE_DIAMETER = CIRCLE_RADIUS * 2
const FONT_SIZE = "10pt"
function xOffset(nodeIndex) {
return (nodeIndex * (CIRCLE_DIAMETER + H_PADDING)) + H_PADDING + CIRCLE_RADIUS
}
function yOffset(layerIndex) {
return (layerIndex * (CIRCLE_DIAMETER + V_PADDING)) + V_PADDING + CIRCLE_RADIUS
}
function lineColor(weight) {
let hue = weight < 0 ? 0 : 120
let lightness = Math.round(50 + Math.abs(weight) * 50)
return `hsl(${hue}, 100%, ${lightness}%)`
}
class Graph {
constructor({logicCenter}) {
this.nodeArray = logicCenter.nodeArray
this.links = getLinks(this.nodeArray)
this.svg = d3.select("svg")
this.layers = logicCenter.layers
// Preprocess the nodes so they have their layer
this.layers.forEach((layer, layerNum) => {
layer.forEach((node, offsetInLayer) => {
node.layerNum = layerNum
node.offsetInLayer = offsetInLayer
})
})
}
render() {
let layerGroups = this.svg.selectAll(".layer-groups")
.data(this.layers)
layerGroups.enter()
.append("g")
.attr("id", (d, i) => `layer-${i}`)
.classed("layer-groups", true)
let nodeGroups = layerGroups.selectAll(".node-groups")
.data(d => d)
let nodeEnter = nodeGroups.enter()
.append("g")
.attr("id", d => `node-${d.layerNum}-${d.offsetInLayer}`)
.classed("node-groups", true)
nodeEnter.append("circle")
.classed("node", true)
.attr("r", CIRCLE_RADIUS)
.attr("cx", d => xOffset(d.offsetInLayer))
.attr("cy", d => yOffset(d.layerNum))
.merge(nodeGroups.select("circle"))
.attr("fill", d => determineFill(d))
nodeEnter.append("text")
.classed("node-text", true)
.attr("x", d => xOffset(d.offsetInLayer))
.attr("y", d => yOffset(d.layerNum))
.attr("font-size", FONT_SIZE)
.attr("text-anchor", "middle")
.attr("dy", "0.3em")
.text(d => d.name)
.merge(nodeGroups.select("text"))
.attr("fill", d => determineText(d))
// Making links
let linkGraph = this.svg.selectAll('.links')
.data(this.links)
let linkEnter = linkGraph.enter()
// Links from one layer to another
let lines = linkEnter.filter(d => d.source !== d.target)
.append("line")
.classed("links", true)
.attr("id", d => `from-${d.source.name}-to-${d.target.name}`)
.attr("x1", d => xOffset(d.source.offsetInLayer))
.attr("y1", d => yOffset(d.source.layerNum) + CIRCLE_RADIUS)
.attr("x2", d => xOffset(d.target.offsetInLayer))
.attr("y2", d => yOffset(d.target.layerNum) - CIRCLE_RADIUS)
// Need to handle self-links differently
let paths = linkEnter.filter(d => d.source === d.target)
.append("path")
.classed("links", true)
.attr("d", d => {
let x = xOffset(d.source.offsetInLayer) + CIRCLE_RADIUS
let y = yOffset(d.source.layerNum)
let c1x = x + CIRCLE_RADIUS * 0.75
let c1y = y - CIRCLE_RADIUS * 0.75
let c2x = x + CIRCLE_RADIUS * 0.75
let c2y = y + CIRCLE_RADIUS * 0.75
return `M ${x},${y} C${c1x},${c1y} ${c2x},${c2y} ${x},${y}`
})
// Update the stroke on everything the same
linkGraph.merge(paths).merge(lines)
.attr("stroke", d => lineColor(calcWeight(d.source, d.target)))
}
}
function wrapSecret(secretColor) {
return d => '#' + secretColor(d).toString(16)
}
function determineFill({active, isRewarding, isPunishing, stateful}) {
if (isRewarding) {
return active ? '#1010ff' : '#10107f'
} else if (isPunishing) {
return active ? '#ff0000' : '#7f0000'
} else if (stateful) {
return active ? '#00ff00' : '#007f00'
} else {
return active ? '#ffffff' : '#000000'
}
}
function determineText({active, isRewarding, isPunishing, stateful}) {
if (isRewarding || isPunishing) {
return '#ffffff'
} else {
return active ? '#000000' : '#ffffff'
}
}
function calcWeight(parent, child) {
let currentProb = child.prob()
// Calculate what this link would do if it were the opposite
// activity
let hypotheticalActives = child.parents
.map(p => p === parent ? !p.active : p.active)
let alternativeProb = child.hypothetical(hypotheticalActives)
return currentProb - alternativeProb
}
function getLinks(nodes) {
return nodes.reduce((sum, parent) =>
sum.concat(parent.children.map(child => ({
source: parent,
target: child,
}))), [])
}
exports.Graph = Graph