diff --git a/.gitignore b/.gitignore index 34cc083..ae43b4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ data node_modules +npm-debug.log tmp yarn.lock diff --git a/index.html b/index.html index b874f17..271e9fa 100644 --- a/index.html +++ b/index.html @@ -4,18 +4,9 @@ - - - - - + + - - - - - - @@ -54,6 +45,7 @@ better layout: embiggen current event flag better colors webpack 2 / css modules? + remove dependencies from Diagram tweet it! diff --git a/js/bundle.js b/js/bundle.js new file mode 100644 index 0000000..5b153d0 --- /dev/null +++ b/js/bundle.js @@ -0,0 +1,845 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _matrices = __webpack_require__(1); + + var _matrices2 = _interopRequireDefault(_matrices); + + var _diagram = __webpack_require__(2); + + var _diagram2 = _interopRequireDefault(_diagram); + + var _ui = __webpack_require__(4); + + var _ui2 = _interopRequireDefault(_ui); + + var _sorter = __webpack_require__(3); + + var _sorter2 = _interopRequireDefault(_sorter); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + __webpack_require__(5); + __webpack_require__(6); + __webpack_require__(7); + __webpack_require__(8); + __webpack_require__(9); + __webpack_require__(10); + + var main = { + changeEvent: function changeEvent(e) { + var target = _ui2.default.findRoot(e.target); + main.setState({ eventKey: target.getAttribute(_ui2.default.DATA.EVENT) }); + main.updateUI(); + }, + + changeSort: function changeSort(e) { + var target = _ui2.default.findRoot(e.target); + main.setState({ sort: target.getAttribute(_ui2.default.DATA.SORT) }); + main.updateUI(); + }, + + changeRound: function changeRound(e) { + var target = _ui2.default.findRoot(e.target); + var r = target.getAttribute(_ui2.default.DATA.ROUND); + + var state = main.getState(); + var roundsToShow = state.rounds ? state.rounds.split(',') : []; + var i = roundsToShow.indexOf(r); + i === -1 ? roundsToShow.push(r) : roundsToShow.splice(i, 1); + + main.setState({ rounds: roundsToShow }); + main.updateUI(); + }, + + getRounds: function getRounds(eventKey) { + var rounds = {}; + + main.json.tourneys[eventKey].games.forEach(function (game) { + var name = _ui2.default.getRoundName(main.json.rounds[game.rId]); + if (rounds[name] === undefined) { + rounds[name] = []; + } + + rounds[name].push({ + id: game.rId, + name: name + }); + }); + + return rounds; + }, + + generateUI: function generateUI() { + var state = main.getState(); + + _ui2.default.buildEventsPane(main.changeEvent); + _ui2.default.buildSortPane(main.changeSort); + _ui2.default.buildRoundsPane(main.changeRound); + }, + + updateUI: function updateUI() { + var state = main.getState(); + + var matrix = _matrices2.default.buildMatrix(main.json, state.eventKey); + _diagram2.default.clear(); + _diagram2.default.build(main.json, state.eventKey, state.sort, _ui2.default.SORT_TYPES, matrix); + + _ui2.default.updateEventsPane(state.eventKey); + _ui2.default.updateSortPane(state.sort); + _ui2.default.updateRoundsPane(state.rounds.split(','), main.json.rounds); + }, + + fetch: function fetch(url) { + return new Promise(function (resolve, reject) { + var listener = function listener(_ref) { + var req = _ref.srcElement; + + req.status === 200 ? resolve(req.responseText) : reject("busted"); + }; + + var req = new XMLHttpRequest(); + req.addEventListener('load', listener); + req.open('GET', url); + req.send(); + }); + }, + + initJSON: function initJSON(strData) { + main.json = JSON.parse(strData); + }, + + getState: function getState() { + var params = window.location.href.split('?')[1]; + + if (!params) { + return {}; + } + + return params.split('&').reduce(function (acc, v) { + var tmp = v.split('='); + acc[tmp[0]] = tmp[1]; + return acc; + }, {}); + }, + + initState: function initState() { + var state = main.getState(); + + if (state.eventKey === undefined) { + state.eventKey = "1930"; + } + + if (state.sort === undefined) { + state.sort = null; + } + + if (state.rounds === undefined) { + state.rounds = Object.values(_ui2.default.ROUND_TYPES); + } + + main.setState(state); + }, + + setState: function setState(next) { + var state = main.getState(); + var url = window.location.href.split('?')[0]; + + state.eventKey = next.eventKey || state.eventKey; + state.rounds = next.rounds || state.rounds; + state.sort = next.sort || state.sort || null; + + var params = []; + for (var key in state) { + params.push(key + '=' + state[key]); + } + + history.pushState(null, null, url + '?' + params.join('&')); + } + }; + + main.fetch('worldcup.json').then(main.initJSON).then(main.initState).then(main.generateUI).then(main.updateUI); + +/***/ }, +/* 1 */ +/***/ function(module, exports) { + + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + var Matrices = { + // A x A array of nulls; null-null relationships are omitted in chord diagram. + createEmptyMatrix: function createEmptyMatrix(len) { + var empty = Array.apply(null, Array(len)); + return empty.map(function () { + return empty.map(function () { + return null; + }); + }); + }, + + // Scalar data points (sum of goals scored). + buildMatrix: function buildMatrix(json, eventKey) { + var teams = json.tourneys[eventKey].teams; + var matrix = Matrices.createEmptyMatrix(teams.length); + + json.tourneys[eventKey].games.forEach(function (g) { + var i1 = teams.findIndex(function (v) { + return v.tId === g.t1; + }); + var i2 = teams.findIndex(function (v) { + return v.tId === g.t2; + }); + + matrix[i1][i2] = g.s1 + g.se1 + g.sp1; + matrix[i2][i1] = g.s2 + g.se2 + g.sp2; + }, []); + + return matrix; + }, + + swapIndices: function swapIndices(arr, i, j) { + var tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + + return arr; + } + }; + + exports.default = Matrices; + +/***/ }, +/* 2 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _sorter = __webpack_require__(3); + + var _sorter2 = _interopRequireDefault(_sorter); + + var _matrices = __webpack_require__(1); + + var _matrices2 = _interopRequireDefault(_matrices); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + var Diagram = { + clear: function clear() { + return d3.select('svg').selectAll("*").remove(); + }, + + swapGroupArcs: function swapGroupArcs(chords, i, j) { + if (i < 0 || j < 0 || i === j) { + return chords; + } + + // Determine which arc is closer to 0, so earlier arc always swaps with later. + var f = chords.groups[i].endAngle < chords.groups[j].endAngle ? i : j; + var s = chords.groups[i].endAngle < chords.groups[j].endAngle ? j : i; + + var fst = Object.assign({}, chords.groups[f]); + var snd = Object.assign({}, chords.groups[s]); + + var fstAngle = fst.endAngle - fst.startAngle; + var sndAngle = snd.endAngle - snd.startAngle; + var offsetAngle = sndAngle - fstAngle; + + // Bring first forward. + chords.groups[f].startAngle = snd.endAngle - fstAngle; + chords.groups[f].endAngle = snd.endAngle; + chords.groups[f].index = snd.index; + + // Bring second back. + chords.groups[s].startAngle = fst.startAngle; + chords.groups[s].endAngle = fst.startAngle + sndAngle; + chords.groups[s].index = fst.index; + + // Bump other groups forward. + for (var ii = f + 1; ii < s; ii++) { + chords.groups[ii].startAngle += offsetAngle; + chords.groups[ii].endAngle += offsetAngle; + } + + chords.forEach(function (v) { + if (v.source.index === f) { + v.source.startAngle += snd.endAngle - fst.endAngle; + v.source.endAngle += snd.endAngle - fst.endAngle; + } + + if (v.target.index === f) { + v.target.startAngle += snd.endAngle - fst.endAngle; + v.target.endAngle += snd.endAngle - fst.endAngle; + } + + if (v.source.index === s) { + v.source.startAngle -= snd.startAngle - fst.startAngle; + v.source.endAngle -= snd.startAngle - fst.startAngle; + } + + if (v.target.index === s) { + v.target.startAngle -= snd.startAngle - fst.startAngle; + v.target.endAngle -= snd.startAngle - fst.startAngle; + } + + if (v.source.index > f && v.source.index < s) { + v.source.startAngle += offsetAngle; + v.source.endAngle += offsetAngle; + } + + if (v.target.index > f && v.target.index < s) { + v.target.startAngle += offsetAngle; + v.target.endAngle += offsetAngle; + } + + if ((v.source.index === f || v.source.index === s) && (v.target.index === f || v.target.index === s)) { + var _tmp = v.source.index; + v.source.index = v.target.index; + v.target.index = _tmp; + } else if (v.source.index === f) { + v.source.index = s; + } else if (v.target.index === f) { + v.target.index = s; + } else if (v.source.index === s) { + v.source.index = f; + } else if (v.target.index === s) { + v.target.index = f; + } + }); + + // Swap array positions. + var tmp = chords.groups[f]; + chords.groups[f] = chords.groups[s]; + chords.groups[s] = tmp; + + return chords; + }, + + swapGroups: function swapGroups(data, eventKey, chords, i, j) { + Diagram.swapGroupArcs(chords, i, j); + _matrices2.default.swapIndices(data.tourneys[eventKey].teams, i, j); + return chords; + }, + + getCountryName: function getCountryName(data, eventKey, n) { + var team = data.tourneys[eventKey].teams[n]; + return data.countries[team.cId]; + }, + + getPopulation: function getPopulation(data, eventKey, n) { + var team = data.tourneys[eventKey].teams[n]; + return team.p; + }, + + getGoalsFor: function getGoalsFor(data, eventKey, n) { + var team = data.tourneys[eventKey].teams[n]; + return team.gf; + }, + + getGoalsAgainst: function getGoalsAgainst(data, eventKey, n) { + var team = data.tourneys[eventKey].teams[n]; + return team.ga; + }, + + build: function build(data, eventKey, sort, 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 - 125, + innerRadius = outerRadius - outerArcThickness; + + 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; + 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; + 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; + } + + var arc = d3.arc().innerRadius(innerRadius).outerRadius(outerRadius); + + var ribbon = d3.ribbon().radius(innerRadius); + + // const color = d3.scaleOrdinal(d3.schemeCategory20); + // const color = d3.scaleOrdinal(d3.schemeCategory10); + + var len = data.tourneys[eventKey].teams.length; + // const color = d3.scaleLinear().domain([0, len]).range(["#edf8b1", "#081d58"]).interpolate(d3.interpolateRgb); + // const color = d3.scaleLinear().domain([0, len]).range(["#aaa", "green"]).interpolate(d3.interpolateRgb); + var color = d3.scaleLinear().domain([0, len]).range(["gainsboro", "darkgreen"]).interpolate(d3.interpolateRgb); + + // const colors = ["#ffffcc","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#0c2c84"]; + // const colors = ["#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"]; + // const color = i => colors[i % colors.length]; + + var g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")").datum(sortedChords); + + var 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); + + 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]; + + 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); + }); + + return game.rId; + }).style("fill", function (d) { + return color(d.target.index); + }).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]; + var t2 = data.tourneys[eventKey].teams[d.target.index]; + + 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 e1 = game.se1 ? '(+' + game.se1 + ' in extended time)' : ''; + var e2 = game.se2 ? '(+' + game.se2 + ' in extended time)' : ''; + + var p1 = game.sp1 ? '(+' + game.sp1 + ' in penalties)' : ''; + var p2 = game.sp2 ? '(+' + game.sp2 + ' in penalties)' : ''; + + return c1 + ': ' + game.s1 + ' ' + e1 + ' ' + p1 + '\n' + c2 + ': ' + game.s2 + ' ' + e2 + ' ' + p2; + }); + + 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)" : ""); + }).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; + } + + return data.countries[team.cId] + ' ' + metric; + }); + } + }; + + exports.default = Diagram; + +/***/ }, +/* 3 */ +/***/ function(module, exports) { + + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + var Sorter = { + sort: function sort(chords, start, end, getVal, swap) { + if (end <= start) { + return chords; + } + + var left = start; + var right = end; + + var pivot = Math.floor((right + left) / 2); + var pivotval = getVal(pivot); + var tmp; + var rval; + + while (left <= right) { + while (getVal(left) < pivotval) { + left++; + } + + while (getVal(right) > pivotval) { + right--; + } + + if (left <= right) { + chords = swap(chords, left, right); + + left++; + right--; + } + break; + } + + Sorter.sort(chords, start, right, getVal, swap); + Sorter.sort(chords, left, end, getVal, swap); + + return chords; + } + }; + + exports.default = Sorter; + +/***/ }, +/* 4 */ +/***/ function(module, exports) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + var UI = { + CLASSNAMES: { + EVENT: { + ITEM: 'event-item', + YEAR: 'event-year', + FLAG: 'event-flag', + LOCATION: 'event-location' + }, + + SORT: { + ITEM: 'sort-item', + TEXT: 'sort-text' + }, + + ROUND: { + ITEM: 'round-item', + HIDE: 'round-hide', + TEXT: 'round-text' + } + }, + + SORT_TYPES: { + GOALS: 'goals', + COUNTRY: 'country', + POPULATION: 'population' + }, + + ROUND_TYPES: { + PRELIM: 'prelims', + ROUNDOF16: 'round-of-16', + QUARTERFINAL: 'quarterfinals', + SEMIFINAL: 'semifinals', + CONSOLATION: 'consolation', + FINAL: 'finals' + }, + + DATA: { + ROOT: 'data-root', + EVENT: 'data-event-key', + ROUND: 'data-round-key', + SORT: 'data-sort-key' + }, + + I18N: { + HIDE: 'hide', + SHOW: 'show' + }, + + findRoot: function findRoot(node) { + while (node !== document.body && node.getAttribute(UI.DATA.ROOT) === null) { + node = node.parentNode; + } + + return node; + }, + + clearActive: function clearActive(nodes) { + nodes.forEach(function (node) { + var classes = node.className.split(' '); + classes.splice(1, classes.indexOf('active')); + node.className = classes.join(' '); + }); + }, + + updateEventsPane: function updateEventsPane(eventKey) { + var eventItems = document.querySelectorAll('.' + UI.CLASSNAMES.EVENT.ITEM); + UI.clearActive(eventItems); + + var activeEventNode = document.querySelector('[' + UI.DATA.EVENT + '="' + eventKey + '"'); + activeEventNode.className += ' active'; + }, + + updateRoundsPane: function updateRoundsPane(roundsToShow, allRounds) { + var roundsItems = document.querySelectorAll('.' + UI.CLASSNAMES.ROUND.ITEM); + UI.clearActive(roundsItems); + + roundsItems.forEach(function (item) { + var type = item.getAttribute(UI.DATA.ROUND); + var hide = item.querySelector('.' + UI.CLASSNAMES.ROUND.HIDE); + hide.innerHTML = UI.I18N.SHOW; + + if (roundsToShow.indexOf(type) > -1) { + item.className += ' active'; + hide.innerHTML = UI.I18N.HIDE; + } + }); + + var ribbons = document.querySelectorAll('.ribbon'); + ribbons.forEach(function (ribbon) { + var name = allRounds[ribbon.getAttribute("data-round-id")]; + var type = UI.getRoundType(name); + + roundsToShow.indexOf(type) === -1 ? ribbon.style.display = 'none' : ribbon.style.display = 'inline'; + }); + }, + + updateSortPane: function updateSortPane(sort) { + var sortItems = document.querySelectorAll('.sort-item'); + UI.clearActive(sortItems); + + sortItems.forEach(function (node) { + if (node.getAttribute(UI.DATA.SORT) === sort) { + node.className += ' active'; + } + }); + }, + + buildEventsPane: function buildEventsPane(onClick) { + var eventsList = [{ year: 1930, location: "Uruguay", icon: "uy" }, { year: 1934, location: "Italy", icon: "it" }, { year: 1938, location: "France", icon: "fr" }, { year: 1954, location: "Switzerland", icon: "ch" }, { year: 1958, location: "Sweden", icon: "se" }, { year: 1962, location: "Chile", icon: "cl" }, { year: 1966, location: "England", icon: "gb" }, { year: 1970, location: "Mexico", icon: "mx" }, { year: 1974, location: "West Germany", icon: "de" }, { year: 1978, location: "Argentina", icon: "ar" }, { year: 1982, location: "Spain", icon: "es" }, { year: 1986, location: "Mexico", icon: "mx" }, { year: 1990, location: "Italy", icon: "it" }, { year: 1994, location: "USA", icon: "us" }, { year: 1998, location: "France", icon: "fr" }, { year: 2002, location: "Japan / South Korea", icon: "jp" }, { year: 2006, location: "Germany", icon: "de" }, { year: 2010, location: "Johannesburg", icon: "za" }, { year: 2014, location: "Brazil", icon: "br" }]; + + var eventsDiv = document.querySelector('.events'); + + while (eventsDiv.hasChildNodes()) { + eventsDiv.firstChild.remove(); + } + + eventsList.forEach(function (evt) { + var item = document.createElement('div'); + var year = document.createElement('div'); + var location = document.createElement('div'); + var flag = document.createElement('div'); + var flagIcon = document.createElement('div'); + + item.className = UI.CLASSNAMES.EVENT.ITEM; + item.addEventListener('click', onClick); + item.setAttribute(UI.DATA.EVENT, evt.year); + item.setAttribute(UI.DATA.ROOT, true); + + year.className = UI.CLASSNAMES.EVENT.YEAR; + year.innerHTML = evt.year; + + location.className = UI.CLASSNAMES.EVENT.LOCATION; + location.innerHTML = evt.location; + + flag.className = UI.CLASSNAMES.EVENT.FLAG; + flagIcon.className = 'flag-icon flag-icon-' + evt.icon; + + flag.appendChild(flagIcon); + item.appendChild(flag); + item.appendChild(year); + item.appendChild(location); + + eventsDiv.appendChild(item); + }); + }, + + buildSortPane: function buildSortPane(onClick) { + var sortList = [{ text: 'Order by continent', value: null }, { text: 'Order by goals scored', value: UI.SORT_TYPES.GOALS }, { text: 'Order by country name', value: UI.SORT_TYPES.COUNTRY }, { text: 'Order by country population', value: UI.SORT_TYPES.POPULATION }]; + + var sortDiv = document.querySelector('.sort'); + + while (sortDiv.hasChildNodes()) { + sortDiv.firstChild.remove(); + } + + sortList.forEach(function (sort) { + var item = document.createElement('div'); + var text = document.createElement('div'); + + item.className = UI.CLASSNAMES.SORT.ITEM; + item.addEventListener('click', onClick); + item.setAttribute(UI.DATA.SORT, sort.value); + item.setAttribute(UI.DATA.ROOT, true); + + text.className = UI.CLASSNAMES.SORT.TEXT; + text.innerHTML = sort.text; + + item.appendChild(text); + sortDiv.appendChild(item); + }); + }, + + buildRoundsPane: function buildRoundsPane(onClick) { + var roundsList = [{ text: 'Preliminaries', value: UI.ROUND_TYPES.PRELIM }, { text: 'Round of 16', value: UI.ROUND_TYPES.ROUNDOF16 }, { text: 'Quarterfinals', value: UI.ROUND_TYPES.QUARTERFINAL }, { text: 'Semifinals', value: UI.ROUND_TYPES.SEMIFINAL }, { text: 'Consolation', value: UI.ROUND_TYPES.CONSOLATION }, { text: 'Final', value: UI.ROUND_TYPES.FINAL }]; + + var roundsDiv = document.querySelector('.rounds'); + + while (roundsDiv.hasChildNodes()) { + roundsDiv.firstChild.remove(); + } + + roundsList.forEach(function (round) { + var item = document.createElement('div'); + item.className = UI.CLASSNAMES.ROUND.ITEM; + item.addEventListener('click', onClick); + item.setAttribute(UI.DATA.ROUND, round.value); + item.setAttribute(UI.DATA.ROOT, true); + + var hide = document.createElement('div'); + hide.className = UI.CLASSNAMES.ROUND.HIDE; + hide.innerHTML = 'hide'; + + var text = document.createElement('div'); + text.className = UI.CLASSNAMES.ROUND.TEXT; + text.innerHTML = round.text; + + item.appendChild(text); + item.appendChild(hide); + roundsDiv.appendChild(item); + }); + }, + + getRoundType: function getRoundType(name) { + var name2 = name.match(/^Matchday/) ? 'First round' : name; + + switch (name2) { + case 'Preliminary round': + case 'First round': + case 'First round replays': + case 'Group-1 Play-off': + case 'Group-2 Play-off': + case 'Group-3 Play-off': + case 'Group-4 Play-off': + return UI.ROUND_TYPES.PRELIM; + case 'Round of 16': + return UI.ROUND_TYPES.ROUNDOF16; + case 'Third place match': + case 'Third-place match': + case 'Match for third place': + case 'Third place play-off': + case 'Third-place play-off': + return UI.ROUND_TYPES.CONSOLATION; + case 'Quarterfinals': + case 'Quarter-finals': + case 'Quarter-finals replays': + return UI.ROUND_TYPES.QUARTERFINAL; + case 'Semifinals': + case 'Semi-finals': + return UI.ROUND_TYPES.SEMIFINAL; + case 'Final': + case 'Finals': + return UI.ROUND_TYPES.FINAL; + } + + console.error('Unknown round name: ' + name); + return name; + } + }; + + exports.default = UI; + +/***/ }, +/* 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 + +/***/ } +/******/ ]); \ No newline at end of file diff --git a/js/diagram.js b/js/diagram.js index 60a5173..7fd87f3 100644 --- a/js/diagram.js +++ b/js/diagram.js @@ -1,5 +1,5 @@ -const goalsFor = {}; -const goalsAgainst = {}; +import Sorter from './sorter'; +import Matrices from './matrices'; const Diagram = { clear: () => d3.select('svg').selectAll("*").remove(), @@ -251,3 +251,5 @@ const Diagram = { }); }, }; + +export default Diagram; diff --git a/js/main.js b/js/index.js similarity index 91% rename from js/main.js rename to js/index.js index c1bae92..0a7a99c 100644 --- a/js/main.js +++ b/js/index.js @@ -1,3 +1,14 @@ +import Matrices from './matrices'; +import Diagram from './diagram'; +import UI from './ui'; +import Sorter from './sorter' + +require('../res/reset.scss'); +require('../res/options.scss'); +require('../res/events.scss'); +require('../res/diagram.scss'); +require('../res/sort.scss'); +require('../res/rounds.scss'); const main = { changeEvent: (e) => { @@ -119,7 +130,7 @@ const main = { state.sort = next.sort || state.sort || null; const params = []; - for (key in state) { + for (let key in state) { params.push(`${key}=${state[key]}`); } diff --git a/js/matrices.js b/js/matrices.js index f74f3e5..9c16cc4 100644 --- a/js/matrices.js +++ b/js/matrices.js @@ -29,3 +29,5 @@ const Matrices = { return arr; }, }; + +export default Matrices; diff --git a/js/sorter.js b/js/sorter.js index 3c03a3b..956730b 100644 --- a/js/sorter.js +++ b/js/sorter.js @@ -36,3 +36,5 @@ const Sorter = { return chords; } }; + +export default Sorter; diff --git a/js/ui.js b/js/ui.js index 9e66d0c..d391e63 100644 --- a/js/ui.js +++ b/js/ui.js @@ -270,3 +270,5 @@ const UI = { return name; }, }; + +export default UI; diff --git a/package.json b/package.json index a7e7241..e3dd705 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,10 @@ "asynckit": "^0.4.0", "aws-sign2": "^0.6.0", "aws4": "^1.5.0", + "babel": "^6.5.2", + "babel-core": "^6.18.2", + "babel-loader": "^6.2.7", + "babel-preset-es2015": "^6.18.0", "bl": "^1.1.2", "boom": "^2.10.1", "caseless": "^0.11.0", @@ -19,10 +23,12 @@ "commander": "^2.9.0", "core-util-is": "^1.0.2", "cryptiles": "^2.0.5", + "css-loader": "^0.25.0", "dashdash": "^1.14.0", "delayed-stream": "^1.0.0", "escape-string-regexp": "^1.0.5", "extend": "^3.0.0", + "extract-text-webpack-plugin": "^1.0.1", "extsprintf": "^1.0.2", "forever-agent": "^0.6.1", "form-data": "^2.0.0", @@ -47,6 +53,7 @@ "jsprim": "^1.3.1", "mime-db": "^1.24.0", "mime-types": "^2.1.12", + "node-sass": "^3.11.2", "node-uuid": "^1.4.7", "oauth-sign": "^0.8.2", "pinkie": "^2.0.4", @@ -55,6 +62,7 @@ "qs": "^6.2.1", "readable-stream": "^2.0.6", "request": "^2.75.0", + "sass-loader": "^4.0.2", "sntp": "^1.0.9", "sqlite-to-json": "^0.1.2", "sqlite3": "^3.1.6", @@ -62,17 +70,19 @@ "string_decoder": "^0.10.31", "stringstream": "^0.0.5", "strip-ansi": "^3.0.1", + "style-loader": "^0.13.1", "supports-color": "^2.0.0", "tough-cookie": "^2.3.1", "tunnel-agent": "^0.4.3", "tweetnacl": "^0.14.3", "util-deprecate": "^1.0.2", "verror": "^1.3.6", + "webpack": "^1.13.3", "xtend": "^4.0.1" }, "devDependencies": {}, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Working\"" }, "repository": { "type": "git", diff --git a/res/diagram.css b/res/diagram.scss similarity index 100% rename from res/diagram.css rename to res/diagram.scss diff --git a/res/events.css b/res/events.scss similarity index 100% rename from res/events.css rename to res/events.scss diff --git a/res/options.css b/res/options.scss similarity index 100% rename from res/options.css rename to res/options.scss diff --git a/res/reset.scss b/res/reset.scss new file mode 100644 index 0000000..76643d6 --- /dev/null +++ b/res/reset.scss @@ -0,0 +1,17 @@ +* { + border-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: sans-serif; + padding: 20px; +} + +.visualization { + height: 700px; + margin: 0 auto; + position: relative; + width: 1100px; +} diff --git a/res/rounds.css b/res/rounds.scss similarity index 100% rename from res/rounds.css rename to res/rounds.scss diff --git a/res/sort.css b/res/sort.scss similarity index 100% rename from res/sort.css rename to res/sort.scss diff --git a/res/style.css b/res/style.css new file mode 100644 index 0000000..fe0bf33 --- /dev/null +++ b/res/style.css @@ -0,0 +1,171 @@ +* { + border-sizing: border-box; + margin: 0; + padding: 0; } + +body { + font-family: sans-serif; + padding: 20px; } + +.visualization { + height: 700px; + margin: 0 auto; + position: relative; + width: 1100px; } +.options { + color: white; + left: 900px; + height: 100%; + min-width: 200px; + position: absolute; + text-align: right; + top: 0; + width: 200px; } + +.option-divider { + margin: 19px 0 19px 25%; + width: 75%; } + +.option-item { + background: teal; + border-radius: 16px 0 0 16px; + cursor: pointer; + height: 24px; + line-height: 24px; + margin-bottom: 4px; + padding: 4px 16px 4px 4px; } + +.option-item:hover { + opacity: 0.7; } + +.option-item.inactive { + opacity: 0.2; } + +.option-text { + display: inline-block; + font-size: 12px; } + +.option-toggle { + display: inline-block; + margin-left: 25px; } +.events { + display: table; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 200px; } + +.event-item { + /*background: #CBFFC0;*/ + border-color: #fff; + /*border-radius: 0 16px 16px 0;*/ + border-style: solid; + border-width: 1px 0; + cursor: pointer; + filter: grayscale(100%); + height: 24px; + margin-bottom: 4px; + opacity: 0.2; + padding: 4px 4px 4px 44px; + position: relative; } + +.event-item.active, +.event-item.active:hover { + background: #fdfdfd; + border-color: #aaa; + filter: grayscale(0%); + opacity: 1; } + +.event-item:hover { + filter: grayscale(0%); + opacity: 0.8; } + +.event-flag { + font-size: 0; + left: 4px; + position: absolute; + top: 4px; } + +.event-flag .flag-icon { + font-size: 24px; } + +.event-location { + font-size: 10px; + line-height: 10px; } + +.event-year { + font-size: 13px; + line-height: 14px; } +.diagram { + font-size: 10px; + height: 700px; + left: 200px; + position: absolute; + top: 0; + width: 800px; + z-index: 0; } + +.group-tick line { + stroke: #ddd; } + +.ribbon { + fill-opacity: 0.4; + stroke-width: 1; + stroke-opacity: 0.1; } + +.ribbon:hover { + fill-opacity: 1; + stroke-opacity: 1; } +.sort { + position: absolute; + right: 0; + text-align: right; + top: 0; + width: 200px; + z-index: 1; } + +.sort-item { + border: 1px solid brickred; + cursor: pointer; + font-size: 13px; + line-height: 34px; + padding: 0 10px; } + +.sort-item:hover { + background: yellow; } + +.sort-item.active { + background: red; } +.rounds { + bottom: 0; + color: #000; + position: absolute; + right: 0; + text-align: right; + width: 200px; + z-index: 1; } + +.round-item { + cursor: pointer; + font-size: 13px; + line-height: 34px; + opacity: 0.2; + padding: 0 10px; } + +.round-item.active { + opacity: 1; } + +.round-item:hover { + background: yellow; } + +.round-hide { + display: inline-block; + font-size: 9px; + margin-left: 20px; + text-align: right; + text-transform: uppercase; + width: 30px; } + +.round-text { + display: inline-block; } diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..68f8d50 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,42 @@ +var ExtractTextPlugin = require('extract-text-webpack-plugin'); + +module.exports = { + entry: './js/index.js', + + module: { + loaders: [ + { + test: /\.js$/, + exclude: __dirname + '/node_modules', + loader: 'babel-loader', + query: { + presets: ['es2015'] + } + }, + { + test: /\.(scss|css)$/, + include: __dirname + '/res', + loader: ExtractTextPlugin.extract('css!sass?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'), + }, + { + test: /\.png$/, + loader: "file?name=[path][name].[ext]" + }, + { + test : /\.woff2?$/, + loader : 'file-loader' + } + ] + }, + + output: { + path: __dirname, + filename: './js/bundle.js' + }, + + plugins: [ + new ExtractTextPlugin('./res/style.css', { + allChunks: true + }) + ] +};