|
// This is an example Chaism plugin that uses D3 to make a scatter plot. |
|
// Draws from this Scatter Plot example http://bl.ocks.org/curran/134ed87c99257e3f2e31 |
|
function ScatterPlot() { |
|
|
|
var my = ChiasmComponent({ |
|
|
|
margin: { |
|
left: 20, |
|
top: 20, |
|
right: 20, |
|
bottom: 20 |
|
}, |
|
|
|
xColumn: Model.None, |
|
yColumn: Model.None, |
|
colorColumn: Model.None, |
|
|
|
xScaleDomain: Model.None, |
|
yScaleDomain: Model.None, |
|
|
|
// "r" stands for radius. |
|
rColumn: Model.None, |
|
|
|
// The circle radius used if rColumn is not specified. |
|
rDefault: 10, |
|
|
|
// The range of the radius scale if rColumn is specified. |
|
rMin: 0, |
|
rMax: 10, |
|
|
|
fillDefault: "black", |
|
stroke: "none", |
|
strokeWidth: "1px", |
|
borderRectFill: "lightgray", |
|
|
|
brushEnabled: false, |
|
brushIntervalX: Model.None, |
|
brushIntervalY: Model.None |
|
|
|
}); |
|
|
|
var xScale = d3.scale.linear(); |
|
var yScale = d3.scale.linear(); |
|
var rScale = d3.scale.sqrt(); |
|
var colorScale = d3.scale.category10(); |
|
|
|
var brush = d3.svg.brush() |
|
.x(xScale) |
|
.y(yScale) |
|
.on("brush", onBrush); |
|
|
|
var svg = d3.select(my.initSVG()); |
|
|
|
var clipRect = svg |
|
.append("clipPath") |
|
.attr("id", "clip") |
|
.append("rect"); |
|
|
|
var g = svg.append("g"); |
|
|
|
var borderRect = g.append("rect") |
|
.style("stroke", "black") |
|
.style("stroke-width", 1); |
|
|
|
var circlesG = g.append("g") |
|
.style("clip-path", "url(#clip)"); |
|
|
|
var brushG = g.append("g") |
|
.attr("class", "brush"); |
|
|
|
// Respond to changes in size and margin. |
|
// Inspired by D3 margin convention from http://bl.ocks.org/mbostock/3019563 |
|
my.when(["box", "margin"], function(box, margin){ |
|
|
|
my.innerBox = { |
|
width: box.width - margin.left - margin.right, |
|
height: box.height - margin.top - margin.bottom |
|
}; |
|
|
|
g.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
|
|
|
}); |
|
|
|
my.when(["innerBox"], function (innerBox){ |
|
borderRect |
|
.attr("width", innerBox.width) |
|
.attr("height", innerBox.height); |
|
clipRect |
|
.attr("width", innerBox.width) |
|
.attr("height", innerBox.height); |
|
}); |
|
|
|
my.when(["data", "innerBox", "xColumn", "xScaleDomain"], |
|
function (data, innerBox, xColumn, xScaleDomain){ |
|
if(xColumn !== Model.None){ |
|
xScale.range([0, innerBox.width]); |
|
|
|
if(xScaleDomain !== Model.None){ |
|
xScale.domain(xScaleDomain); |
|
} else { |
|
xScale.domain(d3.extent(data, function (d){ return d[xColumn]; })); |
|
} |
|
|
|
my.x = function (d){ return xScale(d[xColumn]); }; |
|
} |
|
}); |
|
|
|
my.when(["data", "innerBox", "yColumn", "yScaleDomain"], |
|
function (data, innerBox, yColumn, yScaleDomain){ |
|
if(yColumn !== Model.None){ |
|
|
|
if(yScaleDomain !== Model.None){ |
|
yScale.domain(yScaleDomain); |
|
} else { |
|
yScale.domain(d3.extent(data, function (d){ return d[yColumn]; })); |
|
} |
|
|
|
yScale.range([innerBox.height, 0]); |
|
|
|
my.y = function (d){ return yScale(d[yColumn]); }; |
|
} |
|
}); |
|
|
|
// Generate a function or constant for circle radius, |
|
// depending on whether or not rColumn is defined. |
|
my.when(["data", "rColumn", "rDefault", "rMin", "rMax"], |
|
function (data, rColumn, rDefault, rMin, rMax){ |
|
|
|
if(rColumn === Model.None){ |
|
my.r = rDefault; |
|
} else { |
|
rScale |
|
.domain(d3.extent(data, function (d){ return d[rColumn]; })) |
|
.range([rMin, rMax]); |
|
my.r = function (d){ return rScale(d[rColumn]); }; |
|
} |
|
}); |
|
|
|
my.when(["data", "colorColumn"], |
|
function (data, colorColumn){ |
|
|
|
if(colorColumn === Model.None){ |
|
my.fill = fillDefault; |
|
} else { |
|
colorScale |
|
.domain(data.map(function(d){ return d[colorColumn] })); |
|
my.fill = function (d){ return colorScale(d[colorColumn]) } |
|
} |
|
|
|
|
|
}); |
|
|
|
my.when(["borderRectFill"], function (borderRectFill){ |
|
borderRect.style("fill", borderRectFill); |
|
}) |
|
|
|
my.when([ "data", "x", "y", "r", "fill", "stroke", "strokeWidth" ], |
|
function (data, x, y, r, fill, stroke, strokeWidth){ |
|
|
|
// Render the circles of the scatter plot. |
|
var circles = circlesG.selectAll("circle").data(data); |
|
circles.enter().append("circle"); |
|
circles.exit().remove(); |
|
circles |
|
.attr("cx", x) |
|
.attr("cy", y) |
|
.attr("r", r) |
|
.attr("fill", fill) |
|
.attr("stroke", stroke) |
|
.attr("stroke-width", strokeWidth); |
|
}); |
|
|
|
my.when("brushEnabled", function (brushEnabled){ |
|
brushG.remove(); |
|
if(brushEnabled){ |
|
g.node().appendChild(brushG.node()); |
|
} |
|
}); |
|
|
|
function onBrush() { |
|
if(brush.empty()){ |
|
my.brushIntervalX = Model.None; |
|
my.brushIntervalY = Model.None; |
|
} else { |
|
var e = brush.extent(); |
|
my.brushIntervalX = [e[0][0], e[1][0]]; |
|
my.brushIntervalY = [e[0][1], e[1][1]]; |
|
} |
|
} |
|
|
|
my.when(["brushIntervalX", "brushIntervalY", "x", "y"], |
|
function (brushIntervalX, brushIntervalY){ |
|
if(brushIntervalX !== Model.None && brushIntervalY !== Model.None){ |
|
brush.extent([ |
|
[brushIntervalX[0], brushIntervalY[0]], |
|
[brushIntervalX[1], brushIntervalY[1]] |
|
]); |
|
} |
|
brushG.call(brush); |
|
}); |
|
|
|
return my; |
|
} |