diff --git a/index.html b/index.html index 3244155..c0c8d51 100644 --- a/index.html +++ b/index.html @@ -59,9 +59,9 @@ 1934: Patched. Italy Spain replay 1-0! 1938: - Switzerland Germany replay 4-2 - Cuba Romania replay 3-2 - Brazil Czech replay 2-1 + Switzerland Germany 1-1 replay 4-2 + Cuba Romania 3-3 replay 3-2 + Brazil Czech 1-1 replay 2-1 1950: OK 1954: Germany Turkey playoff 7-2 @@ -82,7 +82,13 @@ 1986: 1990: 1994: + South Korea Bolivia 0-0 + Ireland Norway 0-0 + Brazil Sweden replay 1998: + Paraguay Bulgaria 0-0 + Spain Paraguay 0-0 + Netherlands Belgium 0-0 2002: France Uruguay 0-0 Nigeria England 0-0 diff --git a/js/bundle.js b/js/bundle.js index 4d11526..0282a68 100644 --- a/js/bundle.js +++ b/js/bundle.js @@ -58,13 +58,12 @@ var _ui2 = _interopRequireDefault(_ui); - var _hotfixes = __webpack_require__(13); + var _hotfixes = __webpack_require__(5); var _hotfixes2 = _interopRequireDefault(_hotfixes); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - __webpack_require__(5); __webpack_require__(6); __webpack_require__(7); __webpack_require__(8); @@ -72,6 +71,7 @@ __webpack_require__(10); __webpack_require__(11); __webpack_require__(12); + __webpack_require__(13); var main = { changeEvent: function changeEvent(e) { @@ -105,22 +105,46 @@ main.updateUI(); }, - getRounds: function getRounds(eventKey) { - var rounds = {}; + // DEPRECATED? 161120 + // getRounds: (eventKey) => { + // const rounds = {}; + // + // main.json.tourneys[eventKey].games.forEach(game => { + // const name = UI.getRoundName(main.json.rounds[game.rId]); + // if (rounds[name] === undefined) { + // rounds[name] = []; + // } + // + // rounds[name].push({ + // id: game.rId, + // name: name, + // }); + // }); + // + // return rounds; + // }, + + getDuplicates: function getDuplicates(eventKey) { + var games = {}; main.json.tourneys[eventKey].games.forEach(function (game) { - var name = _ui2.default.getRoundName(main.json.rounds[game.rId]); - if (rounds[name] === undefined) { - rounds[name] = []; + var teams = [game.t1, game.t2].sort(); + var addr = teams[0] + '-' + teams[1]; + + if (games[addr] === undefined) { + games[addr] = []; } - rounds[name].push({ - id: game.rId, - name: name - }); + games[addr].push(game); }); - return rounds; + return Object.keys(games).reduce(function (acc, k) { + if (games[k].length > 1) { + acc.push(games[k]); + } + + return acc; + }, []); }, generateUI: function generateUI() { @@ -135,10 +159,124 @@ updateUI: function updateUI() { var state = main.getState(); + var eventKey = state.eventKey; + + var matrix = _matrices2.default.buildMatrix(main.json, eventKey); + + var duplicates = main.getDuplicates(eventKey); + + var tmp = _diagram2.default.buildChords({ + data: main.json, + sort: state.sort, + eventKey: eventKey, + matrix: matrix, + SORT_TYPES: _ui2.default.SORT_TYPES + }); + + var getScore = function getScore(game) { + var s1 = game.s1; + var s2 = game.s2; + + if (game.sp1 !== null) { + s1 = game.sp1; + } else if (game.se1 !== null) { + s1 = game.se1; + } + + if (game.sp2 !== null) { + s2 = game.sp2; + } else if (game.se2 !== null) { + s2 = game.se2; + } + + return { s1: s1, s2: s2 }; + }; + + var chords = tmp.reduce(function (acc, d) { + var tSrc = main.json.tourneys[eventKey].teams[d.source.index].tId; + var tTgt = main.json.tourneys[eventKey].teams[d.target.index].tId; + + d.game = main.json.tourneys[eventKey].games.find(function (g) { + return g.t1 === tSrc && g.t2 === tTgt || g.t1 === tTgt && g.t2 === tSrc; + }); + + duplicates.forEach(function (games) { + if (games[0] === d.game || games[1] === d.game) { + var gameNew = games[0] === d.game ? games[1] : games[0]; + var sourceNew = Object.assign({}, d.source); + var targetNew = Object.assign({}, d.target); + + var sourceAngle = d.source.endAngle - d.source.startAngle; + var targetAngle = d.target.endAngle - d.target.startAngle; + + var _getScore = getScore(games[0]), + s1g0 = _getScore.s1, + s2g0 = _getScore.s2; + + var _getScore2 = getScore(games[1]), + s1g1 = _getScore2.s1, + s2g1 = _getScore2.s2; + + var totals = { src: 0, tgt: 0 }; + var offset = { src: 0, tgt: 0 }; + + totals.src += games[0].t1 === tSrc ? s1g0 : s2g0; + totals.tgt += games[0].t1 === tTgt ? s1g0 : s2g0; + + totals.src += games[1].t1 === tSrc ? s1g1 : s2g1; + totals.tgt += games[1].t1 === tTgt ? s1g1 : s2g1; + + offset.src = games[0].t1 === tSrc ? s1g0 : s2g0; + offset.tgt = games[0].t1 === tTgt ? totals.tgt - s1g0 : totals.tgt - s2g0; + + sourceNew.startAngle = d.source.startAngle + sourceAngle * (offset.src / totals.src); + d.source.endAngle = d.source.startAngle + sourceAngle * (offset.src / totals.src); + + targetNew.endAngle = d.target.startAngle + targetAngle * (offset.tgt / totals.tgt); + d.target.startAngle = d.target.startAngle + targetAngle * (offset.tgt / totals.tgt); + + acc.push({ source: sourceNew, target: targetNew, game: gameNew }); + } + }); + + acc.push(d); + + return acc; + + // 127/110 Germany 153/137 Switzerland + // if (`${d.source.index},${d.source.subindex}` === "14,10" && + // `${d.target.index},${d.target.subindex}` === "5,2") { + // // source has to split into 1/5, 4/5 + // + // const sourceAngle = d.source.endAngle - d.source.startAngle; + // const targetAngle = d.target.endAngle - d.target.startAngle; + // + // const sourceNew = Object.assign({}, d.source); + // sourceNew.startAngle = d.source.startAngle + sourceAngle * (1/5); + // d.source.endAngle = d.source.startAngle + sourceAngle * (1/5); + // + // const targetNew = Object.assign({}, d.target); + // targetNew.endAngle = d.target.startAngle + targetAngle * (2/3); + // d.target.startAngle = d.target.startAngle + targetAngle * (2/3); + // + // chords.push({ source: sourceNew, target: targetNew }); + // } + }, []); + + chords.groups = tmp.groups; + // + // console.warn(test) - var matrix = _matrices2.default.buildMatrix(main.json, state.eventKey); _diagram2.default.clear(); - _diagram2.default.build(main.json, state.eventKey, state.sort, state.scheme, _ui2.default.SORT_TYPES, matrix); + + var color = _diagram2.default.buildColorScheme({ + scheme: parseInt(state.scheme), + len: main.json.tourneys[state.eventKey].teams.length + }); + + var container = _diagram2.default.buildContainer(chords); + _diagram2.default.buildArcs({ container: container, color: color, eventKey: eventKey, data: main.json }); + _diagram2.default.buildRibbons({ container: container, color: color, chords: chords, eventKey: eventKey, data: main.json }); _ui2.default.updateTourneyPane(state.eventKey); _ui2.default.updateEventsPane(state.eventKey); @@ -267,8 +405,8 @@ s2 += g.s2; } - matrix[i1][i2] = g.s1 + g.se1 + g.sp1; - matrix[i2][i1] = g.s2 + g.se2 + g.sp2; + matrix[i1][i2] = s1; + matrix[i2][i1] = s2; }, []); return matrix; @@ -295,6 +433,8 @@ value: true }); + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + var _sorter = __webpack_require__(3); var _sorter2 = _interopRequireDefault(_sorter); @@ -422,82 +562,81 @@ return team.ga; }, - build: function build(data, eventKey, sort, scheme, SORT_TYPES, matrix) { - var svg = d3.select("svg"), - width = svg.attr("width"), - height = svg.attr("height"), - outerArcThickness = 5, - outerRadius = Math.min(width, height) * 0.5 - 130, - innerRadius = outerRadius - outerArcThickness; + buildChords: function buildChords(_ref) { + var matrix = _ref.matrix, + data = _ref.data, + eventKey = _ref.eventKey, + sort = _ref.sort, + SORT_TYPES = _ref.SORT_TYPES; var chords = d3.chord().padAngle(0.05).call(null, matrix); - var sortedChords = chords; switch (sort) { case SORT_TYPES.COUNTRY: - sortedChords = _sorter2.default.sort(chords, 0, chords.groups.length - 1, Diagram.getCountryName.bind(null, data, eventKey), Diagram.swapGroups.bind(null, data, eventKey)); - break; + return _sorter2.default.sort(chords, 0, chords.groups.length - 1, Diagram.getCountryName.bind(null, data, eventKey), Diagram.swapGroups.bind(null, data, eventKey)); case SORT_TYPES.GOALS: - sortedChords = _sorter2.default.sort(chords, 0, chords.groups.length - 1, Diagram.getGoalsFor.bind(null, data, eventKey), Diagram.swapGroups.bind(null, data, eventKey)); - break; + return _sorter2.default.sort(chords, 0, chords.groups.length - 1, Diagram.getGoalsFor.bind(null, data, eventKey), Diagram.swapGroups.bind(null, data, eventKey)); case SORT_TYPES.POPULATION: - sortedChords = _sorter2.default.sort(chords, 0, chords.groups.length - 1, Diagram.getPopulation.bind(null, data, eventKey), Diagram.swapGroups.bind(null, data, eventKey)); - break; + return _sorter2.default.sort(chords, 0, chords.groups.length - 1, Diagram.getPopulation.bind(null, data, eventKey), Diagram.swapGroups.bind(null, data, eventKey)); } - var arc = d3.arc().innerRadius(innerRadius).outerRadius(outerRadius); + return chords; + }, - var ribbon = d3.ribbon().radius(innerRadius); + buildDimensions: function buildDimensions() { + var svgW = d3.select("svg").attr("width"); + var svgH = d3.select("svg").attr("height"); + var arcT = 5; + var outerR = Math.min(svgW, svgH) * 0.5 - 130; + var innerR = outerR - arcT; - var len = data.tourneys[eventKey].teams.length; - var color = d3.scaleOrdinal(d3.schemeCategory20); + return { svgW: svgW, svgH: svgH, arcT: arcT, outerR: outerR, innerR: innerR }; + }, - (function () { - switch (parseInt(scheme)) { - case 1: - color = d3.scaleLinear().domain([0, len]).range(["#fff", "green"]).interpolate(d3.interpolateRgb); - break; - case 2: - var colors = ["#ffffd9", "#edf8b1", "#c7e9b4", "#7fcdbb", "#41b6c4", "#1d91c0", "#225ea8", "#253494", "#081d58"]; - color = function color(i) { - return colors[i % colors.length]; - }; - break; - case 3: - color = d3.scaleLinear().domain([0, len]).range(["red", "blue"]).interpolate(d3.interpolateRgb); - break; - case 4: - color = d3.scaleOrdinal(d3.schemeCategory10); - break; - } - })(); + buildColorScheme: function buildColorScheme(_ref2) { + var scheme = _ref2.scheme, + len = _ref2.len; - var g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")").datum(sortedChords); + if (scheme === 1) { + return d3.scaleLinear().domain([0, len]).range(["#fff", "green"]).interpolate(d3.interpolateRgb); + } else if (scheme === 2) { + var _ret = function () { + var colors = ["#ffffd9", "#edf8b1", "#c7e9b4", "#7fcdbb", "#41b6c4", "#1d91c0", "#225ea8", "#253494", "#081d58"]; - var group = g.append("g").attr("class", "groups").selectAll("g").data(function (chords) { - return chords.groups; - }).enter().append("g"); + return { + v: function v(i) { + return colors[i % colors.length]; + } + }; + }(); + + if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; + } else if (scheme === 3) { + return d3.scaleLinear().domain([0, len]).range(["red", "blue"]).interpolate(d3.interpolateRgb); + } else if (scheme === 4) { + return d3.scaleOrdinal(d3.schemeCategory10); + } - 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); + return d3.scaleOrdinal(d3.schemeCategory20); + }, - g.append("g").attr("class", "ribbons").selectAll("path").data(function (chords) { - return chords; - }).enter().append("path").attr("d", ribbon).attr("data-round-id", function (d) { - var t1 = data.tourneys[eventKey].teams[d.source.index]; - var t2 = data.tourneys[eventKey].teams[d.target.index]; + buildRibbons: function buildRibbons(_ref3) { + var container = _ref3.container, + color = _ref3.color, + chords = _ref3.chords, + data = _ref3.data, + eventKey = _ref3.eventKey; - var game = data.tourneys[eventKey].games.find(function (v) { - return (v.t1 === t1.tId || v.t1 === t2.tId) && (v.t2 === t1.tId || v.t2 === t2.tId); - }); + var dimensions = Diagram.buildDimensions(); + var ribbon = d3.ribbon().radius(dimensions.innerR); - return game.rId; - }).style("fill", function (d) { + container.append("g").attr("class", "ribbons").selectAll("path").data(function (chords) { + return chords; + }).enter().append("path").attr("d", ribbon).attr('data-round-id', function (d) { + return d.game.rId; + }).style('fill', function (d) { return color(d.target.index); - }).style("stroke", function (d) { + }).style('stroke', function (d) { return d3.rgb(color(d.target.index)).darker(); }).classed("ribbon", true).append("title").text(function (d) { var t1 = data.tourneys[eventKey].teams[d.source.index]; @@ -506,60 +645,76 @@ var c1 = data.countries[t1.cId]; var c2 = data.countries[t2.cId]; - var game = data.tourneys[eventKey].games.find(function (v) { - return (v.t1 === t1.tId || v.t1 === t2.tId) && (v.t2 === t1.tId || v.t2 === t2.tId); - }); - - var s1 = game.t1 === t1.tId ? game.s1 : game.s2; - var s2 = game.t2 === t2.tId ? game.s2 : game.s1; + var s1 = d.game.t1 === t1.tId ? d.game.s1 : d.game.s2; + var s2 = d.game.t2 === t2.tId ? d.game.s2 : d.game.s1; - if (game.sp1 !== null && game.sp2 !== null) { - s1 = game.t1 === t1.tId ? game.sp1 : game.sp2; - s2 = game.t2 === t2.tId ? game.sp2 : game.sp1; + if (d.game.sp1 !== null && d.game.sp2 !== null) { + s1 = d.game.t1 === t1.tId ? d.game.sp1 : d.game.sp2; + s2 = d.game.t2 === t2.tId ? d.game.sp2 : d.game.sp1; s1 += " (Penalties)"; s2 += " (Penalties)"; - } else if (game.se1 !== null && game.se2 !== null) { - s1 = game.t1 === t1.tId ? game.se1 : game.se2; - s2 = game.t2 === t2.tId ? game.se2 : game.se1; + } else if (d.game.se1 !== null && d.game.se2 !== null) { + s1 = d.game.t1 === t1.tId ? d.game.se1 : d.game.se2; + s2 = d.game.t2 === t2.tId ? d.game.se2 : d.game.se1; s1 += " (Extended time)"; s2 += " (Extended time)"; } - // const s1 = game.t1 === t1.tId ? game.s1 : game.s2; - // const s2 = game.t2 === t2.tId ? game.s2 : game.s1; - // - // const e1 = game.se1 ? `(+${game.se1} in extended time)` : ''; - // const e2 = game.se2 ? `(+${game.se2} in extended time)` : ''; - // - // const p1 = game.sp1 ? `(+${game.sp1} in penalties)` : ''; - // const p2 = game.sp2 ? `(+${game.sp2} in penalties)` : ''; + // console.warn(t1.tId, c1, t2.tId, c2, s1, s2) - return c1 + ': ' + s1 + '\n' + c2 + ': ' + s2 + '\n' + data.rounds[game.rId]; + return c1 + ': ' + s1 + '\n' + c2 + ': ' + s2 + '\n' + data.rounds[d.game.rId] + '\n \nsource (i,j) val: (' + d.source.index + ', ' + d.source.subindex + ') ' + d.source.value + '\n \ntarget (i,j) val: (' + d.target.index + ', ' + d.target.subindex + ') ' + d.target.value; }); + }, + + buildArcs: function buildArcs(_ref4) { + var container = _ref4.container, + color = _ref4.color, + eventKey = _ref4.eventKey, + data = _ref4.data; + + var dimensions = Diagram.buildDimensions(); + var arc = d3.arc().innerRadius(dimensions.innerR).outerRadius(dimensions.outerR); + + var group = container.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 - 91) + ")" + "translate(" + (innerRadius + 26) + ")" + (d.angle > Math.PI ? "rotate(180)" : ""); + return "rotate(" + (d.angle * 180 / Math.PI - 91) + ")" + "translate(" + (dimensions.innerR + 26) + ")" + (d.angle > Math.PI ? "rotate(180)" : ""); }).style("text-anchor", function (d) { return d.angle > Math.PI ? "end" : null; }).text(function (d) { var team = data.tourneys[eventKey].teams[d.index]; var metric = ''; - switch (sort) { - case SORT_TYPES.GOALS: - metric = '(' + team.gf + ')'; - break; - // case SORT_TYPES.POPULATION: - // metric = `(${Number(team.p).toLocaleString()})`; - // break; - } + // switch (sort) { + // case SORT_TYPES.GOALS: + // metric = `(${team.gf})`; + // break; + // // case SORT_TYPES.POPULATION: + // // metric = `(${Number(team.p).toLocaleString()})`; + // // break; + // } return data.countries[team.cId] + ' ' + metric; }); + }, + + buildContainer: function buildContainer(chords) { + var svg = d3.select("svg"); + var dimensions = Diagram.buildDimensions(); + + return svg.append("g").attr("transform", 'translate(' + dimensions.svgW / 2 + ',' + dimensions.svgH / 2 + ')').datum(chords); } }; @@ -1000,54 +1155,6 @@ /***/ }, /* 5 */ -/***/ function(module, exports) { - - // removed by extract-text-webpack-plugin - -/***/ }, -/* 6 */ -/***/ function(module, exports) { - - // removed by extract-text-webpack-plugin - -/***/ }, -/* 7 */ -/***/ function(module, exports) { - - // removed by extract-text-webpack-plugin - -/***/ }, -/* 8 */ -/***/ function(module, exports) { - - // removed by extract-text-webpack-plugin - -/***/ }, -/* 9 */ -/***/ function(module, exports) { - - // removed by extract-text-webpack-plugin - -/***/ }, -/* 10 */ -/***/ function(module, exports) { - - // removed by extract-text-webpack-plugin - -/***/ }, -/* 11 */ -/***/ function(module, exports) { - - // removed by extract-text-webpack-plugin - -/***/ }, -/* 12 */ -/***/ function(module, exports) { - - // removed by extract-text-webpack-plugin - -/***/ }, -/* 13 */ /***/ function(module, exports) { "use strict"; @@ -1122,5 +1229,53 @@ exports.default = hotfixes; +/***/ }, +/* 6 */ +/***/ function(module, exports) { + + // removed by extract-text-webpack-plugin + +/***/ }, +/* 7 */ +/***/ function(module, exports) { + + // removed by extract-text-webpack-plugin + +/***/ }, +/* 8 */ +/***/ function(module, exports) { + + // removed by extract-text-webpack-plugin + +/***/ }, +/* 9 */ +/***/ function(module, exports) { + + // removed by extract-text-webpack-plugin + +/***/ }, +/* 10 */ +/***/ function(module, exports) { + + // removed by extract-text-webpack-plugin + +/***/ }, +/* 11 */ +/***/ function(module, exports) { + + // removed by extract-text-webpack-plugin + +/***/ }, +/* 12 */ +/***/ function(module, exports) { + + // removed by extract-text-webpack-plugin + +/***/ }, +/* 13 */ +/***/ function(module, exports) { + + // removed by extract-text-webpack-plugin + /***/ } /******/ ]); \ No newline at end of file diff --git a/js/diagram.js b/js/diagram.js index 2e73963..0925343 100644 --- a/js/diagram.js +++ b/js/diagram.js @@ -120,68 +120,126 @@ const Diagram = { return team.ga; }, - build: (data, eventKey, sort, scheme, SORT_TYPES, matrix) => { - const svg = d3.select("svg"), - width = (svg.attr("width")), - height = (svg.attr("height")), - outerArcThickness = 5, - outerRadius = Math.min(width, height) * 0.5 - 130, - innerRadius = outerRadius - outerArcThickness; - + buildChords: ({ matrix, data, eventKey, sort, SORT_TYPES }) => { const chords = d3.chord() .padAngle(0.05) .call(null, matrix); - let sortedChords = chords; switch (sort) { case SORT_TYPES.COUNTRY: - sortedChords = Sorter.sort(chords, 0, chords.groups.length - 1, - Diagram.getCountryName.bind(null, data, eventKey), - Diagram.swapGroups.bind(null, data, eventKey)); - break; + return Sorter.sort(chords, 0, chords.groups.length - 1, + Diagram.getCountryName.bind(null, data, eventKey), + Diagram.swapGroups.bind(null, data, eventKey)); case SORT_TYPES.GOALS: - sortedChords = Sorter.sort(chords, 0, chords.groups.length - 1, - Diagram.getGoalsFor.bind(null, data, eventKey), - Diagram.swapGroups.bind(null, data, eventKey)); - break; + return Sorter.sort(chords, 0, chords.groups.length - 1, + Diagram.getGoalsFor.bind(null, data, eventKey), + Diagram.swapGroups.bind(null, data, eventKey)); case SORT_TYPES.POPULATION: - sortedChords = Sorter.sort(chords, 0, chords.groups.length - 1, - Diagram.getPopulation.bind(null, data, eventKey), - Diagram.swapGroups.bind(null, data, eventKey)); - break; + return Sorter.sort(chords, 0, chords.groups.length - 1, + Diagram.getPopulation.bind(null, data, eventKey), + Diagram.swapGroups.bind(null, data, eventKey)); } - const arc = d3.arc() - .innerRadius(innerRadius) - .outerRadius(outerRadius); - - const ribbon = d3.ribbon() - .radius(innerRadius); - - const len = data.tourneys[eventKey].teams.length; - let color = d3.scaleOrdinal(d3.schemeCategory20); - - switch (parseInt(scheme)) { - case 1: - color = d3.scaleLinear().domain([0, len]).range(["#fff", "green"]).interpolate(d3.interpolateRgb); - break; - case 2: - const colors = ["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"]; - color = i => colors[i % colors.length]; - break; - case 3: - color = d3.scaleLinear().domain([0, len]).range(["red", "blue"]).interpolate(d3.interpolateRgb); - break; - case 4: - color = d3.scaleOrdinal(d3.schemeCategory10); - break; + + + return chords; + }, + + buildDimensions: () => { + const svgW = d3.select("svg").attr("width"); + const svgH = (d3.select("svg").attr("height")); + const arcT = 5; + const outerR = Math.min(svgW, svgH) * 0.5 - 130; + const innerR = outerR - arcT; + + return { svgW, svgH, arcT, outerR, innerR}; + }, + + buildColorScheme: ({ scheme, len }) => { + if (scheme === 1) { + return d3.scaleLinear() + .domain([0, len]) + .range(["#fff", "green"]) + .interpolate(d3.interpolateRgb); + } else if (scheme === 2) { + const colors = [ + "#ffffd9", + "#edf8b1", + "#c7e9b4", + "#7fcdbb", + "#41b6c4", + "#1d91c0", + "#225ea8", + "#253494", + "#081d58" + ]; + + return (i => colors[i % colors.length]); + } else if (scheme === 3) { + return d3.scaleLinear() + .domain([0, len]) + .range(["red", "blue"]) + .interpolate(d3.interpolateRgb); + } else if (scheme === 4) { + return d3.scaleOrdinal(d3.schemeCategory10); } - const g = svg.append("g") - .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")") - .datum(sortedChords); + return d3.scaleOrdinal(d3.schemeCategory20); + }, - const group = g.append("g") + buildRibbons: ({ container, color, chords, data, eventKey }) => { + const dimensions = Diagram.buildDimensions(); + const ribbon = d3.ribbon().radius(dimensions.innerR); + + container.append("g") + .attr("class", "ribbons") + .selectAll("path") + .data(chords => chords) + .enter().append("path") + .attr("d", ribbon) + .attr('data-round-id', d => d.game.rId) + .style('fill', d => color(d.target.index)) + .style('stroke', d => d3.rgb(color(d.target.index)).darker()) + .classed("ribbon", true) + .append("title") + .text(function(d) { + const t1 = data.tourneys[eventKey].teams[d.source.index]; + const t2 = data.tourneys[eventKey].teams[d.target.index]; + + const c1 = data.countries[t1.cId]; + const c2 = data.countries[t2.cId]; + + let s1 = d.game.t1 === t1.tId ? d.game.s1 : d.game.s2; + let s2 = d.game.t2 === t2.tId ? d.game.s2 : d.game.s1; + + if (d.game.sp1 !== null && d.game.sp2 !== null) { + s1 = d.game.t1 === t1.tId ? d.game.sp1 : d.game.sp2; + s2 = d.game.t2 === t2.tId ? d.game.sp2 : d.game.sp1; + + s1 += " (Penalties)"; + s2 += " (Penalties)"; + } + else if (d.game.se1 !== null && d.game.se2 !== null) { + s1 = d.game.t1 === t1.tId ? d.game.se1 : d.game.se2; + s2 = d.game.t2 === t2.tId ? d.game.se2 : d.game.se1; + + s1 += " (Extended time)"; + s2 += " (Extended time)"; + } + + // console.warn(t1.tId, c1, t2.tId, c2, s1, s2) + + return `${c1}: ${s1}\n${c2}: ${s2}\n${data.rounds[d.game.rId]} + \nsource (i,j) val: (${d.source.index}, ${d.source.subindex}) ${d.source.value} + \ntarget (i,j) val: (${d.target.index}, ${d.target.subindex}) ${d.target.value}`; + }); + }, + + buildArcs: ({ container, color, eventKey, data }) => { + const dimensions = Diagram.buildDimensions(); + const arc = d3.arc().innerRadius(dimensions.innerR).outerRadius(dimensions.outerR) + + const group = container.append("g") .attr("class", "groups") .selectAll("g") .data(chords => chords.groups) @@ -192,73 +250,12 @@ const Diagram = { .style("stroke", d => d3.rgb(color(d.index)).darker()) .attr("d", arc); - g.append("g") - .attr("class", "ribbons") - .selectAll("path") - .data(function(chords) { return chords; }) - .enter().append("path") - .attr("d", ribbon) - .attr("data-round-id", (d) => { - const t1 = data.tourneys[eventKey].teams[d.source.index]; - const t2 = data.tourneys[eventKey].teams[d.target.index]; - - const game = data.tourneys[eventKey].games.find(v => { - return (v.t1 === t1.tId || v.t1 === t2.tId) && (v.t2 === t1.tId || v.t2 === t2.tId); - }); - - return game.rId; - }) - .style("fill", (d) => color(d.target.index)) - .style("stroke", (d) => d3.rgb(color(d.target.index)).darker()) - .classed("ribbon", true) - .append("title") - .text(function(d) { - const t1 = data.tourneys[eventKey].teams[d.source.index]; - const t2 = data.tourneys[eventKey].teams[d.target.index]; - - const c1 = data.countries[t1.cId]; - const c2 = data.countries[t2.cId]; - - const game = data.tourneys[eventKey].games.find(v => { - return (v.t1 === t1.tId || v.t1 === t2.tId) && (v.t2 === t1.tId || v.t2 === t2.tId); - }); - - let s1 = game.t1 === t1.tId ? game.s1 : game.s2; - let s2 = game.t2 === t2.tId ? game.s2 : game.s1; - - if (game.sp1 !== null && game.sp2 !== null) { - s1 = game.t1 === t1.tId ? game.sp1 : game.sp2; - s2 = game.t2 === t2.tId ? game.sp2 : game.sp1; - - s1 += " (Penalties)"; - s2 += " (Penalties)"; - } - else if (game.se1 !== null && game.se2 !== null) { - s1 = game.t1 === t1.tId ? game.se1 : game.se2; - s2 = game.t2 === t2.tId ? game.se2 : game.se1; - - s1 += " (Extended time)"; - s2 += " (Extended time)"; - } - - // const s1 = game.t1 === t1.tId ? game.s1 : game.s2; - // const s2 = game.t2 === t2.tId ? game.s2 : game.s1; - // - // const e1 = game.se1 ? `(+${game.se1} in extended time)` : ''; - // const e2 = game.se2 ? `(+${game.se2} in extended time)` : ''; - // - // const p1 = game.sp1 ? `(+${game.sp1} in penalties)` : ''; - // const p2 = game.sp2 ? `(+${game.sp2} in penalties)` : ''; - - return `${c1}: ${s1}\n${c2}: ${s2}\n${data.rounds[game.rId]}`; - }); - 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 - 91) + ")" - + "translate(" + (innerRadius + 26) + ")" + + "translate(" + (dimensions.innerR + 26) + ")" + (d.angle > Math.PI ? "rotate(180)" : ""); }) .style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; }) @@ -266,18 +263,29 @@ const Diagram = { const team = data.tourneys[eventKey].teams[d.index]; let metric = ''; - switch (sort) { - case SORT_TYPES.GOALS: - metric = `(${team.gf})`; - break; - // case SORT_TYPES.POPULATION: - // metric = `(${Number(team.p).toLocaleString()})`; - // break; - } + // switch (sort) { + // case SORT_TYPES.GOALS: + // metric = `(${team.gf})`; + // break; + // // case SORT_TYPES.POPULATION: + // // metric = `(${Number(team.p).toLocaleString()})`; + // // break; + // } return `${data.countries[team.cId]} ${metric}`; }); }, + + buildContainer: (chords) => { + const svg = d3.select("svg"); + const dimensions = Diagram.buildDimensions(); + + return svg.append("g") + .attr("transform", `translate(${ dimensions.svgW / 2 },${ dimensions.svgH / 2 })`) + .datum(chords); + + + }, }; export default Diagram; diff --git a/js/index.js b/js/index.js index 7c547d5..ea8898b 100644 --- a/js/index.js +++ b/js/index.js @@ -44,22 +44,46 @@ const main = { main.updateUI(); }, - getRounds: (eventKey) => { - const rounds = {}; + // DEPRECATED? 161120 + // getRounds: (eventKey) => { + // const rounds = {}; + // + // main.json.tourneys[eventKey].games.forEach(game => { + // const name = UI.getRoundName(main.json.rounds[game.rId]); + // if (rounds[name] === undefined) { + // rounds[name] = []; + // } + // + // rounds[name].push({ + // id: game.rId, + // name: name, + // }); + // }); + // + // return rounds; + // }, + + getDuplicates: (eventKey) => { + const games = {}; main.json.tourneys[eventKey].games.forEach(game => { - const name = UI.getRoundName(main.json.rounds[game.rId]); - if (rounds[name] === undefined) { - rounds[name] = []; + const teams = [game.t1, game.t2].sort(); + const addr = `${teams[0]}-${teams[1]}`; + + if (games[addr] === undefined) { + games[addr] = []; } - rounds[name].push({ - id: game.rId, - name: name, - }); + games[addr].push(game); }); - return rounds; + return Object.keys(games).reduce((acc, k) => { + if (games[k].length > 1) { + acc.push(games[k]); + } + + return acc; + }, []); }, generateUI: () => { @@ -74,10 +98,120 @@ const main = { updateUI: () => { const state = main.getState(); + const eventKey = state.eventKey; + + const matrix = Matrices.buildMatrix(main.json, eventKey); + + const duplicates = main.getDuplicates(eventKey); + + const tmp = Diagram.buildChords({ + data: main.json, + sort: state.sort, + eventKey, + matrix, + SORT_TYPES: UI.SORT_TYPES, + }); + + const getScore = (game) => { + let s1 = game.s1; + let s2 = game.s2; + + if (game.sp1 !== null) { + s1 = game.sp1; + } else if (game.se1 !== null) { + s1 = game.se1; + } + + if (game.sp2 !== null) { + s2 = game.sp2; + } else if (game.se2 !== null) { + s2 = game.se2; + } + + return { s1, s2 } + }; + + const chords = tmp.reduce((acc, d) => { + const tSrc = main.json.tourneys[eventKey].teams[d.source.index].tId; + const tTgt = main.json.tourneys[eventKey].teams[d.target.index].tId; + + d.game = main.json.tourneys[eventKey].games + .find(g => { + return (g.t1 === tSrc && g.t2 === tTgt) || + (g.t1 === tTgt && g.t2 === tSrc) + }); + + duplicates.forEach(games => { + if (games[0] === d.game || games[1] === d.game) { + const gameNew = (games[0] === d.game ? games[1] : games[0]); + const sourceNew = Object.assign({}, d.source); + const targetNew = Object.assign({}, d.target); + + const sourceAngle = d.source.endAngle - d.source.startAngle; + const targetAngle = d.target.endAngle - d.target.startAngle; + + const { s1: s1g0, s2: s2g0 } = getScore(games[0]); + const { s1: s1g1, s2: s2g1 } = getScore(games[1]); + const totals = { src: 0, tgt: 0 }; + const offset = { src: 0, tgt: 0 } + + totals.src += (games[0].t1 === tSrc ? s1g0 : s2g0); + totals.tgt += (games[0].t1 === tTgt ? s1g0 : s2g0); + + totals.src += (games[1].t1 === tSrc ? s1g1 : s2g1); + totals.tgt += (games[1].t1 === tTgt ? s1g1 : s2g1); + + offset.src = (games[0].t1 === tSrc ? s1g0 : s2g0); + offset.tgt = (games[0].t1 === tTgt ? (totals.tgt - s1g0) : (totals.tgt - s2g0)); + + sourceNew.startAngle = d.source.startAngle + sourceAngle * (offset.src / totals.src); + d.source.endAngle = d.source.startAngle + sourceAngle * (offset.src / totals.src); + + targetNew.endAngle = d.target.startAngle + targetAngle * (offset.tgt / totals.tgt); + d.target.startAngle = d.target.startAngle + targetAngle * (offset.tgt / totals.tgt); + + acc.push({ source: sourceNew, target: targetNew, game: gameNew }); + } + }); + + acc.push(d); + + return acc; + + // 127/110 Germany 153/137 Switzerland + // if (`${d.source.index},${d.source.subindex}` === "14,10" && + // `${d.target.index},${d.target.subindex}` === "5,2") { + // // source has to split into 1/5, 4/5 + // + // const sourceAngle = d.source.endAngle - d.source.startAngle; + // const targetAngle = d.target.endAngle - d.target.startAngle; + // + // const sourceNew = Object.assign({}, d.source); + // sourceNew.startAngle = d.source.startAngle + sourceAngle * (1/5); + // d.source.endAngle = d.source.startAngle + sourceAngle * (1/5); + // + // const targetNew = Object.assign({}, d.target); + // targetNew.endAngle = d.target.startAngle + targetAngle * (2/3); + // d.target.startAngle = d.target.startAngle + targetAngle * (2/3); + // + // chords.push({ source: sourceNew, target: targetNew }); + // } + }, []); + + chords.groups = tmp.groups; + // + // console.warn(test) - const matrix = Matrices.buildMatrix(main.json, state.eventKey); Diagram.clear(); - Diagram.build(main.json, state.eventKey, state.sort, state.scheme, UI.SORT_TYPES, matrix); + + const color = Diagram.buildColorScheme({ + scheme: parseInt(state.scheme), + len: main.json.tourneys[state.eventKey].teams.length, + }); + + const container = Diagram.buildContainer(chords); + Diagram.buildArcs({ container, color, eventKey, data: main.json }); + Diagram.buildRibbons({ container, color, chords, eventKey, data: main.json }); UI.updateTourneyPane(state.eventKey); UI.updateEventsPane(state.eventKey); diff --git a/js/matrices.js b/js/matrices.js index e1c262e..a08e875 100644 --- a/js/matrices.js +++ b/js/matrices.js @@ -36,9 +36,9 @@ const Matrices = { else { s2 += g.s2; } - - matrix[i1][i2] = g.s1 + g.se1 + g.sp1; - matrix[i2][i1] = g.s2 + g.se2 + g.sp2; + + matrix[i1][i2] = s1; + matrix[i2][i1] = s2; }, []); return matrix;