A dynamic scatter plot of Baseball data that uses ReactiveModel.
Inspired by Baseball Scatterplot.
Forked from Responsive Axes with ReactiveModel.
forked from curran's block: Dynamic Baseball Scatter Plot
xxxxxxxxxx
<html>
<head>
<meta charset="utf-8">
<title>Scatter Plot with ReactiveModel</title>
<script src="//d3js.org/d3.v4.0.0-alpha.49.min.js"></script>
<script src="//datavis-tech.github.io/reactive-model/reactive-model-v0.11.0.min.js"></script>
<style>
/* Make the chart container fill the page using CSS. */
#chart-container {
position: fixed;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
}
.axis text {
font: 10pt sans-serif;
}
.axis-label {
font: 14pt sans-serif;
}
.axis path {
display: none;
}
.tick line {
fill: none;
stroke: #DDD;
stroke-width: 1px;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<!-- The SVG graphics will be injected into this div. -->
<div id="chart-container"></div>
<script>
var transitionDuration = 800;
// Resizes the SVG container.
function SVG(my){
my("svg")
("width", 100)
("height", 100)
("svg-width", function (svg, width){
svg.attr("width", width);
}, "svg, width")
("svg-height", function (svg, height){
svg.attr("height", height);
}, "svg, height");
}
// Encapsulates the margin convention.
function Margin(my){
my("marginTop", 50)
("marginBottom", 50)
("marginLeft", 50)
("marginRight", 50)
("innerWidth", function (width, marginLeft, marginRight){
return width - marginLeft - marginRight;
}, "width, marginLeft, marginRight")
("innerHeight", function (height, marginTop, marginBottom){
return height - marginTop - marginBottom;
}, "height, marginTop, marginBottom")
("g", function (svg){
return svg.append("g");
}, "svg")
("g-transform", function (g, marginLeft, marginTop){
g.attr("transform", "translate(" + marginLeft + "," + marginTop + ")");
}, "g, marginLeft, marginTop");
}
// Adds the "data" property.
function Data(my){
my("data");
}
// Adds a column and accessor for the given column name.
function Column(my, name){
my(name + "Column")
(name + "Accessor", function (column){
return function (d){ return d[column]; };
}, name + "Column");
}
// Sets up a linear scale with the given name.
function ScaleLinear(my, name){
var scale = d3.scaleLinear();
my(name + "ScaleDomain", function (data, accessor){
return d3.extent(data, accessor);
}, "data, " + name + "Accessor");
if(name === "x"){
my("xScaleRange", function (innerWidth){
return [0, innerWidth];
}, "innerWidth");
} else if(name === "y"){
my("yScaleRange", function (innerHeight){
return [innerHeight, 0];
}, "innerHeight");
}
my(name + "Scale", function(domain, range){
return scale
.domain(domain)
.range(range)
.nice();
}, name + "ScaleDomain, " + name + "ScaleRange")
(name + "Scaled", function(scale, accessor){
return function (d){
return scale(accessor(d));
};
}, name + "Scale, " + name + "Accessor");
}
// Sets up an axis with the given name ("x" or "y")
function Axis(my, name){
var axisLengthProperty;
var tickSizeProperty;
var axis;
// Approximate number of pixels between ticks.
my(name + "AxisTickSpacing", 70)
(name + "AxisG", function (g){
return g.append("g").attr("class", name + " axis");
}, "g");
if(name === "x"){
axisLengthProperty = "innerWidth";
tickSizeProperty = "innerHeight";
axis = d3.axisBottom();
my(function(xAxisG, innerHeight){
xAxisG.attr("transform", "translate(0," + innerHeight + ")");
}, "xAxisG, innerHeight");
} else if(name === "y"){
axisLengthProperty = "innerHeight";
tickSizeProperty = "innerWidth";
axis = d3.axisLeft();
}
my(name + "AxisTicks", function (xAxisTickSpacing, axisLength){
return axisLength / xAxisTickSpacing;
}, name + "AxisTickSpacing," + axisLengthProperty)
(name + "Axis", function(ticks, scale, tickSize){
return axis
.scale(scale)
.tickSize(-tickSize)
.ticks(ticks);
}, name + "AxisTicks, " + name + "Scale," + tickSizeProperty)
(function(axisG, axis){
axis(axisG.transition().duration(transitionDuration));
}, name + "AxisG, " + name + "Axis");
}
function AxisLabel(my, name){
my(name + "AxisLabelOffset", 15)
(name + "AxisLabel", function (svg){
return svg.append("text")
.attr("class", name + " axis-label")
.style("text-anchor", "middle");
}, "svg")
my(function (axisLabel, column){
axisLabel.text(column);
}, name + "AxisLabel," + name + "Column");
if(name === "x"){
my(function (axisLabel, marginLeft, innerWidth){
axisLabel.attr("x", marginLeft + innerWidth / 2);
}, "xAxisLabel, marginLeft, innerWidth")
(function (axisLabel, height, offset){
axisLabel.attr("y", height - offset);
}, "xAxisLabel, height, xAxisLabelOffset");
} else if(name === "y"){
my(function (label, offset, innerHeight, marginTop){
label.attr("transform", [
"translate(",
offset,
",",
marginTop + innerHeight / 2,
") rotate(-90)"
].join(""));
}, "yAxisLabel, yAxisLabelOffset, innerHeight, marginTop");
}
}
function ScatterPlotMarks(my){
my("circleG", function (g){
return g.append("g");
}, "g")
(function (circleG, data, xScaled, yScaled){
var circle = circleG.selectAll("circle").data(data);
circle.exit().remove();
circle.enter().append("circle")
.attr("r", 5)
.attr("fill", "none")
.attr("stroke", "rgba(0,0,0,0.7)")
.merge(circle)
.transition().duration(transitionDuration)
.attr("cx", xScaled)
.attr("cy", yScaled);
}, "circleG, data, xScaled, yScaled");
}
// The constructor for an "axis visualization" component.
// Renders a gray rectangle with responsive axes.
function ScatterPlot(){
return ReactiveModel()
.call(SVG)
.call(Margin)
.call(Data)
.call(Column, "x")
.call(Column, "y")
.call(ScaleLinear, "x")
.call(ScaleLinear, "y")
.call(ScatterPlotMarks)
.call(Axis, "x")
.call(Axis, "y")
.call(AxisLabel, "x")
.call(AxisLabel, "y");
}
// Respond to resize by setting width and height from DOM element.
function Resize(my, el){
function resize(){
my.width(el.clientWidth)
.height(el.clientHeight);
}
resize();
window.addEventListener("resize", resize);
}
// The main program that uses the ScatterPlot component.
function main(){
// Set up the ScatterPlot instance.
var container = d3.select("#chart-container"),
scatterPlot = ScatterPlot()
.svg(container.append("svg"))
.call(Resize, container.node());
// Tweak properties
scatterPlot
.marginTop(10)
.marginBottom(40)
.marginLeft(70)
.marginRight(40)
.xAxisLabelOffset(10)
.yAxisLabelOffset(20)
.yAxisTickSpacing(20);
// Configure the visual encoding.
scatterPlot
.xColumn("atbat")
.yColumn("hits");
var interestingColumns = ["atbat", "hits", "homeruns", "rbi", "runs", "walks", "years"];
// Load the data.
d3.csv("Baseball.csv", function (d){
interestingColumns.forEach(function (column){
d[column] = +d[column];
});
return d;
}, function(err, data){
scatterPlot.data(data);
});
function randomColumn(){
var i = Math.floor(Math.random() * interestingColumns.length);
return interestingColumns[i];
}
// Update the X and Y columns randomly.
setInterval(function (){
scatterPlot.xColumn(randomColumn());
}, 2345);
setInterval(function (){
scatterPlot.yColumn(randomColumn());
}, 3456);
}
main();
</script>
</body>
</html>
https://d3js.org/d3.v4.0.0-alpha.49.min.js
https://datavis-tech.github.io/reactive-model/reactive-model-v0.11.0.min.js