xxxxxxxxxx
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
let margin = { top: 20, right: 40, bottom: 20, left: 40 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
padding = 20, fill = 'black';
let svg = d3.select('body').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
d3.csv('tmdb_5000_movies.csv', (d) => {
return {
budget: +d.budget,
popularity: +d.popularity,
revenue: +d.revenue,
runtime: +d.runtime,
vote_average: +d.vote_average,
vote_count: +d.vote_count,
release_date: new Date(d.release_date),
title: d.title
};
})
.then((data) => {
console.log(data)
let scatter = new scatterPlot(svg, data, width / 2 - padding, height / 2 - padding, 'budget', 'popularity');
let bar = new barPlot(svg, data, width / 2 - padding, height / 2 - padding, 'Average Vote', 'Vote Count', { left: width / 2 + padding, top: 0 })
let time = timePlot(svg, data, width, height / 2 - padding, 'Date', 'Run Time', { left: 0, top: height / 2 + padding })
time.updateArr.push({ plot: bar, func: bar.updateData });
bar.updateArr.push({ plot: scatter, func: scatter.update });
})
function timePlot(elem, data, width, height, x_label, y_label, translate) {
this.updateArr = [];
let g = elem.append('g')
.attr('transform', `translate(${translate.left}, ${translate.top})`);
data = data.sort((a, b) => d3.descending(a.release_date, b.release_date))
let x = d3.scaleTime()
.domain(d3.extent(data, d => d.release_date))
.range([0, width])
let xAxis = g.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x));
let y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.runtime)])
.range([height, 0]);
let yAxis = g.append('g')
.call(d3.axisLeft(y));
// line config
let lineData = data.map(d => ({ date: new Date(d.release_date.getFullYear(), 0, 1)}))
.filter((date, i, self) => self.findIndex(d => d.date.getTime() === date.date.getTime()) === i);
lineData.forEach(ld => {
ld.value = data.filter(d => d.release_date.getFullYear() === ld.date.getFullYear()).length;
});
// console.log('lineData', lineData);
let lineY = d3.scaleLinear()
.domain(d3.extent(lineData, d => d.value))
.range([height, 0]);
let lineYAxis = g.append('g')
.attr('transform', `translate(${width}, 0)`)
.call(d3.axisRight(lineY));
let line = d3.line()
.x(d => x(d.date))
.y(d => lineY(d.value));
let linePath = g.append('path')
.datum(lineData)
.attr('d', line)
.attr('fill', 'none')
.attr('stroke', 'white');
let circles = g.selectAll('circle.time')
.data(data)
.enter().append('circle')
.attr('class', 'time')
.attr('cx', x(x.domain[0]))
.attr('cy', y(y.domain().reduce((a, c) => a + c, 0) / 2))
.attr('fill', fill)
circles.transition().duration(100)
.delay((d, i) => i + 2)
.ease(d3.easeQuadIn)
.attr('cx', d => x(d.release_date))
.attr('cy', d => y(d.runtime))
.attr('r', 1);
addLabels(g, x_label, y_label, width, height, 'white');
let brushed = () => {
let selection = d3.event.selection;
if (selection === null) {
// console.log('no selection')
} else {
const [mins, maxes] = selection;
const x0 = x.invert(mins[0]),
y1 = y.invert(mins[1]),
x1 = x.invert(maxes[0]),
y0 = y.invert(maxes[1]);
circles.attr('stroke', 'none');
let brushedData = circles
.filter(d => {
let xScaleBool = d.release_date > x0 && d.release_date < x1;
let yScaleBool = d.runtime > y0 && d.runtime < y1;
return xScaleBool && yScaleBool;
})
.attr('stroke-width', 1)
.attr('stroke', 'white').data();
// console.log('brushedData', brushedData)
this.updateArr.forEach(d => {
// console.log('d', d)
d.func.call(d.plot, brushedData)
})
}
}
let brushEnd = () => {
let selection = d3.event.selection;
if (selection === null) {
circles.attr('stroke', 'none')
this.updateArr.forEach(d => {
// console.log('d', circles.data())
d.func.call(d.plot, circles.data())
})
}
}
let brush = g.append('g')
.attr('class', 'brush')
.call(d3.brush()
.on('brush', brushed)
.on('end', brushEnd)
);
return this;
}
timePlot.prototype.brushed = function () {
return this;
}
function barPlot(elem, data, width, height, x_label, y_label, translate) {
// console.log('transform', translate)
this.ogData = data;
this.height = height;
this.data = this.formatData(data)
this.updateArr = [];
this.g = elem.append('g')
.attr('transform', `translate(${translate.left}, ${translate.top})`);
this.x = d3.scaleBand()
.padding(.1)
.domain([...new Set(this.data.sort((a, b) => a.key - b.key).map(d => d.key))])
.rangeRound([0, width]);
this.xAxis = this.g.append('g')
.attr('transform', `translate(0, ${this.height})`)
.call(d3.axisBottom(this.x));
this.y = d3.scaleLinear()
.domain(d3.extent(this.data, d => d.value))
.range([this.height, 0])
this.yAxis = this.g.append('g')
.call(d3.axisLeft(this.y));
this.bars = this.g.selectAll('rect.bar')
.data(this.data)
.enter().append('rect')
.classed('bar', true)
.attr('x', d => this.x(d.key))
.attr('width', this.x.bandwidth())
.attr('y', this.height)
.attr('height', 0)
this.bars.transition().duration(1000)
.ease(d3.easeBounce)
.attr('y', d => this.y(d.value))
.attr('height', d => this.height - this.y(d.value))
.attr('fill', fill);
this.clickBars = this.g.selectAll('rect.clickable')
.data(this.data)
.enter().append('rect')
.classed('clickable', true)
.attr('x', d => this.x(d.key))
.attr('width', this.x.bandwidth())
.attr('y', 0)
.attr('height', this.height)
.style('opacity', 0)
this.clickBars.on('mouseover', () => {
d3.select(d3.event.target).style('opacity', .2)
})
this.clickBars.on('mouseout', () => {
d3.select(d3.event.target).style('opacity', 0)
})
this.clickBars.on('click', () => {
const clickElem = d3.select(d3.event.target);
this.bars.attr('stroke', 'none');
let updateData = [];
// console.log('dataclicked', clickElem.attr('data-clicked'))
if (clickElem.attr('data-clicked') === 'true') {
clickElem.attr('data-clicked', false);
} else {
this.clickBars.attr('data-clicked', false);
clickElem.attr('data-clicked', true);
let barData = this.bars.filter(d => d.key === clickElem.data()[0].key).attr('stroke', 'white').data();
updateData = this.ogData.filter(d => Math.floor(d.vote_average) === barData[0].key);
}
this.updateArr.forEach(d => {
d.func.call(d.plot, updateData);
})
})
addLabels(this.g, 'Average Vote', 'Vote Count', width, this.height, 'white');
// // axis labels
// let xLabel = svg.append('text')
// .text('Average Vote')
// .attr('fill', 'seagreen')
// .attr('text-anchor', 'end')
// .attr('x', width - margin.right)
// .attr('y', height - margin.bottom)
// let yLabel = svg.append('text')
// .text('Vote Count')
// .attr('fill', 'seagreen')
// .attr('text-anchor', 'end')
// .attr('transform', 'rotate(-90)')
// .attr('x', -margin.top)
// .attr('y', margin.right)
return this;
}
barPlot.prototype.formatData = function (newData) {
return [...new Set(newData.map(d => Math.floor(d.vote_average)))]
.map(d => {
return {
key: d,
value: newData.filter(g => g.vote_average === d).length,
}
});
}
barPlot.prototype.updateData = function (newData) {
this.data = this.formatData(newData);
this.bars.data(this.data.sort((a,b) => a.key - b.key));
//this.bars.exit().remove();
this.bars.enter();
this.bars.transition().duration(500)
.attr('x', d => this.x(d.key))
.attr('y', d => this.y(d.value))
.attr('height', d => this.height - this.y(d.value))
}
function scatterPlot(elem, data, width, height, x_label, y_label, translate) {
this.g = elem.append('g')
.attr('transform', translate);
this.data = data.sort((a, b) => d3.ascending(a[x_label], b[x_label]))
// x things
this.x =
// d3.scalePow()
// .exponent(0.2)
d3.scaleLinear()
.domain(d3.extent(this.data, d => d[x_label]))
.range([0, width]);
this.xAxis = this.g.append('g')
.attr('transform', `translate(0, ${height})`)
//.call(d3.axisBottom(x));
.call(d3.axisBottom(this.x).tickFormat(d3.format("($.2s")).ticks(3));
// y things
this.y = d3.scaleLinear()
.domain(d3.extent(this.data, d => d[y_label]))
.range([height, 0]);
this.yAxis = this.g.append('g')
.call(d3.axisLeft(this.y));
this.circles = this.g.selectAll('circle.movie')
.data(this.data)
.enter()
.append('circle')
.attr('class', 'movie')
.attr('r', 1)
.attr('cx', width / 2)
.attr('cy', this.y(this.y.domain()[1]))
.attr('fill', fill)
this.circles.transition().duration(100)
.delay((d, i) => i + 2)
.ease(d3.easeBounce)
.attr('cx', d => this.x(d[x_label]))
.attr('cy', d => this.y(d[y_label]));
addLabels(this.g, x_label, y_label, width, height, 'white');
// // axis labels
// let xLabel = g.append('text')
// .text(x_label)
// .attr('text-anchor', 'end')
// .attr('x', width - margin.right)
// .attr('y', height - margin.bottom)
// let yLabel = g.append('text')
// .text(y_label)
// .attr('text-anchor', 'end')
// .attr('transform', 'rotate(-90)')
// .attr('x', -margin.top)
// .attr('y', margin.right)
}
scatterPlot.prototype.update = function(newData) {
// console.log('data', newData);
this.circles.attr('stroke', 'none');
newData.forEach(d => {
this.circles.filter(f => f === d).attr('stroke', 'white');
})
}
function addLabels(elem, x_label, y_label, width, height, fill = 'black') {
// axis labels
let xLabel = elem.append('text')
.text(x_label)
.attr('fill', fill)
.attr('text-anchor', 'end')
.attr('x', width - margin.right/2)
.attr('y', height - margin.bottom)
let yLabel = elem.append('text')
.text(y_label)
.attr('fill', fill)
.attr('text-anchor', 'end')
.attr('transform', 'rotate(-90)')
.attr('x', -margin.top)
.attr('y', margin.right/2)
}
svg.append('rect')
.attr('transform', `translate(${-margin.left}, ${-margin.top})`)
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.attr('fill', '#777')
.attr('stroke-width', 3);
</script>
</body>
</html>
https://d3js.org/d3.v5.min.js