Thomas Vachuska

Added GUI to intent perf app to monitor performance stats in real-time.

Fixed app ids for metrics app.

Change-Id: Icea99991ad71c80c53a832c236dcc05fefbb9b02
...@@ -65,7 +65,7 @@ public class IntentPerfCollector { ...@@ -65,7 +65,7 @@ public class IntentPerfCollector {
65 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 65 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
66 protected ClusterService clusterService; 66 protected ClusterService clusterService;
67 67
68 - @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY) 68 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
69 protected IntentPerfUi ui; 69 protected IntentPerfUi ui;
70 70
71 // Auxiliary structures used to accrue data for normalized time interval 71 // Auxiliary structures used to accrue data for normalized time interval
...@@ -99,6 +99,7 @@ public class IntentPerfCollector { ...@@ -99,6 +99,7 @@ public class IntentPerfCollector {
99 nodeToIndex.put(nodes[i].id(), i); 99 nodeToIndex.put(nodes[i].id(), i);
100 } 100 }
101 101
102 + ui.setHeaders(getSampleHeaders());
102 clearSamples(); 103 clearSamples();
103 log.info("Started"); 104 log.info("Started");
104 } 105 }
......
...@@ -99,7 +99,7 @@ public class IntentPerfInstaller { ...@@ -99,7 +99,7 @@ public class IntentPerfInstaller {
99 private static final int DEFAULT_NUM_NEIGHBORS = 0; 99 private static final int DEFAULT_NUM_NEIGHBORS = 0;
100 100
101 private static final int START_DELAY = 5_000; // ms 101 private static final int START_DELAY = 5_000; // ms
102 - private static final int REPORT_PERIOD = 5_000; //ms 102 + private static final int REPORT_PERIOD = 1_000; //ms
103 103
104 private static final String START = "start"; 104 private static final String START = "start";
105 private static final String STOP = "stop"; 105 private static final String STOP = "stop";
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
15 */ 15 */
16 package org.onosproject.intentperf; 16 package org.onosproject.intentperf;
17 17
18 +import com.fasterxml.jackson.databind.node.ArrayNode;
18 import com.fasterxml.jackson.databind.node.ObjectNode; 19 import com.fasterxml.jackson.databind.node.ObjectNode;
19 import com.google.common.collect.ImmutableList; 20 import com.google.common.collect.ImmutableList;
20 import com.google.common.collect.ImmutableSet; 21 import com.google.common.collect.ImmutableSet;
...@@ -23,6 +24,7 @@ import org.apache.felix.scr.annotations.Component; ...@@ -23,6 +24,7 @@ import org.apache.felix.scr.annotations.Component;
23 import org.apache.felix.scr.annotations.Deactivate; 24 import org.apache.felix.scr.annotations.Deactivate;
24 import org.apache.felix.scr.annotations.Reference; 25 import org.apache.felix.scr.annotations.Reference;
25 import org.apache.felix.scr.annotations.ReferenceCardinality; 26 import org.apache.felix.scr.annotations.ReferenceCardinality;
27 +import org.apache.felix.scr.annotations.Service;
26 import org.onlab.osgi.ServiceDirectory; 28 import org.onlab.osgi.ServiceDirectory;
27 import org.onosproject.intentperf.IntentPerfCollector.Sample; 29 import org.onosproject.intentperf.IntentPerfCollector.Sample;
28 import org.onosproject.ui.UiConnection; 30 import org.onosproject.ui.UiConnection;
...@@ -34,14 +36,17 @@ import org.onosproject.ui.UiView; ...@@ -34,14 +36,17 @@ import org.onosproject.ui.UiView;
34 import java.util.Collection; 36 import java.util.Collection;
35 import java.util.HashSet; 37 import java.util.HashSet;
36 import java.util.List; 38 import java.util.List;
39 +import java.util.Random;
37 import java.util.Set; 40 import java.util.Set;
41 +import java.util.TimerTask;
38 42
39 import static java.util.Collections.synchronizedSet; 43 import static java.util.Collections.synchronizedSet;
40 44
41 /** 45 /**
42 * Mechanism to stream data to the GUI. 46 * Mechanism to stream data to the GUI.
43 */ 47 */
44 -@Component(immediate = true, enabled = false) 48 +@Component(immediate = true, enabled = true)
49 +@Service(value = IntentPerfUi.class)
45 public class IntentPerfUi { 50 public class IntentPerfUi {
46 51
47 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 52 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
...@@ -53,9 +58,25 @@ public class IntentPerfUi { ...@@ -53,9 +58,25 @@ public class IntentPerfUi {
53 private UiExtension uiExtension = new UiExtension(views, this::newHandlers, 58 private UiExtension uiExtension = new UiExtension(views, this::newHandlers,
54 getClass().getClassLoader()); 59 getClass().getClassLoader());
55 60
61 + private List<String> headers = ImmutableList.of("One", "Two", "Three", "Four", "Five");
62 +
63 + private Random random = new Random();
64 + private TimerTask task;
65 +
56 @Activate 66 @Activate
57 protected void activate() { 67 protected void activate() {
58 uiExtensionService.register(uiExtension); 68 uiExtensionService.register(uiExtension);
69 +// task = new TimerTask() {
70 +// @Override
71 +// public void run() {
72 +// Sample sample = new Sample(System.currentTimeMillis(), headers.size());
73 +// for (int i = 0; i < headers.size(); i++) {
74 +// sample.data[i] = 25_000 + random.nextInt(20_000) - 5_000;
75 +// }
76 +// reportSample(sample);
77 +// }
78 +// };
79 +// SharedExecutors.getTimer().scheduleAtFixedRate(task, 1000, 1000);
59 } 80 }
60 81
61 @Deactivate 82 @Deactivate
...@@ -74,6 +95,15 @@ public class IntentPerfUi { ...@@ -74,6 +95,15 @@ public class IntentPerfUi {
74 } 95 }
75 } 96 }
76 97
98 + /**
99 + * Sets the headers for the subsequently reported samples.
100 + *
101 + * @param headers list of headers for future samples
102 + */
103 + public void setHeaders(List<String> headers) {
104 + this.headers = headers;
105 + }
106 +
77 // Creates and returns session specific message handler. 107 // Creates and returns session specific message handler.
78 private Collection<UiMessageHandler> newHandlers() { 108 private Collection<UiMessageHandler> newHandlers() {
79 return ImmutableList.of(new StreamingControl()); 109 return ImmutableList.of(new StreamingControl());
...@@ -90,7 +120,22 @@ public class IntentPerfUi { ...@@ -90,7 +120,22 @@ public class IntentPerfUi {
90 120
91 @Override 121 @Override
92 public void process(ObjectNode message) { 122 public void process(ObjectNode message) {
93 - streamingEnabled = message.path("event").asText("unknown").equals("initPerfStart"); 123 + streamingEnabled = message.path("event").asText("unknown").equals("intentPerfStart");
124 + if (streamingEnabled) {
125 + sendHeaders();
126 + }
127 + }
128 +
129 + private void sendHeaders() {
130 + ArrayNode an = mapper.createArrayNode();
131 + for (String header : headers) {
132 + an.add(header);
133 + }
134 +
135 + ObjectNode sn = mapper.createObjectNode();
136 + sn.set("headers", an);
137 +
138 + connection().sendMessage("intentPerfHeaders", 0, sn);
94 } 139 }
95 140
96 @Override 141 @Override
...@@ -106,10 +151,18 @@ public class IntentPerfUi { ...@@ -106,10 +151,18 @@ public class IntentPerfUi {
106 } 151 }
107 152
108 private void send(Sample sample) { 153 private void send(Sample sample) {
109 - // FIXME: finish this 154 + if (streamingEnabled) {
110 - ObjectNode sn = mapper.createObjectNode() 155 + ArrayNode an = mapper.createArrayNode();
111 - .put("time", sample.time); 156 + for (double d : sample.data) {
112 - connection().sendMessage("intentPerf", 0, sn); 157 + an.add(d);
158 + }
159 +
160 + ObjectNode sn = mapper.createObjectNode();
161 + sn.put("time", sample.time);
162 + sn.set("data", an);
163 +
164 + connection().sendMessage("intentPerfSample", 0, sn);
165 + }
113 } 166 }
114 } 167 }
115 168
......
...@@ -18,39 +18,35 @@ ...@@ -18,39 +18,35 @@
18 ONOS GUI -- Intent Perf View -- CSS file 18 ONOS GUI -- Intent Perf View -- CSS file
19 */ 19 */
20 20
21 -.light #ov-intentPerf { 21 +svg {
22 - color: navy; 22 + font: 12px sans-serif;
23 } 23 }
24 24
25 -.dark #ov-intentPerf { 25 +.line {
26 - color: #1e5e6f; 26 + fill: none;
27 -} 27 + stroke: #000;
28 - 28 + stroke-width: 2px;
29 -.dark a {
30 - color: #88c;
31 -}
32 -
33 -#ov-intentPerf .msg {
34 - color: darkorange;
35 -}
36 -
37 -.light #ov-intentPerf .msg {
38 - color: darkorange;
39 -}
40 -
41 -.dark #ov-intentPerf .msg {
42 - color: #904e00;
43 } 29 }
44 30
45 -
46 -
47 .axis path, 31 .axis path,
48 .axis line { 32 .axis line {
49 fill: none; 33 fill: none;
50 - stroke: #000; 34 + stroke-width: 2px;
51 shape-rendering: crispEdges; 35 shape-rendering: crispEdges;
52 } 36 }
53 37
54 -.browser text { 38 +.light .axis path,
55 - text-anchor: end; 39 +.light .axis line,
40 +.light .axis text {
41 + stroke: #999;
42 +}
43 +
44 +.dark .axis path,
45 +.dark .axis line,
46 +.dark .axis text {
47 + stroke: #eee;
48 +}
49 +
50 +.axis text {
51 + stroke-width: 0.3;
56 } 52 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -15,10 +15,12 @@ ...@@ -15,10 +15,12 @@
15 --> 15 -->
16 16
17 <!-- Intent Performance partial HTML --> 17 <!-- Intent Performance partial HTML -->
18 -<div id="ov-sample"> 18 +<div id="ov-intentPerf">
19 <h2> Intent Performance View </h2> 19 <h2> Intent Performance View </h2>
20 20
21 - <span class="msg">{{ ctrl.message }}</span> 21 + <div id="intent-perf-chart"
22 - 22 + resize
23 - <div id="intent-perf-chart"></div> 23 + ng-style="resizeWithOffset(56, 12)"
24 + notifier="ctrl.notifyResize()">
25 + </div>
24 </div> 26 </div>
......
...@@ -21,122 +21,248 @@ ...@@ -21,122 +21,248 @@
21 'use strict'; 21 'use strict';
22 22
23 // injected refs 23 // injected refs
24 - var $log, tbs, flash; 24 + var $log, tbs, ts, wss, sus, flash, fs, mast;
25 25
26 - function start() { 26 + // internal state
27 - //var format = d3.time.format("%m/%d/%y"); 27 + var handlerMap,
28 - var format = d3.time.format("%H:%M:%S"); 28 + openListener,
29 - var samples = []; 29 + theSample = [],
30 - 30 + graph;
31 - var margin = {top: 20, right: 30, bottom: 30, left: 40},
32 - width = 960 - margin.left - margin.right,
33 - height = 500 - margin.top - margin.bottom;
34 31
35 - var x = d3.time.scale() 32 + // ==========================
36 - .range([0, width]);
37 33
38 - var y = d3.scale.linear() 34 + function createGraph(h) {
39 - .range([height, 0]); 35 + var stopped = false,
36 + n = 243,
37 + duration = 750,
38 + now = new Date(Date.now() - duration),
39 + headers = h,
40 + data = [];
40 41
41 - var z = d3.scale.category20c(); 42 + var dim = fs.windowSize(mast.mastHeight());
43 + var margin, width, height, x, y;
44 + var svg, axis;
42 45
43 - var xAxis = d3.svg.axis() 46 + var lines = [],
44 - .scale(x) 47 + paths = [];
45 - .orient("bottom")
46 - .ticks(d3.time.seconds);
47 48
48 - var yAxis = d3.svg.axis() 49 + var transition = d3.select({}).transition()
49 - .scale(y) 50 + .duration(duration)
50 - .orient("left"); 51 + .ease("linear");
51 52
52 - var stack = d3.layout.stack() 53 + svg = d3.select("#intent-perf-chart").append("p")
53 - .offset("zero") 54 + .append("svg").attr("id", "intent-perf-svg")
54 - .values(function(d) { return d.values; }) 55 + .append("g").attr("id", "intent-perf-svg-g");
55 - .x(function(d) { return d.date; })
56 - .y(function(d) { return d.value; });
57 56
58 - var nest = d3.nest() 57 + svg.append("defs").append("clipPath").attr("id", "intent-perf-clip")
59 - .key(function(d) { return d.key; }); 58 + .append("rect");
60 -
61 - var area = d3.svg.area()
62 - .interpolate("cardinal")
63 - .x(function(d) { return x(d.date); })
64 - .y0(function(d) { return y(d.y0); })
65 - .y1(function(d) { return y(d.y0 + d.y); });
66 -
67 - var svg = d3.select("body").append("svg")
68 - .attr("width", width + margin.left + margin.right)
69 - .attr("height", height + margin.top + margin.bottom)
70 - .append("g")
71 - .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
72 59
73 - svg.append("g") 60 + axis = svg.append("g")
74 .attr("class", "x axis") 61 .attr("class", "x axis")
75 - .attr("transform", "translate(0," + height + ")") 62 + .attr("id", "intent-perf-x");
76 - .call(xAxis); 63 +
64 + svg.append("g").attr("class", "y axis")
65 + .attr("id", "intent-perf-yl");
77 66
78 svg.append("g") 67 svg.append("g")
79 .attr("class", "y axis") 68 .attr("class", "y axis")
80 - .call(yAxis); 69 + .attr("id", "intent-perf-yr");
70 +
71 + resize(dim);
72 +
73 + headers.forEach(function (h, li) {
74 + // Prime the data to match the headeres and zero it out.
75 + data[li] = d3.range(n).map(function() { return 0 });
76 + theSample[li] = 0;
77 +
78 + // Create the lines
79 + lines[li] = d3.svg.line()
80 + .interpolate("basis")
81 + .x(function(d, i) { return x(now - (n - 1 - i) * duration); })
82 + .y(function(d, i) { return y(d); });
81 83
82 - function fetchData() { 84 + // Create the SVG paths
83 - d3.csv("app/view/intentPerf/data.csv", function (data) { 85 + paths[li] = svg.append("g")
84 - samples = data; 86 + .attr("clip-path", "url(#intent-perf-clip)")
85 - updateGraph(); 87 + .append("path")
88 + .datum(function () { return data[li]; })
89 + .attr("id", "line" + li)
90 + .style("stroke", lineColor(li))
91 + .attr("class", "line");
86 }); 92 });
93 +
94 + function lineColor(li) {
95 + return li < headers.length - 1 ?
96 + sus.cat7().getColor(li, false, ts.theme()) :
97 + ts.theme() == 'light' ? '#333' : '#eee';
98 + }
99 +
100 + function tick() {
101 + if (stopped) {
102 + return;
103 + }
104 +
105 + transition = transition.each(function() {
106 + // update the domains
107 + now = new Date();
108 + x.domain([now - (n - 2) * duration, now - duration]);
109 +
110 + data.forEach(function (d, li) {
111 + // push the new most recent sample onto the back
112 + d.push(theSample[li]);
113 +
114 + // redraw the line and slide it left
115 + paths[li].attr("d", lines[li]).attr("transform", null);
116 + paths[li].transition().attr("transform", "translate(" + x(now - (n - 1) * duration) + ")");
117 +
118 + // pop the old data point off the front
119 + d.shift();
120 + });
121 +
122 + // slide the x-axis left
123 + axis.call(x.axis);
124 + }).transition().each("start", tick);
87 } 125 }
88 126
89 - function updateGraph() { 127 + function start() {
90 - samples.forEach(function(d) { 128 + stopped = false;
91 - d.date = format.parse(d.date); 129 + headers.forEach(function (h, li) {
92 - d.value = +d.value; 130 + theSample[li] = 0;
93 }); 131 });
132 + tick();
133 + }
134 +
135 + function stop() {
136 + stopped = true;
137 + }
94 138
95 - var layers = stack(nest.entries(samples)); 139 + function resize(dim) {
140 + margin = {top: 20, right: 90, bottom: 20, left: 70};
141 + width = dim.width - margin.right - margin.left;
142 + height = 480 - margin.top - margin.bottom;
96 143
97 - x.domain(d3.extent(samples, function(d) { return d.date; })); 144 + x = d3.time.scale()
98 - y.domain([0, d3.max(samples, function(d) { return d.y0 + d.y; })]); 145 + .domain([now - (n - 2) * duration, now - duration])
146 + .range([0, width]);
99 147
100 - svg.selectAll(".layer") 148 + y = d3.scale.linear()
101 - .data(layers) 149 + .domain([0, 200000])
102 - .enter().append("path") 150 + .range([height, 0]);
103 - .attr("class", "layer")
104 - .attr("d", function(d) { return area(d.values); })
105 - .style("fill", function(d, i) { return z(i); });
106 151
107 - svg.select(".x") 152 + d3.select("#intent-perf-svg")
153 + .attr("width", width + margin.left + margin.right)
154 + .attr("height", height + margin.top + margin.bottom);
155 + d3.select("#intent-perf-svg-g")
156 + .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
157 +
158 + d3.select("#intent-pef-clip rect").attr("width", width).attr("height", height);
159 +
160 + d3.select("#intent-perf-x")
108 .attr("transform", "translate(0," + height + ")") 161 .attr("transform", "translate(0," + height + ")")
109 - .call(xAxis); 162 + .call(x.axis = d3.svg.axis().scale(x).orient("bottom"));
163 +
164 + d3.select("#intent-perf-yl")
165 + .call(d3.svg.axis().scale(y).orient("left"))
166 + d3.select("#intent-perf-yr")
167 + .attr("transform", "translate(" + width + " ,0)")
168 + .call(d3.svg.axis().scale(y).orient("right"))
169 + }
170 +
171 + return {
172 + start: start,
173 + stop: stop,
174 + resize: resize
175 + };
176 + }
110 177
111 - svg.select(".y")
112 - .call(yAxis);
113 178
114 - console.log('tick'); 179 + function wsOpen(host, url) {
180 + $log.debug('IntentPerf: web socket open - cluster node:', host, 'URL:', url);
181 + // Request batch of initial data from the new server
182 + wss.sendEvent('intentPerfStart');
115 } 183 }
184 +
185 + function createAndInitGraph(d) {
186 + if (!graph) {
187 + d.headers.push("total");
188 + graph = createGraph(d.headers);
189 + }
190 + graph.start();
116 } 191 }
117 192
118 - start(); 193 + function graphResized(dim) {
194 + $log.info("Resized: " + dim.width + "x" + dim.height);
195 + graph.resize(dim);
196 + }
197 +
198 + function recordSample(sample) {
199 + var total = 0;
200 + sample.data.forEach(function (d, i) {
201 + theSample[i] = d;
202 + total = total + d;
203 + });
204 + theSample[sample.data.length] = total;
205 + }
206 +
207 + function createHandlerMap() {
208 + handlerMap = {
209 + intentPerfHeaders: createAndInitGraph,
210 + intentPerfSample: recordSample
211 + };
212 + }
213 +
214 + //setInterval(function () { theSample = samples[++cs]; }, 5000);
215 + //createGraph();
119 216
120 // define the controller 217 // define the controller
121 218
122 angular.module('ovIntentPerf', ['onosUtil']) 219 angular.module('ovIntentPerf', ['onosUtil'])
123 .controller('OvIntentPerfCtrl', 220 .controller('OvIntentPerfCtrl',
124 - ['$scope', '$log', 'ToolbarService', 'FlashService', 221 + ['$scope', '$log', 'ToolbarService', 'WebSocketService',
222 + 'ThemeService', 'FlashService', 'SvgUtilService', 'FnService',
223 + 'MastService',
125 224
126 - function ($scope, _$log_, _tbs_, _flash_) { 225 + function ($scope, _$log_, _tbs_, _wss_, _ts_, _flash_, _sus_, _fs_, _mast_) {
127 - var self = this 226 + var self = this;
128 227
129 $log = _$log_; 228 $log = _$log_;
130 tbs = _tbs_; 229 tbs = _tbs_;
230 + wss = _wss_;
231 + ts = _ts_;
131 flash = _flash_; 232 flash = _flash_;
233 + sus = _sus_;
234 + fs = _fs_;
235 + mast = _mast_;
132 236
133 - self.message = 'Hey there dudes!'; 237 + createHandlerMap();
134 - start();
135 238
136 - // Clean up on destroyed scope 239 + self.notifyResize = function () {
240 + graphResized(fs.windowSize(mast.mastHeight()));
241 + };
242 +
243 + function start() {
244 + openListener = wss.addOpenListener(wsOpen);
245 + wss.bindHandlers(handlerMap);
246 + wss.sendEvent('intentPerfStart');
247 + $log.debug('intentPerf comms started');
248 + }
249 +
250 + function stop() {
251 + graph.stop();
252 + wss.sendEvent('intentPerfStop');
253 + wss.unbindHandlers(handlerMap);
254 + wss.removeOpenListener(openListener);
255 + openListener = null;
256 + graph = null;
257 + $log.debug('intentPerf comms stopped');
258 + }
259 +
260 + // Cleanup on destroyed scope..
137 $scope.$on('$destroy', function () { 261 $scope.$on('$destroy', function () {
262 + $log.log('OvIntentPerfCtrl is saying Buh-Bye!');
263 + stop();
138 }); 264 });
139 265
140 - $log.log('OvIntentPerfCtrl has been created'); 266 + start();
141 }]); 267 }]);
142 }()); 268 }());
......
...@@ -18,8 +18,6 @@ ...@@ -18,8 +18,6 @@
18 <head> 18 <head>
19 <title>Dev View</title> 19 <title>Dev View</title>
20 <script src="tp/d3.min.js"></script> 20 <script src="tp/d3.min.js"></script>
21 - <script src="tp/jquery-2.1.1.min.js"></script>
22 -
23 <link rel="stylesheet" href="app/view/intentPerf/intentPerf.css"> 21 <link rel="stylesheet" href="app/view/intentPerf/intentPerf.css">
24 </head> 22 </head>
25 <body> 23 <body>
......
1 +<!DOCTYPE html>
2 +<meta charset="utf-8">
3 +<style>
4 +
5 + svg {
6 + font: 10px sans-serif;
7 + }
8 +
9 + .line {
10 + fill: none;
11 + stroke: darkgreen;
12 + stroke-width: 2px;
13 + }
14 +
15 + .axis path,
16 + .axis line {
17 + fill: none;
18 + stroke: #999;
19 + stroke-width: 2px;
20 + shape-rendering: crispEdges;
21 + }
22 +
23 +</style>
24 +<body>
25 +<script src="http://d3js.org/d3.v3.min.js"></script>
26 +<script>
27 + (function () {
28 + var cs = 0,
29 + samples = [
30 + 89.53,
31 + 37515.81,
32 + 104609.6,
33 + 113105.11,
34 + 103194.74,
35 + 122151.63,
36 + 128623.9,
37 + 137325.33,
38 + 154897.31,
39 + 161235.07,
40 + 162025.4,
41 + 164902.64,
42 + 158196.26,
43 + 161072.44,
44 + 160792.54,
45 + 164692.44,
46 + 161979.74,
47 + 162137.4,
48 + 159325.19,
49 + 170465.44,
50 + 168186.46,
51 + 171152.34,
52 + 168221.02,
53 + 167440.73,
54 + 165003.39,
55 + 166855.18,
56 + 157268.79,
57 + 164087.54,
58 + 162265.21,
59 + 165990.16,
60 + 176364.01,
61 + 172064.07,
62 + 184872.24,
63 + 183249.8,
64 + 182282.47,
65 + 171475.11,
66 + 158880.58,
67 + 166016.69,
68 + 168233.16,
69 + 177759.92,
70 + 179742.87,
71 + 170819.44,
72 + 167577.73,
73 + 169479.9,
74 + 175544.89,
75 + 183792.01,
76 + 184689.52,
77 + 178503.87,
78 + 173219.27,
79 + 179085.49,
80 + 179700.54,
81 + 174281.17,
82 + 181353.08,
83 + 180173.14,
84 + 184093.16,
85 + 186011.5,
86 + 176952.79,
87 + 175319.2,
88 + 169001.05,
89 + 174545.12,
90 + 169156.29,
91 + 171804.3,
92 + 159155.54,
93 + 154709.96,
94 + 157263.97
95 + ],
96 + theSample,
97 + headers = [ "Whole", "Half", "Third" ];
98 +
99 + var n = 243,
100 + duration = 750,
101 + now = new Date(Date.now() - duration),
102 + data = [];
103 +
104 + headers.forEach(function (d, li) {
105 + data[li] = d3.range(n).map(function () { return 0; });
106 + });
107 +
108 + var margin = {top: 20, right: 100, bottom: 20, left: 100},
109 + width = 960 - margin.right,
110 + height = 512 - margin.top - margin.bottom;
111 +
112 + var x = d3.time.scale()
113 + .domain([now - (n - 2) * duration, now - duration])
114 + .range([0, width]);
115 +
116 + var y = d3.scale.linear()
117 + .domain([0, 200000])
118 + .range([height, 0]);
119 +
120 + var svg = d3.select("body").append("p").append("svg")
121 + .attr("width", width + margin.left + margin.right)
122 + .attr("height", height + margin.top + margin.bottom)
123 + .append("g")
124 + .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
125 +
126 + svg.append("defs").append("clipPath")
127 + .attr("id", "clip")
128 + .append("rect")
129 + .attr("width", width)
130 + .attr("height", height);
131 +
132 + var axis = svg.append("g")
133 + .attr("class", "x axis")
134 + .attr("transform", "translate(0," + height + ")")
135 + .call(x.axis = d3.svg.axis().scale(x).orient("bottom"));
136 +
137 + svg.append("g")
138 + .attr("class", "y axis")
139 + .call(d3.svg.axis().scale(y).orient("left"));
140 +
141 + svg.append("g")
142 + .attr("class", "y axis")
143 + .attr("transform", "translate(" + width + " ,0)")
144 + .call(d3.svg.axis().scale(y).orient("right"));
145 +
146 + var lines = [], paths = [];
147 + data.forEach(function (p, li) {
148 + lines[li]= d3.svg.line()
149 + .interpolate("basis")
150 + .x(function (d, i) {
151 + return x(now - (n - 1 - i) * duration);
152 + })
153 + .y(function (d, i) {
154 + return y(d);
155 + });
156 +
157 + paths[li] = svg.append("g")
158 + .attr("clip-path", "url(#clip)")
159 + .append("path")
160 + .datum(function () { return data[li]; })
161 + .attr("id", "line" + li)
162 + .attr("class", "line");
163 + });
164 +
165 + var transition = d3.select({}).transition()
166 + .duration(750)
167 + .ease("linear");
168 +
169 + function tick() {
170 + transition = transition.each(function () {
171 + // update the domains
172 + now = new Date();
173 + x.domain([now - (n - 2) * duration, now - duration]);
174 +
175 + data.forEach(function (d, li) {
176 + // push the new most recent sample onto the back
177 + d.push(theSample[li]);
178 +
179 + // redraw the line and slide it left
180 + paths[li].attr("d", lines[li]).attr("transform", null);
181 + paths[li].transition().attr("transform", "translate(" + x(now - (n - 1) * duration) + ")");
182 +
183 + // pop the old data point off the front
184 + d.shift();
185 + });
186 +
187 + // slide the x-axis left
188 + axis.call(x.axis);
189 +
190 + }).transition().each("start", tick);
191 + }
192 +
193 + function setSample() {
194 + var v = samples[cs++];
195 + theSample = [ v, v/2, v/3 ];
196 + }
197 +
198 + setSample();
199 + setInterval(setSample, 1000);
200 + tick();
201 +
202 + })()
203 +</script>
204 +</body>
205 +</html>
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
21 /* ------ for summary-list tables ------ */ 21 /* ------ for summary-list tables ------ */
22 22
23 table.summary-list { 23 table.summary-list {
24 - margin: 0 50px 50px 50px; 24 + margin: 0 20px 16px 12px;
25 font-size: 10pt; 25 font-size: 10pt;
26 border-spacing: 0; 26 border-spacing: 0;
27 } 27 }
......
...@@ -48,6 +48,10 @@ body.dark { ...@@ -48,6 +48,10 @@ body.dark {
48 height: 100%; 48 height: 100%;
49 } 49 }
50 50
51 +#view h2 {
52 + padding-left: 12px;
53 +}
54 +
51 .light #view h2 { 55 .light #view h2 {
52 color: #800; 56 color: #800;
53 } 57 }
......