d3.json("flowers.json", function(flower) { // Size parameters. var size = 150, padding = 20; // Color scale. var color = d3.scale.ordinal().range([ "rgb(50%, 0%, 0%)", "rgb(0%, 50%, 0%)", "rgb(0%, 0%, 50%)" ]); // Position scales. var position = {}; flower.traits.forEach(function(trait) { function value(d) { return d[trait]; } position[trait] = d3.scale.linear() .domain([d3.min(flower.values, value), d3.max(flower.values, value)]) .range([padding / 2, size - padding / 2]); }); // Root panel. var svg = d3.select("#chart") .append("svg:svg") .attr("width", size * flower.traits.length) .attr("height", size * flower.traits.length); // One column per trait. var column = svg.selectAll("g") .data(flower.traits) .enter().append("svg:g") .attr("transform", function(d, i) { return "translate(" + i * size + ",0)"; }); // One row per trait. var row = column.selectAll("g") .data(cross(flower.traits)) .enter().append("svg:g") .attr("transform", function(d, i) { return "translate(0," + i * size + ")"; }); // X-ticks. TODO Cross the trait into the tick data? row.selectAll("line.x") .data(function(d) { return position[d.x].ticks(5).map(position[d.x]); }) .enter().append("svg:line") .attr("class", "x") .attr("x1", function(d) { return d; }) .attr("x2", function(d) { return d; }) .attr("y1", padding / 2) .attr("y2", size - padding / 2); // Y-ticks. TODO Cross the trait into the tick data? row.selectAll("line.y") .data(function(d) { return position[d.y].ticks(5).map(position[d.y]); }) .enter().append("svg:line") .attr("class", "y") .attr("x1", padding / 2) .attr("x2", size - padding / 2) .attr("y1", function(d) { return d; }) .attr("y2", function(d) { return d; }); // Frame. row.append("svg:rect") .attr("x", padding / 2) .attr("y", padding / 2) .attr("width", size - padding) .attr("height", size - padding) .style("fill", "none") .style("stroke", "#aaa") .style("stroke-width", 1.5) .attr("pointer-events", "all") .on("mousedown", mousedown); // Dot plot. var dot = row.selectAll("circle") .data(cross(flower.values)) .enter().append("svg:circle") .attr("cx", function(d) { return position[d.x.x](d.y[d.x.x]); }) .attr("cy", function(d) { return size - position[d.x.y](d.y[d.x.y]); }) .attr("r", 3) .style("fill", function(d) { return color(d.y.species); }) .style("fill-opacity", .5) .attr("pointer-events", "none"); d3.select(window) .on("mousemove", mousemove) .on("mouseup", mouseup); var rect, x0, x1, count; function mousedown() { x0 = d3.svg.mouse(this); count = 0; rect = d3.select(this.parentNode) .append("svg:rect") .style("fill", "#999") .style("fill-opacity", .5); d3.event.preventDefault(); } function mousemove() { if (!rect) return; x1 = d3.svg.mouse(rect.node()); x1[0] = Math.max(padding / 2, Math.min(size - padding / 2, x1[0])); x1[1] = Math.max(padding / 2, Math.min(size - padding / 2, x1[1])); var minx = Math.min(x0[0], x1[0]), maxx = Math.max(x0[0], x1[0]), miny = Math.min(x0[1], x1[1]), maxy = Math.max(x0[1], x1[1]); rect .attr("x", minx - .5) .attr("y", miny - .5) .attr("width", maxx - minx + 1) .attr("height", maxy - miny + 1); var v = rect.node().__data__, x = position[v.x], y = position[v.y], mins = x.invert(minx), maxs = x.invert(maxx), mint = y.invert(size - maxy), maxt = y.invert(size - miny); count = 0; svg.selectAll("circle") .style("fill", function(d) { return mins <= d.y[v.x] && maxs >= d.y[v.x] && mint <= d.y[v.y] && maxt >= d.y[v.y] ? (count++, color(d.y.species)) : "#ccc"; }); } function mouseup() { if (!rect) return; rect.remove(); rect = null; if (!count) svg.selectAll("circle") .style("fill", function(d) { return color(d.y.species); }); } });