You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

429 lines
16 KiB

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* {
border-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font: 10px sans-serif;
padding: 20px;
}
.notes {
font-size:14px;
}
.visualization {
height: 700px;
margin: 0 auto;
position: relative;
width: 1100px;
}
</style>
<script src="http://d3js.org/d3.v4.0.0-alpha.50.min.js"></script>
<script src="http://d3js.org/d3-chord.v0.0.min.js"></script>
<link rel="stylesheet" href="res/flags.min.css">
<link rel="stylesheet" href="res/options.css">
<link rel="stylesheet" href="res/events.css">
<link rel="stylesheet" href="res/diagram.css">
</head>
<body>
<div class="visualization">
<div class="events">
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-uy"></div></div>
<div class="event-year">1930</div>
<div class="event-location">Uruguay</div>
</div>
<div class="event-item active">
<div class="event-flag"><div class="flag-icon flag-icon-it"></div></div>
<div class="event-year">1934</div>
<div class="event-location">Italy</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-fr"></div></div>
<div class="event-year">1938</div>
<div class="event-location">France</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-ch"></div></div>
<div class="event-year">1954</div>
<div class="event-location">Switzerland</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-se"></div></div>
<div class="event-year">1958</div>
<div class="event-location">Sweden</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-cl"></div></div>
<div class="event-year">1962</div>
<div class="event-location">Chile</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-gb"></div></div>
<div class="event-year">1966</div>
<div class="event-location">England</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-mx"></div></div>
<div class="event-year">1970</div>
<div class="event-location">Mexico</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-de"></div></div>
<div class="event-year">1974</div>
<div class="event-location">West Germany</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-ar"></div></div>
<div class="event-year">1978</div>
<div class="event-location">Argentina</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-es"></div></div>
<div class="event-year">1982</div>
<div class="event-location">Spain</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-mx"></div></div>
<div class="event-year">1986</div>
<div class="event-location">Mexico</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-it"></div></div>
<div class="event-year">1990</div>
<div class="event-location">Italy</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-us"></div></div>
<div class="event-year">1994</div>
<div class="event-location">USA</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-fr"></div></div>
<div class="event-year">1998</div>
<div class="event-location">France</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-jp"></div></div>
<div class="event-year">2002</div>
<div class="event-location">Japan / South Korea</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-de"></div></div>
<div class="event-year">2006</div>
<div class="event-location">Germany</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-za"></div></div>
<div class="event-year">2010</div>
<div class="event-location">Johannesburg</div>
</div>
<div class="event-item">
<div class="event-flag"><div class="flag-icon flag-icon-br"></div></div>
<div class="event-year">2014</div>
<div class="event-location">Brazil</div>
</div>
</div>
<div class="options">
<div class="option-item inactive">
<div class="option-text">Round Robin</div>
<div class="option-toggle">Hide</div>
</div>
<div class="option-item">
<div class="option-text">Round of 16</div>
<div class="option-toggle">Hide</div>
</div>
<div class="option-item">
<div class="option-text">Quarterfinals</div>
<div class="option-toggle">Hide</div>
</div>
<div class="option-item">
<div class="option-text">Semifinals</div>
<div class="option-toggle">Hide</div>
</div>
<div class="option-item">
<div class="option-text">Third Place</div>
<div class="option-toggle">Hide</div>
</div>
<div class="option-item">
<div class="option-text">Final</div>
<div class="option-toggle">Hide</div>
</div>
<hr class="option-divider">
<div class="option-item">
<div class="option-text">Africa</div>
<div class="option-toggle">Hide</div>
</div>
<div class="option-item">
<div class="option-text">Asia</div>
<div class="option-toggle">Hide</div>
</div>
<div class="option-item">
<div class="option-text">Australia</div>
<div class="option-toggle">Hide</div>
</div>
<div class="option-item">
<div class="option-text">Europe</div>
<div class="option-toggle">Hide</div>
</div>
<div class="option-item">
<div class="option-text">North America</div>
<div class="option-toggle">Hide</div>
</div>
<div class="option-item">
<div class="option-text">South America</div>
<div class="option-toggle">Hide</div>
</div>
<hr class="option-divider">
<div class="option-item">
<div class="option-text">Sort By Country Name</div>
</div>
<div class="option-item">
<div class="option-text">Sort By Goals Scored</div>
</div>
</div>
<div class="diagram">
<svg width="600" height="600"></svg>
</div>
</div>
<script>
// const buildLookup = (endpoints, result) => {
// const lookup = {};
//
// result.map((v, i) => {
// const json = JSON.parse(v);
// const address = endpoints[i].split('.').shift();
// lookup[address] = json;
// });
//
// // Team list is ~220 items; assign indices to only teams in this tournament.
// lookup.reducedTeams = lookup.games.reduce((acc, v) => {
// if (acc[v.t1] === undefined) {
// acc[v.t1] = Object.keys(acc).length;
// }
//
// return acc;
// }, {});
//
// return lookup;
// };
//
// const buildChordMatrix = (games, reducedTeams) => {
// // Create A x A array of nulls; null-null relationships are omitted.
// const empty = Array.apply(null, Array(Object.keys(reducedTeams).length));
// const result = empty.map(() => empty.map(() => null));
//
// games.forEach(v => {
// // Use indexes from reduced lists to populate final matrix.
// const t1 = reducedTeams[v.t1];
// const t2 = reducedTeams[v.t2];
//
// result[t1][t2] = v.s1 + v.s1e + v.s1p;
// result[t2][t1] = v.s2 + v.s2e + v.s2p;
// }, []);
//
// return result;
// };
//
// const teamNameFromIndex = (lookup, index) => {
// for (let i in lookup.reducedTeams) {
// if (lookup.reducedTeams[i] === index) {
// return lookup.teams[i];
// }
// }
//
// return "Unknown";
// };
//
// const main = (data) => {
// const lookup = buildLookup(endpoints, data);
// const chordMatrix = buildChordMatrix(lookup.games, lookup.reducedTeams)
// const metaMatrix = buildMetaMatrix(lookup.games, lookup.reducedTeams)
//
// const svg = d3.select("svg"),
// width = +svg.attr("width"),
// height = +svg.attr("height"),
// outerArcThickness = 5,
// outerRadius = Math.min(width, height) * 0.5 - 100,
// innerRadius = outerRadius - outerArcThickness;
//
// const chord = d3.chord()
// .padAngle(0.05);
//
// const arc = d3.arc()
// .innerRadius(innerRadius)
// .outerRadius(outerRadius);
//
// const ribbon = d3.ribbon()
// .radius(innerRadius);
//
// // const color = d3.scaleOrdinal(d3.schemeCategory20);
// // const color = d3.scaleOrdinal(d3.schemeCategory10);
// // const color = d3.scaleOrdinal(d3.interpolateCool);
// // const color = d3.scaleSequential(d3.interpolateRainbow);
//
// const color = d3.scaleLinear().domain([0,10]).range(["red", "blue"]).interpolate(d3.interpolateRgb)
//
// // const colors = ["#f1eef6","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#91003f"];
// // const colors = ["#ffffcc","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#0c2c84"];
// // const colors = ["#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"];
// // const colors = ["#1B9E77", "#D95F02", "#7570B3", "#E7298A", "#66A61E", "#E6AB02", "#A6761D", "#666666"];
// // const color = i => colors[i % colors.length];
//
// const g = svg.append("g")
// .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
// .datum(chord(chordMatrix));
//
// const group = g.append("g")
// .attr("class", "groups")
// .selectAll("g")
// .data(function(chords) { return chords.groups; })
// .enter().append("g");
//
// group.append("path")
// .style("fill", function(d) { return color(d.index); })
// .style("stroke", function(d) { return d3.rgb(color(d.index)).darker(); })
// .attr("d", arc)
//
// group.append("text")
// .each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; })
// .attr("dy", ".35em")
// .attr("transform", function(d) {
// return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
// + "translate(" + (innerRadius + 26) + ")"
// + (d.angle > Math.PI ? "rotate(180)" : "");
// })
// .style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
// .text(function(d) {
// // SORT BY GOALS SCORED
// // SORT BY ALPHABETICAL
// // SORT BY COUNTRY SIZE
// // SORT BY COUNTRY POPULATION
// // COLOR BY CONTINENT
// // STRANGE EXTENDED TIME CHILE-BRAZIL - FIX BY HAND?
// return teamNameFromIndex(lookup, d.index);
// });
//
// g.append("g")
// .attr("class", "ribbons")
// .selectAll("path")
// .data(function(chords) { return chords; })
// .enter().append("path")
// .attr("d", ribbon)
// .style("fill", function(d) { return color(d.target.index); })
// .style("stroke", function(d) { return d3.rgb(color(d.target.index)).darker(); })
// .attr("class", "ribbon")
// .append("title")
// .text(function(d) {
// const meta1 = metaMatrix[d.target.index][d.source.index];
// const meta2 = metaMatrix[d.source.index][d.target.index];
//
// const g = meta1.game;
//
// const t1 = lookup.teams[g.t1];
// const t2 = lookup.teams[g.t2];
//
// const e1 = g.s1e ? `(+${g.s1e} in extended time)` : '';
// const e2 = g.s2e ? `(+${g.s2e} in extended time)` : '';
//
// const p1 = g.s1p ? `(+${g.s1p} in penalties)` : '';
// const p2 = g.s2p ? `(+${g.s2p} in penalties)` : '';
//
// return `${t1}: ${g.s1} ${e1} ${p1}\n${t2}: ${g.s2} ${e2} ${p2}`;
// });
// };
const Data = {
// Identical structure of chord matrix but with { game, team }.
buildMetaMatrix: (eventKey) => {
console.info(Data.tourneys)
// const event = Data.json[eventKey];
// console.warn(event)
// const empty = Array.apply(null, Array(Object.keys(reducedTeams).length));
// const matrix = empty.map(() => empty.map(() => null));
//
// games.forEach(g => {
// const t1 = reducedTeams[g.t1];
// const t2 = reducedTeams[g.t2];
//
// matrix[t1][t2] = { game: g, team: g.t1 };
// matrix[t2][t1] = { game: g, team: g.t2 };
// }, []);
//
// return matrix;
},
};
const main = function(data) {
Object.assign(Data, JSON.parse(data));
Data.buildMetaMatrix("1930");
// Data.buildChordMatrix();
// Diagram.build();
}
//===== Entry point
const fetch = (url) => new Promise((resolve, reject) => {
const listener = ({ srcElement: req }) => {
req.status === 200 ? resolve(req.responseText) : reject("busted");
};
const req = new XMLHttpRequest();
req.addEventListener('load', listener);
req.open('GET', url);
req.send();
});
fetch('worldcup.json').then(main);
</script>
<div class="notes">
<h5>Lessons learned</h5>
scaleOrdinal vs scaleLinear (only 2 colors!) vs interpolateCool vs d3.schemeColor20c<br>
ribbon source vs index (change fill them differently)
<a href="https://github.com/d3/d3-scale-chromatic">https://github.com/d3/d3-scale-chromatic</a>
https://bost.ocks.org/mike/uberdata/
http://bl.ocks.org/mbostock/1046712
https://bl.ocks.org/mbostock/4062006
http://projects.delimited.io/experiments/chord-transitions/demos/trade.html
https://github.com/jokecamp/sportdb-build-scripts
https://groups.google.com/forum/#!topic/opensport/593H1O7yIdE
https://github.com/openfootball/datafile/blob/master/worldcup.rb
https://openfootball.github.io/questions.html
<h5>TODO</h5>
add team name to each arc
build dataset
tweet it!
</div>
</body>
</html>
`