block by renecnielsen 80c9757989e58644fc32aa7ccf9c2173

d3-annotation v2.0

Full Screen

d3-annotation v2.0

Updated features for d3-annotation, a full post here.

This block uses all three of the new features:

forked from susielu‘s block: d3-annotation v2.0

index.js

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    margin = { top: 30, right: 130, bottom: 50, left: 340 };

const x = d3.scaleLinear().range([margin.left, width - margin.right]).domain([2013, 2017]);
const y = d3.scaleLinear().range([height - margin.bottom, margin.top]);

d3.json("yearNetwork.json", function (error, json) {
  if (error) throw error;

  y.domain([0, d3.max(json.networkLines, d => d.max)]);
  var line = d3.line().x(function (d) {
    return x(d.year);
  }).y(function (d) {
    return y(d.value);
  });

  const networkLines = json.networkLines;

  const svg = d3.select("svg");
  const colors = {
    HBO: "black",
    Netflix: "#D32F2F",
    NBC: "#ffc107",
    "FX Networks": "#0097a7",
    ABC: "#00BFA5",
    CBS: "#00BCD4",
    FOX: "#3f51b5",
    Showtime: "#C5CAE9",
    AMC: "#D32F2F",
    PBS: "#B39DDB",
    Amazon: "#ffc107",
    "Nat Geo": "#ff9800",
    Hulu: "#00BFA5"
  };

  svg.append("g").attr("class", "lineChart");
  const highlight = ["HBO", "Netflix"];
  svg.select("g.lineChart").selectAll("path.segment").data(networkLines.sort((a, b) => a.total - b.total)).enter().append("path").attr("d", d => {
    return line(d.line);
  }).style("stroke", (d, i) => {
    return colors[d.network] || "grey";
  }).style("stroke-dasharray", (d, i) => {
    return highlight.indexOf(d.network) !== -1 ? "none" : "2, 4";
  });

  /* Code below relevant for annotations */
  let previousNY = 0;
  const labelAnnotations = networkLines.sort(
  //sort annotations by last data point for ordering
  (a, b) => b.line[b.line.length - 1].value - a.line[a.line.length - 1].value).reduce((p, c) => {
    //push annotation down if it will overlap
    const ypx = y(c.line[c.line.length - 1].value);
    let ny;

    if (ypx - previousNY < 10) {
      ny = previousNY + 15;
    }

    p.push({
      note: { label: c.network, orientation: "leftRight", align: "middle" },
      y: ypx,
      x: width - margin.right,
      dx: highlight.indexOf(c.network) !== -1 ? 20 : 5,
      id: c.network,
      color: colors[c.network],
      disable: ["connector"],
      ny //use ny to directly place the note in xy space if needed
    });
    previousNY = ny || ypx;

    return p;
  }, []);

  const axisAnnotations = json.networkLines.filter(d => d.network === "HBO")[0].line.map(d => ({
    note: { label: d.year, align: "middle", lineType: "none" },
    type: d3.annotationXYThreshold,
    ny: 190,
    className: "axis",
    y: 190,
    x: x(d.year),
    subject: {
      y1: y(0),
      y2: y(d.value)
    }
  }));

  const labels = networkLines.filter(d => highlight.indexOf(d.network) !== -1).reduce((p, c) => {
    p = p.concat(c.line.map(d => {
      return {
        network: c.network,
        year: d.year,
        value: d.value
      };
    }));
    return p;
  }, []);

  const badgeAnnotations = labels.map(d => {
    return {
      subject: {
        text: d.value,
        radius: 12
      },
      color: colors[d.network],
      type: d3.annotationBadge,
      x: x(d.year),
      y: y(d.value)
    };
  });

  const makeAnnotations = d3.annotation().type(d3.annotationLabel).annotations([...labelAnnotations, ...axisAnnotations, ...badgeAnnotations]);

  d3.select("svg").append("g").attr("class", "annotation-group").call(makeAnnotations);

  svg.append("line").attr("class", "baseline axis").attr("x1", x(2013)).attr("x2", x(2017)).attr("y1", y(0)).attr("y2", y(0)).style("stroke", "lightgrey");
});

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <link href='https://fonts.googleapis.com/css?family=Lato:300,900' rel='stylesheet' type='text/css'>

  <style>
    body {
      background-color: whitesmoke;
    }

    svg {
      background-color: white;
      font-family: 'Lato';
    }

    path.line {
      stroke: lightgrey;
    }

    .annotation path.connector {
      stroke-dasharray: 1, 1;
    }

    .annotation-note-title {
      font-weight: bold;
    }

    .annotation.xythreshold {
      cursor: move;
    }


    .annotation text {
      font-size: .7em;
      text-transform: uppercase;
      font-weight: bold;
    }


    text.title {
      font-size: 1.1em;
    }

    .lineChart path {
      fill: none;
      stroke-width: 2;
    }

    path.domain {
      stroke: lightgrey;
    }

    .annotation.axis .annotation-note-bg {
      fill: white;
      stroke: white;
      stroke-width: 10;
    }

    .annotation.axis path {
      stroke: lightgrey;
    }

    .annotation.axis text {
      fill: lightgrey;
    }

    .annotation.title text {
      font-size: 2em;
      font-weight: 100;
    }

    div.title {
      font-size: 2em;
      width: 200px;
      position: absolute;
      left: 70px;
      top: 100px;
      font-family: "Lato"
    }

    .annotation.badge text {
      font-weight: normal;
      font-size: 10px;
    }

    .subject-ring {
      display: none;
    }
  </style>
</head>

<body>
  <svg width=960 height=500>
  </svg>
  <div class="title">
    <b style="color:#d32f2f">Netflix</b> Challenges <b>HBO</b> at the 2017 Emmys
  </div>
  <script src="https://d3js.org/d3.v4.js"></script>
  <script src="https://cdn.rawgit.com/susielu/d3-annotation/75ff6169/d3-annotation.js"></script>
  <script src="index.js"></script>
</body>

</html>

index-es6.js

var svg = d3.select("svg"),
  width = +svg.attr("width"),
  height = +svg.attr("height"),
  margin = { top: 30, right: 130, bottom: 50, left: 340 }

const x = d3
  .scaleLinear()
  .range([margin.left, width - margin.right])
  .domain([2013, 2017])
const y = d3.scaleLinear().range([height - margin.bottom, margin.top])

d3.json("yearNetwork.json", function(error, json) {
  if (error) throw error

  y.domain([0, d3.max(json.networkLines, d => d.max)])
  var line = d3
    .line()
    .x(function(d) {
      return x(d.year)
    })
    .y(function(d) {
      return y(d.value)
    })

  const networkLines = json.networkLines

  const svg = d3.select("svg")
  const colors = {
    HBO: "black",
    Netflix: "#D32F2F",
    NBC: "#ffc107",
    "FX Networks": "#0097a7",
    ABC: "#00BFA5",
    CBS: "#00BCD4",
    FOX: "#3f51b5",
    Showtime: "#C5CAE9",
    AMC: "#D32F2F",
    PBS: "#B39DDB",
    Amazon: "#ffc107",
    "Nat Geo": "#ff9800",
    Hulu: "#00BFA5"
  }

  svg.append("g").attr("class", "lineChart")
  const highlight = ["HBO", "Netflix"]
  svg
    .select("g.lineChart")
    .selectAll("path.segment")
    .data(networkLines.sort((a, b) => a.total - b.total))
    .enter()
    .append("path")
    .attr("d", d => {
      return line(d.line)
    })
    .style("stroke", (d, i) => {
      return colors[d.network] || "grey"
    })
    .style("stroke-dasharray", (d, i) => {
      return highlight.indexOf(d.network) !== -1 ? "none" : "2, 4"
    })

  /* Code below relevant for annotations */
  let previousNY = 0
  const labelAnnotations = networkLines
    .sort(
      //sort annotations by last data point for ordering
      (a, b) =>
        b.line[b.line.length - 1].value - a.line[a.line.length - 1].value
    )
    .reduce((p, c) => {
      //push annotation down if it will overlap
      const ypx = y(c.line[c.line.length - 1].value)
      let ny

      if (ypx - previousNY < 10) {
        ny = previousNY + 15
      }

      p.push({
        note: { label: c.network, orientation: "leftRight", align: "middle" },
        y: ypx,
        x: width - margin.right,
        dx: highlight.indexOf(c.network) !== -1 ? 20 : 5,
        id: c.network,
        color: colors[c.network],
        disable: ["connector"],
        ny //use ny to directly place the note in xy space if needed
      })
      previousNY = ny || ypx

      return p
    }, [])

  const axisAnnotations = json.networkLines
    .filter(d => d.network === "HBO")[0]
    .line.map(d => ({
      note: { label: d.year, align: "middle", lineType: "none" },
      type: d3.annotationXYThreshold,
      ny: 190,
      className: "axis",
      y: 190,
      x: x(d.year),
      subject: {
        y1: y(0),
        y2: y(d.value)
      }
    }))

  const labels = networkLines
    .filter(d => highlight.indexOf(d.network) !== -1)
    .reduce((p, c) => {
      p = p.concat(
        c.line.map(d => {
          return {
            network: c.network,
            year: d.year,
            value: d.value
          }
        })
      )
      return p
    }, [])

  const badgeAnnotations = labels.map(d => {
    return {
      subject: {
        text: d.value,
        radius: 12
      },
      color: colors[d.network],
      type: d3.annotationBadge,
      x: x(d.year),
      y: y(d.value)
    }
  })

  const makeAnnotations = d3
    .annotation()
    .type(d3.annotationLabel)
    .annotations([...labelAnnotations, ...axisAnnotations, ...badgeAnnotations])

  d3
    .select("svg")
    .append("g")
    .attr("class", "annotation-group")
    .call(makeAnnotations)

  svg
    .append("line")
    .attr("class", "baseline axis")
    .attr("x1", x(2013))
    .attr("x2", x(2017))
    .attr("y1", y(0))
    .attr("y2", y(0))
    .style("stroke", "lightgrey")
})

yearNetwork.json

{
  "networkLines": [
    {
      "network": "AMC",
      "line": [
        { "year": "2013", "value": 26 },
        { "year": "2014", "value": 26 },
        { "year": "2015", "value": 24 },
        { "year": "2016", "value": 24 },
        { "year": "2017", "value": 13 }
      ],
      "total": 113,
      "max": 26
    },

    {
      "network": "Showtime",
      "line": [
        { "year": "2013", "value": 32 },
        { "year": "2014", "value": 24 },
        { "year": "2015", "value": 18 },
        { "year": "2016", "value": 22 },
        { "year": "2017", "value": 15 }
      ],
      "total": 111,
      "max": 32
    },

    {
      "network": "ABC",
      "line": [
        { "year": "2013", "value": 45 },
        { "year": "2014", "value": 37 },
        { "year": "2015", "value": 42 },
        { "year": "2016", "value": 35 },
        { "year": "2017", "value": 33 }
      ],
      "total": 192,
      "max": 45
    },
    {
      "network": "Netflix",
      "line": [
        { "year": "2013", "value": 14 },
        { "year": "2014", "value": 31 },
        { "year": "2015", "value": 34 },
        { "year": "2016", "value": 54 },
        { "year": "2017", "value": 91 }
      ],
      "total": 224,
      "max": 91
    },
    {
      "network": "HBO",
      "line": [
        { "year": "2013", "value": 109 },
        { "year": "2014", "value": 99 },
        { "year": "2015", "value": 126 },
        { "year": "2016", "value": 94 },
        { "year": "2017", "value": 111 }
      ],
      "total": 539,
      "max": 126
    },
    {
      "network": "CBS",
      "line": [
        { "year": "2013", "value": 54 },
        { "year": "2014", "value": 47 },
        { "year": "2015", "value": 41 },
        { "year": "2016", "value": 35 },
        { "year": "2017", "value": 29 }
      ],
      "total": 206,
      "max": 54
    },
    {
      "network": "FX Networks",
      "line": [
        { "year": "2013", "value": 26 },
        { "year": "2014", "value": 45 },
        { "year": "2015", "value": 39 },
        { "year": "2016", "value": 57 },
        { "year": "2017", "value": 55 }
      ],
      "total": 222,
      "max": 57
    },
    {
      "network": "NBC",
      "line": [
        { "year": "2013", "value": 53 },
        { "year": "2014", "value": 47 },
        { "year": "2015", "value": 43 },
        { "year": "2016", "value": 41 },
        { "year": "2017", "value": 64 }
      ],
      "total": 248,
      "max": 64
    },
    {
      "network": "FOX",
      "line": [
        { "year": "2013", "value": 20 },
        { "year": "2014", "value": 21 },
        { "year": "2015", "value": 36 },
        { "year": "2016", "value": 30 },
        { "year": "2017", "value": 20 }
      ],
      "total": 127,
      "max": 36
    },

    {
      "network": "PBS",
      "line": [
        { "year": "2013", "value": 25 },
        { "year": "2014", "value": 34 },
        { "year": "2015", "value": 30 },
        { "year": "2016", "value": 26 },
        { "year": "2017", "value": 11 }
      ],
      "total": 126,
      "max": 34
    },

    {
      "network": "Nat Geo",
      "line": [
        { "year": "2014", "value": 4 },
        { "year": "2015", "value": 4 },
        { "year": "2016", "value": 10 },
        { "year": "2017", "value": 15 }
      ],
      "total": 33,
      "max": 10
    },
    {
      "network": "Amazon",
      "line": [
        { "year": "2015", "value": 12 },
        { "year": "2016", "value": 16 },
        { "year": "2017", "value": 16 }
      ],
      "total": 44,
      "max": 16
    },

    {
      "network": "Hulu",
      "line": [{ "year": "2016", "value": 2 }, { "year": "2017", "value": 18 }],
      "total": 20,
      "max": 18
    }
  ]
}