├── README.md
├── favicon.ico
├── spaces
├── github.svg
├── matches.tsv
├── LICENSE
├── annotations.json
├── index.html
├── style.css
├── swoopy-drag.js
└── script.js
/README.md:
--------------------------------------------------------------------------------
1 | # worlds-2019
2 | https://rocket3989.github.io/worlds-2019/
3 |
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket3989/worlds-2019/HEAD/favicon.ico
--------------------------------------------------------------------------------
/spaces:
--------------------------------------------------------------------------------
1 | [master cd8fe61] tabs -
2 | 2 files changed, 40 insertions(+), 40 deletions(-)
3 | create mode 100644 spaces
4 | Already up to date.
5 |
6 |
--------------------------------------------------------------------------------
/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/matches.tsv:
--------------------------------------------------------------------------------
1 | group t1 t2 winner date
2 | C FNC SKT 2 10-12
3 | C RNG CG 1 10-12
4 | D IG AHQ 1 10-12
5 | D DWG TL 2 10-12
6 | B JT FPX 1 10-12
7 | B GAM SPY 2 10-12
8 | C SKT RNG 1 10-13
9 | C FNC CG 1 10-13
10 | D TL IG 2 10-13
11 | D AHQ DWG 2 10-13
12 | A GRF G2 2 10-13
13 | A C9 HKA 1 10-13
14 | D IG DWG 2 10-14
15 | D AHQ TL 2 10-14
16 | B FPX SPY 1 10-14
17 | B JT GAM 2 10-14
18 | A GRF HKA 1 10-14
19 | A G2 C9 1 10-14
20 | C RNG FNC 1 10-15
21 | C CG SKT 2 10-15
22 | B FPX GAM 1 10-15
23 | B SPY JT 2 10-15
24 | A C9 GRF 2 10-15
25 | A HKA G2 2 10-15
26 | B GAM FPX TBD 10-17
27 | B JT SPY TBD 10-17
28 | B GAM JT TBD 10-17
29 | B SPY FPX TBD 10-17
30 | B SPY GAM TBD 10-17
31 | B FPX JT TBD 10-17
32 | A GRF C9 TBD 10-18
33 | A G2 HKA TBD 10-18
34 | A HKA GRF TBD 10-18
35 | A C9 G2 TBD 10-18
36 | A HKA C9 TBD 10-18
37 | A G2 GRF TBD 10-18
38 | C RNG SKT TBD 10-19
39 | C CG FNC TBD 10-19
40 | C SKT FNC TBD 10-19
41 | C CG RNG TBD 10-19
42 | C SKT CG TBD 10-19
43 | C FNC RNG TBD 10-19
44 | D AHQ IG TBD 10-20
45 | D TL DWG TBD 10-20
46 | D TL AHQ TBD 10-20
47 | D DWG IG TBD 10-20
48 | D DWG AHQ TBD 10-20
49 | D IG TL TBD 10-20
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 rocket3989
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/annotations.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "path": "M -36,279 A 45.026 45.026 0 0 0 43,310",
4 | "text": "Tap to see how the first match affects the group",
5 | "team": "C9",
6 | "lw": 21,
7 | "textOffset": [
8 | -47,
9 | 244
10 | ]
11 | },
12 | {
13 | "path": "M -15,-69 A 105.02 105.02 0 0 0 -3,39",
14 | "text": "C9 needs to beat GRF for the best chance out",
15 | "team": "C9",
16 | "lw": 22,
17 | "textOffset": [
18 | -47,
19 | -94
20 | ]
21 | },
22 | {
23 | "path": "M -2000 0",
24 | "text": "Two wins advance JT or FPX out of groups",
25 | "team": "FPX",
26 | "lw": 22,
27 | "textOffset": [
28 | -107,
29 | 12
30 | ]
31 | },
32 | {
33 | "path": "M -2000 0",
34 | "text": "Two loses knock GAM and SPY out",
35 | "team": "SPY",
36 | "lw": 22,
37 | "textOffset": [
38 | -107,
39 | 12
40 | ]
41 | },
42 | {
43 | "path": "M 261,-64 A 62.002 62.002 0 0 1 182,4",
44 | "text": "Even with three wins, CG can still be eliminated",
45 | "team": "CG",
46 | "lw": 24,
47 | "textOffset": [
48 | 183,
49 | -88
50 | ]
51 | },
52 | {
53 | "path": "M 261,-64 A 62.002 62.002 0 0 1 182,4",
54 | "text": "AHQ needs to go 3-0 and win a tiebreaker to advance",
55 | "team": "AHQ",
56 | "lw": 24,
57 | "textOffset": [
58 | 183,
59 | -88
60 | ]
61 | },
62 | {
63 | "path": "M 286,220 A 123.355 123.355 0 0 1 105,221",
64 | "text": "G2 can lose all three games and still advance without a tiebreaker",
65 | "team": "G2",
66 | "lw": 24,
67 | "textOffset": [
68 | 241, 185
69 | ]
70 | },
71 | {
72 | "path": "M -26,-55 A 179.253 179.253 0 0 0 -6,62",
73 | "text": "CG has a slim chance of getting a tiebreaker without beating SKT",
74 | "team": "CG",
75 | "lw": 24,
76 | "textOffset": [
77 | -92,-90
78 | ]
79 | },
80 | {
81 | "path": "M -2000",
82 | "text": "With each of the top three teams holding a win over another, Group D is poised to have an exciting finish",
83 | "team": "TL",
84 | "lw": 24,
85 | "textOffset": [
86 | 234,-137
87 | ]
88 | }
89 | ]
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
17 |
18 |
19 | 2019 Worlds Group Advancement
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
47 |
48 | 2019 Worlds Group Advancement
49 | The second half of League of Legends’ World Group Stage start in just one day! Each team will play the other three teams in their group once more. The best two teams in each group advance to quarterfinals.
50 | The charts below show how each team could advance. With six games left in each group, there are 26 = 64 ways for group play to end. Each of these 64 outcomes is represented by a circle under each team. Green circles show scenarios in which a team advances, and red circles show elimination. Yellow circles indicate scenarios with a tiebreaker to decide advancement. Clicking on a match below the group will select an outcome of that match, filtering out other scenarios.
51 | With only one possible elimination scenario each, G2 and SKT have the best positioning out of all teams.
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | code
64 | 2019 MSI
65 | 2018 Worlds
66 | 2018 MSI
67 | 2017 Worlds
68 | 2017 MSI
69 | 2016 Worlds
70 |
71 |
72 |
73 |
74 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | button {
2 | display: inline-block;
3 | border: none;
4 | padding: 5px;
5 | margin: 0px 5px;
6 | background: rgb(218, 218, 218);
7 | color: #000000;
8 | font-family: sans-serif;
9 | font-size: .9rem;
10 | cursor: pointer;
11 | text-align: center;
12 | transition: background 250ms ease-in-out,
13 | transform 150ms ease;
14 | -webkit-appearance: none;
15 | -moz-appearance: none;
16 | }
17 |
18 | button:hover,
19 | button:focus {
20 | background: rgb(194, 194, 194);
21 | }
22 |
23 |
24 | button:active {
25 | transform: scale(0.99);
26 | }
27 |
28 | svg{
29 | font-family: monospace;
30 | overflow: visible;
31 | font-size: 14px;
32 | }
33 |
34 | svg text{
35 | -webkit-text-shadow: 0 1px 0 #F5F5F5, 1px 0 0 #F5F5F5, 0 -1px 0 #F5F5F5, -1px 0 0 #F5F5F5;
36 | text-shadow: 0 1px 0 #F5F5F5, 1px 0 0 #F5F5F5, 0 -1px 0 #F5F5F5, -1px 0 0 #F5F5F5;
37 |
38 | }
39 |
40 | html {
41 | min-width: 760px;
42 | background-color: #F5F5F5;
43 | font-weight: normal;
44 | margin: auto 0px ;
45 |
46 |
47 | }
48 |
49 | h1{
50 | text-align: center;
51 | }
52 |
53 | div.matches{
54 | -webkit-user-select: none;
55 | -moz-user-select: none;
56 | -khtml-user-select: none;
57 | -ms-user-select: none;
58 | }
59 |
60 | .annotations{
61 | pointer-events: all;
62 | }
63 |
64 |
65 | .group{
66 | margin: 0px auto;
67 | padding-left: 10px;
68 | display: inline-block;
69 | }
70 |
71 | .group-header{
72 | text-align: center;
73 | margin-bottom: -0px;
74 | margin-top: 80px;
75 | opacity: 0;
76 | }
77 |
78 | sup, sub {
79 | vertical-align: baseline;
80 | position: relative;
81 | top: -0.4em;
82 | }
83 | sub {
84 | top: 0.4em;
85 | }
86 |
87 | .team{
88 | display: inline-block;
89 | width: 300px;
90 | padding-left: 30px;
91 | padding-right: 30px;
92 | position: relative;
93 | z-index: 0;
94 | }
95 |
96 | .active{
97 | stroke: black;
98 | stroke-width: 4;
99 | }
100 |
101 | .hidden{
102 | opacity: .2;
103 | }
104 |
105 |
106 | div.tooltip {
107 | top: -1000px;
108 | position: fixed;
109 | padding: 10px;
110 | background: rgba(255, 255, 255, .90);
111 | border: 1px solid lightgray;
112 | pointer-events: none;
113 | font-family: monospace;
114 | width: 200px;
115 |
116 |
117 | }
118 | .tooltip > .game{
119 | display: inline-block;
120 | width: 100px;
121 | height: 25px;
122 | }
123 | .tooltip-hidden{
124 | opacity: 0;
125 | transition: all .3s;
126 | transition-delay: .1s;
127 | }
128 | .tooltip.game{
129 | width: 50px;
130 | }
131 |
132 |
133 | .game .won{
134 | font-weight: 700;
135 | text-decoration: underline;
136 | }
137 |
138 | .matches{
139 | font-size: 16px;
140 | text-align: center;
141 | margin-left: 0px;
142 | }
143 |
144 | .matches .game{
145 | display: inline-block;
146 | margin-bottom: 10px;
147 | width: 100px;
148 | cursor: pointer;
149 | border: 1px solid black;
150 | margin-right: 10px;
151 | opacity: .6;
152 | font-family: monospace;
153 | }
154 | .matches .game.active{
155 | opacity: 1;
156 | }
157 |
158 |
159 | .annotations text{
160 | font-size: 12px;
161 | }
162 | html{
163 | background-color: #f5f5f5;
164 | font-weight: normal;
165 | }
166 |
167 | body{
168 | max-width: 750px;
169 | margin: 0px auto;
170 | font-family: 'Roboto Slab', serif;
171 | line-height: 1.55em;
172 | font-size: 16px;
173 | margin-top: 5px;
174 | margin-bottom: 80px;
175 | }
176 |
177 | @media(max-width: 760px){
178 | body{
179 | padding: 5px;
180 | margin: 0px 8px;
181 | }
182 | .matches{
183 | margin-left: 20px;
184 | }
185 | }
186 |
187 | p{
188 | line-height: 1.55em;
189 | }
190 |
191 | a{
192 | color: black;
193 | }
194 |
195 | .header{
196 | position: relative;
197 | color: black;
198 | font-size: 16px;
199 | height: 24px;
200 | overflow: visible;
201 | /*font-family: Consolas, monaco, 'Lucida Console', monospace; */
202 | /*font-family: 'Roboto', serif;*/
203 | font-family: 'Roboto Slab', serif;
204 | }
205 | .header-left{
206 | float: left;
207 | }
208 | .header-right{
209 | float: right;
210 | }
211 | .header span{
212 | opacity: .5;
213 | }
214 |
215 | .header a{
216 | opacity: .5;
217 | text-decoration: none;
218 | }
219 | .header a:hover{
220 | opacity: 1
221 | }
222 | .header img{
223 | width: 16px;
224 | margin-right: 2px;
225 | position: relative;
226 | top: 2px;
227 | }
228 |
229 |
230 | h1,h2,h3,h4,h5{
231 | font-family: 'Roboto', sans-serif;
232 | margin-top: 1.5em;
233 | margin-bottom: .5em;
234 | }
235 | h1{
236 | font-weight: 500;
237 | font-size: 34px;
238 | margin-bottom: 1.2em;
239 | line-height: 1.3em;
240 | margin-top: 1.2em;
241 | }
242 | h2,h3,h4,h5{
243 | font-size: 22px;
244 | }
245 |
246 | :not(pre) > code{
247 | background: #dedede;
248 | padding: .05em;
249 | padding-left: .2em;
250 | padding-right: .2em;
251 | border-radius: 2px;
252 | font-family: monospace;
253 | }
254 |
255 | strong{
256 | font-weight: bold;
257 | }
258 |
259 | .annotations path{
260 | fill: none;
261 | stroke: #000;
262 | stroke-width: .6px;
263 | }
264 |
265 | @media (prefers-color-scheme: dark) {
266 | html{
267 | background-color: #222;
268 | color: #aaa
269 | }
270 | svg text{
271 | /*-webkit-text-stroke: 4px navy;*/
272 | -webkit-text-shadow: 0 1px 0 #222, 1px 0 0 #222, 0 -1px 0 #222, -1px 0 0 #222;
273 | text-shadow: 0 1px 0 #222, 1px 0 0 #222, 0 -1px 0 #222, -1px 0 0 #222;
274 | color: #aaa
275 |
276 | }
277 | text{
278 | color: #aaa;
279 | fill: #aaa;
280 | }
281 | path {
282 | stroke: #aaa;
283 | }
284 | .annotations path {
285 | fill: none;
286 | stroke: #aaa;
287 | }
288 | marker {
289 | stroke: #aaa;
290 | fill: #aaa
291 | }
292 | a{
293 | color: #aaa;
294 | stroke: #aaa;
295 | fill: #aaa
296 | }
297 | body{
298 | color: #aaa;
299 | }
300 |
301 | div.tooltip {
302 | background:#222e;
303 | }
304 | .matches .game{
305 | border: 1px solid #aaa;
306 | }
307 | .header{
308 | color: #aaa;
309 | }
310 | .scenario.active{
311 | stroke: #aaa;
312 | stroke-width: 3;
313 |
314 | }
315 | }
--------------------------------------------------------------------------------
/swoopy-drag.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3')) :
3 | typeof define === 'function' && define.amd ? define(['exports', 'd3'], factory) :
4 | (factory((global.d3 = global.d3 || {}),global.d3));
5 | }(this, function (exports,d3) { 'use strict';
6 |
7 | function swoopyDrag(){
8 | var x = function(d){ return d }
9 | var y = function(d){ return d }
10 |
11 | var annotations = []
12 | var annotationSel
13 |
14 | var draggable = false
15 |
16 | var dispatch = d3.dispatch('drag')
17 |
18 | var textDrag = d3.drag()
19 | .on('drag', function(d){
20 | var x = d3.event.x
21 | var y = d3.event.y
22 | d.textOffset = [x, y].map(Math.round)
23 |
24 | d3.select(this).call(translate, d.textOffset)
25 |
26 | dispatch.call('drag')
27 | })
28 | .subject(function(d){ return {x: d.textOffset[0], y: d.textOffset[1]} })
29 |
30 | var circleDrag = d3.drag()
31 | .on('drag', function(d){
32 | var x = d3.event.x
33 | var y = d3.event.y
34 | d.pos = [x, y].map(Math.round)
35 |
36 | var parentSel = d3.select(this.parentNode)
37 |
38 | var path = ''
39 | var points = parentSel.selectAll('circle').data()
40 | if (points[0].type == 'A'){
41 | path = calcCirclePath(points)
42 | } else{
43 | points.forEach(function(d){ path = path + d.type + d.pos })
44 | }
45 |
46 | parentSel.select('path').attr('d', path).datum().path = path
47 | d3.select(this).call(translate, d.pos)
48 |
49 | dispatch.call('drag')
50 | })
51 | .subject(function(d){ return {x: d.pos[0], y: d.pos[1]} })
52 |
53 |
54 | var rv = function(sel){
55 | annotationSel = sel.html('').selectAll('g')
56 | .data(annotations).enter()
57 | .append('g')
58 | .call(translate, function(d){ return [x(d), y(d)] })
59 |
60 | var textSel = annotationSel.append('text')
61 | .call(translate, ƒ('textOffset'))
62 | .text(ƒ('text'))
63 |
64 | annotationSel.append('path')
65 | .attr('d', ƒ('path'))
66 |
67 | if (!draggable) return
68 |
69 | annotationSel.style('cursor', 'pointer')
70 | textSel.call(textDrag)
71 |
72 | annotationSel.selectAll('circle').data(function(d){
73 | var points = []
74 |
75 | if (~d.path.indexOf('A')){
76 | //handle arc paths seperatly -- only one circle supported
77 | var pathNode = d3.select(this).select('path').node()
78 | var l = pathNode.getTotalLength()
79 |
80 | points = [0, .5, 1].map(function(d){
81 | var p = pathNode.getPointAtLength(d*l)
82 | return {pos: [p.x, p.y], type: 'A'}
83 | })
84 | } else{
85 | var i = 1
86 | var type = 'M'
87 | var commas = 0
88 |
89 | for (var j = 1; j < d.path.length; j++){
90 | var curChar = d.path[j]
91 | if (curChar == ',') commas++
92 | if (curChar == 'L' || curChar == 'C' || commas == 2){
93 | points.push({pos: d.path.slice(i, j).split(','), type: type})
94 | type = curChar
95 | i = j + 1
96 | commas = 0
97 | }
98 | }
99 |
100 | points.push({pos: d.path.slice(i, j).split(','), type: type})
101 | }
102 |
103 | return points
104 | }).enter().append('circle')
105 | .attr('r', 8)
106 | .attr('fill', 'rgba(0,0,0,0)')
107 | .attr('stroke', '#333')
108 | .attr('stroke-dasharray', '2 2')
109 | .call(translate, ƒ('pos'))
110 | .call(circleDrag)
111 |
112 | dispatch.call('drag')
113 | }
114 |
115 |
116 | rv.annotations = function(_x){
117 | if (typeof(_x) == 'undefined') return annotations
118 | annotations = _x
119 | return rv
120 | }
121 | rv.x = function(_x){
122 | if (typeof(_x) == 'undefined') return x
123 | x = _x
124 | return rv
125 | }
126 | rv.y = function(_x){
127 | if (typeof(_x) == 'undefined') return y
128 | y = _x
129 | return rv
130 | }
131 | rv.draggable = function(_x){
132 | if (typeof(_x) == 'undefined') return draggable
133 | draggable = _x
134 | return rv
135 | }
136 | rv.on = function() {
137 | var value = dispatch.on.apply(dispatch, arguments);
138 | return value === dispatch ? rv : value;
139 | }
140 |
141 | return rv
142 |
143 | //convert 3 points to an Arc Path
144 | function calcCirclePath(points){
145 | var a = points[0].pos
146 | var b = points[2].pos
147 | var c = points[1].pos
148 |
149 | var A = dist(b, c)
150 | var B = dist(c, a)
151 | var C = dist(a, b)
152 |
153 | var angle = Math.acos((A*A + B*B - C*C)/(2*A*B))
154 |
155 | //calc radius of circle
156 | var K = .5*A*B*Math.sin(angle)
157 | var r = A*B*C/4/K
158 | r = Math.round(r*1000)/1000
159 |
160 | //large arc flag
161 | var laf = +(Math.PI/2 > angle)
162 |
163 | //sweep flag
164 | var saf = +((b[0] - a[0])*(c[1] - a[1]) - (b[1] - a[1])*(c[0] - a[0]) < 0)
165 |
166 | return ['M', a, 'A', r, r, 0, laf, saf, b].join(' ')
167 | }
168 |
169 | function dist(a, b){
170 | return Math.sqrt(
171 | Math.pow(a[0] - b[0], 2) +
172 | Math.pow(a[1] - b[1], 2))
173 | }
174 |
175 |
176 | //no jetpack dependency
177 | function translate(sel, pos){
178 | sel.attr('transform', function(d){
179 | var posStr = typeof(pos) == 'function' ? pos(d) : pos
180 | return 'translate(' + posStr + ')'
181 | })
182 | }
183 |
184 | function ƒ(str){ return function(d){ return d[str] } }
185 | }
186 |
187 | exports.swoopyDrag = swoopyDrag;
188 |
189 | Object.defineProperty(exports, '__esModule', { value: true });
190 |
191 | }));
--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
1 | // http://esports-assets.s3.amazonaws.com/production/files/rules/2017-World-Championship-Rules-v-17-3.pdf
2 | // https://esports-assets.s3.amazonaws.com/production/files/rules/2017-MSI-Ruleset.pdf
3 | var byGroup
4 | var colorChoice = 0
5 | var staticPick = false;
6 | var ƒ = d3.f
7 | // console.clear()
8 | function readFile(){
9 | d3.loadData('annotations.json', 'matches.tsv', function(err, res){
10 | d3.selectAll('.group-header').st({opacity: 1})
11 |
12 | annotations = res[0]
13 | matches = res[1]
14 |
15 | teams2wins = {}
16 |
17 | matches.forEach(function(d, i){
18 | d.winner = +d.winner
19 | if( i < 24 || !staticPick){
20 | d.wName = d['t' + d.winner]
21 | d.actualWinner = !(d.winner === 0) ? d.winner : 3
22 | }
23 | d.complete = i < 24
24 | d.allTeams = d.t1 + '-' + d.t2
25 | if (!teams2wins[d.t1]) teams2wins[d.t1] = 0
26 | if (!teams2wins[d.t2]) teams2wins[d.t2] = 0
27 | if (d.date < "10-17") teams2wins[d.wName]++ //make sure to change key each year
28 | })
29 |
30 | byGroup = d3.nestBy(matches, ƒ('group'))
31 | reDraw();
32 | })
33 | }
34 | readFile()
35 |
36 | function static(){
37 | staticPick = !staticPick
38 | readFile();
39 | }
40 |
41 | function changeColor(){
42 | colorChoice ^= 1;
43 | reDraw();
44 | }
45 |
46 | function changeSeed(){
47 | colorChoice ^= 2;
48 | reDraw();
49 | }
50 |
51 | function reDraw(){
52 | byGroup.forEach(drawGroup)
53 | }
54 |
55 |
56 |
57 | function scoreMatches(matches){
58 | var teams = d3.nestBy(matches, ƒ('t1')).map(function(d){
59 | return {name: d.key, w: 0}
60 | })
61 | var nameToTeam = {}
62 | teams.forEach(function(d){ nameToTeam[d.name] = d })
63 | matches.forEach(addMatchWins)
64 |
65 | teams.forEach(function(d){
66 | d.wins = d.w
67 | d.w = 0
68 | })
69 |
70 | d3.nestBy(teams, ƒ('wins')).forEach(function(d){
71 | if (d.length == 1 || d.length == 4) return
72 |
73 |
74 | var tiedTeams = d.map(ƒ('name')).join('-')
75 | var tiedMatches = matches
76 | .filter(function(d){
77 | return ~tiedTeams.indexOf(d.t1) && ~tiedTeams.indexOf(d.t2) })
78 | tiedMatches.forEach(addMatchWins)
79 |
80 | // in 3-way tie, only head2head winning record gets out of tiebreaker
81 | if (d.length != 3) return
82 | // console.log(d.length, d.map(d => d.w).join(' '))
83 | // console.log(d.length, JSON.parse(JSON.stringify(d)))
84 | d.forEach(function(d){ d.w = d.w > 2 ? d.w : 0 })
85 | })
86 |
87 |
88 | var advanceSlots = 2
89 |
90 | d3.nestBy(teams, function(d){ return d.w + d.wins*10 })
91 | .sort(d3.descendingKey('key'))
92 | .forEach(function(d){
93 | if (d.length <= advanceSlots){
94 | d.forEach(function(d){ d.advance = 't'})
95 | if(advanceSlots == 2){
96 | if(d.length == 2){ //check for tied first place
97 | against = matches.filter(match => {return match.allTeams == d[0].name + '-' + d[1].name
98 | || match.allTeams == d[1].name + '-' + d[0].name})
99 | if(against[0].actualWinner == against[1].actualWinner){
100 | d.forEach(function(d){ if (d.name == against[0].actualWinner) d.advance = 'u'})
101 | }
102 | }
103 | else
104 | d.forEach(function(d){ d.advance = 'u'})
105 | }
106 | } else if (advanceSlots > 0){
107 | d.forEach(function(d){ d.advance = 'm'})
108 | } else{
109 | d.forEach(function(d){ d.advance = 'f' })
110 | if(d.length == 2){ //check for tied last place
111 | against = matches.filter(match => {return match.allTeams == d[0].name + '-' + d[1].name
112 | || match.allTeams == d[1].name + '-' + d[0].name})
113 | if(against[0].actualWinner == against[1].actualWinner){
114 | d.forEach(function(d){ if (d.name != against[0].actualWinner) d.advance = 'e'})
115 | }
116 | }
117 | if(advanceSlots == - 1){
118 | d[0].advance = 'e'
119 | }
120 | }
121 | advanceSlots -= d.length
122 | })
123 |
124 | function addMatchWins(d){ nameToTeam[d['t' + d.winner]].w++ }
125 |
126 |
127 | return teams
128 | }
129 |
130 | function drawGroup(gMatches){
131 | var sel = d3.select('#group-' + gMatches.key.toLowerCase()).html('')
132 |
133 | var complete = gMatches.filter(d => d.complete)
134 | var incomplete = gMatches.filter(function(d){ return !d.complete })
135 | scenarios = d3.range(64).map(function(i){
136 | incomplete.forEach(function(d, j){
137 | d.winner = (i >> j) % 2 ? 1 : 2
138 | d.wName = d['t' + d.winner]
139 | })
140 |
141 | return {
142 | str: incomplete.map(ƒ('winner')).join(''),
143 | teams: scoreMatches(gMatches),
144 | incomplete: JSON.parse(JSON.stringify(incomplete))}
145 | })
146 |
147 | var teams = d3.nestBy(gMatches, ƒ('t1')).map(function(d){
148 | return {name: d.key, w: 0, actualWins: teams2wins[d.key]}
149 | }).sort(d3.descendingKey('actualWins'))
150 |
151 | sel.appendMany('div.team', teams)
152 | .each(function(d){ drawResults(d3.select(this), scenarios, d.name, complete, incomplete) })
153 |
154 | incomplete.forEach(function(d){ d.clicked = (+d.actualWinner || 3) - 1 })
155 | var gameSel = sel.append('div.matches')
156 | .st({marginTop: 50})
157 | .appendMany('div.game', incomplete)
158 | .on('click', function(d){
159 | d.clicked = (d.clicked + 1) % 3
160 |
161 | d3.select(this).selectAll('.teamabv')
162 | .classed('won', function(e, i){ return i + 1 == d.clicked })
163 | d3.select(this).classed('active', d.clicked)
164 |
165 | var str = incomplete.map(ƒ('clicked')).join('')
166 | sel.selectAll('circle.scenario').classed('hidden', function(d){
167 | return d.incomplete.some(function(d, i){
168 | return str[i] != '0' && str[i] != d.winner
169 | })
170 | })
171 | })
172 | gameSel.append('span.teamabv').text(ƒ('t1'))
173 | gameSel.append('span').text(' v. ')
174 | gameSel.append('span.teamabv').text(ƒ('t2'))
175 | gameSel.each(function(d){ d3.select(this).on('click').call(this, d) })
176 | }
177 |
178 |
179 | function drawResults(sel, scenarios, name, complete, incomplete){
180 |
181 | scenarios.forEach(function(d){
182 | d.team = d.teams.filter(function(d){ return d.name == name })[0]
183 | d.wins = d.team.wins
184 |
185 | d.playedIn = d.incomplete.filter(function(d){
186 | d.currentWon = name == d.wName
187 | return name == d.t1 || name == d.t2 })
188 | d.recordStr = d.playedIn.map(function(d){ return +d.currentWon }).join('')
189 | })
190 |
191 | var against = []
192 | scenarios[0].playedIn.forEach(function(d){
193 | var otherTeam = name == d.t1 ? d.t2 : d.t1
194 | against.push(otherTeam)
195 | })
196 |
197 | var completeIn = complete.filter(function(d){
198 | d.currentWon = name == d.wName
199 | d.otherTeam = name == d.t1 ? d.t2 : d.t1
200 | return name == d.t1 || name == d.t2 })
201 |
202 | var pBeat = completeIn.filter(ƒ('currentWon'))
203 | var pLost = completeIn.filter(function(d){ return !d.currentWon })
204 |
205 | var pStr = 'Lost to '
206 | pStr += pLost.map(ƒ('otherTeam')).join(' and ')
207 |
208 | if (pBeat.length){
209 | pStr += ' // Beat ' + pBeat.map(ƒ('otherTeam')).join(' and ')
210 | } else{
211 | pStr = pStr.replace(' and ', ', ')
212 | }
213 | if (!pLost.length) pStr = pStr.replace('Lost to //', '').replace(' and ', ', ')
214 | // pStr += ' previously'
215 |
216 | var byWins = d3.nestBy(scenarios, ƒ('wins'))
217 | byWins.forEach(function(d, i){
218 | d.sort(d3.descendingKey(ƒ('team', 'advance')))
219 | d.byRecordStr = _.sortBy(d3.nestBy(d, ƒ('recordStr')), 'key')
220 | if (i == 1) d.byRecordStr.reverse()
221 | })
222 |
223 | var width = 315, height = 300
224 | var svg = sel.append('svg').at({width, height}).st({margin: 20})
225 | .append('g').translate([0, 100])
226 | var gSel = d3.select(sel.node().parentNode)
227 |
228 | var swoopySel = svg.append('g.annotations')
229 |
230 | svg.append('text').text(name)
231 | .translate([10*3.5 + 100, -60]).at({textAnchor: 'middle', fontSize: 20})
232 |
233 | svg.append('text').text(pStr)
234 | .translate([10*3.5 + 100, -45]).at({textAnchor: 'middle', fontSize: 12, fill: '#888'})
235 |
236 |
237 | var winsSel = svg.appendMany('g', byWins.sort(d3.descendingKey('key')))
238 | .translate(function(d, i){ return [0, i*80 + (i == 3 ? -15*2 : i > 0 ? -8 : 0)] })
239 |
240 | winsSel.append('text')
241 | .text(function(d, i){ return i == 1 ? 'Only Lose To...' : i == 2 ? 'Only Beat...' : '' })
242 | .at({textAnchor: 'middle', x: 10*3.5 + 100, y: -30, fill: '#888', fontSize: 12})
243 |
244 | var recordSel = winsSel.appendMany('g', ƒ('byRecordStr'))
245 | .translate(function(d, i){ return [d.key == '000' || d.key == '111' ? 100 : i*100, 0] })
246 |
247 | recordSel.append('text')
248 | .text(function(d){
249 | var s
250 | if (d.key == '111') s = 'Win Next Three'
251 | if (d.key == '000') s = 'Lose Next Three'
252 | if (d.key == '001') s = against[2]
253 | if (d.key == '010') s = against[1]
254 | if (d.key == '100') s = against[0]
255 | if (d.key == '011') s = against[0]
256 | if (d.key == '101') s = against[1]
257 | if (d.key == '110') s = against[2]
258 | return s
259 | })
260 | .at({textAnchor: 'middle', x: 10*3.5, y: -10})
261 |
262 | recordSel.appendMany('circle.scenario', ƒ())
263 | .at({r: 5, fill: ƒ('team', color[colorChoice]), cx: function(d, i){return i*10} })
264 | .call(d3.attachTooltip)
265 | .on('mouseout', function(){gSel.selectAll('circle.scenario').classed('active', false).at('r', 5) })
266 | .on('mouseover', function(d){
267 | gSel.selectAll('circle.scenario')
268 | .classed('active', 0)
269 | .attr('r', 5)
270 | .filter(function(e){ return d.str == e.str })
271 | .classed('active', 1)
272 | .attr('r', 8)
273 | .raise()
274 |
275 | var tt = d3.select('.tooltip').html('')
276 | var gameSel = tt.appendMany('div.game', incomplete)
277 | gameSel.append('span').text(ƒ('t1')).classed('won', function(e, i){ return d.str[i] == 1 })
278 | gameSel.append('span').text(' v. ')
279 | gameSel.append('span').text(ƒ('t2')).classed('won', function(e, i){ return d.str[i] == 2 })
280 |
281 | var byAdvanceSel = tt.appendMany('div.advance', d3.nestBy(d.teams, ƒ('advance')).sort(d3.descendingKey('key')))
282 | .text(function(d){
283 | return d.map(ƒ('name')).join(' and ') + {u:' first seed',t: ' advance', m: ' tie', f: (d.length > 1 ? ' are' : ' is') + ' eliminated', e:' last place'}[d.key]
284 | })
285 | })
286 |
287 | .on('click', function(d){
288 | current = []
289 | blank = true
290 | d3.selectAll('.matches > .game').each(function(e, i){
291 | if (e.group == d.incomplete[0].group){
292 | current.push(e.clicked)
293 | if (e.clicked != 0) blank = false
294 | }
295 | })
296 | reset = !d3.select(this).classed('hidden') && !blank
297 | d3.selectAll('.matches > .game').each(function(e, i){
298 | if (e.group == d.incomplete[0].group){
299 | if(reset) e.clicked = -1
300 | else if(blank)
301 | e.clicked = parseInt(d.str[i % 6]) - 1
302 | else if(parseInt(d.str[i % 6]) != current[i % 6])
303 | e.clicked = -1
304 | else e.clicked--
305 | d3.select(this).dispatch("click")
306 | }
307 | })
308 | })
309 |
310 | var swoopy = d3.swoopyDrag()
311 | .draggable(0)
312 | .x(function(){ return 0 })
313 | .y(function(){ return 0 })
314 | .annotations(annotations.filter(function(d){ return d.team == name }))
315 |
316 | swoopySel.call(swoopy)
317 | swoopySel.selectAll('path').attr('marker-end', 'url(#arrow)')
318 | swoopySel.selectAll('text')
319 | .each(function(d){
320 | d3.select(this)
321 | .text('') //clear existing text
322 | .tspans(d3.wordwrap(d.text, d.lw || 5)) //wrap after 20 char
323 | })
324 |
325 | }
326 |
327 |
328 | color = [ function color(d){ return {u:'#4CAF50',t: '#4CAF50', m: '#FF9800', f: '#f73434', e: '#f73434'}[d.advance] },
329 | function color(d){ return {u:'#2c7bb6',t: '#2c7bb6', m: '#dfaf90', f: '#f73434', e: '#f73434'}[d.advance] },
330 | function color(d){ return {u:'#36e03d',t: '#4CAF50', m: '#FF9800', f: '#f73434', e: '#b80f02'}[d.advance] },
331 | function color(d){ return {u:'#5aa4db',t: '#2c7bb6', m: '#dfaf90', f: '#f73434', e: '#b80f02'}[d.advance] }]
332 |
333 | d3.select('html').selectAppend('div.tooltip').classed('tooltip-hidden', 1)
334 |
335 | d3.select('html').selectAppend('svg.marker')
336 | .append('marker')
337 | .attr('id', 'arrow')
338 | .attr('viewBox', '-10 -10 20 20')
339 | .attr('markerWidth', 20)
340 | .attr('markerHeight', 20)
341 | .attr('orient', 'auto')
342 | .append('path')
343 | .attr('d', 'M-6.75,-6.75 L 0,0 L -6.75,6.75')
344 |
--------------------------------------------------------------------------------