An example of a Scatter Plot component using a React-inspired pattern for stateless components.
forked from curran‘s block: Scatter Plot with Color Legend
forked from curran‘s block: Responsive Scatter Plot I
<!DOCTYPE html>
<html>
<head>
<title>Solution</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.24.0/d3-legend.min.js"></script>
<style>
body {
margin: 0px;
}
.domain {
display: none;
}
.tick line {
stroke: #C0C0BB;
}
.tick text, .legendCells text {
fill: #8E8883;
font-size: 28pt;
font-family: sans-serif;
}
.axis-label, .legend-label {
fill: #635F5D;
font-size: 50pt;
font-family: sans-serif;
}
/* Make the chart container fill the page using CSS. */
#visualization {
position: fixed;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
}
</style>
</head>
<body>
<div id="visualization"></div>
<script>
// A Scatter Plot component, using the revealing module pattern.
const ScatterPlot = (() => {
const xScale = d3.scaleLinear();
const yScale = d3.scaleLinear();
const colorScale = d3.scaleOrdinal()
.range(d3.schemeCategory10);
const xAxis = d3.axisBottom()
.scale(xScale)
.tickPadding(15);
const yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5)
.tickPadding(15);
const colorLegend = d3.legendColor()
.scale(colorScale)
.shape('circle');
let data;
let width;
let height;
let xValue;
let xLabel;
let yValue;
let yLabel;
let colorValue;
let colorLabel;
let margin = { left: 120, right: 300, top: 20, bottom: 120 };
let colorLegendX = 60;
let colorLegendY = 150;
let colorLegendLabelX = -30;
let colorLegendLabelY = -40;
let xAxisLabelOffset = 100;
let yAxisLabelOffset = -60;
let circleOpacity = 1;
let circleRadius = 10;
const my = selection => {
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
xAxis.tickSize(-innerHeight);
yAxis.tickSize(-innerWidth);
let g = selection.selectAll('.container').data([null]);
const gEnter = g.enter().append('g').attr('class', 'container');
g = gEnter
.merge(g)
.attr('transform', `translate(${margin.left},${margin.top})`);
const xAxisGEnter = gEnter.append('g').attr('class', 'x-axis');
const xAxisG = xAxisGEnter
.merge(g.select('.x-axis'))
.attr('transform', `translate(0, ${innerHeight})`);
xAxisGEnter
.append('text')
.attr('class', 'axis-label')
.attr('y', xAxisLabelOffset)
.merge(xAxisG.select('.axis-label'))
.attr('x', innerWidth / 2)
.text(xLabel);
const yAxisGEnter = gEnter.append('g').attr('class', 'y-axis');
const yAxisG = yAxisGEnter.merge(g.select('.y-axis'));
yAxisGEnter
.append('text')
.attr('class', 'axis-label')
.attr('y', yAxisLabelOffset)
.style('text-anchor', 'middle')
.merge(yAxisG.select('.axis-label'))
.attr('x', -innerHeight / 2)
.attr('transform', `rotate(-90)`)
.text(yLabel);
const colorLegendGEnter = gEnter.append('g').attr('class', 'legend');
const colorLegendG = colorLegendGEnter
.merge(g.select('.legend'))
.attr('transform', `translate(${innerWidth + colorLegendX},${colorLegendY})`);
colorLegendGEnter
.append('text')
.attr('class', 'legend-label')
.attr('x', colorLegendLabelX)
.attr('y', colorLegendLabelY)
.merge(colorLegendG.select('legend-label'))
.text(colorLabel);
xScale
.domain(d3.extent(data, xValue))
.range([0, innerWidth])
.nice();
yScale
.domain(d3.extent(data, yValue))
.range([innerHeight, 0])
.nice();
const circles = g.selectAll('.mark').data(data);
circles
.enter().append('circle')
.attr('class', 'mark')
.attr('fill-opacity', circleOpacity)
.attr('r', circleRadius)
.merge(circles)
.attr('cx', d => xScale(xValue(d)))
.attr('cy', d => yScale(yValue(d)))
.attr('fill', d => colorScale(colorValue(d)));
xAxisG.call(xAxis);
yAxisG.call(yAxis);
colorLegendG.call(colorLegend)
.selectAll('.cell text')
.attr('dy', '0.1em');
};
my.margin = function(_) {
return arguments.length ? (margin = _, my) : margin;
};
my.data = function(_) {
return arguments.length ? (data = _, my) : data;
};
my.width = function(_) {
return arguments.length ? (width = _, my) : width;
};
my.height = function(_) {
return arguments.length ? (height = _, my) : height;
};
my.xValue = function(_) {
return arguments.length ? (xValue = _, my) : xValue;
};
my.xLabel = function(_) {
return arguments.length ? (xLabel = _, my) : xLabel;
};
my.yValue = function(_) {
return arguments.length ? (yValue = _, my) : yValue;
};
my.yLabel = function(_) {
return arguments.length ? (yLabel = _, my) : yLabel;
};
my.colorValue = function(_) {
return arguments.length ? (colorValue = _, my) : colorValue;
};
my.colorLabel = function(_) {
return arguments.length ? (colorLabel = _, my) : colorLabel;
};
my.colorLegendX = function(_) {
return arguments.length ? (colorLegendX = _, my) : colorLegendX;
};
my.colorLegendY = function(_) {
return arguments.length ? (colorLegendY = _, my) : colorLegendY;
};
my.colorLegendLabelX = function(_) {
return arguments.length ? (colorLegendLabelX = _, my) : colorLegendLabelX;
};
my.colorLegendLabelY = function(_) {
return arguments.length ? (colorLegendY = _, my) : colorLegendY;
};
my.xAxisLabelOffset = function(_) {
return arguments.length ? (xAxisLabelOffset = _, my) : xAxisLabelOffset;
};
my.yAxisLabelOffset = function(_) {
return arguments.length ? (yAxisLabelOffset = _, my) : yAxisLabelOffset;
};
my.circleOpacity = function(_) {
return arguments.length ? (circleOpacity = _, my) : circleOpacity;
};
my.circleRadius = function(_) {
return arguments.length ? (circleRadius = _, my) : circleRadius;
};
return my;
});
// The main entry point, which uses the Scatter Plot component.
function main(){
const visualization = d3.select('#visualization');
const visualizationDiv = visualization.node();
const svg = visualization.append('svg');
const scatterPlot = ScatterPlot()
.xValue(d => d.sepalLength)
.xLabel('Sepal Length')
.yValue(d => d.petalLength)
.yLabel('Petal Length')
.colorValue(d => d.species)
.colorLabel('Species')
.circleOpacity(0.6)
.circleRadius(8);
const row = d => {
d.petalLength = +d.petalLength;
d.petalWidth = +d.petalWidth;
d.sepalLength = +d.sepalLength;
d.sepalWidth = +d.sepalWidth;
return d;
};
d3.csv('iris.csv', row, data => {
scatterPlot.data(data);
// Render the scatter plot with updated width and height.
const render = () => {
const width = visualizationDiv.clientWidth;
const height = visualizationDiv.clientHeight;
scatterPlot
.width(width)
.height(height);
svg
.attr('width', width)
.attr('height', height)
.call(scatterPlot);
}
// Draw for the first time to initialize.
render();
// Redraw based on the new size whenever the browser window is resized.
window.addEventListener('resize', render);
});
}
main();
</script>
</body>
</html>
sepalLength,sepalWidth,petalLength,petalWidth,species
5.1,3.5,1.4,0.2,setosa
4.9,3.0,1.4,0.2,setosa
4.7,3.2,1.3,0.2,setosa
4.6,3.1,1.5,0.2,setosa
5.0,3.6,1.4,0.2,setosa
5.4,3.9,1.7,0.4,setosa
4.6,3.4,1.4,0.3,setosa
5.0,3.4,1.5,0.2,setosa
4.4,2.9,1.4,0.2,setosa
4.9,3.1,1.5,0.1,setosa
5.4,3.7,1.5,0.2,setosa
4.8,3.4,1.6,0.2,setosa
4.8,3.0,1.4,0.1,setosa
4.3,3.0,1.1,0.1,setosa
5.8,4.0,1.2,0.2,setosa
5.7,4.4,1.5,0.4,setosa
5.4,3.9,1.3,0.4,setosa
5.1,3.5,1.4,0.3,setosa
5.7,3.8,1.7,0.3,setosa
5.1,3.8,1.5,0.3,setosa
5.4,3.4,1.7,0.2,setosa
5.1,3.7,1.5,0.4,setosa
4.6,3.6,1.0,0.2,setosa
5.1,3.3,1.7,0.5,setosa
4.8,3.4,1.9,0.2,setosa
5.0,3.0,1.6,0.2,setosa
5.0,3.4,1.6,0.4,setosa
5.2,3.5,1.5,0.2,setosa
5.2,3.4,1.4,0.2,setosa
4.7,3.2,1.6,0.2,setosa
4.8,3.1,1.6,0.2,setosa
5.4,3.4,1.5,0.4,setosa
5.2,4.1,1.5,0.1,setosa
5.5,4.2,1.4,0.2,setosa
4.9,3.1,1.5,0.1,setosa
5.0,3.2,1.2,0.2,setosa
5.5,3.5,1.3,0.2,setosa
4.9,3.1,1.5,0.1,setosa
4.4,3.0,1.3,0.2,setosa
5.1,3.4,1.5,0.2,setosa
5.0,3.5,1.3,0.3,setosa
4.5,2.3,1.3,0.3,setosa
4.4,3.2,1.3,0.2,setosa
5.0,3.5,1.6,0.6,setosa
5.1,3.8,1.9,0.4,setosa
4.8,3.0,1.4,0.3,setosa
5.1,3.8,1.6,0.2,setosa
4.6,3.2,1.4,0.2,setosa
5.3,3.7,1.5,0.2,setosa
5.0,3.3,1.4,0.2,setosa
7.0,3.2,4.7,1.4,versicolor
6.4,3.2,4.5,1.5,versicolor
6.9,3.1,4.9,1.5,versicolor
5.5,2.3,4.0,1.3,versicolor
6.5,2.8,4.6,1.5,versicolor
5.7,2.8,4.5,1.3,versicolor
6.3,3.3,4.7,1.6,versicolor
4.9,2.4,3.3,1.0,versicolor
6.6,2.9,4.6,1.3,versicolor
5.2,2.7,3.9,1.4,versicolor
5.0,2.0,3.5,1.0,versicolor
5.9,3.0,4.2,1.5,versicolor
6.0,2.2,4.0,1.0,versicolor
6.1,2.9,4.7,1.4,versicolor
5.6,2.9,3.6,1.3,versicolor
6.7,3.1,4.4,1.4,versicolor
5.6,3.0,4.5,1.5,versicolor
5.8,2.7,4.1,1.0,versicolor
6.2,2.2,4.5,1.5,versicolor
5.6,2.5,3.9,1.1,versicolor
5.9,3.2,4.8,1.8,versicolor
6.1,2.8,4.0,1.3,versicolor
6.3,2.5,4.9,1.5,versicolor
6.1,2.8,4.7,1.2,versicolor
6.4,2.9,4.3,1.3,versicolor
6.6,3.0,4.4,1.4,versicolor
6.8,2.8,4.8,1.4,versicolor
6.7,3.0,5.0,1.7,versicolor
6.0,2.9,4.5,1.5,versicolor
5.7,2.6,3.5,1.0,versicolor
5.5,2.4,3.8,1.1,versicolor
5.5,2.4,3.7,1.0,versicolor
5.8,2.7,3.9,1.2,versicolor
6.0,2.7,5.1,1.6,versicolor
5.4,3.0,4.5,1.5,versicolor
6.0,3.4,4.5,1.6,versicolor
6.7,3.1,4.7,1.5,versicolor
6.3,2.3,4.4,1.3,versicolor
5.6,3.0,4.1,1.3,versicolor
5.5,2.5,4.0,1.3,versicolor
5.5,2.6,4.4,1.2,versicolor
6.1,3.0,4.6,1.4,versicolor
5.8,2.6,4.0,1.2,versicolor
5.0,2.3,3.3,1.0,versicolor
5.6,2.7,4.2,1.3,versicolor
5.7,3.0,4.2,1.2,versicolor
5.7,2.9,4.2,1.3,versicolor
6.2,2.9,4.3,1.3,versicolor
5.1,2.5,3.0,1.1,versicolor
5.7,2.8,4.1,1.3,versicolor
6.3,3.3,6.0,2.5,virginica
5.8,2.7,5.1,1.9,virginica
7.1,3.0,5.9,2.1,virginica
6.3,2.9,5.6,1.8,virginica
6.5,3.0,5.8,2.2,virginica
7.6,3.0,6.6,2.1,virginica
4.9,2.5,4.5,1.7,virginica
7.3,2.9,6.3,1.8,virginica
6.7,2.5,5.8,1.8,virginica
7.2,3.6,6.1,2.5,virginica
6.5,3.2,5.1,2.0,virginica
6.4,2.7,5.3,1.9,virginica
6.8,3.0,5.5,2.1,virginica
5.7,2.5,5.0,2.0,virginica
5.8,2.8,5.1,2.4,virginica
6.4,3.2,5.3,2.3,virginica
6.5,3.0,5.5,1.8,virginica
7.7,3.8,6.7,2.2,virginica
7.7,2.6,6.9,2.3,virginica
6.0,2.2,5.0,1.5,virginica
6.9,3.2,5.7,2.3,virginica
5.6,2.8,4.9,2.0,virginica
7.7,2.8,6.7,2.0,virginica
6.3,2.7,4.9,1.8,virginica
6.7,3.3,5.7,2.1,virginica
7.2,3.2,6.0,1.8,virginica
6.2,2.8,4.8,1.8,virginica
6.1,3.0,4.9,1.8,virginica
6.4,2.8,5.6,2.1,virginica
7.2,3.0,5.8,1.6,virginica
7.4,2.8,6.1,1.9,virginica
7.9,3.8,6.4,2.0,virginica
6.4,2.8,5.6,2.2,virginica
6.3,2.8,5.1,1.5,virginica
6.1,2.6,5.6,1.4,virginica
7.7,3.0,6.1,2.3,virginica
6.3,3.4,5.6,2.4,virginica
6.4,3.1,5.5,1.8,virginica
6.0,3.0,4.8,1.8,virginica
6.9,3.1,5.4,2.1,virginica
6.7,3.1,5.6,2.4,virginica
6.9,3.1,5.1,2.3,virginica
5.8,2.7,5.1,1.9,virginica
6.8,3.2,5.9,2.3,virginica
6.7,3.3,5.7,2.5,virginica
6.7,3.0,5.2,2.3,virginica
6.3,2.5,5.0,1.9,virginica
6.5,3.0,5.2,2.0,virginica
6.2,3.4,5.4,2.3,virginica
5.9,3.0,5.1,1.8,virginica