Skip to content
Browse files

progressive rendering

  • Loading branch information...
1 parent 473b909 commit fc11f044a489bc2085af58aee14661da8e3caaeb @syntagmatic committed
Showing with 7,904 additions and 53 deletions.
  1. +18 −0 README.md
  2. +94 −2 d3.parcoords.js
  3. +7,638 −0 data/nutrients.csv
  4. +67 −0 example.html
  5. +85 −50 index.html
  6. +2 −1 style.css
View
18 README.md
@@ -213,6 +213,24 @@ Change the opacity of the polylines, also the foreground context's globalAlpha.
Set the xscale, yscale, and canvas sizes. Usually this is called automatically, such as on render() or resize() events
+<a name="parcoords_mode" href="#parcoords_mode">#</a> parcoords.<b>mode</b>(*type*)
+
+Toggle the rendering mode. Currently there are two options:
+
+* *"default"* renders instantaneously, but is less responsive with more than ~2000 rows of data
+* *"queue"* puts the data in a queue, and renders progressively. This way the user can interact with the chart during rendering.
+
+<a name="parcoords_rate" href="#parcoords_rate">#</a> parcoords.<b>rate</b>(*integer*)
+
+Change the number of lines drawn each frame when the <a href="#parcoords_mode">rendering mode</a> is set to *"queue"*. Some suggested values for different machines are:
+
+* Mobile phone or iPad: 12-30
+* Normal PC with Firefox: 20-60
+* Normal PC with Chrome/Safari: 30-100
+* Fast PC with Chrome/Safari: 100-300
+
+In the future, an automatic rate adjuster will be included to optimize this number on-the-fly.
+
<a name="parcoords_detectDimensions" href="#parcoords_detectDimensions">#</a> parcoords.<b>detectDimensions</b>()
Set the quantative dimensions using based on the first row of data.
View
96 d3.parcoords.js
@@ -3,6 +3,8 @@ d3.parcoords = function(config) {
dimensions: [],
data: [],
brushed: false,
+ mode: "default",
+ rate: 10,
width: 600,
height: 300,
margin: { top: 24, right: 0, bottom: 12, left: 0 },
@@ -64,6 +66,7 @@ d3.parcoords = function(config) {
.on("width", function(d) { pc.resize(); })
.on("height", function(d) { pc.resize(); })
.on("margin", function(d) { pc.resize(); })
+ .on("rate", function(d) { rqueue.rate(d.value); })
.on("data", function(d) {
if (flags.shadows) paths(__.data, ctx.shadows);
})
@@ -123,19 +126,36 @@ d3.parcoords = function(config) {
return this;
};
+ var rqueue = d3.parcoords.renderQueue(path_foreground)
+ .rate(50)
+ .clear(function() { pc.clear('foreground'); });
+
pc.render = function() {
// try to autodetect dimensions and create scales
if (!__.dimensions.length) pc.detectDimensions();
if (!(__.dimensions[0] in yscale)) pc.autoscale();
+ pc.render[__.mode]();
+
+ events.render.call(this);
+ return this;
+ };
+
+ pc.render.default = function() {
pc.clear('foreground');
if (__.brushed) {
__.brushed.forEach(path_foreground);
} else {
__.data.forEach(path_foreground);
}
- events.render.call(this);
- return this;
+ };
+
+ pc.render.queue = function() {
+ if (__.brushed) {
+ rqueue(__.brushed);
+ } else {
+ rqueue(__.data);
+ }
};
pc.shadows = function() {
@@ -478,3 +498,75 @@ d3.parcoords.adjacent_pairs = function(arr) {
};
return ret;
};
+
+d3.parcoords.renderQueue = (function(func) {
+ var _queue = [], // data to be rendered
+ _rate = 10, // number of calls per frame
+ _invalidate = function() {}, // invalidate last render queue
+ _clear = function() {}; // clearing function
+
+ var rq = function(data) {
+ if (data) rq.data(data);
+ _invalidate();
+ _clear();
+ rq.render();
+ };
+
+ rq.render = function() {
+ var valid = true;
+ _invalidate = rq.invalidate = function() {
+ valid = false;
+ };
+
+ function doFrame() {
+ if (!valid) return true;
+ if (!_queue.length) return true;
+ var chunk = _queue.splice(0,_rate);
+ chunk.map(func);
+ timer_frame(doFrame);
+ }
+
+ doFrame();
+ };
+
+ rq.data = function(data) {
+ _invalidate();
+ _queue = data.slice(0);
+ return rq;
+ };
+
+ rq.add = function(data) {
+ _queue = _queue.concat(data);
+ };
+
+ rq.rate = function(value) {
+ if (!arguments.length) return _rate;
+ _rate = value;
+ return rq;
+ };
+
+ rq.remaining = function() {
+ return _queue.length;
+ };
+
+ // clear the canvas
+ rq.clear = function(func) {
+ if (!arguments.length) {
+ _clear();
+ return rq;
+ }
+ _clear = func;
+ return rq;
+ };
+
+ rq.invalidate = _invalidate;
+
+ var timer_frame = window.requestAnimationFrame
+ || window.webkitRequestAnimationFrame
+ || window.mozRequestAnimationFrame
+ || window.oRequestAnimationFrame
+ || window.msRequestAnimationFrame
+ || function(callback) { setTimeout(callback, 17); };
+
+ return rq;
+});
View
7,638 data/nutrients.csv
7,638 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
67 example.html
@@ -0,0 +1,67 @@
+<link rel="stylesheet" type="text/css" href="d3.parcoords.css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<script src="d3.v2.js"></script>
+<script src="d3.parcoords.js"></script>
+<div id="example1" class="parcoords"></div>
+
+<script id="brushing">// linear color scale
+var blue_to_brown = d3.scale.linear()
+ .domain([9, 50])
+ .range(["steelblue", "brown"])
+ .interpolate(d3.interpolateLab);
+
+// interact with this variable from a javascript console
+var pc1;
+
+// load csv file and create the chart
+d3.csv('data/cars.csv', function(data) {
+ pc1 = d3.parcoords()("#example1")
+ .data(data)
+ .color(function(d) { return blue_to_brown(d['economy (mpg)']); }) // quantitative color scale
+ .alpha(0.4)
+ .render()
+ .mode('queue')
+ .brushable() // enable brushing
+ .interactive() // command line mode
+
+ var explore_count = 0;
+ var exploring = {};
+ var explore_start = false;
+ pc1.svg
+ .selectAll(".dimension")
+ .style("cursor", "pointer")
+ .on("click", function(d) {
+ exploring[d] = d in exploring ? false : true;
+ event.preventDefault();
+ if (exploring[d]) d3.timer(explore(d,explore_count));
+ });
+
+ function explore(dimension,count) {
+ if (!explore_start) {
+ explore_start = true;
+ d3.timer(pc1.brush);
+ }
+ var speed = (Math.round(Math.random()) ? 1 : -1) * (Math.random()+0.5);
+ return function(t) {
+ if (!exploring[dimension]) return true;
+ var domain = pc1.yscale[dimension].domain();
+ var width = (domain[1] - domain[0])/4;
+
+ var center = width*1.5*(1+Math.sin(speed*t/1200)) + domain[0];
+
+ pc1.yscale[dimension].brush.extent([
+ d3.max([center-width*0.01, domain[0]-width/400]),
+ d3.min([center+width*1.01, domain[1]+width/100])
+ ])(pc1.g()
+ .filter(function(d) {
+ return d == dimension;
+ })
+ );
+ };
+ };
+
+});
+</script>
+
+
+
View
135 index.html
@@ -79,6 +79,54 @@
});
</script>
+<h3>Getting Started</h3>
+<div id="example" class="parcoords" style="width:360px;height:150px"></div>
+<p>
+Make a container div in HTML
+</p>
+<pre>
+&lt;div id="<strong>example</strong>" class="parcoords" style="width:300px;height:150px"&gt;&lt;/div&gt;
+</pre>
+<p>
+Make some data in JavaScript
+</p>
+<pre>
+var <strong>data</strong> = [
+ [0,-0,0,0,0,3 ],
+ [1,-1,1,2,1,6 ],
+ [2,-2,4,4,0.5,2],
+ [3,-3,9,6,0.33,4],
+ [4,-4,16,8,0.25,9]
+];
+</pre>
+<p>
+Create a parallel coordinates plot
+</p>
+<pre>
+var pc = d3.parcoords()("#<strong>example</strong>")
+ .data(<strong>data</strong>)
+ .render()
+ .createAxes();
+</pre>
+<script>
+// x,-x,x^,ax,1/x, random
+var data = [
+ [0,-0,0,0,0,3 ],
+ [1,-1,1,2,1,6 ],
+ [2,-2,4,4,0.5,2],
+ [3,-3,9,6,0.33,4],
+ [4,-4,16,8,0.25,9]
+];
+
+var pc = d3.parcoords()("#example")
+ .data(data)
+ .render()
+ .ticks(2)
+ .tickSubdivide(true)
+ .createAxes();
+</script>
+<p>That's all there is to it!</p>
+
<h3>Reordering</h3>
<p>
@@ -300,55 +348,6 @@
<p>Try using <a href="https://github.com/jasondavies/science.js/tree/master/src/stats">science.js</a>, <a href="http://harthur.github.com/clusterfck/">clusterfck</a>, or implement your own <a href="http://en.wikipedia.org/wiki/List_of_statistics_articles">statistics</a>. You may find <a href="http://underscorejs.org/">Underscore.js</a> and <a href="https://github.com/syntagmatic/underscore.math/blob/master/underscore.math.js">Underscore.math</a> useful to calculate statistics from scratch.</p>
-<h3>Getting Started</h3>
-<div id="example" class="parcoords" style="width:360px;height:150px"></div>
-<p>
-Make a container div in HTML
-</p>
-<pre>
-&lt;div id="<strong>example</strong>" class="parcoords" style="width:300px;height:150px"&gt;&lt;/div&gt;
-</pre>
-<p>
-Make some data in JavaScript
-</p>
-<pre>
-// x,-x,x^,ax,1/x, random
-var <strong>data</strong> = [
- [0,-0,0,0,0,3 ],
- [1,-1,1,2,1,6 ],
- [2,-2,4,4,0.5,2],
- [3,-3,9,6,0.33,4],
- [4,-4,16,8,0.25,9]
-];
-</pre>
-<p>
-Create a parallel coordinates plot
-</p>
-<pre>
-var pc = d3.parcoords()("#<strong>example</strong>")
- .data(<strong>data</strong>)
- .render()
- .createAxes();
-</pre>
-<script>
-// x,-x,x^,ax,1/x, random
-var data = [
- [0,-0,0,0,0,3 ],
- [1,-1,1,2,1,6 ],
- [2,-2,4,4,0.5,2],
- [3,-3,9,6,0.33,4],
- [4,-4,16,8,0.25,9]
-];
-
-var pc = d3.parcoords()("#example")
- .data(data)
- .render()
- .ticks(2)
- .tickSubdivide(true)
- .createAxes();
-</script>
-<p>That's all there is to it!</p>
-
<h3>Compositing and Opacity</h3>
<p>Control <a href="https://developer.mozilla.org/en-US/docs/Canvas_tutorial/Compositing">compositing</a> and opacity with <b>composite()</b> and <b>alpha()</b>, respectively. Compositing controls how pixels are combined to form the final image.
</p>
@@ -418,6 +417,43 @@
});
</script>
+<h3>Progressive Rendering</h3>
+
+<p>With thousands of data points, the chart will get less responsive when brushing. The interface may stutter or freeze, frustrating the uesr. To solve this, enable progressive rendering by setting the render <a href="https://github.com/syntagmatic/parallel-coordinates#parcoords_mode">mode</a> to <em>"queue"</em>.</p>
+<p>Change the speed of rendering by setting the <a href="https://github.com/syntagmatic/parallel-coordinates#parcoords_rate">rate</a>. You can read more about this technique on the <a href="http://bl.ocks.org/d/3341641/">Render Queue</a> page.</p>
+
+<div id="example-progressive" class="parcoords" style="width:1200px;margin-left:-200px;"></div>
+<pre><a href="#" class="show-code" data-code="progressive">Show code</a></pre>
+
+<script id="progressive">// linear color scale
+// interact with this variable from a javascript console
+var pc_progressive;
+
+// load csv file and create the chart
+d3.csv('data/nutrients.csv', function(data) {
+ var colorgen = d3.scale.category20();
+ var colors = {};
+ _(data).chain()
+ .pluck('group')
+ .uniq()
+ .each(function(d,i) {
+ colors[d] = colorgen(i);
+ });
+
+ var color = function(d) { return colors[d.group]; };
+
+ pc_progressive = d3.parcoords()("#example-progressive")
+ .data(data)
+ .color(color)
+ .alpha(0.4)
+ .mode("queue")
+ .render()
+ .brushable() // enable brushing
+ .interactive() // command line mode
+
+});
+</script>
+
<h3>More on the way...</h3>
<p>This library is in active development, so keep an eye out for new features and documentation. Major changes will be announced on the <a href="https://groups.google.com/forum/?fromgroups#!forum/d3-js">d3.js mailing list</a>, or you can <a href="https://github.com/syntagmatic/parallel-coordinates">watch the GitHub repository</a> to track every change.</p>
@@ -426,7 +462,6 @@
For past versions of parallel coordinates that demonstrate various features and techniques, see this <a href="http://bl.ocks.org/syntagmatic">list of bl.ocks</a>.
</p>
-
<h3>Credits</h3>
<p>Created by Kai Chang with many <a href="https://github.com/syntagmatic/parallel-coordinates#credits">collaborators</a></p>
View
3 style.css
@@ -24,7 +24,8 @@ ul {
#example1,
#example2,
#example3,
-#example-zscore {
+#example-zscore,
+#example-progressive {
height: 200px;
margin: 12px 0;
}

0 comments on commit fc11f04

Please sign in to comment.
Something went wrong with that request. Please try again.