block by curran 298c130e67b40ed84f8c919e748d2e72

Scatter Plot with Color Legend

Full Screen

An example of a stylized scatter plot with re-usable code organization using D3 v4.

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Solution</title>
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <style>
      body {
        margin: 0px;
      }
      .axis .tick line {
        stroke-width: 2px;
        stroke: #dddddd;
      }
      .axis .tick text {
      	font-size: 30px;
        fill: #8E8883;
      }
      .axis .domain {
        display: none;
      }
      .axis__label {
        text-anchor: middle;
        font-size: 50px;
        fill: #635F5D;
      }
      .legend .tick text {
        font-size: 30px;
        fill: #8E8883;
        font-family: sans-serif;
        alignment-baseline: middle;
      }
      .legend__label {
        font-size: 45px;
        fill: #635F5D;
        font-family: sans-serif;
      }
    </style>
  </head>
  <body>
    <svg width="960" height="500"></svg>
    <script>
      const xValue = d => d.sepalLength;
      const yValue = d => d.petalLength;
      const colorValue = d => d.species;
      const xLabel = "Sepal Length";
      const yLabel = "Petal Length";
      const margin = {top: 30, right: 300, bottom: 100, left: 100};
      
      const svg = d3.select("svg");
      const width = +svg.attr("width");
      const height = +svg.attr("height");
      const innerWidth = width - margin.left - margin.right;
      const innerHeight = height - margin.top - margin.bottom;
      const xScale = d3.scaleLinear().range([0, innerWidth]);
      const yScale = d3.scaleLinear().range([innerHeight, 0]);
      const colorScale = d3.scaleOrdinal()
        .range(["#eb8e37", "#1ac6cf", "#e35dd4"]);
      
      const xAxis = d3.axisBottom()
        .scale(xScale)
        .tickSizeInner(-innerHeight)
        .tickPadding(15);
      const yAxis = d3.axisLeft()
        .scale(yScale)
        .tickSizeInner(-innerWidth)
        .ticks(5)
        .tickPadding(10);
      
      const g = svg.append("g")
          .attr("transform", `translate(${margin.left}, ${margin.top})`);
      const xAxisG = g.append("g")
          .attr("class", "axis")
          .attr("transform", `translate(0, ${innerHeight})`);
      const yAxisG = g.append("g")
          .attr("class", "axis");
      
      xAxisG
        .append("text")
          .attr("class", "axis__label")
          .attr("x", innerWidth / 2)
          .attr("y", 85)
          .text(xLabel);
      
      yAxisG
        .append("text")
          .attr("class", "axis__label")
          .attr("transform", "rotate(-90)")
          .attr("x", -innerHeight / 2)
          .attr("y", -45)
          .text(yLabel);
      
      function colorLegend(selection, props){
        const colorScale = props.colorScale;
        const positionX = props.positionX;
        const positionY = props.positionY;
        const tickRadius = props.tickRadius;
        const tickSpacing = props.tickSpacing;
        const tickPadding = props.tickPadding;
        const label = props.label;
        const labelX = props.labelX;
        const labelY = props.labelY;
        
        let legendG = selection
          .selectAll(".legend--color")
          .data([null]);
        legendG = legendG
          .enter().append("g")
            .attr("class", "legend legend--color")
          .merge(legendG)
            .attr("transform", `translate(${positionX}, ${positionY})`);
        
        const legendLabel = legendG
          .selectAll(".legend__label")
          .data([null]);
        legendLabel
          .enter().append("text")
            .attr("class", "legend__label")
          .merge(legendLabel)
            .attr("x", labelX)
            .attr("y", labelY)
            .text(label);
        
        const ticks = legendG
          .selectAll(".tick")
          .data(colorScale.domain());
        const ticksEnter = ticks
          .enter().append("g")
            .attr("class", "tick");
        ticksEnter
          .merge(ticks)
            .attr("transform", (d, i) => `translate(0, ${i * tickSpacing})`);
        ticks.exit().remove();
        
        ticksEnter
          .append("circle")
          .merge(ticks.select("circle"))
            .attr("r", tickRadius)
            .attr("fill", colorScale);
        
        ticksEnter
          .append("text")
          .merge(ticks.select("text"))
            .attr("x", tickRadius + tickPadding)
            .text(d => d);
      }
      
      function render(data){
        xScale
          .domain(d3.extent(data, xValue))
          .nice();
        yScale
          .domain(d3.extent(data, yValue))
          .nice();
        colorScale
          .domain(d3.set(data.map(colorValue)).values().sort());
        
        xAxisG.call(xAxis);
        yAxisG.call(yAxis);
        
        const circles = g.selectAll("circle").data(data);
        circles.exit().remove();
        circles
          .enter().append("circle")
            .attr("r", 5)
          .merge(circles)
            .attr("cx", d => xScale(xValue(d)))
            .attr("cy", d => yScale(yValue(d)))
            .attr("fill", d => colorScale(colorValue(d)));
        
        svg.call(colorLegend, {
          colorScale: colorScale,
          positionX: 750,
          positionY: 200,
          tickRadius: 12,
          tickSpacing: 35,
          tickPadding: 6,
          label: "Species",
          labelX: -20,
          labelY: -30
        });
      }
            
      function type(d){
        d.sepalLength = +d.sepalLength;
        d.sepalWidth = +d.sepalWidth;
        d.petalLength = +d.petalLength;
        d.petalWidth = +d.petalWidth;
        return d;
      }
      
      d3.csv("iris.csv", type, render);
      
    </script>
  </body>
</html>

iris.csv

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