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
license: mit |
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
[{"x":0,"y":10,"id":"lineEmoji_t-5_point-10"},{"x":1,"y":10,"id":"lineEmoji_t-4_point-10"},{"x":2,"y":10,"id":"lineEmoji_t-3_point-10"},{"x":2,"y":0,"id":"lineEmoji_t-3_point-0"},{"x":2,"y":25,"id":"lineEmoji_t-3_point-25"},{"x":2,"y":12,"id":"lineEmoji_t-3_point-12"},{"x":3,"y":11,"id":"lineEmoji_t-2_point-11"},{"x":3,"y":2,"id":"lineEmoji_t-2_point-2"},{"x":3,"y":25,"id":"lineEmoji_t-2_point-25"},{"x":3,"y":13,"id":"lineEmoji_t-2_point-13"},{"x":4,"y":15,"id":"lineEmoji_t-1_point-15"},{"x":4,"y":5,"id":"lineEmoji_t-1_point-5"},{"x":4,"y":25,"id":"lineEmoji_t-1_point-25"},{"x":5,"y":17.5,"id":"lineEmoji_t0_point-17-5"},{"x":5,"y":10,"id":"lineEmoji_t0_point-10"},{"x":5,"y":25,"id":"lineEmoji_t0_point-25"},{"x":6,"y":20,"id":"lineEmoji_t1_point-20"},{"x":6,"y":15,"id":"lineEmoji_t1_point-15"},{"x":6,"y":25,"id":"lineEmoji_t1_point-25"},{"x":7,"y":22.5,"id":"lineEmoji_t2_point-22-5"},{"x":7,"y":20,"id":"lineEmoji_t2_point-20"},{"x":7,"y":25,"id":"lineEmoji_t2_point-25"},{"x":8,"y":25,"id":"lineEmoji_t3_point-25"},{"x":9,"y":27.5,"id":"lineEmoji_t4_point-27-5"},{"x":9,"y":30,"id":"lineEmoji_t4_point-30"},{"x":9,"y":25,"id":"lineEmoji_t4_point-25"},{"x":10,"y":30,"id":"lineEmoji_t5_point-30"},{"x":10,"y":35,"id":"lineEmoji_t5_point-35"},{"x":10,"y":25,"id":"lineEmoji_t5_point-25"},{"x":11,"y":32.5,"id":"lineEmoji_t6_point-32-5"},{"x":11,"y":40,"id":"lineEmoji_t6_point-40"},{"x":11,"y":25,"id":"lineEmoji_t6_point-25"},{"x":12,"y":35,"id":"lineEmoji_t7_point-35"},{"x":12,"y":45,"id":"lineEmoji_t7_point-45"},{"x":12,"y":25,"id":"lineEmoji_t7_point-25"},{"x":13,"y":37.5,"id":"lineEmoji_t8_point-37-5"},{"x":13,"y":50,"id":"lineEmoji_t8_point-50"},{"x":13,"y":25,"id":"lineEmoji_t8_point-25"},{"x":14,"y":40,"id":"lineEmoji_t9_point-40"},{"x":14,"y":55,"id":"lineEmoji_t9_point-55"},{"x":15,"y":42.5,"id":"lineEmoji_t10_point-42-5"},{"x":15,"y":60,"id":"lineEmoji_t10_point-60"},{"x":16,"y":45,"id":"lineEmoji_t11_point-45"},{"x":16,"y":65,"id":"lineEmoji_t11_point-65"},{"x":17,"y":47.5,"id":"lineEmoji_t12_point-47-5"},{"x":17,"y":70,"id":"lineEmoji_t12_point-70"},{"x":18,"y":50,"id":"lineEmoji_t13_point-50"},{"x":18,"y":75,"id":"lineEmoji_t13_point-75"},{"x":19,"y":52.5,"id":"lineEmoji_t14_point-52-5"},{"x":19,"y":80,"id":"lineEmoji_t14_point-80"},{"x":20,"y":55,"id":"lineEmoji_t15_point-55"},{"x":20,"y":85,"id":"lineEmoji_t15_point-85"},{"x":20,"y":65,"id":"lineEmoji_t15_point-65"},{"x":21,"y":57.5,"id":"lineEmoji_t16_point-57-5"},{"x":21,"y":90,"id":"lineEmoji_t16_point-90"},{"x":22,"y":60,"id":"lineEmoji_t17_point-60"},{"x":22,"y":95,"id":"lineEmoji_t17_point-95"},{"x":23,"y":62.5,"id":"lineEmoji_t18_point-62-5"},{"x":23,"y":100,"id":"lineEmoji_t18_point-100"},{"x":24,"y":75,"id":"lineEmoji_t19_point-75"},{"x":25,"y":67.5,"id":"lineEmoji_t20_point-67-5"},{"x":25,"y":110,"id":"lineEmoji_t20_point-110"},{"x":26,"y":70,"id":"lineEmoji_t21_point-70"},{"x":26,"y":115,"id":"lineEmoji_t21_point-115"},{"x":27,"y":72.5,"id":"lineEmoji_t22_point-72-5"},{"x":27,"y":120,"id":"lineEmoji_t22_point-120"},{"x":27,"y":118,"id":"lineEmoji_t22_point-118"},{"x":28,"y":75,"id":"lineEmoji_t23_point-75"},{"x":28,"y":125,"id":"lineEmoji_t23_point-125"},{"x":29,"y":77.5,"id":"lineEmoji_t24_point-77-5"},{"x":29,"y":130,"id":"lineEmoji_t24_point-130"},{"x":29,"y":150,"id":"lineEmoji_t24_point-150"}] |
<!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'); | |
const defaults = { | |
margin: { left: 120, right: 300, top: 20, bottom: 120 }, | |
colorLegendX: 60, | |
colorLegendY: 150, | |
colorLegendLabelX: -30, | |
colorLegendLabelY: -40, | |
xAxisLabelOffset: 100, | |
yAxisLabelOffset: -60, | |
circleOpacity: 1, | |
circleRadius: 10 | |
}; | |
return (svg, props) => { | |
const { | |
data, | |
width, | |
height, | |
xValue, | |
xLabel, | |
yValue, | |
yLabel, | |
colorValue, | |
colorLabel, | |
margin, | |
colorLegendX, | |
colorLegendY, | |
colorLegendLabelX, | |
colorLegendLabelY, | |
xAxisLabelOffset, | |
yAxisLabelOffset, | |
circleOpacity, | |
circleRadius, | |
} = Object.assign({}, defaults, props); | |
const innerWidth = width - margin.left - margin.right; | |
const innerHeight = height - margin.top - margin.bottom; | |
xAxis.tickSize(-innerHeight); | |
yAxis.tickSize(-innerWidth); | |
svg | |
.attr('width', width) | |
.attr('height', height); | |
let g = svg.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'); | |
} | |
})(); | |
// 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 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 => { | |
// Render the scatter plot with updated width and height. | |
const render = () => { | |
ScatterPlot(svg, { | |
data, | |
width: visualizationDiv.clientWidth, | |
height: visualizationDiv.clientHeight, | |
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 | |
}); | |
} | |
// 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> |
�PNG | |