Built with blockbuilder.org
forked from sxywu‘s block: DS July: Code 1
forked from sxywu‘s block: DS July: Code 2
forked from sxywu‘s block: DS July: Code 3
forked from sxywu‘s block: DS July: Code 4
forked from sxywu‘s block: DS July: Code 5
forked from sxywu‘s block: DS July: Code 6
forked from sxywu‘s block: DS July: Code 7
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.js'></script>
<link href='https://fonts.googleapis.com/css?family=Libre+Baskerville:400,700' rel='stylesheet' type='text/css'>
<style>
</style>
</head>
<body>
<svg></svg>
<script>
const width = 4134;
const height = 213;
var strokeColor = '#444';
var flowerSize = 160;
var svg = d3.select('svg');
var petalPaths = [[
'M0 0',
"C50 50 50 100 0 100",
"C-50 100 -50 50 0 0"
],
[
'M-35 0',
'C-25 25 25 25 35 0',
'C50 25 25 75 0 100',
'C-25 75 -50 25 -35 0'
],
[
'M0 0',
'C50 40 50 70 20 100',
'L0 85',
'L-20 100',
'C-50 70 -50 40 0 0'
],
[
'M0 0',
'C50 25 50 75 0 100',
'C-50 75 -50 25 0 0'
]];
var leaf = [
'M0 15',
'C15 40 15 60 0 75',
'C-15 60 -15 40 0 15'
];
const moviesToDraw = [
'Straight Outta Compton',
'Finding Dory',
'Despicable Me 2',
'Brave',
'The Dark Knight Rises',
'Harry Potter and the Deathly Hallows: Part 2',
'Inception',
'Despicable Me',
'Spider-Man 2',
'Batman Begins',
'Toy Story 3',
'Lilo & Stitch',
'Rush Hour 2',
// 'X-Men',
// 'The Sixth Sense',
'Saving Private Ryan',
// 'The Rock',
// 'Pocahontas',
'Forrest Gump',
'Slumdog Millionaire',
'The Dark Knight',
'WALL·E',
'Harry Potter and the Half-Blood Prince',
'Ratatouille',
'Harry Potter and the Order of the Phoenix',
'Mr. & Mrs. Smith',
"Pirates of the Caribbean: Dead Man's Chest",
'Pirates of the Caribbean: The Curse of the Black Pearl'
]
const simulation = d3.forceSimulation()
.force('collide', d3.forceCollide().radius(d => d.radius + 40))
.force('center', d3.forceCenter(width / 2, height / 2))
.force('charge', d3.forceManyBody(-240));
const numPetalScale = d3.scaleQuantize()
.range(_.range(5, 15));
const flowerSizeScale = d3.scaleLinear()
.range([.05 * flowerSize, .5 * flowerSize]);
const petalScale = d3.scaleOrdinal()
.domain(['G', 'PG', 'PG-13', 'R'])
.range(_.range(4));
const petalColors = d3.scaleOrdinal()
.range(['#FFB09E', '#CBF2BD', '#AFE9FF', '#FFC8F0', '#FFF2B4']);
const defs = d3.select('svg')
.style('isolation', 'isolate')
.attr('width', width)
.attr('height', height)
.append("defs");
defs.append("filter")
.attr("id", "motionFilter") //Give it a unique ID
.attr("width", "300%") //Increase the width of the filter region to remove blur "boundary"
.attr("x", "-100%") //Make sure the center of the "width" lies in the middle of the element
.append("feGaussianBlur") //Append a filter technique
.attr("in", "SourceGraphic") //Perform the blur on the applied element
.attr("stdDeviation", "8 8"); //Do a blur of 8 standard deviations in the horizontal and vertical direction
d3.json('movies.json', function(movies) {
movies = _.chain(movies)
.map(function(movie) {
movie.year = parseInt(movie.Year);
movie.genres = movie.Genre.split(', ');
movie.rating = parseFloat(movie.imdbRating);
movie.votes = parseInt(movie.imdbVotes.replace(/\,/g, ''));
return movie;
}).value()
// number of petals depending on number of rating votes
const votesExtent = d3.extent(movies, function(d) {return d.votes})
numPetalScale.domain(votesExtent)
// overall flower size from rating
const ratingExtent = d3.extent(movies, function(d) {return d.rating})
flowerSizeScale.domain(ratingExtent)
// get the top 4 genres by count
const topGenres = _.chain(movies)
.map('genres').flatten()
.countBy().toPairs()
.sortBy(1).map(0)
.takeRight(4)
.value();
topGenres.push('Other');
petalColors.domain(topGenres);
// calculate movie data with each of the petals as well as
movies = _.chain(movies)
.filter(movie => _.includes(moviesToDraw, movie.Title))
.map(d => {
const radius = flowerSizeScale(d.rating);
const scale = radius / 100;
const rotate = _.random(240, 360);
const numPetals = numPetalScale(d.votes);
const petals = _.times(numPetals, i => {
return {
scale,
rotate: (i / numPetals) * 360,
path: petalPaths[petalScale(d.Rated)],
}
});
// if there's only one genre, center the circle
const cy = d.genres.length === 1 ? 0 : -0.14 * flowerSize;
const colors = _.map(d.genres, function(genre, i) {
genre = _.includes(topGenres, genre) ? genre : 'Other';
return {
cy,
scale,
angle: (360/d.genres.length) * i,
fill: petalColors(genre),
}
});
return {radius, petals, colors, scale, rotate};
}).shuffle().value();
// use force simulation to calculate positions
simulation.nodes(movies);
_.times(10000, i => {
simulation.tick();
// make sure it's within bounds
_.each(movies, d => {
if (0 > d.x - d.radius) {
d.x = d.radius + 5;
} else if (d.x + d.radius >= width) {
d.x = width - d.radius - 5;
}
if (0 > d.y - d.radius) {
d.y = _.random(0.5 * d.radius, 1 * d.radius);
// d.y = d.radius - 5;
} else if (d.y + d.radius >= height) {
d.y = height - _.random(0.5 * d.radius, 1 * d.radius);
// d.y = height - d.radius + 5;
}
});
});
console.log(JSON.stringify(movies))
const flowers = d3.select('svg').selectAll('g.flower')
.data(_.values(movies)).enter().append('g')
.classed('flower', true)
.attr('transform', d => `translate(${d.x}, ${d.y})rotate(${d.rotate})scale(${d.scale})`);
// colors
flowers.selectAll('circle')
.data(d => d.colors).enter().append('circle')
.attr('cy', d => d.cy)
.attr('r', 0.45 * flowerSize)
.attr('fill', d => d.fill)
.attr('transform', d => `rotate(${d.angle})`)
.style("filter", "url(#motionFilter)")
.style('mix-blend-mode', 'multiply');
// petals
flowers.selectAll('path')
.data(d => d.petals).enter().append('path')
.attr('transform', d => `rotate(${d.rotate})`)
.attr('d', d => d.path)
.attr('stroke', strokeColor)
.attr('stroke-width', d => 2.5 / d.scale)
.attr('fill', 'none');
});
</script>
</body>