-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
205 lines (186 loc) · 9.58 KB
/
index.html
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
<!doctype html>
<html>
<head>
<title>Ladder Diagram Test</title>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<!--link rel="stylesheet" href="../site.css" /-->
<style> /*http://colorbrewer2.org/#type=sequential&scheme=YlGnBu&n=6*/
#legs rect { stroke: none; }
#legs text {
fill: #888;
stroke: none;
font-family: sans-serif;
font-weight: bold;
}
.technician { fill: #ffffcc; }
.mechanic { fill: #c7e9b4; }
.engineer { fill: #7fcdbb; }
.supervisor { fill: #41b6c4; }
.representative { fill: #2c7fb8; }
.customer { fill: #253494; }
.user { fill: lightslategrey; }
#legs g.user text { fill: darkslategrey; }
.agent { fill: darkslategrey; }
#legs g.agent text { fill: lightslategrey; }
#rungs path { stroke: none; }
.request { fill: goldenrod; }
.accept { fill: green; }
.reject { fill: red; }
.msg { fill: royalblue; }
.reply { fill: red; }
.session { fill: rebeccapurple; }
.readout {
font-size: 15px;
font-family: sans-serif;
background-color: white;
border-radius: 15px;
padding: 15px;
overflow: hidden;
max-width: 200px;
max-height: 200px;
}
p {
position: relative;
width: 80%;
left: 10%
}
svg {
position: relative;
width: 60%;
left: 20%
}
</style>
</head>
<body>
<p>A simple D3 ladder diagram component which plots Messages between different Sources on a timeline.
Sources must have 'id' and 'class' attributes. Messages must have 'time', 'class',
'source', and 'target' attributes. Class attributes are added to SVG elements so they can be
styled with CSS. Optionally, messages can be displayed in a popup on hover.
</p>
<p>
This updating example is supposed to simulate an undisciplined chain of command. Yellow
represents requests, while green and red are yes and no's respectively. You'll notice
employees occasionally go over their bosses heads. For the sources I used a color scheme from
<a target="_base" href="http://colorbrewer2.org/#type=sequential&scheme=YlGnBu&n=6">Cynthia Brewer</a>,
which really wasn't meant to be used with my dingy traffic light colors.
</p>
<svg id="chain" viewbox="0 0 800 320" preserveAspectRatio="xMidYMid meet"></svg>
<p>
The second dataset represents a
<a href="https://en.wikipedia.org/wiki/Session_Initiation_Protocol">SIP call flow</a>.
Hover over ladder rungs for more information. Also, the proportions of the graphic are configurable.
Scroll wheel while mousing over the Source Labels to alter the look.
</p>
<svg id="sip" viewbox="0 0 600 300" preserveAspectRatio="xMidYMid meet"></svg>
<p>
I still need to add source reordering and zoom controls. I was also toying with the idea
of adding small plots or horizon charts on each sourceline- though that might make
it too busy to be useful...
</p>
<script src="https://d3js.org/d3.v4.js"></script>
<script type="module">
import Ladder from './ladder.js';
import Readout from './readout.js';
let readout = Readout();
let sipsvg = d3.selectAll('svg#sip');
let sip = Ladder( sipsvg, 'sip', 0, 0, 600, 300, readout )
.sources( [
{id:'Alice', class:'user'},
{id:'Server', class:'agent'},
{id:'Proxy', class:'agent'},
{id:'Boris', class:'user'}
] )
.events( [
{time:40, source:'Alice', target:'Server', class:'msg Invite'},
{time:80, source:'Server', target:'Alice', class:'reply Moved(302)'},
{time:120, source:'Alice', target:'Server', class:'msg Ack'},
{time:160, source:'Alice', target:'Proxy', class:'msg Invite'},
{time:200, source:'Proxy', target:'Boris', class:'msg Invite'},
{time:240, source:'Proxy', target:'Alice', class:'reply Trying'},
{time:280, source:'Boris', target:'Proxy', class:'reply Ringing'},
{time:320, source:'Proxy', target:'Alice', class:'reply Ringing'},
{time:360, source:'Boris', target:'Proxy', class:'reply OK'},
{time:400, source:'Proxy', target:'Alice', class:'reply OK'},
{time:440, source:'Alice', target:'Proxy', class:'msg Ack'},
{time:480, source:'Proxy', target:'Boris', class:'msg Ack'},
{time:520, source:'Alice', target:'Boris', class:'session Media(RTP)'},
{time:560, source:'Boris', target:'Proxy', class:'msg Bye'},
{time:600, source:'Proxy', target:'Alice', class:'msg Bye'},
{time:640, source:'Alice', target:'Proxy', class:'reply OK(200)'},
{time:680, source:'Proxy', target:'Boris', class:'reply OK(200)'}
] )
.interval( 700 );
sip();
const sources = [
{ id:'Alice', class:"technician" },
{ id:'Bob', class:"mechanic" },
{ id:'Caleb', class:"engineer" },
{ id:'Donna', class:"supervisor" },
{ id:'Eric', class:"representative" },
{ id:'Fanny', class:"customer" }
];
let events = [
{time:0, class: 'request', source: 'Alice', target: 'Bob', origin:'Alice'}
// {time:0, class: 'request', source:'Fanny', target: 'Donna'},
// {time:1, class: 'request', source:'Fanny', target: 'Bob'},
// {time:2, class: 'accept', source:'Donna', target: 'Fanny'},
// {time:3, class: 'reject', source:'Bob', target: 'Fanny'}
];
let getSuperior = function ( name ) {
let n = sources.findIndex( function(d) {return d.id==name;} );
return (n!=-1 && n!=sources.length-1) ? sources[n+1] : null;
};
let getSubordinate = function( name ) {
let n = sources.findIndex( function(d) {return d.id==name;} );
return (n!=-1 && n!=0) ? sources[n-1] : null;
}
let svg = d3.selectAll('svg#chain');
let ladder = Ladder( svg, 'test', 0, 0, 800, 300, readout)
.sources( sources )
.events ( events )
.interval( 200 );
ladder();
// A little generator that simulates requests in a chain of command
d3.interval( function(elapsed) {
let last = events[events.length-1];
let event = null;
let t = last.time + 8 + Math.random()*8;//elapsed //d3.now();
// if a request hasn't been answered
if (last.class=='request') {
// half the time the superior knows the answer, Fanny always knows the answer
if (Math.random()<0.5 || last.target=='Fanny') {
// 3/4ths the time the answer is permissive
if(Math.random()<0.75)
event = { time:t, class:'accept', source:last.target, target:last.source, origin:last.origin };
else
event = { time:t, class:'reject', source:last.target, target:last.source, origin:last.origin };
}
// otherwise they pass it up the chain
else {
const superior = getSuperior( last.target );
event = { time:t, class:'request', source:last.target, target:superior.id, origin:last.origin };
}
// pass answers back down the chain of command
} else {
// if the originator hasn't recieved the answer, pass it to the subordinate
if (last.target!=last.origin) {
let subordinate = getSubordinate( last.target );
event = { time:t, class:last.class, source:last.target, target:subordinate.id, origin:last.origin};
}
// otherwise randomly generate a new request
else {
let r1 = Math.floor(Math.random()*(sources.length-1));
let origin = sources[ r1 ];
let r2 = r1 +1 + Math.floor(Math.random()*(sources.length-r1-1));
let superior = sources[ r2 ];
//let superior = getSuperior( origin.id );
event = { time:t, class:'request', source: origin.id, target:superior.id, origin:origin.id};
}
}
// add the message and update the diagram...
ladder.add( event );
ladder();
}, 1000);
</script>
</body>
</html>