GUI -- Fixed intent perf GUI styling.
Change-Id: I552d3a50f7f4dd5bb1df7115c15eb6a04f538378
Showing
8 changed files
with
96 additions
and
314 deletions
| ... | @@ -37,10 +37,8 @@ import java.util.HashMap; | ... | @@ -37,10 +37,8 @@ import java.util.HashMap; |
| 37 | import java.util.LinkedList; | 37 | import java.util.LinkedList; |
| 38 | import java.util.List; | 38 | import java.util.List; |
| 39 | import java.util.Map; | 39 | import java.util.Map; |
| 40 | -import java.util.concurrent.ExecutorService; | ||
| 41 | -import java.util.concurrent.Executors; | ||
| 42 | 40 | ||
| 43 | -import static org.onlab.util.Tools.groupedThreads; | 41 | +import static org.onlab.util.SharedExecutors.getPoolThreadExecutor; |
| 44 | import static org.slf4j.LoggerFactory.getLogger; | 42 | import static org.slf4j.LoggerFactory.getLogger; |
| 45 | 43 | ||
| 46 | /** | 44 | /** |
| ... | @@ -78,18 +76,13 @@ public class IntentPerfCollector { | ... | @@ -78,18 +76,13 @@ public class IntentPerfCollector { |
| 78 | private Map<NodeId, Integer> nodeToIndex; | 76 | private Map<NodeId, Integer> nodeToIndex; |
| 79 | 77 | ||
| 80 | private NodeId nodeId; | 78 | private NodeId nodeId; |
| 81 | - private ExecutorService messageHandlingExecutor; | ||
| 82 | 79 | ||
| 83 | @Activate | 80 | @Activate |
| 84 | public void activate() { | 81 | public void activate() { |
| 85 | nodeId = clusterService.getLocalNode().id(); | 82 | nodeId = clusterService.getLocalNode().id(); |
| 86 | 83 | ||
| 87 | - // TODO: replace with shared executor | ||
| 88 | - messageHandlingExecutor = Executors.newSingleThreadExecutor( | ||
| 89 | - groupedThreads("onos/perf", "message-handler")); | ||
| 90 | - | ||
| 91 | communicationService.addSubscriber(SAMPLE, new InternalSampleCollector(), | 84 | communicationService.addSubscriber(SAMPLE, new InternalSampleCollector(), |
| 92 | - messageHandlingExecutor); | 85 | + getPoolThreadExecutor()); |
| 93 | 86 | ||
| 94 | nodes = clusterService.getNodes().toArray(new ControllerNode[]{}); | 87 | nodes = clusterService.getNodes().toArray(new ControllerNode[]{}); |
| 95 | Arrays.sort(nodes, (a, b) -> a.id().toString().compareTo(b.id().toString())); | 88 | Arrays.sort(nodes, (a, b) -> a.id().toString().compareTo(b.id().toString())); |
| ... | @@ -99,14 +92,13 @@ public class IntentPerfCollector { | ... | @@ -99,14 +92,13 @@ public class IntentPerfCollector { |
| 99 | nodeToIndex.put(nodes[i].id(), i); | 92 | nodeToIndex.put(nodes[i].id(), i); |
| 100 | } | 93 | } |
| 101 | 94 | ||
| 102 | - ui.setHeaders(getSampleHeaders()); | ||
| 103 | clearSamples(); | 95 | clearSamples(); |
| 96 | + ui.setCollector(this); | ||
| 104 | log.info("Started"); | 97 | log.info("Started"); |
| 105 | } | 98 | } |
| 106 | 99 | ||
| 107 | @Deactivate | 100 | @Deactivate |
| 108 | public void deactivate() { | 101 | public void deactivate() { |
| 109 | - messageHandlingExecutor.shutdown(); | ||
| 110 | communicationService.removeSubscriber(SAMPLE); | 102 | communicationService.removeSubscriber(SAMPLE); |
| 111 | log.info("Stopped"); | 103 | log.info("Stopped"); |
| 112 | } | 104 | } | ... | ... |
| ... | @@ -156,7 +156,7 @@ public class IntentPerfInstaller { | ... | @@ -156,7 +156,7 @@ public class IntentPerfInstaller { |
| 156 | private ExecutorService workers; | 156 | private ExecutorService workers; |
| 157 | private ApplicationId appId; | 157 | private ApplicationId appId; |
| 158 | private Listener listener; | 158 | private Listener listener; |
| 159 | - private boolean stopped; | 159 | + private boolean stopped = true; |
| 160 | 160 | ||
| 161 | private Timer reportTimer; | 161 | private Timer reportTimer; |
| 162 | 162 | ||
| ... | @@ -247,14 +247,19 @@ public class IntentPerfInstaller { | ... | @@ -247,14 +247,19 @@ public class IntentPerfInstaller { |
| 247 | } | 247 | } |
| 248 | 248 | ||
| 249 | public void start() { | 249 | public void start() { |
| 250 | + if (stopped) { | ||
| 251 | + stopped = false; | ||
| 250 | communicationService.broadcast(new ClusterMessage(nodeId, CONTROL, START.getBytes())); | 252 | communicationService.broadcast(new ClusterMessage(nodeId, CONTROL, START.getBytes())); |
| 251 | startTestRun(); | 253 | startTestRun(); |
| 252 | } | 254 | } |
| 255 | + } | ||
| 253 | 256 | ||
| 254 | public void stop() { | 257 | public void stop() { |
| 258 | + if (!stopped) { | ||
| 255 | communicationService.broadcast(new ClusterMessage(nodeId, CONTROL, STOP.getBytes())); | 259 | communicationService.broadcast(new ClusterMessage(nodeId, CONTROL, STOP.getBytes())); |
| 256 | stopTestRun(); | 260 | stopTestRun(); |
| 257 | } | 261 | } |
| 262 | + } | ||
| 258 | 263 | ||
| 259 | private void logConfig(String prefix) { | 264 | private void logConfig(String prefix) { |
| 260 | log.info("{} with appId {}; numKeys = {}; cyclePeriod = {} ms; numNeighbors={}", | 265 | log.info("{} with appId {}; numKeys = {}; cyclePeriod = {} ms; numNeighbors={}", |
| ... | @@ -282,7 +287,6 @@ public class IntentPerfInstaller { | ... | @@ -282,7 +287,6 @@ public class IntentPerfInstaller { |
| 282 | } | 287 | } |
| 283 | 288 | ||
| 284 | private void stopTestRun() { | 289 | private void stopTestRun() { |
| 285 | - stopped = true; | ||
| 286 | if (reporterTask != null) { | 290 | if (reporterTask != null) { |
| 287 | reporterTask.cancel(); | 291 | reporterTask.cancel(); |
| 288 | reporterTask = null; | 292 | reporterTask = null; |
| ... | @@ -293,6 +297,11 @@ public class IntentPerfInstaller { | ... | @@ -293,6 +297,11 @@ public class IntentPerfInstaller { |
| 293 | } catch (InterruptedException e) { | 297 | } catch (InterruptedException e) { |
| 294 | log.warn("Failed to stop worker", e); | 298 | log.warn("Failed to stop worker", e); |
| 295 | } | 299 | } |
| 300 | + | ||
| 301 | + sampleCollector.recordSample(0, 0); | ||
| 302 | + sampleCollector.recordSample(0, 0); | ||
| 303 | + stopped = true; | ||
| 304 | + | ||
| 296 | log.info("Stopped test run"); | 305 | log.info("Stopped test run"); |
| 297 | } | 306 | } |
| 298 | 307 | ... | ... |
| ... | @@ -36,9 +36,7 @@ import org.onosproject.ui.UiView; | ... | @@ -36,9 +36,7 @@ import org.onosproject.ui.UiView; |
| 36 | import java.util.Collection; | 36 | import java.util.Collection; |
| 37 | import java.util.HashSet; | 37 | import java.util.HashSet; |
| 38 | import java.util.List; | 38 | import java.util.List; |
| 39 | -import java.util.Random; | ||
| 40 | import java.util.Set; | 39 | import java.util.Set; |
| 41 | -import java.util.TimerTask; | ||
| 42 | 40 | ||
| 43 | import static java.util.Collections.synchronizedSet; | 41 | import static java.util.Collections.synchronizedSet; |
| 44 | 42 | ||
| ... | @@ -58,25 +56,11 @@ public class IntentPerfUi { | ... | @@ -58,25 +56,11 @@ public class IntentPerfUi { |
| 58 | private UiExtension uiExtension = new UiExtension(views, this::newHandlers, | 56 | private UiExtension uiExtension = new UiExtension(views, this::newHandlers, |
| 59 | getClass().getClassLoader()); | 57 | getClass().getClassLoader()); |
| 60 | 58 | ||
| 61 | - private List<String> headers = ImmutableList.of("One", "Two", "Three", "Four", "Five"); | 59 | + private IntentPerfCollector collector; |
| 62 | - | ||
| 63 | - private Random random = new Random(); | ||
| 64 | - private TimerTask task; | ||
| 65 | 60 | ||
| 66 | @Activate | 61 | @Activate |
| 67 | protected void activate() { | 62 | protected void activate() { |
| 68 | uiExtensionService.register(uiExtension); | 63 | 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); | ||
| 80 | } | 64 | } |
| 81 | 65 | ||
| 82 | @Deactivate | 66 | @Deactivate |
| ... | @@ -96,12 +80,12 @@ public class IntentPerfUi { | ... | @@ -96,12 +80,12 @@ public class IntentPerfUi { |
| 96 | } | 80 | } |
| 97 | 81 | ||
| 98 | /** | 82 | /** |
| 99 | - * Sets the headers for the subsequently reported samples. | 83 | + * Binds the sample collector. |
| 100 | * | 84 | * |
| 101 | - * @param headers list of headers for future samples | 85 | + * @param collector list of headers for future samples |
| 102 | */ | 86 | */ |
| 103 | - public void setHeaders(List<String> headers) { | 87 | + public void setCollector(IntentPerfCollector collector) { |
| 104 | - this.headers = headers; | 88 | + this.collector = collector; |
| 105 | } | 89 | } |
| 106 | 90 | ||
| 107 | // Creates and returns session specific message handler. | 91 | // Creates and returns session specific message handler. |
| ... | @@ -122,22 +106,10 @@ public class IntentPerfUi { | ... | @@ -122,22 +106,10 @@ public class IntentPerfUi { |
| 122 | public void process(ObjectNode message) { | 106 | public void process(ObjectNode message) { |
| 123 | streamingEnabled = message.path("event").asText("unknown").equals("intentPerfStart"); | 107 | streamingEnabled = message.path("event").asText("unknown").equals("intentPerfStart"); |
| 124 | if (streamingEnabled) { | 108 | if (streamingEnabled) { |
| 125 | - sendHeaders(); | 109 | + sendInitData(); |
| 126 | } | 110 | } |
| 127 | } | 111 | } |
| 128 | 112 | ||
| 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); | ||
| 139 | - } | ||
| 140 | - | ||
| 141 | @Override | 113 | @Override |
| 142 | public void init(UiConnection connection, ServiceDirectory directory) { | 114 | public void init(UiConnection connection, ServiceDirectory directory) { |
| 143 | super.init(connection, directory); | 115 | super.init(connection, directory); |
| ... | @@ -152,18 +124,34 @@ public class IntentPerfUi { | ... | @@ -152,18 +124,34 @@ public class IntentPerfUi { |
| 152 | 124 | ||
| 153 | private void send(Sample sample) { | 125 | private void send(Sample sample) { |
| 154 | if (streamingEnabled) { | 126 | if (streamingEnabled) { |
| 127 | + connection().sendMessage("intentPerfSample", 0, sampleNode(sample)); | ||
| 128 | + } | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + private void sendInitData() { | ||
| 132 | + ObjectNode rootNode = mapper.createObjectNode(); | ||
| 155 | ArrayNode an = mapper.createArrayNode(); | 133 | ArrayNode an = mapper.createArrayNode(); |
| 156 | - for (double d : sample.data) { | 134 | + ArrayNode sn = mapper.createArrayNode(); |
| 157 | - an.add(d); | 135 | + rootNode.set("headers", an); |
| 136 | + rootNode.set("samples", sn); | ||
| 137 | + | ||
| 138 | + collector.getSampleHeaders().forEach(an::add); | ||
| 139 | + collector.getSamples().forEach(s -> sn.add(sampleNode(s))); | ||
| 140 | + connection().sendMessage("intentPerfInit", 0, rootNode); | ||
| 158 | } | 141 | } |
| 159 | 142 | ||
| 160 | - ObjectNode sn = mapper.createObjectNode(); | 143 | + private ObjectNode sampleNode(Sample sample) { |
| 161 | - sn.put("time", sample.time); | 144 | + ObjectNode sampleNode = mapper.createObjectNode(); |
| 162 | - sn.set("data", an); | 145 | + ArrayNode an = mapper.createArrayNode(); |
| 146 | + sampleNode.put("time", sample.time); | ||
| 147 | + sampleNode.set("data", an); | ||
| 163 | 148 | ||
| 164 | - connection().sendMessage("intentPerfSample", 0, sn); | 149 | + for (double d : sample.data) { |
| 150 | + an.add(d); | ||
| 165 | } | 151 | } |
| 152 | + return sampleNode; | ||
| 166 | } | 153 | } |
| 154 | + | ||
| 167 | } | 155 | } |
| 168 | 156 | ||
| 169 | } | 157 | } | ... | ... |
| 1 | -date,value,node | ||
| 2 | -00:55:15,68.38,node1 | ||
| 3 | -00:55:15,55.61,node2 | ||
| 4 | -00:55:15,74.00,node3 | ||
| 5 | -00:55:30,74.20,node1 | ||
| 6 | -00:55:30,77.60,node2 | ||
| 7 | -00:55:30,74.80,node3 | ||
| 8 | -00:55:45,74.60,node1 | ||
| 9 | -00:55:45,72.80,node2 | ||
| 10 | -00:55:45,77.00,node3 | ||
| 11 | -00:56:00,73.60,node1 | ||
| 12 | -00:56:00,75.00,node2 | ||
| 13 | -00:56:00,76.98,node3 | ||
| 14 | -00:56:15,75.82,node1 | ||
| 15 | -00:56:15,75.40,node2 | ||
| 16 | -00:56:15,76.00,node3 | ||
| 17 | -00:56:30,75.60,node1 | ||
| 18 | -00:56:30,74.59,node2 | ||
| 19 | -00:56:30,74.01,node3 | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | -key,value,date | ||
| 2 | -Group1,37,00:23:00 | ||
| 3 | -Group2,12,00:23:00 | ||
| 4 | -Group3,46,00:23:00 | ||
| 5 | -Group1,32,00:23:05 | ||
| 6 | -Group2,19,00:23:05 | ||
| 7 | -Group3,42,00:23:05 | ||
| 8 | -Group1,45,00:23:10 | ||
| 9 | -Group2,16,00:23:10 | ||
| 10 | -Group3,44,00:23:10 | ||
| 11 | -Group1,24,00:23:15 | ||
| 12 | -Group2,52,00:23:15 | ||
| 13 | -Group3,64,00:23:15 | ||
| 14 | -Group1,34,00:23:20 | ||
| 15 | -Group2,62,00:23:20 | ||
| 16 | -Group3,74,00:23:20 | ||
| 17 | -Group1,34,00:23:25 | ||
| 18 | -Group2,62,00:23:25 | ||
| 19 | -Group3,74,00:23:25 | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -22,9 +22,8 @@ svg { | ... | @@ -22,9 +22,8 @@ svg { |
| 22 | font: 12px sans-serif; | 22 | font: 12px sans-serif; |
| 23 | } | 23 | } |
| 24 | 24 | ||
| 25 | -.line { | 25 | +.line,.lineTotal { |
| 26 | fill: none; | 26 | fill: none; |
| 27 | - stroke: #000; | ||
| 28 | stroke-width: 2px; | 27 | stroke-width: 2px; |
| 29 | } | 28 | } |
| 30 | 29 | ||
| ... | @@ -37,16 +36,20 @@ svg { | ... | @@ -37,16 +36,20 @@ svg { |
| 37 | 36 | ||
| 38 | .light .axis path, | 37 | .light .axis path, |
| 39 | .light .axis line, | 38 | .light .axis line, |
| 39 | +.light .lineTotal { | ||
| 40 | + stroke: #333; | ||
| 41 | +} | ||
| 42 | + | ||
| 40 | .light .axis text { | 43 | .light .axis text { |
| 41 | - stroke: #999; | 44 | + fill: #333; |
| 42 | } | 45 | } |
| 43 | 46 | ||
| 44 | .dark .axis path, | 47 | .dark .axis path, |
| 45 | .dark .axis line, | 48 | .dark .axis line, |
| 46 | -.dark .axis text { | 49 | +.dark .lineTotal { |
| 47 | stroke: #eee; | 50 | stroke: #eee; |
| 48 | } | 51 | } |
| 49 | 52 | ||
| 50 | -.axis text { | 53 | +.dark .axis text { |
| 51 | - stroke-width: 0.3; | 54 | + fill: #eee; |
| 52 | } | 55 | } | ... | ... |
| ... | @@ -31,7 +31,7 @@ | ... | @@ -31,7 +31,7 @@ |
| 31 | 31 | ||
| 32 | // ========================== | 32 | // ========================== |
| 33 | 33 | ||
| 34 | - function createGraph(h) { | 34 | + function createGraph(h, samples) { |
| 35 | var stopped = false, | 35 | var stopped = false, |
| 36 | n = 243, | 36 | n = 243, |
| 37 | duration = 750, | 37 | duration = 750, |
| ... | @@ -75,7 +75,27 @@ | ... | @@ -75,7 +75,27 @@ |
| 75 | headers.forEach(function (h, li) { | 75 | headers.forEach(function (h, li) { |
| 76 | // Prime the data to match the headers and zero it out. | 76 | // Prime the data to match the headers and zero it out. |
| 77 | data[li] = d3.range(n).map(function() { return 0 }); | 77 | data[li] = d3.range(n).map(function() { return 0 }); |
| 78 | - theSample[li] = 0; | 78 | + |
| 79 | + if (li < headers.length - 1) { | ||
| 80 | + samples.forEach(function (s, i) { | ||
| 81 | + var di = dataIndex(s.time); | ||
| 82 | + if (di >= 0) { | ||
| 83 | + data[li][di] = s.data[li]; | ||
| 84 | + } | ||
| 85 | + }); | ||
| 86 | + | ||
| 87 | + data[li].forEach(function (d, i) { | ||
| 88 | + if (!d && i > 0) { | ||
| 89 | + data[li][i] = data[li][i - 1]; | ||
| 90 | + } | ||
| 91 | + }); | ||
| 92 | + } else { | ||
| 93 | + data[li].forEach(function (t, i) { | ||
| 94 | + for (var si = 0; si < headers.length - 1; si++) { | ||
| 95 | + data[li][i] = data[si][i]; | ||
| 96 | + } | ||
| 97 | + }); | ||
| 98 | + } | ||
| 79 | 99 | ||
| 80 | // Create the lines | 100 | // Create the lines |
| 81 | lines[li] = d3.svg.line() | 101 | lines[li] = d3.svg.line() |
| ... | @@ -88,15 +108,24 @@ | ... | @@ -88,15 +108,24 @@ |
| 88 | .attr("clip-path", "url(#intent-perf-clip)") | 108 | .attr("clip-path", "url(#intent-perf-clip)") |
| 89 | .append("path") | 109 | .append("path") |
| 90 | .datum(function () { return data[li]; }) | 110 | .datum(function () { return data[li]; }) |
| 91 | - .attr("id", "line" + li) | 111 | + .attr("id", "line" + li); |
| 92 | - .style("stroke", lineColor(li)) | 112 | + |
| 93 | - .attr("class", "line"); | 113 | + if (li < headers.length - 1) { |
| 114 | + paths[li].attr("class", "line").style("stroke", lineColor(li)); | ||
| 115 | + } else { | ||
| 116 | + paths[li].attr("class", "lineTotal"); | ||
| 117 | + } | ||
| 94 | }); | 118 | }); |
| 95 | 119 | ||
| 120 | + function dataIndex(time) { | ||
| 121 | + var delta = now.getTime() - time; | ||
| 122 | + var di = Math.round(n - 2 - (delta / duration)); | ||
| 123 | + // $log.info('now=' + now.getTime() + '; then=' + time + '; delta=' + delta + '; di=' + di + ';'); | ||
| 124 | + return di >= n || di < 0 ? -1 : di; | ||
| 125 | + } | ||
| 126 | + | ||
| 96 | function lineColor(li) { | 127 | function lineColor(li) { |
| 97 | - return li < headers.length - 1 ? | 128 | + return sus.cat7().getColor(li, false, ts.theme()); |
| 98 | - sus.cat7().getColor(li, false, ts.theme()) : | ||
| 99 | - ts.theme() === 'light' ? '#333' : '#eee'; | ||
| 100 | } | 129 | } |
| 101 | 130 | ||
| 102 | function tick() { | 131 | function tick() { |
| ... | @@ -130,13 +159,17 @@ | ... | @@ -130,13 +159,17 @@ |
| 130 | function start() { | 159 | function start() { |
| 131 | stopped = false; | 160 | stopped = false; |
| 132 | headers.forEach(function (h, li) { | 161 | headers.forEach(function (h, li) { |
| 133 | - theSample[li] = 0; | 162 | + theSample[li] = data[li][n-1]; |
| 134 | }); | 163 | }); |
| 135 | tick(); | 164 | tick(); |
| 136 | } | 165 | } |
| 137 | 166 | ||
| 138 | function stop() { | 167 | function stop() { |
| 139 | - stopped = true; | 168 | + headers.forEach(function (h, li) { |
| 169 | + theSample[li] = 0; | ||
| 170 | + }); | ||
| 171 | + // Schedule delayed stop to allow 0s to render. | ||
| 172 | + setTimeout(function () { stopped = true; }, 1000); | ||
| 140 | } | 173 | } |
| 141 | 174 | ||
| 142 | function resize(dim) { | 175 | function resize(dim) { |
| ... | @@ -190,7 +223,7 @@ | ... | @@ -190,7 +223,7 @@ |
| 190 | function createAndInitGraph(d) { | 223 | function createAndInitGraph(d) { |
| 191 | if (!graph) { | 224 | if (!graph) { |
| 192 | d.headers.push("total"); | 225 | d.headers.push("total"); |
| 193 | - graph = createGraph(d.headers); | 226 | + graph = createGraph(d.headers, d.samples); |
| 194 | } | 227 | } |
| 195 | graph.start(); | 228 | graph.start(); |
| 196 | } | 229 | } |
| ... | @@ -213,7 +246,7 @@ | ... | @@ -213,7 +246,7 @@ |
| 213 | 246 | ||
| 214 | function createHandlerMap() { | 247 | function createHandlerMap() { |
| 215 | handlerMap = { | 248 | handlerMap = { |
| 216 | - intentPerfHeaders: createAndInitGraph, | 249 | + intentPerfInit: createAndInitGraph, |
| 217 | intentPerfSample: recordSample | 250 | intentPerfSample: recordSample |
| 218 | }; | 251 | }; |
| 219 | } | 252 | } | ... | ... |
| 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> |
-
Please register or login to post a comment