Skip to content

Instantly share code, notes, and snippets.

@chrisbrich
Forked from syntagmatic/README.md
Created December 11, 2012 03:56
Show Gist options
  • Save chrisbrich/4255777 to your computer and use it in GitHub Desktop.
Save chrisbrich/4255777 to your computer and use it in GitHub Desktop.
Null Values/Two Ways - Parallel Coordinates

Null values indicated by space character.

In this situation where we have reorderable axis, polylines with null values can be made to disappear for certain axes orderings. For example, the axes ordering c,a,e,b,f,d will make the polyline for the last element in sparse-data.csv not visible.

Alternately, rather than not drawing the null segements, one can draw then leading to a point outside the bounds of the axes. Lines can't disappear.

Check or uncheck the input box and wiggle the axes to see the two ways of representing nulls.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.background path {
fill: none;
stroke: #ccc;
stroke-opacity: .4;
shape-rendering: crispEdges;
}
.foreground path {
fill: none;
stroke: steelblue;
stroke-opacity: .7;
stroke-width: 2;
}
.brush .extent {
fill-opacity: .3;
stroke: #fff;
shape-rendering: crispEdges;
}
.axis line, .axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
text-shadow: 0 1px 0 #fff;
cursor: move;
}
</style>
<body>
<script src="http://d3js.org/d3.v2.js"></script>
<script>
var m = [30, 10, 50, 10],
w = 960 - m[1] - m[3],
h = 450 - m[0] - m[2];
var x = d3.scale.ordinal().rangePoints([0, w], .5),
y = {},
dragging = {};
var line = d3.svg.line().defined(function(d) { return d[1] != null; }),
axis = d3.svg.axis().orient("left"),
background,
foreground;
var svg = d3.select("body").append("svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.append("g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
var nullOffset = false;
d3.select("body").append("div").classed("option",true).text("Offset?").append("input").attr("type","checkbox")
.on("change",function(){ nullOffset = d3.select(this).property("checked")});
d3.csv("sparse-data.csv", function(cities) {
// Extract the list of dimensions and create a scale for each.
x.domain(dimensions = d3.keys(cities[0]).filter(function(d) {
return d != "City" && (y[d] = d3.scale.linear()
.domain(d3.extent(cities, function(p) { return +p[d]; }))
.range([h, 0]));
}));
// Add grey background lines for context.
background = svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(cities)
.enter().append("path")
.attr("d", path);
// Add blue foreground lines for focus.
foreground = svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(cities)
.enter().append("path")
.attr("d", path);
// Add a group element for each dimension.
var g = svg.selectAll(".dimension")
.data(dimensions)
.enter().append("svg:g")
.attr("class", "dimension")
.attr("transform", function(d) { return "translate(" + x(d) + ")"; })
.call(d3.behavior.drag()
.on("dragstart", function(d) {
dragging[d] = this.__origin__ = x(d);
background.attr("visibility", "hidden");
})
.on("drag", function(d) {
dragging[d] = Math.min(w, Math.max(0, this.__origin__ += d3.event.dx));
foreground.attr("d", path);
dimensions.sort(function(a, b) { return position(a) - position(b); });
x.domain(dimensions);
g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
})
.on("dragend", function(d) {
delete this.__origin__;
delete dragging[d];
transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
transition(foreground)
.attr("d", path);
background
.attr("d", path)
.transition()
.delay(500)
.duration(0)
.attr("visibility", null);
}));
// Add an axis and title.
g.append("g")
.attr("class", "axis")
.each(function(d) { d3.select(this).call(axis.scale(y[d])); })
.append("text")
.attr("text-anchor", "middle")
.attr("y", -9)
.text(String);
// Add and store a brush for each axis.
g.append("g")
.attr("class", "brush")
.each(function(d) { d3.select(this).call(y[d].brush = d3.svg.brush().y(y[d]).on("brush", brush)); })
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
});
function position(d) {
var v = dragging[d];
return v == null ? x(d) : v;
}
function transition(g) {
return g.transition().duration(500);
}
// Returns the path for a given data point.
function path(d) {
return line(dimensions.map(function(p) {
// check for undefined values
if (d[p] == " ") return (nullOffset)?[x(p), y[p].range()[0] + 30]:[x(p), null];
return [x(p), y[p](d[p])];
}));
}
// Handles a brush event, toggling the display of foreground lines.
function brush() {
var actives = dimensions.filter(function(p) { return !y[p].brush.empty(); }),
extents = actives.map(function(p) { return y[p].brush.extent(); });
foreground.style("display", function(d) {
return actives.every(function(p, i) {
return extents[i][0] <= d[p] && d[p] <= extents[i][1];
}) ? null : "none";
});
}
</script>
a b c d e f
2 7 3 9 8
2 1 8 3 2
3 2 4 1
5 2 4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment