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 | |