@tennisvisuals/scoring-visualizations 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/dist/__tests__/LiveEngine.test.d.ts +2 -0
  2. package/dist/__tests__/LiveEngine.test.d.ts.map +1 -0
  3. package/dist/__tests__/LiveEngine.test.js +142 -0
  4. package/dist/__tests__/LiveEngine.test.js.map +1 -0
  5. package/dist/__tests__/buildEpisodes.test.d.ts +2 -0
  6. package/dist/__tests__/buildEpisodes.test.d.ts.map +1 -0
  7. package/dist/__tests__/buildEpisodes.test.js +150 -0
  8. package/dist/__tests__/buildEpisodes.test.js.map +1 -0
  9. package/dist/__tests__/extractors.test.d.ts +2 -0
  10. package/dist/__tests__/extractors.test.d.ts.map +1 -0
  11. package/dist/__tests__/extractors.test.js +50 -0
  12. package/dist/__tests__/extractors.test.js.map +1 -0
  13. package/dist/__tests__/feedMatchUp.test.d.ts +2 -0
  14. package/dist/__tests__/feedMatchUp.test.d.ts.map +1 -0
  15. package/dist/__tests__/feedMatchUp.test.js +96 -0
  16. package/dist/__tests__/feedMatchUp.test.js.map +1 -0
  17. package/dist/engine/LiveEngine.d.ts +42 -0
  18. package/dist/engine/LiveEngine.d.ts.map +1 -0
  19. package/dist/engine/LiveEngine.js +82 -0
  20. package/dist/engine/LiveEngine.js.map +1 -0
  21. package/dist/engine/buildSetMap.d.ts +14 -0
  22. package/dist/engine/buildSetMap.d.ts.map +1 -0
  23. package/dist/engine/buildSetMap.js +42 -0
  24. package/dist/engine/buildSetMap.js.map +1 -0
  25. package/dist/engine/createPlaybackEngine.d.ts +34 -0
  26. package/dist/engine/createPlaybackEngine.d.ts.map +1 -0
  27. package/dist/engine/createPlaybackEngine.js +90 -0
  28. package/dist/engine/createPlaybackEngine.js.map +1 -0
  29. package/dist/engine/feedMatchUp.d.ts +82 -0
  30. package/dist/engine/feedMatchUp.d.ts.map +1 -0
  31. package/dist/engine/feedMatchUp.js +116 -0
  32. package/dist/engine/feedMatchUp.js.map +1 -0
  33. package/dist/episodes/buildEpisodes.d.ts +10 -0
  34. package/dist/episodes/buildEpisodes.d.ts.map +1 -0
  35. package/dist/episodes/buildEpisodes.js +166 -0
  36. package/dist/episodes/buildEpisodes.js.map +1 -0
  37. package/dist/episodes/types.d.ts +45 -0
  38. package/dist/episodes/types.d.ts.map +1 -0
  39. package/dist/episodes/types.js +2 -0
  40. package/dist/episodes/types.js.map +1 -0
  41. package/dist/index.d.ts +23 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +24 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/statistics/index.d.ts +3 -0
  46. package/dist/statistics/index.d.ts.map +1 -0
  47. package/dist/statistics/index.js +2 -0
  48. package/dist/statistics/index.js.map +1 -0
  49. package/dist/statistics/matchStatistics.d.ts +22 -0
  50. package/dist/statistics/matchStatistics.d.ts.map +1 -0
  51. package/dist/statistics/matchStatistics.js +59 -0
  52. package/dist/statistics/matchStatistics.js.map +1 -0
  53. package/dist/visualizations/coronaChart.d.ts +54 -0
  54. package/dist/visualizations/coronaChart.d.ts.map +1 -0
  55. package/dist/visualizations/coronaChart.js +254 -0
  56. package/dist/visualizations/coronaChart.js.map +1 -0
  57. package/dist/visualizations/data/mcpFixtures.json +13166 -0
  58. package/dist/visualizations/data/sampleGame.d.ts +37 -0
  59. package/dist/visualizations/data/sampleGame.d.ts.map +1 -0
  60. package/dist/visualizations/data/sampleGame.js +521 -0
  61. package/dist/visualizations/data/sampleGame.js.map +1 -0
  62. package/dist/visualizations/gameFish.d.ts +16 -0
  63. package/dist/visualizations/gameFish.d.ts.map +1 -0
  64. package/dist/visualizations/gameFish.js +692 -0
  65. package/dist/visualizations/gameFish.js.map +1 -0
  66. package/dist/visualizations/gameTree.d.ts +17 -0
  67. package/dist/visualizations/gameTree.d.ts.map +1 -0
  68. package/dist/visualizations/gameTree.js +837 -0
  69. package/dist/visualizations/gameTree.js.map +1 -0
  70. package/dist/visualizations/groupGames.d.ts +6 -0
  71. package/dist/visualizations/groupGames.d.ts.map +1 -0
  72. package/dist/visualizations/groupGames.js +35 -0
  73. package/dist/visualizations/groupGames.js.map +1 -0
  74. package/dist/visualizations/helpers/JsonViewer.d.ts +27 -0
  75. package/dist/visualizations/helpers/JsonViewer.d.ts.map +1 -0
  76. package/dist/visualizations/helpers/JsonViewer.js +276 -0
  77. package/dist/visualizations/helpers/JsonViewer.js.map +1 -0
  78. package/dist/visualizations/helpers/PlaybackControls.d.ts +12 -0
  79. package/dist/visualizations/helpers/PlaybackControls.d.ts.map +1 -0
  80. package/dist/visualizations/helpers/PlaybackControls.js +98 -0
  81. package/dist/visualizations/helpers/PlaybackControls.js.map +1 -0
  82. package/dist/visualizations/horizonChart.d.ts +59 -0
  83. package/dist/visualizations/horizonChart.d.ts.map +1 -0
  84. package/dist/visualizations/horizonChart.js +215 -0
  85. package/dist/visualizations/horizonChart.js.map +1 -0
  86. package/dist/visualizations/index.d.ts +21 -0
  87. package/dist/visualizations/index.d.ts.map +1 -0
  88. package/dist/visualizations/index.js +23 -0
  89. package/dist/visualizations/index.js.map +1 -0
  90. package/dist/visualizations/legacyRally.d.ts +3 -0
  91. package/dist/visualizations/legacyRally.d.ts.map +1 -0
  92. package/dist/visualizations/legacyRally.js +26 -0
  93. package/dist/visualizations/legacyRally.js.map +1 -0
  94. package/dist/visualizations/matchDashboard.d.ts +39 -0
  95. package/dist/visualizations/matchDashboard.d.ts.map +1 -0
  96. package/dist/visualizations/matchDashboard.js +235 -0
  97. package/dist/visualizations/matchDashboard.js.map +1 -0
  98. package/dist/visualizations/momentumChart.d.ts +12 -0
  99. package/dist/visualizations/momentumChart.d.ts.map +1 -0
  100. package/dist/visualizations/momentumChart.js +454 -0
  101. package/dist/visualizations/momentumChart.js.map +1 -0
  102. package/dist/visualizations/ptsChart.d.ts +14 -0
  103. package/dist/visualizations/ptsChart.d.ts.map +1 -0
  104. package/dist/visualizations/ptsChart.js +925 -0
  105. package/dist/visualizations/ptsChart.js.map +1 -0
  106. package/dist/visualizations/ptsHorizon.d.ts +83 -0
  107. package/dist/visualizations/ptsHorizon.d.ts.map +1 -0
  108. package/dist/visualizations/ptsHorizon.js +290 -0
  109. package/dist/visualizations/ptsHorizon.js.map +1 -0
  110. package/dist/visualizations/rallyTree.d.ts +78 -0
  111. package/dist/visualizations/rallyTree.d.ts.map +1 -0
  112. package/dist/visualizations/rallyTree.js +410 -0
  113. package/dist/visualizations/rallyTree.js.map +1 -0
  114. package/dist/visualizations/simpleChart.d.ts +6 -0
  115. package/dist/visualizations/simpleChart.d.ts.map +1 -0
  116. package/dist/visualizations/simpleChart.js +126 -0
  117. package/dist/visualizations/simpleChart.js.map +1 -0
  118. package/dist/visualizations/statView.d.ts +52 -0
  119. package/dist/visualizations/statView.d.ts.map +1 -0
  120. package/dist/visualizations/statView.js +200 -0
  121. package/dist/visualizations/statView.js.map +1 -0
  122. package/dist/visualizations/typeOf.d.ts +6 -0
  123. package/dist/visualizations/typeOf.d.ts.map +1 -0
  124. package/dist/visualizations/typeOf.js +18 -0
  125. package/dist/visualizations/typeOf.js.map +1 -0
  126. package/dist/visualizations/types/events.d.ts +92 -0
  127. package/dist/visualizations/types/events.d.ts.map +1 -0
  128. package/dist/visualizations/types/events.js +2 -0
  129. package/dist/visualizations/types/events.js.map +1 -0
  130. package/dist/visualizations/types/index.d.ts +79 -0
  131. package/dist/visualizations/types/index.d.ts.map +1 -0
  132. package/dist/visualizations/types/index.js +8 -0
  133. package/dist/visualizations/types/index.js.map +1 -0
  134. package/dist/visualizations/utils/arrays.d.ts +18 -0
  135. package/dist/visualizations/utils/arrays.d.ts.map +1 -0
  136. package/dist/visualizations/utils/arrays.js +46 -0
  137. package/dist/visualizations/utils/arrays.js.map +1 -0
  138. package/dist/visualizations/utils/colorUtils.d.ts +36 -0
  139. package/dist/visualizations/utils/colorUtils.d.ts.map +1 -0
  140. package/dist/visualizations/utils/colorUtils.js +112 -0
  141. package/dist/visualizations/utils/colorUtils.js.map +1 -0
  142. package/dist/visualizations/utils/generateId.d.ts +2 -0
  143. package/dist/visualizations/utils/generateId.d.ts.map +1 -0
  144. package/dist/visualizations/utils/generateId.js +3 -0
  145. package/dist/visualizations/utils/generateId.js.map +1 -0
  146. package/dist/visualizations/utils/keyWalk.d.ts +2 -0
  147. package/dist/visualizations/utils/keyWalk.d.ts.map +1 -0
  148. package/dist/visualizations/utils/keyWalk.js +19 -0
  149. package/dist/visualizations/utils/keyWalk.js.map +1 -0
  150. package/dist/visualizations/utils/math.d.ts +10 -0
  151. package/dist/visualizations/utils/math.d.ts.map +1 -0
  152. package/dist/visualizations/utils/math.js +14 -0
  153. package/dist/visualizations/utils/math.js.map +1 -0
  154. package/dist/visualizations/utils/setDev.d.ts +29 -0
  155. package/dist/visualizations/utils/setDev.d.ts.map +1 -0
  156. package/dist/visualizations/utils/setDev.js +35 -0
  157. package/dist/visualizations/utils/setDev.js.map +1 -0
  158. package/package.json +57 -0
@@ -0,0 +1,837 @@
1
+ /* eslint-disable */
2
+ // @ts-nocheck
3
+ /**
4
+ * Game Tree Visualization - Standalone Version (D3 v7)
5
+ * Now supports both UMO v4 and legacy Episode formats
6
+ */
7
+ import { select, scaleLinear } from 'd3';
8
+ import { buildEpisodes } from '../episodes/buildEpisodes';
9
+ import { keyWalk } from './utils/keyWalk';
10
+ import { generateId } from './utils/generateId';
11
+ import { colorShade, getHexColor } from './utils/colorUtils';
12
+ import { applyMax, applyMin } from './utils/math';
13
+ function get_id(d) {
14
+ return d?.id;
15
+ }
16
+ export function gameTree() {
17
+ // change the gradient direction in lines across width
18
+ // which means changing line so that it is short and wide
19
+ // rather than long and thick
20
+ //
21
+ // bar chart showing win % for each player or primary/opponents
22
+ // for each point position
23
+ const images = { left: undefined, right: undefined };
24
+ // All options that should be accessible to caller
25
+ let data = [];
26
+ const options = {
27
+ id: generateId(),
28
+ width: 150,
29
+ height: 150,
30
+ minMax: 20, // scaling factor for line widths
31
+ // Margins for the SVG
32
+ margins: {
33
+ top: 0,
34
+ right: 0,
35
+ bottom: 0,
36
+ left: 0,
37
+ },
38
+ display: {
39
+ noAd: false,
40
+ leftImg: false,
41
+ rightImg: false,
42
+ showImages: false,
43
+ sizeToFit: true,
44
+ showEmpty: false, // display even if no data
45
+ },
46
+ lines: {
47
+ easing: false, // 'bounce'
48
+ duration: 600,
49
+ points: { winners: '#2ed2db', errors: '#2ed2db', unknown: '#2ed2db' },
50
+ colors: { underlines: '#2ed2db' },
51
+ },
52
+ nodes: {
53
+ colors: { 0: 'black', 1: 'red', neutral: '#ecf0f1' },
54
+ },
55
+ points: {
56
+ winners: [
57
+ 'Winner',
58
+ 'Ace',
59
+ 'Serve Winner',
60
+ 'Passing Shot',
61
+ 'Return Winner',
62
+ 'Forcing Error',
63
+ 'Forcing Volley Error',
64
+ 'Net Cord',
65
+ 'In',
66
+ ],
67
+ errors: [
68
+ 'Unforced Error',
69
+ 'Unforced',
70
+ 'Forced Error',
71
+ 'Error',
72
+ 'Out',
73
+ 'Net',
74
+ 'Netted Passing Shot',
75
+ 'L',
76
+ 'Overhead Passing Shot',
77
+ 'Double Fault',
78
+ ],
79
+ highlight: [], // opposite of filter; filter unhighlighted...
80
+ },
81
+ selectors: {
82
+ enabled: true,
83
+ selected: { 0: false, 1: false },
84
+ },
85
+ labels: { Game: 'GAME', Player: 'Player', Opponent: 'Opponent' },
86
+ };
87
+ // functions which should be accessible via ACCESSORS
88
+ let update;
89
+ // PROGRAMMATIC
90
+ // ------------
91
+ let canvas;
92
+ let radius;
93
+ const transitionTime = 0;
94
+ const pointConnector = 'x';
95
+ let counters = { w: {}, e: {}, p: {}, n: {} };
96
+ let activeNodeName = null;
97
+ function nsId(base) {
98
+ return `${base}_${options.id}`;
99
+ }
100
+ // DEFINABLE EVENTS
101
+ // Define with ACCESSOR function chart.events()
102
+ const events = {
103
+ leftImage: { click: null },
104
+ rightImage: { click: null },
105
+ update: { begin: null, end: null },
106
+ point: { mousemove: null, mouseout: null, click: null },
107
+ node: { mousemove: null, mouseout: null, click: null },
108
+ score: { mousemove: null, mouseout: null, click: null },
109
+ label: { mousemove: null, mouseout: null, click: selectView },
110
+ selector: { mousemove: null, mouseout: null, click: selectView },
111
+ };
112
+ function chart(selection) {
113
+ const root = selection.append('div').attr('class', 'gametreeRoot');
114
+ canvas = root.append('svg');
115
+ update = function (opts) {
116
+ if (!data.length && !options.display.showEmpty) {
117
+ canvas.selectAll('*').remove();
118
+ return;
119
+ }
120
+ counterCalcs();
121
+ if (options.display.sizeToFit || opts?.sizeToFit) {
122
+ const dims = selection.node().getBoundingClientRect();
123
+ options.width = Math.min(dims.width, dims.height);
124
+ }
125
+ const treeWidth = options.width - (options.margins.left + options.margins.right);
126
+ const treeHeight = options.width * 0.9;
127
+ radius = ((treeHeight + treeWidth) / 2) * 0.03;
128
+ const keys = point_lines.map(function (m) {
129
+ return m.id;
130
+ });
131
+ const point_min = applyMin(keys.map(function (k) {
132
+ return isNaN(counters.p[k]) ? 0 : counters.p[k];
133
+ }));
134
+ const point_max = applyMax([
135
+ applyMax(keys.map(function (k) {
136
+ return isNaN(counters.p[k]) ? 0 : counters.p[k];
137
+ })),
138
+ options.minMax,
139
+ ]);
140
+ const scale = scaleLinear()
141
+ .domain([point_min, point_max])
142
+ .range([0, radius * 2]);
143
+ canvas.transition().duration(transitionTime).attr('width', treeWidth).attr('height', treeHeight);
144
+ // D3 v7: Use .join() pattern and save merged selection
145
+ const gradientsMerged = canvas
146
+ .selectAll('.gradient')
147
+ .data(point_lines, get_id)
148
+ .join('linearGradient')
149
+ .attr('id', (d) => {
150
+ return 'gradient' + nsId(d.id);
151
+ })
152
+ .attr('class', 'gradient')
153
+ .attr('gradientUnits', 'userSpaceOnUse')
154
+ .attr('x1', (d) => {
155
+ return d.start.x * treeWidth;
156
+ })
157
+ .attr('y1', (d) => {
158
+ return d.start.y * treeHeight;
159
+ })
160
+ .attr('x2', (d) => {
161
+ return d.end.x * treeWidth;
162
+ })
163
+ .attr('y2', (d) => {
164
+ return d.end.y * treeHeight;
165
+ });
166
+ // D3 v7: Use .join() for stops and operate on saved merged selection
167
+ gradientsMerged
168
+ .selectAll('.points_stop')
169
+ .data((d) => {
170
+ return calcStops(d);
171
+ })
172
+ .join('stop')
173
+ .attr('class', 'points_stop')
174
+ .attr('offset', (d) => {
175
+ return d.offset;
176
+ })
177
+ .attr('stop-color', (d) => {
178
+ return d.color;
179
+ });
180
+ // D3 v7: Use .join() with enter/update transitions
181
+ canvas
182
+ .selectAll('.line')
183
+ .data(point_lines)
184
+ .join((enter) => enter
185
+ .append('line')
186
+ .attr('class', 'line')
187
+ .attr('id', (d) => nsId(d.id))
188
+ .attr('x1', (d) => d.start.x * treeWidth)
189
+ .attr('y1', (d) => d.start.y * treeHeight)
190
+ .attr('x2', (d) => d.end.x * treeWidth)
191
+ .attr('y2', (d) => d.end.y * treeHeight)
192
+ .attr('stroke-width', (d) => (d.width ? d.width : 0))
193
+ .attr('stroke', (d) => 'url(#gradient' + nsId(d.id) + ')')
194
+ .on('mousemove', function (event, d) {
195
+ if (events.point.mousemove)
196
+ events.point.mousemove(d, event);
197
+ })
198
+ .on('mouseout', function (event, d) {
199
+ if (events.point.mouseout)
200
+ events.point.mouseout(d, event);
201
+ })
202
+ .on('click', function (event, d) {
203
+ if (events.point.click)
204
+ events.point.click(d, event);
205
+ }), (update) => update)
206
+ .transition()
207
+ .duration(options.lines.easing || (opts && opts.easing) ? options.lines.duration : 0)
208
+ .attr('x1', (d) => d.start.x * treeWidth)
209
+ .attr('y1', (d) => d.start.y * treeHeight)
210
+ .attr('x2', (d) => d.end.x * treeWidth)
211
+ .attr('y2', (d) => d.end.y * treeHeight)
212
+ .attr('stroke-width', (d) => (counters.p[d.id] ? scale(counters.p[d.id]) : 0));
213
+ canvas
214
+ .selectAll('.uline')
215
+ .data(under_lines)
216
+ .join((enter) => enter
217
+ .append('line')
218
+ .attr('class', 'uline')
219
+ .attr('stroke', function () {
220
+ return options.lines.colors.underlines;
221
+ }))
222
+ .transition()
223
+ .duration(transitionTime)
224
+ .attr('id', function (d) {
225
+ return nsId(d.id);
226
+ })
227
+ .attr('x1', function (d) {
228
+ return d.start.x * treeWidth;
229
+ })
230
+ .attr('y1', function (d) {
231
+ return d.start.y * treeHeight;
232
+ })
233
+ .attr('x2', function (d) {
234
+ return d.end.x * treeWidth;
235
+ })
236
+ .attr('y2', function (d) {
237
+ return d.end.y * treeHeight;
238
+ })
239
+ .attr('stroke-width', (d) => {
240
+ return d.width ? d.width : 0;
241
+ });
242
+ canvas
243
+ .selectAll('.node')
244
+ .data(point_circles)
245
+ .join((enter) => enter
246
+ .append('circle')
247
+ .attr('class', 'node')
248
+ .attr('stroke', function (d) {
249
+ return d.color_pct != undefined
250
+ ? colorShade(getHexColor(options.nodes.colors[d.player]), d.color_pct)
251
+ : options.nodes.colors.neutral;
252
+ })
253
+ .attr('fill', function (d) {
254
+ return d.color_pct != undefined
255
+ ? colorShade(getHexColor(options.nodes.colors[d.player]), d.color_pct)
256
+ : options.nodes.colors.neutral;
257
+ })
258
+ .on('mousemove', function (d, i) {
259
+ if (events.node.mousemove)
260
+ events.node.mousemove(d, i);
261
+ })
262
+ .on('mouseout', function (d, i) {
263
+ if (events.node.mouseout)
264
+ events.node.mouseout(d, i);
265
+ })
266
+ .on('click', function (d, i) {
267
+ if (events.node.click)
268
+ events.node.click(d, i);
269
+ }))
270
+ .transition()
271
+ .duration(transitionTime)
272
+ .attr('cx', function (d) {
273
+ return d.pos.x * treeWidth;
274
+ })
275
+ .attr('cy', function (d) {
276
+ return d.pos.y * treeHeight;
277
+ })
278
+ .attr('r', function () {
279
+ return radius;
280
+ });
281
+ // Bounce animation on the active node
282
+ if (activeNodeName) {
283
+ const activePosKey = 'p' + activeNodeName;
284
+ canvas
285
+ .selectAll('.node')
286
+ .filter(function (d) {
287
+ // Match by position object reference
288
+ return d.pos === pos[activePosKey];
289
+ })
290
+ .transition()
291
+ .duration(200)
292
+ .attr('r', radius * 1.8)
293
+ .transition()
294
+ .duration(300)
295
+ .attr('r', radius);
296
+ }
297
+ canvas
298
+ .selectAll('.score')
299
+ .data(point_text)
300
+ .join((enter) => enter
301
+ .append('text')
302
+ .attr('class', 'score')
303
+ .attr('font-family', 'Lato, Arial, sans-serif')
304
+ .attr('text-anchor', 'middle')
305
+ .attr('alignment-baseline', 'central')
306
+ .attr('stroke', (d) => {
307
+ return d.stroke;
308
+ })
309
+ .attr('fill', (d) => {
310
+ return d.fill;
311
+ })
312
+ .on('mousemove', function (d, i) {
313
+ if (events.score.mousemove)
314
+ events.score.mousemove(d, i);
315
+ })
316
+ .on('mouseout', function (d, i) {
317
+ if (events.score.mouseout)
318
+ events.score.mouseout(d, i);
319
+ })
320
+ .on('click', function (d, i) {
321
+ if (events.score.click)
322
+ events.score.click(d, i);
323
+ }))
324
+ .transition()
325
+ .duration(transitionTime)
326
+ .attr('x', (d) => {
327
+ return d.pos.x * treeWidth;
328
+ })
329
+ .attr('y', (d) => {
330
+ return d.pos.y * treeHeight;
331
+ })
332
+ .attr('font-size', (d) => {
333
+ return radius * d.fontsize + 'px';
334
+ })
335
+ .text((d) => {
336
+ if (radius * d.fontsize > 5)
337
+ return d.text;
338
+ });
339
+ canvas
340
+ .selectAll('.gt_label')
341
+ .data(label_text)
342
+ .join((enter) => enter
343
+ .append('text')
344
+ .attr('class', 'gt_label')
345
+ .attr('alignment-baseline', (d) => {
346
+ return d.baseline ? d.baseline : undefined;
347
+ })
348
+ .attr('text-anchor', (d) => {
349
+ return d.anchor ? d.anchor : undefined;
350
+ })
351
+ .attr('font-family', 'Lato, Arial, sans-serif')
352
+ .attr('stroke', (d) => {
353
+ return d.stroke;
354
+ })
355
+ .attr('fill', (d) => {
356
+ return d.fill;
357
+ })
358
+ .attr('selector', (d) => {
359
+ return d.id;
360
+ })
361
+ .on('click', function (event, d) {
362
+ if (events.label.click)
363
+ events.label.click(event, d, this);
364
+ }))
365
+ .transition()
366
+ .duration(transitionTime)
367
+ .attr('x', (d) => {
368
+ return d.pos.x * treeWidth;
369
+ })
370
+ .attr('y', (d) => {
371
+ return d.pos.y * treeHeight;
372
+ })
373
+ .attr('font-size', (d) => {
374
+ return radius * d.fontsize + 'px';
375
+ })
376
+ .text((d) => {
377
+ if (radius * d.fontsize > 5)
378
+ return options.labels[d.id];
379
+ });
380
+ canvas
381
+ .selectAll('.selector')
382
+ .data(selectors)
383
+ .join((enter) => enter
384
+ .append('circle')
385
+ .attr('class', 'selector')
386
+ .attr('status', function (d) {
387
+ return d.status;
388
+ })
389
+ .attr('id', function (d) {
390
+ return nsId(d.id);
391
+ })
392
+ .attr('selector', function (d) {
393
+ return d.id;
394
+ })
395
+ .attr('stroke', function (_, i) {
396
+ return options.nodes.colors[i];
397
+ })
398
+ .attr('opacity', function (d) {
399
+ return d.opacity;
400
+ })
401
+ .on('mousemove', function (event, d) {
402
+ if (events.selector.mousemove)
403
+ events.selector.mousemove(event, d);
404
+ })
405
+ .on('mouseout', function (event, d) {
406
+ if (events.selector.mouseout)
407
+ events.selector.mouseout(event, d);
408
+ })
409
+ .on('click', function (event, d) {
410
+ if (events.selector.click)
411
+ events.selector.click(event, d, this);
412
+ }))
413
+ .transition()
414
+ .duration(transitionTime)
415
+ .attr('cx', function (d) {
416
+ return d.pos.x * treeWidth;
417
+ })
418
+ .attr('cy', function (d) {
419
+ return d.pos.y * treeHeight + 4;
420
+ })
421
+ .attr('r', function (d) {
422
+ return radius * d.r_pct;
423
+ })
424
+ .attr('stroke-width', function () {
425
+ return radius * 0.25;
426
+ })
427
+ .attr('fill', function (_, i) {
428
+ const neitherSelected = !options.selectors.selected[0] && !options.selectors.selected[1];
429
+ return !options.selectors.enabled || neitherSelected || options.selectors.selected[i]
430
+ ? options.nodes.colors[i]
431
+ : options.nodes.colors.neutral;
432
+ });
433
+ if (options.display.rightImg) {
434
+ images.right = canvas
435
+ .selectAll('image.rightImage')
436
+ .data([0])
437
+ .join((enter) => enter
438
+ .append('image')
439
+ .attr('class', 'rightImage')
440
+ .attr('y', 5)
441
+ .attr('height', '20px')
442
+ .attr('width', '20px')
443
+ .attr('opacity', options.display.showImages ? 1 : 0))
444
+ .attr('x', options.width - (options.margins.right + 30))
445
+ .attr('xlink:href', options.display.rightImg)
446
+ .on('click', function () {
447
+ if (events.rightImage.click)
448
+ events.rightImage.click(options.id);
449
+ });
450
+ }
451
+ else {
452
+ root.selectAll('image.rightImage').remove();
453
+ }
454
+ if (options.display.leftImg) {
455
+ images.left = canvas
456
+ .selectAll('image.leftImage')
457
+ .data([0])
458
+ .join((enter) => enter
459
+ .append('image')
460
+ .attr('class', 'leftImage')
461
+ .attr('y', 5)
462
+ .attr('height', '20px')
463
+ .attr('width', '20px')
464
+ .attr('opacity', options.display.showImages ? 1 : 0))
465
+ .attr('x', 10 + options.margins.left)
466
+ .attr('xlink:href', options.display.leftImg)
467
+ .on('click', function () {
468
+ if (events.leftImage.click)
469
+ events.leftImage.click(options.id);
470
+ });
471
+ }
472
+ else {
473
+ root.selectAll('image.leftImage').remove();
474
+ }
475
+ /*
476
+ function showImages() {
477
+ if (options.display.showImages == false) return;
478
+ if (options.display.leftImg) images.left.attr('opacity', 1);
479
+ if (options.display.rightImg) images.right.attr('opacity', 1);
480
+ }
481
+
482
+ function hideImages() {
483
+ if (options.display.showImages) return;
484
+ if (options.display.leftImg) images.left.attr('opacity', 0);
485
+ if (options.display.rightImg) images.right.attr('opacity', 0);
486
+ }
487
+ */
488
+ };
489
+ }
490
+ // ACCESSORS
491
+ chart.exports = function () {
492
+ return { selectView: selectView };
493
+ };
494
+ // allows updating individual options and suboptions
495
+ // while preserving state of other options
496
+ chart.options = function (values) {
497
+ if (!arguments.length)
498
+ return options;
499
+ keyWalk(values, options);
500
+ if (values.events)
501
+ keyWalk(values.events, events);
502
+ return chart;
503
+ };
504
+ chart.events = function (functions) {
505
+ if (!arguments.length)
506
+ return events;
507
+ keyWalk(functions, events);
508
+ return chart;
509
+ };
510
+ chart.reset = function () {
511
+ data = [];
512
+ clearView();
513
+ counters = { w: {}, e: {}, p: {}, n: {} };
514
+ return chart;
515
+ };
516
+ chart.width = function (value) {
517
+ if (!arguments.length)
518
+ return options.width;
519
+ options.width = value;
520
+ return chart;
521
+ };
522
+ chart.height = function (value) {
523
+ if (!arguments.length)
524
+ return options.height;
525
+ options.height = value;
526
+ return chart;
527
+ };
528
+ chart.data = function (values) {
529
+ if (!arguments.length)
530
+ return data;
531
+ if (values.constructor === Array) {
532
+ chart.reset();
533
+ data = values;
534
+ }
535
+ return chart;
536
+ };
537
+ chart.players = function (names) {
538
+ options.labels.Player = names[0];
539
+ options.labels.Opponent = names[1];
540
+ return chart;
541
+ };
542
+ chart.matchUp = function (matchUpState, names) {
543
+ if (names)
544
+ chart.players(names);
545
+ const episodes = buildEpisodes(matchUpState);
546
+ chart.data(episodes);
547
+ return chart;
548
+ };
549
+ chart.counters = function () {
550
+ counterCalcs();
551
+ return counters;
552
+ };
553
+ chart.update = function (opts) {
554
+ if (events.update.begin)
555
+ events.update.begin();
556
+ if (typeof update === 'function')
557
+ update(opts);
558
+ setTimeout(function () {
559
+ if (events.update.end)
560
+ events.update.end();
561
+ }, transitionTime);
562
+ };
563
+ // REUSABLE FUNCTIONS
564
+ // ------------------
565
+ function counterCalcs() {
566
+ let _data;
567
+ // w = winners, e = errors, p = points, n = nodes
568
+ counters = { w: {}, e: {}, p: {}, n: {} };
569
+ if (options.selectors.selected[0] || options.selectors.selected[1]) {
570
+ const selected = options.selectors.selected[0] ? 0 : 1;
571
+ _data = data.filter(function (f) {
572
+ return f.point.server == selected;
573
+ });
574
+ }
575
+ else {
576
+ _data = data;
577
+ }
578
+ _data = _data.filter(function (f) {
579
+ return !f.point.tiebreak;
580
+ });
581
+ for (let d = 0; d < _data.length; d++) {
582
+ const previous_episode = _data[d - 1];
583
+ const previous = d == 0 || previous_episode.game.complete ? calcPosition([0, 0]) : calcPosition(previous_episode.point.points);
584
+ const progression = 'L' + previous + pointConnector + calcPosition(_data[d].point.points);
585
+ if (options.points.highlight.length && !options.points.highlight.includes(d)) {
586
+ continue;
587
+ }
588
+ counters.p[progression] = counters.p[progression] ? counters.p[progression] + 1 : 1;
589
+ if (options.points.winners.includes(_data[d].point.result)) {
590
+ counters.w[progression] = counters.w[progression] ? counters.w[progression] + 1 : 1;
591
+ }
592
+ else if (options.points.errors.includes(_data[d].point.result)) {
593
+ counters.e[progression] = counters.e[progression] ? counters.e[progression] + 1 : 1;
594
+ }
595
+ }
596
+ // Determine the active node (last point's position)
597
+ if (_data.length > 0) {
598
+ const lastEp = _data[_data.length - 1];
599
+ if (lastEp.game.complete) {
600
+ activeNodeName = null; // game just completed, no active position
601
+ }
602
+ else {
603
+ activeNodeName = calcPosition(lastEp.point.points);
604
+ }
605
+ }
606
+ else {
607
+ activeNodeName = null;
608
+ }
609
+ // make adjustment for multiple dueces
610
+ function calcPosition(points) {
611
+ const point_min = Math.min(...points);
612
+ const diff = point_min >= 4 ? point_min - 3 : 0;
613
+ const pos = points.map((point, index) => options.display.noAd && point == 4 && points[1 - index] == 3 ? 'G' : point - diff);
614
+ return pos.join('-');
615
+ }
616
+ }
617
+ function clearView() {
618
+ if (canvas) {
619
+ canvas.select(`[id=${nsId('Player')}]`).attr('opacity', 0.4).attr('status', 'none').attr('fill', options.nodes.colors.neutral);
620
+ canvas.select(`[id=${nsId('Opponent')}]`).attr('opacity', 0.4).attr('status', 'none').attr('fill', options.nodes.colors.neutral);
621
+ }
622
+ options.selectors.selected[1] = false;
623
+ options.selectors.selected[0] = false;
624
+ }
625
+ function selectView(_event, _datum, self) {
626
+ if (!options.selectors.enabled)
627
+ return;
628
+ const selector = select(self).attr('selector');
629
+ const isOpponent = selector === 'Opponent';
630
+ const playerIdx = isOpponent ? 1 : 0;
631
+ if (canvas.select(`[id=${nsId(selector)}]`).attr('status') == 'none') {
632
+ if (isOpponent) {
633
+ canvas.select(`[id=${nsId('Player')}]`).attr('opacity', 0.4).attr('status', 'none').attr('fill', options.nodes.colors.neutral);
634
+ canvas.select(`[id=${nsId('Opponent')}]`).attr('opacity', 1).attr('status', 'selected').attr('fill', options.nodes.colors[playerIdx]);
635
+ options.selectors.selected[1] = true;
636
+ options.selectors.selected[0] = false;
637
+ }
638
+ else {
639
+ canvas.select(`[id=${nsId('Opponent')}]`).attr('opacity', 0.4).attr('status', 'none').attr('fill', options.nodes.colors.neutral);
640
+ canvas.select(`[id=${nsId('Player')}]`).attr('opacity', 1).attr('status', 'selected').attr('fill', options.nodes.colors[playerIdx]);
641
+ options.selectors.selected[0] = true;
642
+ options.selectors.selected[1] = false;
643
+ }
644
+ }
645
+ else {
646
+ // Clicking already-selected player removes all filters
647
+ options.selectors.selected[0] = false;
648
+ options.selectors.selected[1] = false;
649
+ canvas.select(`[id=${nsId('Player')}]`).attr('opacity', 1).attr('status', 'none').attr('fill', options.nodes.colors[0]);
650
+ canvas.select(`[id=${nsId('Opponent')}]`).attr('opacity', 1).attr('status', 'none').attr('fill', options.nodes.colors[1]);
651
+ }
652
+ update();
653
+ }
654
+ function calcStops(d) {
655
+ if (!counters.p[d.id])
656
+ return [];
657
+ const total_points = counters.p[d.id] == undefined ? 1 : counters.p[d.id];
658
+ const winners = counters.w[d.id] ? counters.w[d.id] : 0;
659
+ const errors = counters.e[d.id] ? counters.e[d.id] : 0;
660
+ const winner_pct = (winners / total_points) * 100;
661
+ // const error_pct = (errors / total_points) * 100;
662
+ const u_pct = ((total_points - (winners + errors)) / total_points) * 100;
663
+ return [
664
+ { offset: '0%', color: options.lines.points.unknown },
665
+ { offset: u_pct + '%', color: options.lines.points.unknown },
666
+ { offset: u_pct + '%', color: options.lines.points.winners },
667
+ { offset: u_pct + winner_pct + '%', color: options.lines.points.winners },
668
+ { offset: u_pct + winner_pct + '%', color: options.lines.points.errors },
669
+ { offset: '100%', color: options.lines.points.errors },
670
+ ];
671
+ }
672
+ // DATA
673
+ // --------------
674
+ const c_start = 0.07;
675
+ const c_dist = 0.14;
676
+ const r_start = 0.05;
677
+ const r_dist = 0.21;
678
+ const f = {
679
+ col1: c_start,
680
+ col2: c_start + c_dist,
681
+ col3: c_start + 2 * c_dist,
682
+ col4: c_start + 3 * c_dist,
683
+ col5: c_start + 4 * c_dist,
684
+ col6: c_start + 5 * c_dist,
685
+ col7: c_start + 6 * c_dist,
686
+ row1: r_start,
687
+ row2: r_start + r_dist,
688
+ row3: r_start + 2 * r_dist,
689
+ row4: r_start + 3 * r_dist,
690
+ row5: r_start + 4 * r_dist,
691
+ foot: r_start + 4.1 * r_dist,
692
+ adr1: r_start + 3.5 * r_dist,
693
+ adc1: c_start + c_dist * 2.5,
694
+ adc2: c_start + c_dist * 3.5,
695
+ selc: c_start * 0.8,
696
+ tslc: c_start,
697
+ sl1r: r_start / 2,
698
+ sl2r: r_start * 1.5,
699
+ plr1: c_start * 0.8,
700
+ plr2: c_start + 4 * c_dist,
701
+ plrs: r_start + 4.25 * r_dist,
702
+ };
703
+ const pos = {
704
+ 'p0-0': { x: f.col4, y: f.row1 },
705
+ 'p1-1': { x: f.col4, y: f.row2 },
706
+ 'p2-2': { x: f.col4, y: f.row3 },
707
+ 'p3-3': { x: f.col4, y: f.row4 },
708
+ 'p1-0': { x: f.col3, y: f.row2 },
709
+ 'p0-1': { x: f.col5, y: f.row2 },
710
+ 'p2-1': { x: f.col3, y: f.row3 },
711
+ 'p1-2': { x: f.col5, y: f.row3 },
712
+ 'p2-0': { x: f.col2, y: f.row3 },
713
+ 'p0-2': { x: f.col6, y: f.row3 },
714
+ 'p3-2': { x: f.col3, y: f.row4 },
715
+ 'p2-3': { x: f.col5, y: f.row4 },
716
+ 'p3-1': { x: f.col2, y: f.row4 },
717
+ 'p1-3': { x: f.col6, y: f.row4 },
718
+ 'p3-0': { x: f.col1, y: f.row4 },
719
+ 'p0-3': { x: f.col7, y: f.row4 },
720
+ 'p3-4': { x: f.adc1, y: f.adr1 },
721
+ 'p4-3': { x: f.adc2, y: f.adr1 },
722
+ 'p5-3': { x: f.adc2, y: f.row5 },
723
+ 'p3-5': { x: f.adc1, y: f.row5 },
724
+ 'p4-1': { x: f.col2, y: f.row5 },
725
+ 'p1-4': { x: f.col6, y: f.row5 },
726
+ 'p4-2': { x: f.col3, y: f.row5 },
727
+ 'p2-4': { x: f.col5, y: f.row5 },
728
+ 'p4-0': { x: f.col1, y: f.row5 },
729
+ 'p0-4': { x: f.col7, y: f.row5 },
730
+ // No Ad
731
+ 'pG-3': { x: f.col4, y: f.row5 },
732
+ 'p3-G': { x: f.col4, y: f.row5 },
733
+ sPlyr: { x: f.adc1, y: f.plrs },
734
+ tPlyr: { x: f.col2, y: f.plrs },
735
+ sOpp: { x: f.adc2, y: f.plrs },
736
+ tOpp: { x: f.col6, y: f.plrs },
737
+ GAME: { x: f.col4, y: f.foot },
738
+ L1s: { x: f.col1, y: f.foot },
739
+ L1e: { x: f.adc1, y: f.foot },
740
+ L2s: { x: f.adc2, y: f.foot },
741
+ L2e: { x: f.col7, y: f.foot },
742
+ };
743
+ const point_circles = [
744
+ { name: '0-0', pos: pos['p0-0'], player: 0 },
745
+ { name: '15-15', pos: pos['p1-1'], player: 0 },
746
+ { name: '30-30', pos: pos['p2-2'], player: 0 },
747
+ { name: '40-40', pos: pos['p3-3'], player: 0 },
748
+ { name: '0-15', pos: pos['p0-1'], color_pct: 0.4, player: 1 },
749
+ { name: '15-30', pos: pos['p1-2'], color_pct: 0.4, player: 1 },
750
+ { name: '0-30', pos: pos['p0-2'], color_pct: 0.2, player: 1 },
751
+ { name: '30-40', pos: pos['p2-3'], color_pct: 0.4, player: 1 },
752
+ { name: '15-40', pos: pos['p1-3'], color_pct: 0.2, player: 1 },
753
+ { name: '0-40', pos: pos['p0-3'], color_pct: 0, player: 1 },
754
+ { name: '40-A', pos: pos['p4-3'], color_pct: 0.5, player: 1 },
755
+ { name: '15-0', pos: pos['p1-0'], color_pct: 0.4, player: 0 },
756
+ { name: '30-15', pos: pos['p2-1'], color_pct: 0.4, player: 0 },
757
+ { name: '30-0', pos: pos['p2-0'], color_pct: 0.2, player: 0 },
758
+ { name: '40-30', pos: pos['p3-2'], color_pct: 0.4, player: 0 },
759
+ { name: '40-15', pos: pos['p3-1'], color_pct: 0.2, player: 0 },
760
+ { name: '40-0', pos: pos['p3-0'], color_pct: 0, player: 0 },
761
+ { name: 'A-40', pos: pos['p3-4'], color_pct: 0.5, player: 0 },
762
+ ];
763
+ const point_lines = [
764
+ { id: 'L0-0x0-1', start: pos['p0-0'], end: pos['p0-1'] },
765
+ { id: 'L0-0x1-0', start: pos['p0-0'], end: pos['p1-0'] },
766
+ { id: 'L0-1x0-2', start: pos['p0-1'], end: pos['p0-2'] },
767
+ { id: 'L0-1x1-1', start: pos['p0-1'], end: pos['p1-1'] },
768
+ { id: 'L1-0x2-0', start: pos['p1-0'], end: pos['p2-0'] },
769
+ { id: 'L1-0x1-1', start: pos['p1-0'], end: pos['p1-1'] },
770
+ { id: 'L1-1x1-2', start: pos['p1-1'], end: pos['p1-2'] },
771
+ { id: 'L1-1x2-1', start: pos['p1-1'], end: pos['p2-1'] },
772
+ { id: 'L2-0x2-1', start: pos['p2-0'], end: pos['p2-1'] },
773
+ { id: 'L2-0x3-0', start: pos['p2-0'], end: pos['p3-0'] },
774
+ { id: 'L2-1x2-2', start: pos['p2-1'], end: pos['p2-2'] },
775
+ { id: 'L2-1x3-1', start: pos['p2-1'], end: pos['p3-1'] },
776
+ { id: 'L2-2x2-3', start: pos['p2-2'], end: pos['p2-3'] },
777
+ { id: 'L2-2x3-2', start: pos['p2-2'], end: pos['p3-2'] },
778
+ { id: 'L0-2x0-3', start: pos['p0-2'], end: pos['p0-3'] },
779
+ { id: 'L0-2x1-2', start: pos['p0-2'], end: pos['p1-2'] },
780
+ { id: 'L0-3x0-4', start: pos['p0-3'], end: pos['p0-4'] },
781
+ { id: 'L0-3x1-3', start: pos['p0-3'], end: pos['p1-3'] },
782
+ { id: 'L1-2x2-2', start: pos['p1-2'], end: pos['p2-2'] },
783
+ { id: 'L1-2x1-3', start: pos['p1-2'], end: pos['p1-3'] },
784
+ { id: 'L1-3x2-3', start: pos['p1-3'], end: pos['p2-3'] },
785
+ { id: 'L1-3x1-4', start: pos['p1-3'], end: pos['p1-4'] },
786
+ { id: 'L2-3x3-3', start: pos['p2-3'], end: pos['p3-3'] },
787
+ { id: 'L2-3x2-4', start: pos['p2-3'], end: pos['p2-4'] },
788
+ { id: 'L3-3x3-4', start: pos['p3-3'], end: pos['p4-3'] },
789
+ { id: 'L3-3x4-3', start: pos['p3-3'], end: pos['p3-4'] },
790
+ { id: 'L3-2x3-3', start: pos['p3-2'], end: pos['p3-3'] },
791
+ { id: 'L3-2x4-2', start: pos['p3-2'], end: pos['p4-2'] },
792
+ { id: 'L3-1x3-2', start: pos['p3-1'], end: pos['p3-2'] },
793
+ { id: 'L3-1x4-1', start: pos['p3-1'], end: pos['p4-1'] },
794
+ { id: 'L3-0x3-1', start: pos['p3-0'], end: pos['p3-1'] },
795
+ { id: 'L3-0x4-0', start: pos['p3-0'], end: pos['p4-0'] },
796
+ { id: 'L3-4x3-5', start: pos['p4-3'], end: pos['p5-3'] },
797
+ { id: 'L4-3x5-3', start: pos['p3-4'], end: pos['p3-5'] },
798
+ // no Ad
799
+ { id: 'L3-3xG-3', start: pos['p3-3'], end: pos['pG-3'] },
800
+ { id: 'L3-3x3-G', start: pos['p3-3'], end: pos['p3-G'] },
801
+ ];
802
+ const under_lines = [
803
+ { stroke: 'blue', start: pos['L1s'], end: pos['L1e'], width: 2 },
804
+ { stroke: 'blue', start: pos['L2s'], end: pos['L2e'], width: 2 },
805
+ ];
806
+ const point_text = [
807
+ { pos: pos['p0-0'], fill: 'black', fontsize: 0.7, text: '0-0' },
808
+ { pos: pos['p1-1'], fill: 'black', fontsize: 0.7, text: '15-15' },
809
+ { pos: pos['p2-2'], fill: 'black', fontsize: 0.7, text: '30-30' },
810
+ { pos: pos['p3-3'], fill: 'black', fontsize: 0.7, text: '40-40' },
811
+ { pos: pos['p0-1'], fill: 'white', fontsize: 0.7, text: '0-15' },
812
+ { pos: pos['p1-2'], fill: 'white', fontsize: 0.7, text: '15-30' },
813
+ { pos: pos['p0-2'], fill: 'white', fontsize: 0.7, text: '0-30' },
814
+ { pos: pos['p2-3'], fill: 'white', fontsize: 0.7, text: '30-40' },
815
+ { pos: pos['p1-3'], fill: 'white', fontsize: 0.7, text: '15-40' },
816
+ { pos: pos['p0-3'], fill: 'white', fontsize: 0.7, text: '0-40' },
817
+ { pos: pos['p4-3'], fill: 'white', fontsize: 0.7, text: '40-A' },
818
+ { pos: pos['p1-0'], fill: 'white', fontsize: 0.7, text: '15-0' },
819
+ { pos: pos['p2-1'], fill: 'white', fontsize: 0.7, text: '30-15' },
820
+ { pos: pos['p2-0'], fill: 'white', fontsize: 0.7, text: '30-0' },
821
+ { pos: pos['p3-2'], fill: 'white', fontsize: 0.7, text: '40-30' },
822
+ { pos: pos['p3-1'], fill: 'white', fontsize: 0.7, text: '40-15' },
823
+ { pos: pos['p3-0'], fill: 'white', fontsize: 0.7, text: '40-0' },
824
+ { pos: pos['p3-4'], fill: 'white', fontsize: 0.7, text: 'A-40' },
825
+ ];
826
+ const label_text = [
827
+ { pos: pos['tPlyr'], fill: 'black', fontsize: 0.9, id: 'Player', anchor: 'middle', baseline: 'hanging' },
828
+ { pos: pos['tOpp'], fill: 'black', fontsize: 0.9, id: 'Opponent', anchor: 'middle', baseline: 'hanging' },
829
+ { pos: pos['GAME'], fill: '#555555', fontsize: 0.9, id: 'Game', anchor: 'middle', baseline: 'central' },
830
+ ];
831
+ const selectors = [
832
+ { id: 'Player', pos: pos['sPlyr'], r_pct: 0.4, opacity: 1, status: 'none' },
833
+ { id: 'Opponent', pos: pos['sOpp'], r_pct: 0.4, opacity: 0.4, status: 'none' },
834
+ ];
835
+ return chart;
836
+ }
837
+ //# sourceMappingURL=gameTree.js.map