block by jeremycflin 6cc18742ed2832e2cc693fc1df87ff7a

Labeled Streamgraph

Full Screen

Uses d3-area-label to position labels on a StreamGraph.

Inspired by Noah Veltman’s Block: Stacked area label placement

Built with blockbuilder.org

forked from curran‘s block: Labeled Streamgraph

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <script src="https://unpkg.com/d3@4.10.0"></script>
    <script src="https://unpkg.com/d3-area-label@1.0.0"></script>
    <title>Area Label Test</title>
    <style>
      .area-label {
        font-family: sans-serif;
        fill-opacity: 0.7;
        fill: white;
      }
      path {
        fill-opacity: 0.6;
      }
    </style>
  </head>
  <body>
    <svg width="960" height="500"></svg>
    <script>
      const generateData = keys => {
        const n = 100
        const prev = {}
        const velocity = {}
        const data = d3.range(n).map((d, i) => {
          const row = { time: i }
          keys.forEach(key => {
            velocity[key] = ((velocity[key] || 0) + (Math.random() - .5)) * 0.9
            const value = Math.max(0.2, (prev[key] || Math.random() * 10) + velocity[key])
            prev[key] = row[key] = value
          })
          return row
        })
        data.keys = keys
        return data
      }
      
      const svg = d3.select('svg')
      const width = +svg.attr('width')
      const height = +svg.attr('height')
      
      const stack = d3.stack().offset(d3.stackOffsetWiggle)
      const xValue = d => d.time
      const xScale = d3.scaleLinear()
      const yScale = d3.scaleLinear()
      const colorScale = d3.scaleOrdinal().range(d3.schemeCategory10)

      const area = d3.area()
        .x(d => xScale(xValue(d.data)))
        .y0(d => yScale(d[0]))
        .y1(d => yScale(d[1]))
        .curve(d3.curveBasis)
      
      const render = (data) => {
        stack.keys(data.keys)
        colorScale.domain(data.keys)
        const stacked = stack(data)
      	
        xScale
          .domain(d3.extent(data, d => xValue(d)))
          .range([0, width])
        
        yScale
          .domain([
            d3.min(stacked[0], d => d[0]),
            d3.max(stacked[stacked.length - 1], d => d[1])
          ])
          .range([height, 0])
        
        const transition = d3.transition().duration(1000)
        
        const paths = svg.selectAll('path').data(stacked)
        paths
          .enter().append('path')
          .merge(paths)
            .attr('fill', d => colorScale(d.key))
            .attr('stroke', d => colorScale(d.key))
          .transition(transition)
            .attr('d', area)

        const labels = svg.selectAll('text').data(stacked)
        labels
          .enter().append('text')
            .attr('class', 'area-label')
          .merge(labels)
            .text(d => d.key)
          .transition(transition)
            .attr('transform', d3.areaLabel(area))
      }
      
      const renderGeneratedData = () => {
        render(generateData([
          'Leonardo',
          'Donatello',
          'Raphael',
          'Michelangelo'
        ]))
      }
      
      renderGeneratedData()
      setInterval(renderGeneratedData, 2000)
      
    </script>
  </body>
</html>