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
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>
|
|
`
|
|
|