block by curran c1ce02fec883faa15fe2191d98985440

Visual List Library

Full Screen

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Shopping App Prototype</title>
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script src="visualList.js"></script>
  </head>
  <body>
    <svg width="960" height="500"></svg>
    <script>
      d3.json('data.json', data => {
        data.forEach((list, i) => {
          setTimeout(() => {
            d3.select('svg').call(visualList, {
              data: list,
              xValue: d => d.price,
              margin: {
                left: 220,
                right: 220,
                top: 20,
                bottom: 20
              },
              barPadding: 0.1,
              yValue: d => d.name,
              verticalSpacing: 60,
              transitionDuration: 800,
              textValue: d => `${d.name} $${d.price}`
            });
          }, i * 1000);
        });
      });
    </script>
  </body>
</html>

data.json

[
  [
    { "name": "Milk", "price": 3 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 20 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 },
    { "name": "Cupcakes", "price": 5 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 },
    { "name": "Apples", "price": 4 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 },
    { "name": "Apples", "price": 4 },
    { "name": "Bread", "price": 4 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 },
    { "name": "Apples", "price": 4 },
    { "name": "Bread", "price": 4 },
    { "name": "Cookies", "price": 6 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 },
    { "name": "Apples", "price": 4 },
    { "name": "Bread", "price": 4 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 },
    { "name": "Apples", "price": 4 },
    { "name": "Bread", "price": 4 },
    { "name": "Bananas", "price": 1 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 },
    { "name": "Apples", "price": 4 },
    { "name": "Bread", "price": 4 },
    { "name": "Bananas", "price": 1 },
    { "name": "Onions", "price": 2 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 },
    { "name": "Apples", "price": 4 },
    { "name": "Bread", "price": 4 },
    { "name": "Bananas", "price": 1 },
    { "name": "Onions", "price": 2 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 },
    { "name": "Apples", "price": 4 },
    { "name": "Bread", "price": 4 },
    { "name": "Bananas", "price": 1 },
    { "name": "Onions", "price": 2 },
    { "name": "Garlic", "price": 1 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 },
    { "name": "Apples", "price": 4 },
    { "name": "Bread", "price": 4 },
    { "name": "Bananas", "price": 1 },
    { "name": "Onions", "price": 2 },
    { "name": "Garlic", "price": 1 },
    { "name": "Smoked Salmon", "price": 14.75 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 },
    { "name": "Apples", "price": 4 },
    { "name": "Bread", "price": 4 },
    { "name": "Bananas", "price": 1 },
    { "name": "Onions", "price": 2 },
    { "name": "Garlic", "price": 1 },
    { "name": "Smoked Salmon", "price": 14.75 },
    { "name": "Broccoli", "price": 3.65 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 },
    { "name": "Apples", "price": 4 },
    { "name": "Bread", "price": 4 },
    { "name": "Bananas", "price": 1 },
    { "name": "Onions", "price": 2 },
    { "name": "Garlic", "price": 1 },
    { "name": "Smoked Salmon", "price": 14.75 },
    { "name": "Broccoli", "price": 3.65 },
    { "name": "Coffee", "price": 5.99 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 },
    { "name": "Apples", "price": 4 },
    { "name": "Bread", "price": 4 },
    { "name": "Bananas", "price": 1 },
    { "name": "Onions", "price": 2 },
    { "name": "Garlic", "price": 1 },
    { "name": "Smoked Salmon", "price": 14.75 },
    { "name": "Broccoli", "price": 3.65 },
    { "name": "Coffee", "price": 5.99 },
    { "name": "Sugar", "price": 9.98 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 },
    { "name": "Apples", "price": 4 },
    { "name": "Bread", "price": 4 },
    { "name": "Bananas", "price": 1 },
    { "name": "Onions", "price": 2 },
    { "name": "Garlic", "price": 1 },
    { "name": "Smoked Salmon", "price": 14.75 },
    { "name": "Broccoli", "price": 3.65 },
    { "name": "Coffee", "price": 5.99 },
    { "name": "Sugar", "price": 9.98 },
    { "name": "Mozzarella Cheese", "price": 4 }
  ],
  [
    { "name": "Milk", "price": 3 },
    { "name": "Eggs", "price": 2 },
    { "name": "Apples", "price": 4 },
    { "name": "Bread", "price": 4 },
    { "name": "Bananas", "price": 1 },
    { "name": "Onions", "price": 2 },
    { "name": "Garlic", "price": 1 },
    { "name": "Smoked Salmon", "price": 14.75 },
    { "name": "Broccoli", "price": 3.65 },
    { "name": "Coffee", "price": 5.99 },
    { "name": "Sugar", "price": 9.98 },
    { "name": "Mozzarella Cheese", "price": 4 },
    { "name": "Tomatoes", "price": 3 }
  ]
]

visualList.js

const visualList = (() => {
  const xScale = d3.scaleLinear();
  const yScale = d3.scaleBand();

  return (svg, props) => {
    const data = props.data;
    const xValue = props.xValue;
    const yValue = props.yValue;
    const textValue = props.textValue;
    const margin = props.margin;
    const barPadding = props.barPadding;
    const verticalSpacing = props.verticalSpacing;
    const transitionDuration = props.transitionDuration;

    const transition = d3.transition()
      .duration(transitionDuration);

    data.sort((a, b) => d3.descending(xValue(a), xValue(b)));

    const width = +svg.attr('width');
    const innerWidth = width - margin.left - margin.right;

    const innerHeight = verticalSpacing * data.length;
    svg
      .transition(transition)
        .attr('height', innerHeight + margin.top + margin.bottom);

    xScale
      .domain([0, d3.max(data, xValue)])
      .range([0, innerWidth]);
    yScale
      .paddingInner(barPadding)
      .paddingOuter(barPadding / 2)
      .domain(data.map(yValue))
      .range([0, innerHeight]);

    let g = svg.selectAll('g').data([null]);
    g = g.enter().append('g')
      .merge(g)
        .attr('transform', `translate(${margin.left},${margin.top})`);

    const groups = g.selectAll('g')
      .data(data, yValue);
    const groupsExit = groups.exit();
    groupsExit
      .transition(transition)
        .remove();
    const groupsEnter = groups
      .enter().append('g')
        .attr('transform', d => `translate(0,${yScale(yValue(d))})`);
    groups
      .merge(groupsEnter)
      .transition(transition)
        .attr('transform', d => `translate(0,${yScale(yValue(d))})`);

    const rects = groupsEnter
      .append('rect')
        .attr('fill', 'steelblue')
        .attr('width', 0)
      .merge(groups.select('rect'))
        .attr('height', yScale.bandwidth())
      .transition(transition)
        .attr('width', d => xScale(xValue(d)));
    groupsExit.select('rect')
      .transition(transition)
        .attr('width', 0);

    const textBackground = groupsEnter
      .append('text')
        .attr('class', 'background')
        .style('font-size', '32pt')
        .style('font-family', 'Sans-Serif')
        .attr('x', 5)
        .attr('dy', '0.32em')
        .attr('fill', 'none')
        .attr('stroke', 'white')
        .attr('stroke-width', 5)
        .attr('stroke-linejoin', 'round')
      .merge(groups.select('.background'))
        .attr('y', yScale.bandwidth() / 2)
        .text(textValue);

    const textForeground = groupsEnter
      .append('text')
        .attr('class', 'foreground')
        .style('font-size', '32pt')
        .style('font-family', 'Sans-Serif')
        .attr('x', 5)
        .attr('dy', '0.32em')
      .merge(groups.select('.foreground'))
        .attr('y', yScale.bandwidth() / 2)
        .text(textValue);
  };
})();