@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.
- package/dist/__tests__/LiveEngine.test.d.ts +2 -0
- package/dist/__tests__/LiveEngine.test.d.ts.map +1 -0
- package/dist/__tests__/LiveEngine.test.js +142 -0
- package/dist/__tests__/LiveEngine.test.js.map +1 -0
- package/dist/__tests__/buildEpisodes.test.d.ts +2 -0
- package/dist/__tests__/buildEpisodes.test.d.ts.map +1 -0
- package/dist/__tests__/buildEpisodes.test.js +150 -0
- package/dist/__tests__/buildEpisodes.test.js.map +1 -0
- package/dist/__tests__/extractors.test.d.ts +2 -0
- package/dist/__tests__/extractors.test.d.ts.map +1 -0
- package/dist/__tests__/extractors.test.js +50 -0
- package/dist/__tests__/extractors.test.js.map +1 -0
- package/dist/__tests__/feedMatchUp.test.d.ts +2 -0
- package/dist/__tests__/feedMatchUp.test.d.ts.map +1 -0
- package/dist/__tests__/feedMatchUp.test.js +96 -0
- package/dist/__tests__/feedMatchUp.test.js.map +1 -0
- package/dist/engine/LiveEngine.d.ts +42 -0
- package/dist/engine/LiveEngine.d.ts.map +1 -0
- package/dist/engine/LiveEngine.js +82 -0
- package/dist/engine/LiveEngine.js.map +1 -0
- package/dist/engine/buildSetMap.d.ts +14 -0
- package/dist/engine/buildSetMap.d.ts.map +1 -0
- package/dist/engine/buildSetMap.js +42 -0
- package/dist/engine/buildSetMap.js.map +1 -0
- package/dist/engine/createPlaybackEngine.d.ts +34 -0
- package/dist/engine/createPlaybackEngine.d.ts.map +1 -0
- package/dist/engine/createPlaybackEngine.js +90 -0
- package/dist/engine/createPlaybackEngine.js.map +1 -0
- package/dist/engine/feedMatchUp.d.ts +82 -0
- package/dist/engine/feedMatchUp.d.ts.map +1 -0
- package/dist/engine/feedMatchUp.js +116 -0
- package/dist/engine/feedMatchUp.js.map +1 -0
- package/dist/episodes/buildEpisodes.d.ts +10 -0
- package/dist/episodes/buildEpisodes.d.ts.map +1 -0
- package/dist/episodes/buildEpisodes.js +166 -0
- package/dist/episodes/buildEpisodes.js.map +1 -0
- package/dist/episodes/types.d.ts +45 -0
- package/dist/episodes/types.d.ts.map +1 -0
- package/dist/episodes/types.js +2 -0
- package/dist/episodes/types.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/statistics/index.d.ts +3 -0
- package/dist/statistics/index.d.ts.map +1 -0
- package/dist/statistics/index.js +2 -0
- package/dist/statistics/index.js.map +1 -0
- package/dist/statistics/matchStatistics.d.ts +22 -0
- package/dist/statistics/matchStatistics.d.ts.map +1 -0
- package/dist/statistics/matchStatistics.js +59 -0
- package/dist/statistics/matchStatistics.js.map +1 -0
- package/dist/visualizations/coronaChart.d.ts +54 -0
- package/dist/visualizations/coronaChart.d.ts.map +1 -0
- package/dist/visualizations/coronaChart.js +254 -0
- package/dist/visualizations/coronaChart.js.map +1 -0
- package/dist/visualizations/data/mcpFixtures.json +13166 -0
- package/dist/visualizations/data/sampleGame.d.ts +37 -0
- package/dist/visualizations/data/sampleGame.d.ts.map +1 -0
- package/dist/visualizations/data/sampleGame.js +521 -0
- package/dist/visualizations/data/sampleGame.js.map +1 -0
- package/dist/visualizations/gameFish.d.ts +16 -0
- package/dist/visualizations/gameFish.d.ts.map +1 -0
- package/dist/visualizations/gameFish.js +692 -0
- package/dist/visualizations/gameFish.js.map +1 -0
- package/dist/visualizations/gameTree.d.ts +17 -0
- package/dist/visualizations/gameTree.d.ts.map +1 -0
- package/dist/visualizations/gameTree.js +837 -0
- package/dist/visualizations/gameTree.js.map +1 -0
- package/dist/visualizations/groupGames.d.ts +6 -0
- package/dist/visualizations/groupGames.d.ts.map +1 -0
- package/dist/visualizations/groupGames.js +35 -0
- package/dist/visualizations/groupGames.js.map +1 -0
- package/dist/visualizations/helpers/JsonViewer.d.ts +27 -0
- package/dist/visualizations/helpers/JsonViewer.d.ts.map +1 -0
- package/dist/visualizations/helpers/JsonViewer.js +276 -0
- package/dist/visualizations/helpers/JsonViewer.js.map +1 -0
- package/dist/visualizations/helpers/PlaybackControls.d.ts +12 -0
- package/dist/visualizations/helpers/PlaybackControls.d.ts.map +1 -0
- package/dist/visualizations/helpers/PlaybackControls.js +98 -0
- package/dist/visualizations/helpers/PlaybackControls.js.map +1 -0
- package/dist/visualizations/horizonChart.d.ts +59 -0
- package/dist/visualizations/horizonChart.d.ts.map +1 -0
- package/dist/visualizations/horizonChart.js +215 -0
- package/dist/visualizations/horizonChart.js.map +1 -0
- package/dist/visualizations/index.d.ts +21 -0
- package/dist/visualizations/index.d.ts.map +1 -0
- package/dist/visualizations/index.js +23 -0
- package/dist/visualizations/index.js.map +1 -0
- package/dist/visualizations/legacyRally.d.ts +3 -0
- package/dist/visualizations/legacyRally.d.ts.map +1 -0
- package/dist/visualizations/legacyRally.js +26 -0
- package/dist/visualizations/legacyRally.js.map +1 -0
- package/dist/visualizations/matchDashboard.d.ts +39 -0
- package/dist/visualizations/matchDashboard.d.ts.map +1 -0
- package/dist/visualizations/matchDashboard.js +235 -0
- package/dist/visualizations/matchDashboard.js.map +1 -0
- package/dist/visualizations/momentumChart.d.ts +12 -0
- package/dist/visualizations/momentumChart.d.ts.map +1 -0
- package/dist/visualizations/momentumChart.js +454 -0
- package/dist/visualizations/momentumChart.js.map +1 -0
- package/dist/visualizations/ptsChart.d.ts +14 -0
- package/dist/visualizations/ptsChart.d.ts.map +1 -0
- package/dist/visualizations/ptsChart.js +925 -0
- package/dist/visualizations/ptsChart.js.map +1 -0
- package/dist/visualizations/ptsHorizon.d.ts +83 -0
- package/dist/visualizations/ptsHorizon.d.ts.map +1 -0
- package/dist/visualizations/ptsHorizon.js +290 -0
- package/dist/visualizations/ptsHorizon.js.map +1 -0
- package/dist/visualizations/rallyTree.d.ts +78 -0
- package/dist/visualizations/rallyTree.d.ts.map +1 -0
- package/dist/visualizations/rallyTree.js +410 -0
- package/dist/visualizations/rallyTree.js.map +1 -0
- package/dist/visualizations/simpleChart.d.ts +6 -0
- package/dist/visualizations/simpleChart.d.ts.map +1 -0
- package/dist/visualizations/simpleChart.js +126 -0
- package/dist/visualizations/simpleChart.js.map +1 -0
- package/dist/visualizations/statView.d.ts +52 -0
- package/dist/visualizations/statView.d.ts.map +1 -0
- package/dist/visualizations/statView.js +200 -0
- package/dist/visualizations/statView.js.map +1 -0
- package/dist/visualizations/typeOf.d.ts +6 -0
- package/dist/visualizations/typeOf.d.ts.map +1 -0
- package/dist/visualizations/typeOf.js +18 -0
- package/dist/visualizations/typeOf.js.map +1 -0
- package/dist/visualizations/types/events.d.ts +92 -0
- package/dist/visualizations/types/events.d.ts.map +1 -0
- package/dist/visualizations/types/events.js +2 -0
- package/dist/visualizations/types/events.js.map +1 -0
- package/dist/visualizations/types/index.d.ts +79 -0
- package/dist/visualizations/types/index.d.ts.map +1 -0
- package/dist/visualizations/types/index.js +8 -0
- package/dist/visualizations/types/index.js.map +1 -0
- package/dist/visualizations/utils/arrays.d.ts +18 -0
- package/dist/visualizations/utils/arrays.d.ts.map +1 -0
- package/dist/visualizations/utils/arrays.js +46 -0
- package/dist/visualizations/utils/arrays.js.map +1 -0
- package/dist/visualizations/utils/colorUtils.d.ts +36 -0
- package/dist/visualizations/utils/colorUtils.d.ts.map +1 -0
- package/dist/visualizations/utils/colorUtils.js +112 -0
- package/dist/visualizations/utils/colorUtils.js.map +1 -0
- package/dist/visualizations/utils/generateId.d.ts +2 -0
- package/dist/visualizations/utils/generateId.d.ts.map +1 -0
- package/dist/visualizations/utils/generateId.js +3 -0
- package/dist/visualizations/utils/generateId.js.map +1 -0
- package/dist/visualizations/utils/keyWalk.d.ts +2 -0
- package/dist/visualizations/utils/keyWalk.d.ts.map +1 -0
- package/dist/visualizations/utils/keyWalk.js +19 -0
- package/dist/visualizations/utils/keyWalk.js.map +1 -0
- package/dist/visualizations/utils/math.d.ts +10 -0
- package/dist/visualizations/utils/math.d.ts.map +1 -0
- package/dist/visualizations/utils/math.js +14 -0
- package/dist/visualizations/utils/math.js.map +1 -0
- package/dist/visualizations/utils/setDev.d.ts +29 -0
- package/dist/visualizations/utils/setDev.d.ts.map +1 -0
- package/dist/visualizations/utils/setDev.js +35 -0
- package/dist/visualizations/utils/setDev.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,925 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import { select, scaleLinear, scaleBand, range, line } from 'd3';
|
|
4
|
+
import { rallyCount } from './legacyRally';
|
|
5
|
+
import { buildEpisodes } from '../episodes/buildEpisodes';
|
|
6
|
+
import { keyWalk } from './utils/keyWalk';
|
|
7
|
+
import { generateId } from './utils/generateId';
|
|
8
|
+
function groupGames(point_episodes) {
|
|
9
|
+
const episodes = point_episodes;
|
|
10
|
+
const games = [{ points: [], range: [0, 0] }];
|
|
11
|
+
let gameCounter = 0;
|
|
12
|
+
let currentGame = 0;
|
|
13
|
+
episodes.forEach((episode) => {
|
|
14
|
+
const point = episode.point;
|
|
15
|
+
if (point.game != currentGame) {
|
|
16
|
+
gameCounter += 1;
|
|
17
|
+
currentGame = point.game;
|
|
18
|
+
games[gameCounter] = { points: [], range: [point.index, point.index] };
|
|
19
|
+
}
|
|
20
|
+
games[gameCounter].points.push(point);
|
|
21
|
+
games[gameCounter].index = gameCounter;
|
|
22
|
+
games[gameCounter].set = episode.set.index;
|
|
23
|
+
games[gameCounter].score = episode.game.games;
|
|
24
|
+
games[gameCounter].complete = episode.game.complete;
|
|
25
|
+
games[gameCounter].range[1] = point.index;
|
|
26
|
+
if (episode.game.complete) {
|
|
27
|
+
games[gameCounter].winner = point.winner;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return games;
|
|
31
|
+
}
|
|
32
|
+
function add_index(d, i) {
|
|
33
|
+
for (const item of d) {
|
|
34
|
+
item['_i'] = i;
|
|
35
|
+
}
|
|
36
|
+
return d;
|
|
37
|
+
}
|
|
38
|
+
export function ptsMatch() {
|
|
39
|
+
let match_data;
|
|
40
|
+
let participantNames = ['Player 1', 'Player 2'];
|
|
41
|
+
const options = {
|
|
42
|
+
id: generateId(),
|
|
43
|
+
class: 'ptsMatch',
|
|
44
|
+
resize: true,
|
|
45
|
+
width: 600,
|
|
46
|
+
height: 80,
|
|
47
|
+
maxHeight: 100,
|
|
48
|
+
margins: {
|
|
49
|
+
top: 0,
|
|
50
|
+
right: 0,
|
|
51
|
+
bottom: 0,
|
|
52
|
+
left: 0,
|
|
53
|
+
},
|
|
54
|
+
set: {
|
|
55
|
+
averagePoints: 56,
|
|
56
|
+
},
|
|
57
|
+
lines: {
|
|
58
|
+
width: 2,
|
|
59
|
+
interpolation: 'linear',
|
|
60
|
+
},
|
|
61
|
+
points: {
|
|
62
|
+
maxWidthPoints: 100,
|
|
63
|
+
},
|
|
64
|
+
score: {
|
|
65
|
+
font: 'Arial',
|
|
66
|
+
fontSize: '12px',
|
|
67
|
+
fontWeight: 'bold',
|
|
68
|
+
reverse: true,
|
|
69
|
+
},
|
|
70
|
+
header: {
|
|
71
|
+
font: 'Arial',
|
|
72
|
+
fontSize: '14px',
|
|
73
|
+
fontWeight: 'bold',
|
|
74
|
+
},
|
|
75
|
+
display: {
|
|
76
|
+
sizeToFit: true,
|
|
77
|
+
transitionTime: 0,
|
|
78
|
+
pointHighlighting: true,
|
|
79
|
+
pointOpacity: 0.4,
|
|
80
|
+
winErrHighlight: true,
|
|
81
|
+
gameHighlighting: true,
|
|
82
|
+
gameOpacity: 0.2,
|
|
83
|
+
gameBoundaries: true,
|
|
84
|
+
gamepoints: false,
|
|
85
|
+
score: true,
|
|
86
|
+
points: true,
|
|
87
|
+
winner: true,
|
|
88
|
+
},
|
|
89
|
+
colors: {
|
|
90
|
+
orientation: 'yellow',
|
|
91
|
+
gamepoints: 'black',
|
|
92
|
+
players: { 0: '#a55194', 1: '#6b6ecf' },
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
// functions which should be accessible via ACCESSORS
|
|
96
|
+
let update;
|
|
97
|
+
// programmatic
|
|
98
|
+
const pts_sets = [];
|
|
99
|
+
let dom_parent;
|
|
100
|
+
// prepare charts
|
|
101
|
+
const pts_charts = [];
|
|
102
|
+
for (let s = 0; s < 5; s++) {
|
|
103
|
+
pts_charts.push(ptsChart());
|
|
104
|
+
}
|
|
105
|
+
// DEFINABLE EVENTS
|
|
106
|
+
// Define with ACCESSOR function chart.events()
|
|
107
|
+
const events = {
|
|
108
|
+
update: { begin: null, end: null },
|
|
109
|
+
setBox: { mouseover: null, mouseout: null },
|
|
110
|
+
pointBars: { mouseover: null, mouseout: null, click: null },
|
|
111
|
+
};
|
|
112
|
+
function chart(selection) {
|
|
113
|
+
dom_parent = selection;
|
|
114
|
+
if (options.display.sizeToFit) {
|
|
115
|
+
const dims = selection.node().getBoundingClientRect();
|
|
116
|
+
options.width = Math.max(dims.width, 400);
|
|
117
|
+
}
|
|
118
|
+
// append svg
|
|
119
|
+
const root = dom_parent
|
|
120
|
+
.append('div')
|
|
121
|
+
.attr('class', options.class + 'root')
|
|
122
|
+
.style('width', options.width + 'px')
|
|
123
|
+
.style('height', options.height + 'px');
|
|
124
|
+
for (let s = 0; s < 5; s++) {
|
|
125
|
+
pts_sets[s] = root.append('div').attr('class', 'pts').style('display', 'none');
|
|
126
|
+
pts_sets[s].call(pts_charts[s]);
|
|
127
|
+
}
|
|
128
|
+
update = function (opts) {
|
|
129
|
+
const sets = match_data.sets();
|
|
130
|
+
if (options.display.sizeToFit || opts?.sizeToFit) {
|
|
131
|
+
const dims = selection.node().getBoundingClientRect();
|
|
132
|
+
options.width = Math.max(dims.width, 400);
|
|
133
|
+
options.height = Math.max((dims.height - (+options.margins.top + +options.margins.bottom)) / sets.length, 20);
|
|
134
|
+
if (options.height > options.maxHeight)
|
|
135
|
+
options.height = options.maxHeight;
|
|
136
|
+
}
|
|
137
|
+
let true_height = 0;
|
|
138
|
+
for (let s = 0; s < pts_charts.length; s++) {
|
|
139
|
+
if (sets?.[s]?.history.points().length) {
|
|
140
|
+
pts_sets[s].style('display', 'inline');
|
|
141
|
+
pts_charts[s].width(options.width);
|
|
142
|
+
pts_charts[s].height(options.height);
|
|
143
|
+
pts_charts[s].update();
|
|
144
|
+
true_height += +options.height + 5;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
pts_sets[s].style('display', 'none');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
root.style('width', options.width + 'px').style('height', true_height + 'px');
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
// ACCESSORS
|
|
154
|
+
// allows updating individual options and suboptions
|
|
155
|
+
// while preserving state of other options
|
|
156
|
+
chart.options = function (values) {
|
|
157
|
+
if (!arguments.length)
|
|
158
|
+
return options;
|
|
159
|
+
keyWalk(values, options);
|
|
160
|
+
if (values.events)
|
|
161
|
+
keyWalk(values.events, events);
|
|
162
|
+
return chart;
|
|
163
|
+
};
|
|
164
|
+
chart.events = function (functions) {
|
|
165
|
+
if (!arguments.length)
|
|
166
|
+
return events;
|
|
167
|
+
keyWalk(functions, events);
|
|
168
|
+
return chart;
|
|
169
|
+
};
|
|
170
|
+
chart.colors = function (colores) {
|
|
171
|
+
if (!arguments.length)
|
|
172
|
+
return options.colors;
|
|
173
|
+
options.colors.players = colores;
|
|
174
|
+
return chart;
|
|
175
|
+
};
|
|
176
|
+
chart.width = function (value) {
|
|
177
|
+
if (!arguments.length)
|
|
178
|
+
return options.width;
|
|
179
|
+
options.width = value;
|
|
180
|
+
if (typeof update === 'function')
|
|
181
|
+
update(true);
|
|
182
|
+
pts_charts.forEach(function (e) {
|
|
183
|
+
e.width(value);
|
|
184
|
+
});
|
|
185
|
+
return chart;
|
|
186
|
+
};
|
|
187
|
+
chart.height = function (value) {
|
|
188
|
+
if (!arguments.length)
|
|
189
|
+
return options.height;
|
|
190
|
+
options.height = value;
|
|
191
|
+
if (typeof update === 'function')
|
|
192
|
+
update(true);
|
|
193
|
+
pts_charts.forEach(function (e) {
|
|
194
|
+
e.height(value);
|
|
195
|
+
});
|
|
196
|
+
return chart;
|
|
197
|
+
};
|
|
198
|
+
chart.duration = function (value) {
|
|
199
|
+
if (!arguments.length)
|
|
200
|
+
return options.display.transitionTime;
|
|
201
|
+
options.display.transitionTime = value;
|
|
202
|
+
return chart;
|
|
203
|
+
};
|
|
204
|
+
chart.update = function (opts) {
|
|
205
|
+
if (!match_data) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
if (events.update.begin)
|
|
209
|
+
events.update.begin();
|
|
210
|
+
const sets = match_data.sets();
|
|
211
|
+
if (!sets || sets.length === 0) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
const maxWidthPoints = Math.max(...sets.map((set, index) => {
|
|
215
|
+
if (!set?.history?.points) {
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
const setPoints = set.history.points();
|
|
219
|
+
const filtered = setPoints.filter((f) => f.set == index);
|
|
220
|
+
return filtered.length;
|
|
221
|
+
}));
|
|
222
|
+
if (sets.length > 1)
|
|
223
|
+
chart.options({ points: { maxWidthPoints } });
|
|
224
|
+
sets.forEach(function (set, i) {
|
|
225
|
+
pts_charts[i].data(set);
|
|
226
|
+
pts_charts[i].options({ id: `${options.id}_${i}`, setIndex: i });
|
|
227
|
+
pts_charts[i].options({
|
|
228
|
+
lines: options.lines,
|
|
229
|
+
points: options.points,
|
|
230
|
+
score: options.score,
|
|
231
|
+
header: options.header,
|
|
232
|
+
});
|
|
233
|
+
pts_charts[i].options({ set: options.set, display: options.display, colors: options.colors });
|
|
234
|
+
pts_charts[i].events(events);
|
|
235
|
+
pts_charts[i].width(options.width).height(options.height).update(opts);
|
|
236
|
+
});
|
|
237
|
+
if (typeof update === 'function')
|
|
238
|
+
update(opts);
|
|
239
|
+
setTimeout(function () {
|
|
240
|
+
if (events.update.end)
|
|
241
|
+
events.update.end();
|
|
242
|
+
}, options.display.transitionTime);
|
|
243
|
+
return true;
|
|
244
|
+
};
|
|
245
|
+
chart.data = function (matchObjectOrEpisodes) {
|
|
246
|
+
if (!arguments.length) {
|
|
247
|
+
return match_data;
|
|
248
|
+
}
|
|
249
|
+
// Support both UMO objects (for main app) and episode arrays (for standalone)
|
|
250
|
+
if (Array.isArray(matchObjectOrEpisodes)) {
|
|
251
|
+
const episodes = matchObjectOrEpisodes;
|
|
252
|
+
// Plain array of episodes - create simple accessor object
|
|
253
|
+
match_data = {
|
|
254
|
+
sets: () => {
|
|
255
|
+
// Group episodes by set
|
|
256
|
+
const setsMap = new Map();
|
|
257
|
+
episodes.forEach((ep) => {
|
|
258
|
+
const setIndex = ep.set?.index ?? 0;
|
|
259
|
+
if (!setsMap.has(setIndex)) {
|
|
260
|
+
setsMap.set(setIndex, []);
|
|
261
|
+
}
|
|
262
|
+
setsMap.get(setIndex).push(ep);
|
|
263
|
+
});
|
|
264
|
+
// Return array of set objects with all required accessors for ptsSet
|
|
265
|
+
return Array.from(setsMap.values()).map((episodes, _setIndex) => {
|
|
266
|
+
const points = episodes.map((ep) => ep.point);
|
|
267
|
+
const lastEpisode = episodes[episodes.length - 1];
|
|
268
|
+
const isComplete = lastEpisode?.set?.complete ?? false;
|
|
269
|
+
return {
|
|
270
|
+
history: {
|
|
271
|
+
points: () => points,
|
|
272
|
+
action: (actionType) => (actionType === 'addPoint' ? episodes : []),
|
|
273
|
+
},
|
|
274
|
+
complete: () => isComplete,
|
|
275
|
+
winner: () => {
|
|
276
|
+
// Determine winner based on game score if set is complete
|
|
277
|
+
if (isComplete) {
|
|
278
|
+
const games = lastEpisode?.game?.games || [0, 0];
|
|
279
|
+
if (games[0] > games[1])
|
|
280
|
+
return 0;
|
|
281
|
+
if (games[1] > games[0])
|
|
282
|
+
return 1;
|
|
283
|
+
}
|
|
284
|
+
return undefined;
|
|
285
|
+
},
|
|
286
|
+
metadata: {
|
|
287
|
+
players: () => {
|
|
288
|
+
const name0 = participantNames[0];
|
|
289
|
+
const name1 = participantNames[1];
|
|
290
|
+
const parts0 = name0.split(' ');
|
|
291
|
+
const parts1 = name1.split(' ');
|
|
292
|
+
return [
|
|
293
|
+
{
|
|
294
|
+
index: 0,
|
|
295
|
+
firstName: parts0[0] || name0,
|
|
296
|
+
lastName: parts0.slice(1).join(' ') || '0',
|
|
297
|
+
participantName: name0,
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
index: 1,
|
|
301
|
+
firstName: parts1[0] || name1,
|
|
302
|
+
lastName: parts1.slice(1).join(' ') || '1',
|
|
303
|
+
participantName: name1,
|
|
304
|
+
},
|
|
305
|
+
];
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
scoreboard: (_perspective) => {
|
|
309
|
+
const games = lastEpisode?.game?.games || [0, 0];
|
|
310
|
+
return `${games[0]}-${games[1]}`;
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
},
|
|
315
|
+
history: {
|
|
316
|
+
points: () => matchObjectOrEpisodes.map((ep) => ep.point),
|
|
317
|
+
action: (actionType) => (actionType === 'addPoint' ? matchObjectOrEpisodes : []),
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
// UMO object - use as-is
|
|
323
|
+
match_data = matchObjectOrEpisodes;
|
|
324
|
+
}
|
|
325
|
+
// Only call update if chart has been initialized (dom_parent exists)
|
|
326
|
+
if (dom_parent) {
|
|
327
|
+
chart.update();
|
|
328
|
+
}
|
|
329
|
+
return chart;
|
|
330
|
+
};
|
|
331
|
+
chart.players = function (names) {
|
|
332
|
+
participantNames = names;
|
|
333
|
+
return chart;
|
|
334
|
+
};
|
|
335
|
+
chart.matchUp = function (matchUpState, names) {
|
|
336
|
+
if (names)
|
|
337
|
+
participantNames = names;
|
|
338
|
+
const episodes = buildEpisodes(matchUpState);
|
|
339
|
+
chart.data(episodes);
|
|
340
|
+
return chart;
|
|
341
|
+
};
|
|
342
|
+
return chart;
|
|
343
|
+
}
|
|
344
|
+
function ptsChart() {
|
|
345
|
+
let set_data;
|
|
346
|
+
let game_data;
|
|
347
|
+
let pointsToSet;
|
|
348
|
+
const winners = new Set(['Ace', 'Winner', 'Serve Winner']);
|
|
349
|
+
const errors = new Set(['Forced Error', 'Unforced Error', 'Double Fault', 'Penalty', 'Out', 'Net']);
|
|
350
|
+
const options = {
|
|
351
|
+
id: generateId(),
|
|
352
|
+
setIndex: 0,
|
|
353
|
+
class: 'ptsChart',
|
|
354
|
+
resize: true,
|
|
355
|
+
width: 600,
|
|
356
|
+
height: 80,
|
|
357
|
+
margins: {
|
|
358
|
+
top: 5,
|
|
359
|
+
right: 15,
|
|
360
|
+
bottom: 5,
|
|
361
|
+
left: 5,
|
|
362
|
+
},
|
|
363
|
+
set: {
|
|
364
|
+
averagePoints: 56,
|
|
365
|
+
},
|
|
366
|
+
lines: {
|
|
367
|
+
width: 2,
|
|
368
|
+
interpolation: 'linear',
|
|
369
|
+
},
|
|
370
|
+
points: {
|
|
371
|
+
maxWidthPoints: 100,
|
|
372
|
+
},
|
|
373
|
+
score: {
|
|
374
|
+
font: 'Arial',
|
|
375
|
+
fontSize: '12px',
|
|
376
|
+
fontWeight: 'bold',
|
|
377
|
+
reverse: true,
|
|
378
|
+
},
|
|
379
|
+
header: {
|
|
380
|
+
font: 'Arial',
|
|
381
|
+
fontSize: '14px',
|
|
382
|
+
fontWeight: 'bold',
|
|
383
|
+
},
|
|
384
|
+
display: {
|
|
385
|
+
transitionTime: 0,
|
|
386
|
+
pointHighlighting: true,
|
|
387
|
+
pointOpacity: 0.4,
|
|
388
|
+
winErrHighlight: true,
|
|
389
|
+
gameHighlighting: true,
|
|
390
|
+
gameOpacity: 0.2,
|
|
391
|
+
gameBoundaries: false,
|
|
392
|
+
gamepoints: false,
|
|
393
|
+
score: true,
|
|
394
|
+
points: true,
|
|
395
|
+
winner: true,
|
|
396
|
+
},
|
|
397
|
+
colors: {
|
|
398
|
+
orientation: 'yellow',
|
|
399
|
+
gamepoints: 'black',
|
|
400
|
+
players: { 0: 'blue', 1: 'purple' },
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
// functions which should be accessible via ACCESSORS
|
|
404
|
+
let update;
|
|
405
|
+
// programmatic
|
|
406
|
+
let dom_parent;
|
|
407
|
+
// DEFINABLE EVENTS
|
|
408
|
+
// Define with ACCESSOR function chart.events()
|
|
409
|
+
const events = {
|
|
410
|
+
setBox: { mouseover: null, mouseout: null },
|
|
411
|
+
update: { begin: null, end: null },
|
|
412
|
+
pointBars: { mouseover: null, mouseout: null, click: null },
|
|
413
|
+
};
|
|
414
|
+
function chart(selection) {
|
|
415
|
+
selection.each(function (_, i, n) {
|
|
416
|
+
dom_parent = select(n[i]);
|
|
417
|
+
// append svg
|
|
418
|
+
const root = dom_parent
|
|
419
|
+
.append('svg')
|
|
420
|
+
.attr('class', options.class + 'root')
|
|
421
|
+
.style('width', options.width + 'px')
|
|
422
|
+
.style('height', options.height + 'px');
|
|
423
|
+
// append children g
|
|
424
|
+
const pts = root
|
|
425
|
+
.append('g')
|
|
426
|
+
.attr('class', options.class + 'pts')
|
|
427
|
+
.attr('transform', 'translate(5, 5)');
|
|
428
|
+
// For Point Bars which must always be on top
|
|
429
|
+
const ptsHover = root
|
|
430
|
+
.append('g')
|
|
431
|
+
.attr('class', options.class + 'pts')
|
|
432
|
+
.attr('transform', 'translate(5, 5)');
|
|
433
|
+
// append labels
|
|
434
|
+
const set_winner = pts
|
|
435
|
+
.append('text')
|
|
436
|
+
.attr('class', options.class + 'Header')
|
|
437
|
+
.attr('opacity', 0)
|
|
438
|
+
.attr('font-size', options.header.fontSize)
|
|
439
|
+
.attr('font-weight', options.header.fontWeight)
|
|
440
|
+
.attr('x', function () {
|
|
441
|
+
return options.margins.left + 'px';
|
|
442
|
+
})
|
|
443
|
+
.attr('y', function () {
|
|
444
|
+
return options.margins.top + 8 + 'px';
|
|
445
|
+
});
|
|
446
|
+
const set_score = pts
|
|
447
|
+
.append('text')
|
|
448
|
+
.attr('class', options.class + 'Score')
|
|
449
|
+
.attr('opacity', 0)
|
|
450
|
+
.attr('font-size', options.score.fontSize)
|
|
451
|
+
.attr('font-weight', options.score.fontWeight)
|
|
452
|
+
.attr('x', function () {
|
|
453
|
+
return options.margins.left + 'px';
|
|
454
|
+
})
|
|
455
|
+
.attr('y', function () {
|
|
456
|
+
return options.margins.top + 20 + 'px';
|
|
457
|
+
});
|
|
458
|
+
// resize used to disable transitions during resize operation
|
|
459
|
+
update = function (_, resize) {
|
|
460
|
+
if (!set_data) {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
root
|
|
464
|
+
.transition()
|
|
465
|
+
.duration(options.display.transitionTime)
|
|
466
|
+
.style('width', options.width + 'px')
|
|
467
|
+
.style('height', options.height + 'px');
|
|
468
|
+
const allActionPoints = set_data.history.action('addPoint');
|
|
469
|
+
const points = allActionPoints.filter((f) => f.point.set == options.setIndex);
|
|
470
|
+
if (!points || points.length === 0) {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
const range_start = points[0].point.index;
|
|
474
|
+
game_data = groupGames(points);
|
|
475
|
+
pointsToSet = points.map((p) => p.needed?.pointsToSet);
|
|
476
|
+
const pts_max = Math.max(...[].concat(pointsToSet.map((p) => p[0]), pointsToSet.map((p) => p[1])));
|
|
477
|
+
const pts_start = Math.max(...pointsToSet[0]);
|
|
478
|
+
// add pts prior to first point
|
|
479
|
+
pointsToSet.unshift([pts_start, pts_start]);
|
|
480
|
+
const longest_rally = Math.max.apply(null, points.map((m) => {
|
|
481
|
+
// Prefer rallyLength if available, fallback to rally
|
|
482
|
+
const rallyValue = m.point.rallyLength || (m.point.rally ? rallyCount(m.point.rally) : 0);
|
|
483
|
+
return rallyValue;
|
|
484
|
+
})) + 2;
|
|
485
|
+
displayScore(resize);
|
|
486
|
+
const xScale = scaleLinear()
|
|
487
|
+
.domain([0, calcWidth()])
|
|
488
|
+
.range([0, options.width - (options.margins.left + options.margins.right)]);
|
|
489
|
+
function pointScale(d, r, a) {
|
|
490
|
+
if (d.range[r] < range_start)
|
|
491
|
+
return xScale(d.range[r] + a);
|
|
492
|
+
return xScale(d.range[r] + a - range_start);
|
|
493
|
+
}
|
|
494
|
+
const yScale = scaleLinear()
|
|
495
|
+
.range([options.height - (options.margins.top + options.margins.bottom), options.margins.bottom])
|
|
496
|
+
.domain([-2, pts_max - 1]);
|
|
497
|
+
// Set Box
|
|
498
|
+
pts.selectAll('.' + options.class + 'SetBox').data([options.id]) // # of list elements only used for index, data not important
|
|
499
|
+
.join(enter => enter
|
|
500
|
+
.append('rect')
|
|
501
|
+
.attr('class', options.class + 'SetBox')
|
|
502
|
+
.style('position', 'relative')
|
|
503
|
+
.attr('stroke', 'black')
|
|
504
|
+
.attr('stroke-width', 1)
|
|
505
|
+
.attr('fill', 'none')
|
|
506
|
+
.on('mouseover', (d, i) => {
|
|
507
|
+
if (events.setBox.mouseover)
|
|
508
|
+
events.setBox.mouseover(d, i);
|
|
509
|
+
})
|
|
510
|
+
.on('mouseout', (d, i) => {
|
|
511
|
+
if (events.setBox.mouseout)
|
|
512
|
+
events.setBox.mouseout(d, i);
|
|
513
|
+
}), update => update, exit => exit
|
|
514
|
+
.transition()
|
|
515
|
+
.duration(resize ? 0 : options.display.transitionTime)
|
|
516
|
+
.style('opacity', 0)
|
|
517
|
+
.remove())
|
|
518
|
+
.transition()
|
|
519
|
+
.duration(resize ? 0 : options.display.transitionTime)
|
|
520
|
+
.attr('height', () => {
|
|
521
|
+
return options.height - (options.margins.top + options.margins.bottom);
|
|
522
|
+
})
|
|
523
|
+
.attr('width', () => {
|
|
524
|
+
return xScale(boxWidth() + 1);
|
|
525
|
+
});
|
|
526
|
+
// Game Boundaries
|
|
527
|
+
pts.selectAll('.' + options.class + 'GameBoundary').data(game_data)
|
|
528
|
+
.join(enter => enter.append('rect').attr('class', options.class + 'GameBoundary'))
|
|
529
|
+
.attr('id', function (_, i) {
|
|
530
|
+
return options.class + options.id + 'boundary' + i;
|
|
531
|
+
})
|
|
532
|
+
.transition()
|
|
533
|
+
.duration(resize ? 0 : options.display.transitionTime)
|
|
534
|
+
.attr('opacity', function () {
|
|
535
|
+
return options.display.gameBoundaries ? 0.02 : 0;
|
|
536
|
+
})
|
|
537
|
+
.attr('transform', function (d) {
|
|
538
|
+
return 'translate(' + pointScale(d, 0, 0) + ', 0)';
|
|
539
|
+
})
|
|
540
|
+
.attr('height', yScale(-2))
|
|
541
|
+
.attr('width', function (d) {
|
|
542
|
+
return pointScale(d, 1, 1) - pointScale(d, 0, 0);
|
|
543
|
+
})
|
|
544
|
+
.attr('stroke', 'black')
|
|
545
|
+
.attr('stroke-width', 1)
|
|
546
|
+
.attr('fill', 'none');
|
|
547
|
+
// Game Boxes
|
|
548
|
+
pts.selectAll('.' + options.class + 'Game').data(game_data)
|
|
549
|
+
.join(enter => enter.append('rect').attr('class', options.class + 'Game'))
|
|
550
|
+
.attr('id', (_, i) => {
|
|
551
|
+
return options.class + options.id + 'game' + i;
|
|
552
|
+
})
|
|
553
|
+
.transition()
|
|
554
|
+
.duration(resize ? 0 : options.display.transitionTime)
|
|
555
|
+
.attr('opacity', () => {
|
|
556
|
+
return options.display.gameBoundaries ? 0.02 : 0;
|
|
557
|
+
})
|
|
558
|
+
.attr('transform', (d) => {
|
|
559
|
+
return 'translate(' + pointScale(d, 0, 0) + ', 0)';
|
|
560
|
+
})
|
|
561
|
+
.attr('height', yScale(-2))
|
|
562
|
+
.attr('width', (d) => {
|
|
563
|
+
return pointScale(d, 1, 1) - pointScale(d, 0, 0);
|
|
564
|
+
})
|
|
565
|
+
.attr('stroke', (d) => {
|
|
566
|
+
return options.colors.players[d.winner];
|
|
567
|
+
})
|
|
568
|
+
.attr('stroke-width', 1)
|
|
569
|
+
.attr('fill', (d) => {
|
|
570
|
+
return d.winner === undefined ? 'none' : options.colors.players[d.winner];
|
|
571
|
+
});
|
|
572
|
+
// Player PTS Lines
|
|
573
|
+
const lineGen = line()
|
|
574
|
+
.x((_, i) => {
|
|
575
|
+
return xScale(i);
|
|
576
|
+
})
|
|
577
|
+
.y((d) => {
|
|
578
|
+
return yScale(pts_max - d);
|
|
579
|
+
});
|
|
580
|
+
pts.selectAll('.' + options.class + 'Line').data([0, 1])
|
|
581
|
+
.join(enter => enter
|
|
582
|
+
.append('path')
|
|
583
|
+
.attr('class', options.class + 'Line')
|
|
584
|
+
.attr('fill', 'none'))
|
|
585
|
+
.attr('id', (d) => {
|
|
586
|
+
return options.class + options.id + 'player' + d + 'Line';
|
|
587
|
+
})
|
|
588
|
+
.transition()
|
|
589
|
+
.duration(resize ? 0 : options.display.transitionTime / 2)
|
|
590
|
+
.style('opacity', 0.1)
|
|
591
|
+
.transition()
|
|
592
|
+
.duration(resize ? 0 : options.display.transitionTime / 2)
|
|
593
|
+
.style('opacity', 1)
|
|
594
|
+
.attr('stroke', (d) => {
|
|
595
|
+
return options.colors.players[d];
|
|
596
|
+
})
|
|
597
|
+
.attr('stroke-width', () => {
|
|
598
|
+
return options.lines.width;
|
|
599
|
+
})
|
|
600
|
+
// .attr('d', function(d: any) { return lineGen(player_data[d]) })
|
|
601
|
+
.attr('d', (d) => {
|
|
602
|
+
return lineGen(pointsToSet.map((p) => p[d]));
|
|
603
|
+
});
|
|
604
|
+
const bp_data = [
|
|
605
|
+
pointsToSet.map((p) => {
|
|
606
|
+
return { pts: p[0] };
|
|
607
|
+
}),
|
|
608
|
+
pointsToSet.map((p) => {
|
|
609
|
+
return { pts: p[1] };
|
|
610
|
+
}),
|
|
611
|
+
];
|
|
612
|
+
const bp_wrappers = pts.selectAll('.' + options.class + 'BPWrapper').data(bp_data)
|
|
613
|
+
.join(enter => enter.append('g').attr('class', options.class + 'BPWrapper'));
|
|
614
|
+
bp_wrappers.selectAll('.' + options.class + 'Breakpoint').data((d, i) => {
|
|
615
|
+
return add_index(d, i);
|
|
616
|
+
})
|
|
617
|
+
.join(enter => enter
|
|
618
|
+
.append('circle')
|
|
619
|
+
.attr('class', options.class + 'Breakpoint')
|
|
620
|
+
.attr('opacity', '0'), update => update, exit => exit.attr('opacity', '0').remove())
|
|
621
|
+
.transition()
|
|
622
|
+
.duration(resize ? 0 : options.display.transitionTime / 2)
|
|
623
|
+
.style('opacity', 0)
|
|
624
|
+
.transition()
|
|
625
|
+
.duration(resize ? 0 : options.display.transitionTime / 2)
|
|
626
|
+
.attr('fill', (d, i) => {
|
|
627
|
+
if (points[i - 1] && points[i - 1].point.isBreakpoint != undefined) {
|
|
628
|
+
return options.colors.players[d._i];
|
|
629
|
+
}
|
|
630
|
+
})
|
|
631
|
+
.style('opacity', (d, i) => {
|
|
632
|
+
if (points[i - 1] && points[i - 1].point.isBreakpoint != undefined) {
|
|
633
|
+
// return points[i - 1].point.isBreakpoint == d._i ? 1 : 0
|
|
634
|
+
return points[i - 1].point.server == 1 - d._i ? 1 : 0;
|
|
635
|
+
}
|
|
636
|
+
})
|
|
637
|
+
.attr('cx', (_, i) => {
|
|
638
|
+
return xScale(i);
|
|
639
|
+
})
|
|
640
|
+
.attr('cy', (d) => {
|
|
641
|
+
return yScale(pts_max - d.pts);
|
|
642
|
+
})
|
|
643
|
+
.attr('r', 2);
|
|
644
|
+
const points_index = range(points.length);
|
|
645
|
+
const barsX = scaleBand()
|
|
646
|
+
.domain(points_index)
|
|
647
|
+
.range([0, xScale(points.length)])
|
|
648
|
+
.round(true);
|
|
649
|
+
const bX = scaleLinear()
|
|
650
|
+
.domain([0, points.length])
|
|
651
|
+
.range([0, xScale(points.length)]);
|
|
652
|
+
// gradients cause hover errors when data is replaced
|
|
653
|
+
pts.selectAll('.gradient' + options.id).remove();
|
|
654
|
+
const gradients = pts.selectAll('.gradient' + options.id).data(range(points.length)) // data not important, only length of array
|
|
655
|
+
.join(enter => enter
|
|
656
|
+
.append('linearGradient')
|
|
657
|
+
.attr('id', (_, i) => {
|
|
658
|
+
return 'gradient' + options.id + i;
|
|
659
|
+
})
|
|
660
|
+
.attr('class', () => {
|
|
661
|
+
return 'gradient' + options.id;
|
|
662
|
+
})
|
|
663
|
+
.attr('gradientUnits', 'userSpaceOnUse')
|
|
664
|
+
.attr('x1', () => {
|
|
665
|
+
return barsX.bandwidth() / 2;
|
|
666
|
+
})
|
|
667
|
+
.attr('y1', () => {
|
|
668
|
+
return 0;
|
|
669
|
+
})
|
|
670
|
+
.attr('x2', () => {
|
|
671
|
+
return barsX.bandwidth() / 2;
|
|
672
|
+
})
|
|
673
|
+
.attr('y2', () => {
|
|
674
|
+
return yScale(-2);
|
|
675
|
+
}))
|
|
676
|
+
.attr('transform', (_, i) => {
|
|
677
|
+
return 'translate(' + bX(i) + ', 0)';
|
|
678
|
+
});
|
|
679
|
+
gradients.selectAll('.points_stop').data((d) => {
|
|
680
|
+
return calcStops(points[d].point);
|
|
681
|
+
})
|
|
682
|
+
.join(enter => enter.append('stop').attr('class', 'points_stop'))
|
|
683
|
+
.attr('offset', (d) => {
|
|
684
|
+
return d.offset;
|
|
685
|
+
})
|
|
686
|
+
.attr('stop-color', (d) => {
|
|
687
|
+
return d.color;
|
|
688
|
+
});
|
|
689
|
+
// Pre-compute point-index → game-index lookup for O(1) hover
|
|
690
|
+
const pointToGame = {};
|
|
691
|
+
game_data.forEach((g, gi) => {
|
|
692
|
+
g.points.forEach((p) => { pointToGame[p.index] = gi; });
|
|
693
|
+
});
|
|
694
|
+
ptsHover.selectAll('.' + options.class + 'Bar').data(range(points.length)) // data not important, only length of array
|
|
695
|
+
.join(enter => enter
|
|
696
|
+
.append('line')
|
|
697
|
+
.attr('class', options.class + 'Bar')
|
|
698
|
+
.attr('opacity', '0')
|
|
699
|
+
.style('pointer-events', 'all')
|
|
700
|
+
.on('mouseover', function (event, d) {
|
|
701
|
+
const i = d; // In D3 v7, d is the datum (index in this case)
|
|
702
|
+
if (options.display.pointHighlighting) {
|
|
703
|
+
select(this).attr('opacity', options.display.pointOpacity);
|
|
704
|
+
}
|
|
705
|
+
if (options.display.gameHighlighting && points[i]) {
|
|
706
|
+
const gameIndex = pointToGame[points[i].point.index];
|
|
707
|
+
if (gameIndex >= 0) {
|
|
708
|
+
pts.select('[id="' + options.class + options.id + 'game' + gameIndex + '"]').attr('opacity', options.display.gameOpacity);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (events.pointBars.mouseover) {
|
|
712
|
+
events.pointBars.mouseover(points[i], i);
|
|
713
|
+
}
|
|
714
|
+
if (i == 0) {
|
|
715
|
+
ptsHover.selectAll('.' + options.class + 'Bar').attr('opacity', options.display.pointOpacity);
|
|
716
|
+
}
|
|
717
|
+
highlightScore(i);
|
|
718
|
+
})
|
|
719
|
+
.on('mouseout', function (event, d) {
|
|
720
|
+
const i = d;
|
|
721
|
+
ptsHover.selectAll('.' + options.class + 'Bar').attr('opacity', 0);
|
|
722
|
+
pts.selectAll('.' + options.class + 'Game').attr('opacity', '0');
|
|
723
|
+
if (events.pointBars.mouseout) {
|
|
724
|
+
events.pointBars.mouseout(points[i], i);
|
|
725
|
+
}
|
|
726
|
+
displayScore();
|
|
727
|
+
})
|
|
728
|
+
.on('click', function (event, d) {
|
|
729
|
+
const i = d;
|
|
730
|
+
if (events.pointBars.click) {
|
|
731
|
+
events.pointBars.click(points[d], i, n[i]);
|
|
732
|
+
}
|
|
733
|
+
}), update => update, exit => exit
|
|
734
|
+
.transition()
|
|
735
|
+
.duration(resize ? 0 : options.display.transitionTime)
|
|
736
|
+
.attr('opacity', '0')
|
|
737
|
+
.remove())
|
|
738
|
+
.attr('opacity', () => {
|
|
739
|
+
const opacity = options.display.winErrHighlight ? '.4' : '0';
|
|
740
|
+
return opacity;
|
|
741
|
+
})
|
|
742
|
+
.attr('transform', (_, i) => {
|
|
743
|
+
return 'translate(' + bX(i) + ', 0)';
|
|
744
|
+
})
|
|
745
|
+
.attr('x1', () => {
|
|
746
|
+
return barsX.bandwidth() / 2;
|
|
747
|
+
})
|
|
748
|
+
.attr('y1', () => {
|
|
749
|
+
const y1 = 0;
|
|
750
|
+
return y1;
|
|
751
|
+
})
|
|
752
|
+
.attr('x2', () => {
|
|
753
|
+
return barsX.bandwidth() / 2;
|
|
754
|
+
})
|
|
755
|
+
.attr('y2', () => {
|
|
756
|
+
const y2 = yScale(-2);
|
|
757
|
+
return y2;
|
|
758
|
+
})
|
|
759
|
+
.attr('stroke-width', () => {
|
|
760
|
+
const width = barsX.bandwidth();
|
|
761
|
+
return width;
|
|
762
|
+
})
|
|
763
|
+
.attr('stroke', (_, i) => {
|
|
764
|
+
return 'url(#gradient' + options.id + i + ')';
|
|
765
|
+
})
|
|
766
|
+
.attr('uid', (_, i) => {
|
|
767
|
+
return 'point' + i;
|
|
768
|
+
});
|
|
769
|
+
function displayScore(resize) {
|
|
770
|
+
const winner = set_data.winner();
|
|
771
|
+
const players = set_data.metadata.players();
|
|
772
|
+
function lastName(name) {
|
|
773
|
+
const split = name.split(' ');
|
|
774
|
+
return split[split.length - 1];
|
|
775
|
+
}
|
|
776
|
+
const legend = winner === undefined
|
|
777
|
+
? `${lastName(players[0].participantName)}/${lastName(players[1].participantName)}`
|
|
778
|
+
: players[winner].participantName;
|
|
779
|
+
set_winner
|
|
780
|
+
.transition()
|
|
781
|
+
.duration(resize ? 0 : options.display.transitionTime)
|
|
782
|
+
.attr('opacity', 1)
|
|
783
|
+
.attr('fill', winner === undefined ? 'black' : options.colors.players[winner])
|
|
784
|
+
.text(legend);
|
|
785
|
+
const game_score = set_data.scoreboard(winner);
|
|
786
|
+
set_score
|
|
787
|
+
.transition()
|
|
788
|
+
.duration(resize ? 0 : options.display.transitionTime)
|
|
789
|
+
.attr('opacity', 1)
|
|
790
|
+
.attr('fill', winner === undefined ? 'black' : options.colors.players[winner])
|
|
791
|
+
.text(game_score);
|
|
792
|
+
}
|
|
793
|
+
function highlightScore(i) {
|
|
794
|
+
const point = points[i]?.point;
|
|
795
|
+
if (!point)
|
|
796
|
+
return;
|
|
797
|
+
const result = point.result || '';
|
|
798
|
+
const rallyLength = point.rallyLength || 0;
|
|
799
|
+
const label = `${result}${rallyLength ? ' (Rally: ' + rallyLength + ')' : ''}`;
|
|
800
|
+
set_winner
|
|
801
|
+
.attr('opacity', 1)
|
|
802
|
+
.attr('fill', options.colors.players[point.winner])
|
|
803
|
+
.text(label);
|
|
804
|
+
set_score
|
|
805
|
+
.attr('opacity', 1)
|
|
806
|
+
.attr('fill', options.colors.players[point.winner])
|
|
807
|
+
.text(point.score || '');
|
|
808
|
+
}
|
|
809
|
+
function calcStops(point) {
|
|
810
|
+
let win_pct = 0;
|
|
811
|
+
let err_pct = 0;
|
|
812
|
+
let u_pct = 0;
|
|
813
|
+
if (options.display.winErrHighlight) {
|
|
814
|
+
const result = point.result;
|
|
815
|
+
const rallyLength = point.rallyLength || (point.rally ? rallyCount(point.rally) : 0);
|
|
816
|
+
const rally_pct = rallyLength ? 100 - Math.floor((rallyLength / longest_rally) * 100) : 100;
|
|
817
|
+
if (winners.has(result)) {
|
|
818
|
+
win_pct = rally_pct;
|
|
819
|
+
}
|
|
820
|
+
else if (errors.has(result)) {
|
|
821
|
+
err_pct = rally_pct;
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
u_pct = rally_pct;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return [
|
|
828
|
+
{ offset: '0%', color: 'blue' },
|
|
829
|
+
{ offset: u_pct + '%', color: 'blue' },
|
|
830
|
+
{ offset: u_pct + '%', color: 'green' },
|
|
831
|
+
{ offset: u_pct + win_pct + '%', color: 'green' },
|
|
832
|
+
{ offset: u_pct + win_pct + '%', color: 'red' },
|
|
833
|
+
{ offset: u_pct + win_pct + err_pct + '%', color: 'red' },
|
|
834
|
+
{ offset: u_pct + win_pct + err_pct + '%', color: options.colors.orientation },
|
|
835
|
+
{ offset: '100%', color: options.colors.orientation },
|
|
836
|
+
];
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
// REUSABLE functions
|
|
842
|
+
// ------------------
|
|
843
|
+
function boxWidth() {
|
|
844
|
+
const dl = set_data.history.points().filter((f) => f.set == options.setIndex).length - 1;
|
|
845
|
+
return set_data.complete() ? dl : Math.max(dl, options.set.averagePoints);
|
|
846
|
+
}
|
|
847
|
+
function calcWidth() {
|
|
848
|
+
const dl = set_data.history.points().filter((f) => f.set == options.setIndex).length - 1;
|
|
849
|
+
return Math.max(dl, options.points.maxWidthPoints, options.set.averagePoints);
|
|
850
|
+
}
|
|
851
|
+
// ACCESSORS
|
|
852
|
+
// allows updating individual options and suboptions
|
|
853
|
+
// while preserving state of other options
|
|
854
|
+
chart.options = function (values) {
|
|
855
|
+
if (!arguments.length)
|
|
856
|
+
return options;
|
|
857
|
+
const vKeys = Object.keys(values);
|
|
858
|
+
const oKeys = Object.keys(options);
|
|
859
|
+
for (const vKey of vKeys) {
|
|
860
|
+
if (oKeys.includes(vKey)) {
|
|
861
|
+
if (typeof options[vKey] == 'object') {
|
|
862
|
+
const sKeys = Object.keys(values[vKey]);
|
|
863
|
+
const osKeys = Object.keys(options[vKey]);
|
|
864
|
+
for (const sKey of sKeys) {
|
|
865
|
+
if (osKeys.includes(sKey)) {
|
|
866
|
+
options[vKey][sKey] = values[vKey][sKey];
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
options[vKey] = values[vKey];
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return chart;
|
|
876
|
+
};
|
|
877
|
+
chart.data = function (set_object) {
|
|
878
|
+
if (!arguments.length)
|
|
879
|
+
return set_data;
|
|
880
|
+
set_data = set_object;
|
|
881
|
+
};
|
|
882
|
+
chart.events = function (functions) {
|
|
883
|
+
if (!arguments.length)
|
|
884
|
+
return events;
|
|
885
|
+
keyWalk(functions, events);
|
|
886
|
+
return chart;
|
|
887
|
+
};
|
|
888
|
+
chart.colors = function (colores) {
|
|
889
|
+
if (!arguments.length)
|
|
890
|
+
return options.colors;
|
|
891
|
+
options.colors.players = colores;
|
|
892
|
+
return chart;
|
|
893
|
+
};
|
|
894
|
+
chart.width = function (value) {
|
|
895
|
+
if (!arguments.length)
|
|
896
|
+
return options.width;
|
|
897
|
+
options.width = value;
|
|
898
|
+
return chart;
|
|
899
|
+
};
|
|
900
|
+
chart.height = function (value) {
|
|
901
|
+
if (!arguments.length)
|
|
902
|
+
return options.height;
|
|
903
|
+
options.height = value;
|
|
904
|
+
return chart;
|
|
905
|
+
};
|
|
906
|
+
chart.update = function (opts) {
|
|
907
|
+
if (events.update.begin)
|
|
908
|
+
events.update.begin();
|
|
909
|
+
if (typeof update === 'function')
|
|
910
|
+
update(opts);
|
|
911
|
+
setTimeout(function () {
|
|
912
|
+
if (events.update.end)
|
|
913
|
+
events.update.end();
|
|
914
|
+
}, options.display.transitionTime);
|
|
915
|
+
return true;
|
|
916
|
+
};
|
|
917
|
+
chart.duration = function (value) {
|
|
918
|
+
if (!arguments.length)
|
|
919
|
+
return options.display.transitionTime;
|
|
920
|
+
options.display.transitionTime = value;
|
|
921
|
+
return chart;
|
|
922
|
+
};
|
|
923
|
+
return chart;
|
|
924
|
+
}
|
|
925
|
+
//# sourceMappingURL=ptsChart.js.map
|