<!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>
[
[
{ "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 }
]
]
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);
};
})();