@luceosports/play-rendering 2.5.5 → 2.5.6

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.
@@ -10,6 +10,7 @@ export class AnimationModel {
10
10
  max: number;
11
11
  }>;
12
12
  get lastLineAnimationMax(): number;
13
+ setOptions(nextOptions: PlayModel['options']): this;
13
14
  start(finishCallback: () => void, progressCallback: (progress: number) => void): void;
14
15
  pause(): void;
15
16
  setProgress(progress: number): this;
@@ -7,6 +7,7 @@ export class LineModel {
7
7
  get color(): Color;
8
8
  set color(value: Color);
9
9
  get type(): LineType;
10
+ get isBallTransferLine(): boolean;
10
11
  get phase(): number;
11
12
  get playerPositionOrigin(): Position;
12
13
  get playerPositionTerminus(): Position;
@@ -33,6 +33,10 @@ export type PlayModelOptions = {
33
33
  playersMap: PlayersMapItem[];
34
34
  labelsOverrideType: 'Initials' | 'Jersey number' | 'Headshot' | null;
35
35
  inDrawingState: boolean;
36
+ showBallMode: boolean;
37
+ // sub-options for showBallMode
38
+ highlightPlayerPuck: boolean;
39
+ showPassLinesDuringPlayback: boolean;
36
40
  }
37
41
 
38
42
  export type TeamPlayer = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luceosports/play-rendering",
3
- "version": "2.5.5",
3
+ "version": "2.5.6",
4
4
  "main": "dist/play-rendering.js",
5
5
  "types": "dist/play-rendering.d.ts",
6
6
  "scripts": {
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <svg fill="#000000" width="800px" height="800px" viewBox="-8 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M368.5 363.9l28.8-13.9c11.1 22.9 26 43.2 44.1 60.9 34-42.5 54.5-96.3 54.5-154.9 0-58.5-20.4-112.2-54.2-154.6-17.8 17.3-32.6 37.1-43.6 59.5l-28.7-14.1c12.8-26 30-49 50.8-69C375.6 34.7 315 8 248 8 181.1 8 120.5 34.6 75.9 77.7c20.7 19.9 37.9 42.9 50.7 68.8l-28.7 14.1c-11-22.3-25.7-42.1-43.5-59.4C20.4 143.7 0 197.4 0 256c0 58.6 20.4 112.3 54.4 154.7 18.2-17.7 33.2-38 44.3-61l28.8 13.9c-12.9 26.7-30.3 50.3-51.5 70.7 44.5 43.1 105.1 69.7 172 69.7 66.8 0 127.3-26.5 171.9-69.5-21.1-20.4-38.5-43.9-51.4-70.6zm-228.3-32l-30.5-9.8c14.9-46.4 12.7-93.8-.6-134l30.4-10c15 45.6 18 99.9.7 153.8zm216.3-153.4l30.4 10c-13.2 40.1-15.5 87.5-.6 134l-30.5 9.8c-17.3-54-14.3-108.3.7-153.8z"/></svg>
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <svg width="800px" height="800px" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="#000000" d="M248.37 41.094c-49.643 1.754-98.788 20.64-137.89 56.656L210.53 197.8c31.283-35.635 45.59-88.686 37.84-156.706zm18.126.107c7.646 71.205-7.793 129.56-43.223 169.345L256 243.27 401.52 97.75c-38.35-35.324-86.358-54.18-135.024-56.55zM97.75 110.48c-36.017 39.102-54.902 88.247-56.656 137.89 68.02 7.75 121.07-6.557 156.707-37.84L97.75 110.48zm316.5 0L268.73 256l32.71 32.71c33.815-30.112 81.05-45.78 138.183-45.11 10.088.118 20.49.753 31.176 1.9-2.37-48.665-21.227-96.672-56.55-135.02zM210.545 223.272c-39.785 35.43-98.14 50.87-169.344 43.223 2.37 48.666 21.226 96.675 56.55 135.025L243.27 256l-32.725-32.727zm225.002 38.27c-51.25.042-92.143 14.29-121.348 39.928l100.05 100.05c36.017-39.102 54.902-88.247 56.656-137.89-12.275-1.4-24.074-2.096-35.36-2.087zM256 268.73L110.48 414.25c38.35 35.324 86.358 54.18 135.024 56.55-7.646-71.205 7.793-129.56 43.223-169.345L256 268.73zm45.47 45.47c-31.283 35.635-45.59 88.686-37.84 156.706 49.643-1.754 98.788-20.64 137.89-56.656L301.47 314.2z"/></svg>
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <svg fill="#000000" width="800px" height="800px" viewBox="-8 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M481.5 60.3c-4.8-18.2-19.1-32.5-37.3-37.4C420.3 16.5 383 8.9 339.4 8L496 164.8c-.8-43.5-8.2-80.6-14.5-104.5zm-467 391.4c4.8 18.2 19.1 32.5 37.3 37.4 23.9 6.4 61.2 14 104.8 14.9L0 347.2c.8 43.5 8.2 80.6 14.5 104.5zM4.2 283.4L220.4 500c132.5-19.4 248.8-118.7 271.5-271.4L275.6 12C143.1 31.4 26.8 130.7 4.2 283.4zm317.3-123.6c3.1-3.1 8.2-3.1 11.3 0l11.3 11.3c3.1 3.1 3.1 8.2 0 11.3l-28.3 28.3 28.3 28.3c3.1 3.1 3.1 8.2 0 11.3l-11.3 11.3c-3.1 3.1-8.2 3.1-11.3 0l-28.3-28.3-22.6 22.7 28.3 28.3c3.1 3.1 3.1 8.2 0 11.3l-11.3 11.3c-3.1 3.1-8.2 3.1-11.3 0L248 278.6l-22.6 22.6 28.3 28.3c3.1 3.1 3.1 8.2 0 11.3l-11.3 11.3c-3.1 3.1-8.2 3.1-11.3 0l-28.3-28.3-28.3 28.3c-3.1 3.1-8.2 3.1-11.3 0l-11.3-11.3c-3.1-3.1-3.1-8.2 0-11.3l28.3-28.3-28.3-28.2c-3.1-3.1-3.1-8.2 0-11.3l11.3-11.3c3.1-3.1 8.2-3.1 11.3 0l28.3 28.3 22.6-22.6-28.3-28.3c-3.1-3.1-3.1-8.2 0-11.3l11.3-11.3c3.1-3.1 8.2-3.1 11.3 0l28.3 28.3 22.6-22.6-28.3-28.3c-3.1-3.1-3.1-8.2 0-11.3l11.3-11.3c3.1-3.1 8.2-3.1 11.3 0l28.3 28.3 28.3-28.5z"/></svg>
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="iso-8859-1"?>
2
+ <svg height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
3
+ viewBox="0 0 32.96 32.96" xml:space="preserve">
4
+ <g>
5
+ <g>
6
+ <g>
7
+ <path style="fill:#010002;" d="M16.48,18.084c-9.101,0-16.479-2.964-16.479-6.622v10.742h0.05c0,3.916,7.357,7.093,16.429,7.093
8
+ c9.077,0,16.432-3.177,16.432-7.093h0.048V11.462C32.961,15.12,25.582,18.084,16.48,18.084z"/>
9
+ <ellipse style="fill:#010002;" cx="16.48" cy="10.285" rx="16.48" ry="6.622"/>
10
+ </g>
11
+ </g>
12
+ </g>
13
+ </svg>
@@ -0,0 +1,21 @@
1
+ <?xml version="1.0" encoding="iso-8859-1"?>
2
+ <svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
3
+ viewBox="0 0 512 512" xml:space="preserve">
4
+ <path style="fill:#A0A0A0;" d="M512,256c0,141.384-114.616,256-256,256S0,397.384,0,256S114.616,0,256,0S512,114.616,512,256z"/>
5
+ <path style="fill:#868686;" d="M512,256c0,141.384-114.615,256-256,256C132.098,512,28.761,423.976,5.098,307.054
6
+ c39.175,56.575,104.53,93.641,178.554,93.641c119.87,0,217.043-97.174,217.043-217.043c0-74.024-37.067-139.38-93.641-178.554
7
+ C423.976,28.761,512,132.098,512,256z"/>
8
+ <path style="fill:#FFFFFF;" d="M464.696,256v0.493c-0.017,4.6-3.751,8.319-8.348,8.319c-0.01,0-0.02,0-0.03,0
9
+ c-4.61-0.017-8.334-3.767-8.319-8.377V256c0-4.61,3.738-8.348,8.348-8.348S464.696,251.39,464.696,256z M454.922,281.456
10
+ c-4.548-0.767-8.854,2.292-9.623,6.837C429.629,380.833,350.019,448,256,448c-4.61,0-8.348,3.738-8.348,8.348
11
+ s3.738,8.348,8.348,8.348c49.603,0,97.675-17.703,135.363-49.849c37.264-31.784,62.264-75.739,70.396-123.767
12
+ C462.529,286.534,459.468,282.226,454.922,281.456z"/>
13
+ <path style="fill:#AAAAAA;" d="M244.87,161.391c0,52.251-42.358,94.609-94.609,94.609s-94.609-42.358-94.609-94.609
14
+ s42.358-94.609,94.609-94.609S244.87,109.141,244.87,161.391z"/>
15
+ <path style="fill:#757575;" d="M166.957,244.862c0-10.725,7.637-19.959,18.187-21.889c20.537-3.757,44.826-5.929,70.856-5.929
16
+ s50.32,2.173,70.856,5.93c10.551,1.93,18.187,11.163,18.187,21.889v22.275c0,10.725-7.637,19.959-18.187,21.889
17
+ c-20.537,3.757-44.826,5.929-70.856,5.929s-50.32-2.173-70.856-5.93c-10.551-1.93-18.187-11.163-18.187-21.889V244.862z"/>
18
+ <path style="fill:#939393;" d="M309.258,239.617c7.236,1.509,12.632,8.234,13.525,16.383c-0.893,8.149-6.29,14.874-13.525,16.383
19
+ c-15.436,3.22-33.692,5.083-53.258,5.083s-37.821-1.862-53.258-5.083c-7.236-1.509-12.632-8.234-13.525-16.383
20
+ c0.893-8.149,6.29-14.874,13.525-16.383c15.436-3.22,33.692-5.083,53.258-5.083S293.821,236.397,309.258,239.617z"/>
21
+ </svg>
@@ -0,0 +1,34 @@
1
+ <?xml version="1.0" encoding="iso-8859-1"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
+ <svg fill="#000000" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
4
+ width="800px" height="800px" viewBox="0 0 72.371 72.372"
5
+ xml:space="preserve">
6
+ <g>
7
+ <path d="M22.57,2.648c-4.489,1.82-8.517,4.496-11.971,7.949C7.144,14.051,4.471,18.08,2.65,22.568C0.892,26.904,0,31.486,0,36.186
8
+ c0,4.699,0.892,9.281,2.65,13.615c1.821,4.489,4.495,8.518,7.949,11.971c3.454,3.455,7.481,6.129,11.971,7.949
9
+ c4.336,1.76,8.917,2.649,13.617,2.649c4.7,0,9.28-0.892,13.616-2.649c4.488-1.82,8.518-4.494,11.971-7.949
10
+ c3.455-3.453,6.129-7.48,7.949-11.971c1.758-4.334,2.648-8.916,2.648-13.615c0-4.7-0.891-9.282-2.648-13.618
11
+ c-1.82-4.488-4.496-8.518-7.949-11.971s-7.479-6.129-11.971-7.949C45.467,0.891,40.887,0,36.187,0
12
+ C31.487,0,26.906,0.891,22.57,2.648z M9.044,51.419c-1.743-1.094-3.349-2.354-4.771-3.838c-2.172-6.112-2.54-12.729-1.101-19.01
13
+ c0.677-1.335,1.447-2.617,2.318-3.845c0.269-0.379,0.518-0.774,0.806-1.142l8.166,4.832c0,0.064,0,0.134,0,0.205
14
+ c-0.021,4.392,0.425,8.752,1.313,13.049c0.003,0.02,0.006,0.031,0.01,0.049l-6.333,9.93C9.314,51.579,9.177,51.503,9.044,51.419z
15
+ M33.324,68.206c1.409,0.719,2.858,1.326,4.347,1.82c-6.325,0.275-12.713-1.207-18.36-4.447L33,68.018
16
+ C33.105,68.085,33.212,68.149,33.324,68.206z M33.274,65.735L17.12,62.856c-1.89-2.295-3.59-4.723-5.051-7.318
17
+ c-0.372-0.66-0.787-1.301-1.102-1.99l6.327-9.92c0.14,0.035,0.296,0.072,0.473,0.119c3.958,1.059,7.986,1.812,12.042,2.402
18
+ c0.237,0.033,0.435,0.062,0.604,0.08l7.584,13.113c-1.316,1.85-2.647,3.69-4.007,5.51C33.764,65.155,33.524,65.446,33.274,65.735z
19
+ M60.15,60.149c-1.286,1.287-2.651,2.447-4.08,3.481c-0.237-1.894-0.646-3.75-1.223-5.563l8.092-15.096
20
+ c2.229-1.015,4.379-2.166,6.375-3.593c0.261-0.185,0.478-0.392,0.646-0.618C69.374,46.561,66.104,54.196,60.15,60.149z
21
+ M59.791,40.571c0.301,0.574,0.598,1.154,0.896,1.742l-7.816,14.58c-0.045,0.01-0.088,0.02-0.133,0.026
22
+ c-4.225,0.789-8.484,1.209-12.779,1.229l-7.8-13.487c1.214-2.254,2.417-4.517,3.61-6.781c0.81-1.536,1.606-3.082,2.401-4.627
23
+ l16.143-1.658C56.29,34.495,58.163,37.457,59.791,40.571z M56.516,23.277c-0.766,2.023-1.586,4.025-2.401,6.031l-15.726,1.615
24
+ c-0.188-0.248-0.383-0.492-0.588-0.725c-1.857-2.103-3.726-4.193-5.592-6.289c0.017-0.021,0.034-0.037,0.051-0.056
25
+ c-0.753-0.752-1.508-1.504-2.261-2.258l4.378-13.181c0.302-0.08,0.606-0.147,0.913-0.18c2.38-0.242,4.763-0.516,7.149-0.654
26
+ c1.461-0.082,2.93-0.129,4.416-0.024l10.832,12.209C57.314,20.943,56.95,22.124,56.516,23.277z M60.15,12.221
27
+ c2.988,2.99,5.302,6.402,6.938,10.047c-2.024-1.393-4.188-2.539-6.463-3.473c-0.354-0.146-0.717-0.275-1.086-0.402L48.877,6.376
28
+ c0.074-0.519,0.113-1.039,0.129-1.563C53.062,6.464,56.864,8.936,60.15,12.221z M25.334,4.182c0.042,0.031,0.062,0.057,0.086,0.064
29
+ c2.437,0.842,4.654,2.082,6.744,3.553l-4.09,12.317c-0.021,0.006-0.041,0.012-0.061,0.021c-0.837,0.346-1.69,0.656-2.514,1.031
30
+ c-3.395,1.543-6.705,3.252-9.823,5.301l-8.071-4.775c0.012-0.252,0.055-0.508,0.141-0.736c0.542-1.444,1.075-2.896,1.688-4.311
31
+ c0.472-1.09,1.01-2.143,1.597-3.172c0.384-0.424,0.782-0.844,1.192-1.254c3.833-3.832,8.363-6.553,13.186-8.162
32
+ C25.384,4.098,25.358,4.139,25.334,4.182z"/>
33
+ </g>
34
+ </svg>
@@ -0,0 +1,37 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+
3
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4
+ <svg height="800px" width="800px" version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
5
+ viewBox="0 0 512 512" xml:space="preserve">
6
+ <style type="text/css">
7
+ .st0{fill:#000000;}
8
+ </style>
9
+ <g>
10
+ <path class="st0" d="M383.301,168.546c-6.107,8.825-12.711,17.27-19.676,25.413c-25.851,30.156-57.049,55.51-92.144,74.679
11
+ c-0.058,2.22-0.058,4.442-0.058,6.604c0,34.227,5.98,68.202,17.455,100.219c5.98-2.591,11.834-5.367,17.698-8.27
12
+ c76.344-38.046,139.189-97.131,181.939-171.012c3.75-6.535,7.335-13.139,10.724-19.86c-5.367-16.461-12.584-32.688-21.585-48.292
13
+ c-13.081-22.626-29.046-42.487-47.172-59.328c-8.64,31.082-21.722,60.322-38.552,86.961
14
+ C389.155,160.043,386.32,164.358,383.301,168.546z"/>
15
+ <path class="st0" d="M358.19,148.385h0.068c2.289-3.273,4.442-6.536,6.536-9.867c17.63-27.877,30.78-58.9,38.231-92.076
16
+ c-6.78-4.752-13.744-9.194-20.893-13.198C343.638,11.532,299.983,0,255.762,0.059c-43.422,0-87.332,10.986-127.725,34.286
17
+ c-11.532,6.662-22.266,14.064-32.318,22.15c86.221,2.834,169.413,29.356,241.443,77.085
18
+ C344.32,138.332,351.353,143.261,358.19,148.385z"/>
19
+ <path class="st0" d="M110.952,193.404c10.676,0.926,21.283,2.465,31.822,4.5c39.283,7.587,77.095,22.325,111.323,43.666
20
+ c32.191-17.269,60.867-40.334,84.488-67.714c-6.224-4.686-12.643-9.185-19.179-13.51c-71.056-47.124-153.692-72.04-239.037-72.04
21
+ c-6.048,0-12.155,0.137-18.195,0.439c-11.045,12.828-20.718,26.581-28.929,41.133c-12.458,22.14-21.585,45.955-27.069,70.618
22
+ c24.546-5.796,49.452-8.572,74.192-8.572C90.605,191.924,100.773,192.42,110.952,193.404z"/>
23
+ <path class="st0" d="M104.601,246.625c0.429-7.149,1.052-14.309,1.782-21.458c-8.63-0.809-17.328-1.179-26.016-1.179
24
+ c-26.523,0-53.29,3.585-79.433,10.92C0.311,242.009,0,249.089,0,256.249c0.059,43.422,11.045,87.332,34.354,127.725
25
+ c23.748,41.074,57.05,73.013,95.534,94.783c8.873,5.065,18.068,9.506,27.496,13.383c-35.328-66.974-53.708-141.04-53.708-216.528
26
+ C103.676,265.931,103.988,256.249,104.601,246.625z"/>
27
+ <path class="st0" d="M271.482,416.964c-4.685-9.682-8.814-19.549-12.399-29.601c-13.003-35.835-19.724-73.764-19.724-112.121
28
+ c0-1.666,0-3.331,0.059-4.997c-31.024-19.801-65.435-33.487-101.202-40.51c-0.681,6.224-1.237,12.506-1.607,18.867
29
+ c-0.556,9-0.867,18.01-0.867,27.01c0,75.799,19.861,149.924,57.975,216.041c2.834,4.928,5.795,9.799,8.873,14.61h0.069
30
+ c17.58,3.769,35.523,5.678,53.601,5.678c24.477,0,49.022-3.516,73.071-10.733c-18.555-19.549-34.412-41.075-47.416-64.082
31
+ C278.203,430.532,274.686,423.811,271.482,416.964z"/>
32
+ <path class="st0" d="M509.848,222.878c-45.576,74.387-110.7,134.086-188.962,172.989c-6.477,3.214-12.956,6.292-19.549,9.136
33
+ c2.649,5.542,5.484,10.978,8.503,16.335c13.939,24.672,31.452,47.425,52.423,67.53c7.344-3.39,14.62-7.082,21.712-11.212
34
+ c41.133-23.756,73.013-57.049,94.793-95.533C500.477,343.639,512,299.973,512,255.752
35
+ C511.941,244.774,511.27,233.807,509.848,222.878z"/>
36
+ </g>
37
+ </svg>
@@ -0,0 +1,44 @@
1
+ import { loadImage } from './helpers/common';
2
+
3
+ import basketballImageData from './assets/balls/basketball.svg';
4
+ import baseballImageData from './assets/balls/baseball.svg';
5
+ import footballImageData from './assets/balls/football.svg';
6
+ import soccerImageData from './assets/balls/soccer.svg';
7
+ import hockeyImageData from './assets/balls/hockey.svg';
8
+ import volleyballImageData from './assets/balls/volleyball.svg';
9
+ import lacrosseImageData from './assets/balls/lacrosse.svg';
10
+
11
+ import { ImageConfigItem } from './models/PlayModel';
12
+ import {
13
+ SPORT_TYPE_BASEBALL,
14
+ SPORT_TYPE_SOFTBALL,
15
+ SPORT_TYPE_BASKETBALL,
16
+ SPORT_TYPE_FOOTBALL,
17
+ SPORT_TYPE_HOCKEY,
18
+ SPORT_TYPE_LACROSSE,
19
+ SPORT_TYPE_LACROSSE_BOX,
20
+ SPORT_TYPE_SOCCER,
21
+ SPORT_TYPE_VOLLEYBALL
22
+ } from './constants';
23
+
24
+ export default async function (): Promise<readonly ImageConfigItem[]> {
25
+ const basketballShape = await loadImage(basketballImageData);
26
+ const baseballShape = await loadImage(baseballImageData);
27
+ const footballShape = await loadImage(footballImageData);
28
+ const soccerShape = await loadImage(soccerImageData);
29
+ const hockeyShape = await loadImage(hockeyImageData);
30
+ const volleyballShape = await loadImage(volleyballImageData);
31
+ const lacrosseShape = await loadImage(lacrosseImageData);
32
+
33
+ return Object.freeze([
34
+ { key: SPORT_TYPE_BASKETBALL, image: basketballShape, dX: 0, dY: 0 },
35
+ { key: SPORT_TYPE_BASEBALL, image: baseballShape, dX: 0, dY: 0 },
36
+ { key: SPORT_TYPE_SOFTBALL, image: baseballShape, dX: 0, dY: 0 },
37
+ { key: SPORT_TYPE_FOOTBALL, image: footballShape, dX: 0, dY: 0 },
38
+ { key: SPORT_TYPE_SOCCER, image: soccerShape, dX: 0, dY: 0 },
39
+ { key: SPORT_TYPE_HOCKEY, image: hockeyShape, dX: 0, dY: 0 },
40
+ { key: SPORT_TYPE_VOLLEYBALL, image: volleyballShape, dX: 0, dY: 0 },
41
+ { key: SPORT_TYPE_LACROSSE, image: lacrosseShape, dX: 0, dY: 0 },
42
+ { key: SPORT_TYPE_LACROSSE_BOX, image: lacrosseShape, dX: 0, dY: 0 }
43
+ ]);
44
+ }
@@ -0,0 +1,47 @@
1
+ import { SPORT_TYPE_BASKETBALL } from '../constants';
2
+ import { SportType } from '../types';
3
+
4
+ type StaticDataWithBalls = {
5
+ balls?: ReadonlyArray<{ key: string; image: CanvasImageSource }>;
6
+ };
7
+
8
+ export type DrawBallObjectPlacement = 'center' | 'overlapBottomRight';
9
+
10
+ export type DrawBallObjectParams = {
11
+ sport: SportType;
12
+ ctx: CanvasRenderingContext2D;
13
+ staticData: StaticDataWithBalls | undefined;
14
+ center: { x: number; y: number };
15
+ puckRadius: number;
16
+ placement: DrawBallObjectPlacement;
17
+ };
18
+
19
+ /**
20
+ * Draw a "ball object" (currently basketball) using shared sizing rules.
21
+ * Future: swap selection logic based on sport type / config without changing call sites.
22
+ */
23
+ export function drawBallObject(params: DrawBallObjectParams): boolean {
24
+ const { sport, ctx, staticData, center, puckRadius, placement } = params;
25
+
26
+ const ball = staticData?.balls?.find(b => b.key === sport);
27
+ if (!ball) return false;
28
+
29
+ // Same sizing rule used elsewhere: ball is 25% of puck diameter => puckRadius * 0.5
30
+ const ballSize = puckRadius * 0.5;
31
+
32
+ let x = center.x - ballSize / 2;
33
+ let y = center.y - ballSize / 2;
34
+
35
+ if (placement === 'overlapBottomRight') {
36
+ // Overlap inside the puck near bottom-right
37
+ const offset = puckRadius * 0.65;
38
+ x = center.x + offset - ballSize / 2;
39
+ y = center.y + offset - ballSize / 2;
40
+ }
41
+
42
+ ctx.save();
43
+ ctx.drawImage(ball.image, x, y, ballSize, ballSize);
44
+ ctx.restore();
45
+
46
+ return true;
47
+ }
@@ -16,6 +16,7 @@ export default class LineLayer extends BaseLayer {
16
16
  this.ctx.lineWidth = this.lineWidth;
17
17
  this.playData.lines.forEach(line => {
18
18
  if (this.options.linesHiddenIds.indexOf(line.id) >= 0) return;
19
+
19
20
  const layerKey = `${_.capitalize(line.type)}LineLayer`;
20
21
  if (lineLayers[layerKey as keyof typeof lineLayers]) {
21
22
  new lineLayers[layerKey as keyof typeof lineLayers](this, line).apply();
@@ -1,5 +1,7 @@
1
1
  import BaseLayer from './base/BaseLayer';
2
2
  import PlayerModel from '../models/PlayerModel';
3
+ import { SPORT_TYPE_BASKETBALL } from '../constants';
4
+ import { drawBallObject } from '../helpers/draw';
3
5
 
4
6
  export default class PlayerLayer extends BaseLayer {
5
7
  get staticData() {
@@ -57,6 +59,25 @@ export default class PlayerLayer extends BaseLayer {
57
59
  }
58
60
  }
59
61
  }
62
+
63
+ if (player.possession && this.options.showBallMode) {
64
+ const playerBall = this.staticData.balls.find(ball => {
65
+ return ball.key === SPORT_TYPE_BASKETBALL;
66
+ });
67
+ if (playerBall) {
68
+ const radiusMultiplier = this.options.legacyPrintStyle ? 1.2 : 1;
69
+ const puckRadius = radiusMultiplier * this.radius;
70
+
71
+ drawBallObject({
72
+ sport: this.playData.sport,
73
+ ctx: this.ctx,
74
+ staticData: this.staticData,
75
+ center: { x, y },
76
+ puckRadius,
77
+ placement: 'overlapBottomRight'
78
+ });
79
+ }
80
+ }
60
81
  });
61
82
 
62
83
  this.ctx.restore();
@@ -77,7 +98,12 @@ export default class PlayerLayer extends BaseLayer {
77
98
  if (player.isDefender && !payerInPosition && isDefaultInputColor) {
78
99
  color = '#bb271b';
79
100
  }
80
- if (player.possession) color = '#ff8000';
101
+
102
+ const shouldHighlightPossession = !this.options.showBallMode || this.options.highlightPlayerPuck;
103
+
104
+ if (player.possession && shouldHighlightPossession) {
105
+ color = `rgba(255, 128, 0, ${alpha})`;
106
+ }
81
107
 
82
108
  if (this.options.legacyPrintStyle) {
83
109
  this.ctx.lineWidth = this.courtTypeConstants.LINE_WIDTH;
@@ -2,7 +2,7 @@ import InternalBaseLayer from '../../base/InternalBaseLayer';
2
2
  import { adjustedBezierCurveWithExclusionZones } from '../../../math/LineDrawingMath';
3
3
  import LineDrawOperationsTrait from '../../../traits/LineDrawOperationsTrait';
4
4
  import LineLayer from '../../LineLayer';
5
- import LineModel from '../../../models/LineModel';
5
+ import LineModel, { LinePartAdjusted } from '../../../models/LineModel';
6
6
  import { CourtPoint, LinePart } from '../../../types';
7
7
 
8
8
  export type MaskSettings = {
@@ -19,6 +19,13 @@ export default class InternalLineLayer extends InternalBaseLayer {
19
19
  protected angleBetweenLastTwoPoints(): number {
20
20
  return 0;
21
21
  }
22
+ maybeDrawBallAtStartPoint(params: {
23
+ linePart: Pick<LinePartAdjusted, 'animationSegment'>;
24
+ controlPoints: CourtPoint[];
25
+ alreadyDrawn: boolean;
26
+ }): boolean {
27
+ return false;
28
+ }
22
29
  // ==============================================================
23
30
 
24
31
  protected lineWidth: number;
@@ -62,7 +69,15 @@ export default class InternalLineLayer extends InternalBaseLayer {
62
69
  const g = lineForPlayerInPosition ? 0 : Math.ceil(green * 255);
63
70
  const b = lineForPlayerInPosition ? 255 : Math.ceil(blue * 255);
64
71
 
65
- const color = `rgba(${r}, ${g}, ${b}, ${alphaOverride || alpha})`;
72
+ const hidePassLikeLinesDuringPlayback =
73
+ !!this.options.showBallMode &&
74
+ !!this.options.animationGlobalProgress &&
75
+ this.options.showPassLinesDuringPlayback === false &&
76
+ this.line.isBallTransferLine;
77
+
78
+ const effectiveAlpha = hidePassLikeLinesDuringPlayback ? 0 : alphaOverride ?? alpha;
79
+
80
+ const color = `rgba(${r}, ${g}, ${b}, ${effectiveAlpha})`;
66
81
 
67
82
  this.ctx.fillStyle = color;
68
83
  this.ctx.strokeStyle = color;
@@ -19,20 +19,30 @@ export default class DribbleLineLayer extends ActionLineLayer {
19
19
  setLineOptions() {
20
20
  const lineParts = [...this.line.getLineParts()];
21
21
  const dribbleLineParts = this.convertLinePartsToDribble(lineParts);
22
+
22
23
  this.line.setLinePartsAdjusted([]);
24
+
23
25
  dribbleLineParts.forEach(lp => {
24
26
  const { controlPoints, lpIndex } = lp;
25
27
  const [firstPoint] = controlPoints;
26
- let alpha = lineParts[lpIndex!].alpha || this.line.color.alpha;
28
+
29
+ let animationSegment: LinePartAdjusted['animationSegment'] | undefined = undefined;
30
+
27
31
  if (this.options.animationGlobalProgress) {
28
32
  const [start, end] = this.line.animationKeyTimeChunks[lpIndex!];
33
+
29
34
  if (_.inRange(this.options.animationGlobalProgress, start, end)) {
30
35
  if (animationProgress(this.options.animationGlobalProgress, start, end) > firstPoint.time!) {
31
- alpha = 0.1;
36
+ animationSegment = 'processed';
37
+ } else {
38
+ animationSegment = 'active';
32
39
  }
40
+ } else if (this.options.animationGlobalProgress > end) {
41
+ animationSegment = 'processed';
33
42
  }
34
43
  }
35
- this.line.addLinePartAdjusted({ ...lp, controlPoints, alpha });
44
+
45
+ this.line.addLinePartAdjusted({ ...lp, controlPoints, animationSegment });
36
46
  });
37
47
  }
38
48
  }
@@ -32,11 +32,13 @@ export default class ShotLineLayer extends ActionLineLayer {
32
32
 
33
33
  this.ctx.lineJoin = 'round';
34
34
 
35
+ let isBallDrawn = false;
36
+
35
37
  this.getProcessedLinePaths().forEach(linePart => {
36
38
  this.ctx.save();
37
39
 
38
- if (linePart.alpha) {
39
- this.setColor(linePart.alpha); // setting color for each line path (animation)
40
+ if (linePart.animationSegment === 'processed') {
41
+ this.setColor(0.1); // processed segment (before animation progress)
40
42
  }
41
43
 
42
44
  this.ctx.beginPath();
@@ -78,6 +80,15 @@ export default class ShotLineLayer extends ActionLineLayer {
78
80
  this.ctx.stroke();
79
81
  });
80
82
 
83
+ // Draw the ball AFTER the stroke so it overlaps the line.
84
+ if (!isBallDrawn) {
85
+ isBallDrawn = this.maybeDrawBallAtStartPoint({
86
+ linePart,
87
+ controlPoints: cp,
88
+ alreadyDrawn: isBallDrawn
89
+ });
90
+ }
91
+
81
92
  this.ctx.restore();
82
93
  });
83
94
 
@@ -30,6 +30,25 @@ export default class AnimationModel {
30
30
  this.timeElapsedSaved = 0;
31
31
  }
32
32
 
33
+ /**
34
+ * Extend current options (do not replace object) – same semantics as PlayModel.setOptions().
35
+ * This is needed because AnimationModel keeps an internal cloned PlayModel instance.
36
+ */
37
+ setOptions(options: Partial<PlayModel['options']>) {
38
+ this.play.options = { ...this.play.options, ...options };
39
+ this.playBase.options = { ...this.playBase.options, ...options };
40
+
41
+ // Recreate frame so any layers/models that cached options are refreshed immediately.
42
+ this.animationFrame = new FrameModel(this.play, this.currentPlayPhase, this.globalProgress).setContext(this.ctx);
43
+
44
+ // If paused/stopped, force immediate visual update.
45
+ if (!this.running) {
46
+ this.animationFrame.setPhase(this.currentPlayPhase).setAnimationGlobalProgress(this.globalProgress).draw();
47
+ }
48
+
49
+ return this;
50
+ }
51
+
33
52
  get animationDuration() {
34
53
  return this.play.playData.animationDuration / this.play.options.speed;
35
54
  }
@@ -252,14 +252,26 @@ export default class FrameModel {
252
252
  if (line.type === 'DRIBBLE') {
253
253
  return linePartsAdjusted.push({ ...line.getLineParts()[index] });
254
254
  }
255
+
255
256
  const linePathSplitted = splitBezierCurveAtTVal(
256
257
  line.getLineParts()[index].controlPoints,
257
258
  this.animationProgress(start, end)
258
259
  ) as LinePartAdjusted['controlPoints'][];
259
- linePartsAdjusted.push({ controlPoints: linePathSplitted[0], alpha: 0.1 });
260
- linePartsAdjusted.push({ controlPoints: linePathSplitted[1], alpha: line.color.alpha });
260
+
261
+ linePartsAdjusted.push({
262
+ controlPoints: linePathSplitted[0],
263
+ animationSegment: 'processed'
264
+ });
265
+
266
+ linePartsAdjusted.push({
267
+ controlPoints: linePathSplitted[1],
268
+ animationSegment: 'active'
269
+ });
261
270
  } else if (this.animationGlobalProgress > end) {
262
- linePartsAdjusted.push({ ...line.getLineParts()[index], alpha: 0.1 });
271
+ linePartsAdjusted.push({
272
+ ...line.getLineParts()[index],
273
+ animationSegment: 'processed'
274
+ });
263
275
  } else {
264
276
  linePartsAdjusted.push({ ...line.getLineParts()[index] });
265
277
  }
@@ -9,7 +9,14 @@ export type LinePartAdjusted = {
9
9
  | [CourtPointAdjusted, CourtPointAdjusted]
10
10
  | [CourtPointAdjusted, CourtPointAdjusted, CourtPointAdjusted]
11
11
  | [CourtPointAdjusted, CourtPointAdjusted, CourtPointAdjusted, CourtPointAdjusted];
12
- alpha?: number;
12
+
13
+ /**
14
+ * Indicates how this segment should be rendered during animation.
15
+ * - 'processed': segment is before current animation progress (dimmed)
16
+ * - 'active': segment is the currently "drawn" / normal colored segment
17
+ */
18
+ animationSegment?: 'processed' | 'active';
19
+
13
20
  lpIndex?: number;
14
21
  };
15
22
 
@@ -97,6 +104,10 @@ export default class LineModel extends Model<LineData, LineAdjusted> {
97
104
  return this._getAttr('type');
98
105
  }
99
106
 
107
+ get isBallTransferLine() {
108
+ return ['PASS', 'HANDOFF', 'SHOT'].includes(this.type);
109
+ }
110
+
100
111
  get phase() {
101
112
  return this._getAttr('phase');
102
113
  }
@@ -22,6 +22,10 @@ export function useDefaults(options?: Partial<PlayModelOptions>): PlayModelOptio
22
22
  flipPlayerLabels: false,
23
23
  legacyPrintStyle: false,
24
24
  playerTokenScale: 1,
25
+ showBallMode: false,
26
+ // showBallMode sub-options (defaults match current behavior)
27
+ highlightPlayerPuck: true,
28
+ showPassLinesDuringPlayback: true,
25
29
  // TODO: refactor NBA court type constants below
26
30
  showHalfCourtCircle: true,
27
31
  playersMap: [],
@@ -1,6 +1,7 @@
1
1
  import _ from 'lodash';
2
2
  import playerHatsConfig from '../playerHatsConfig';
3
3
  import shapesConfig from '../shapesConfig';
4
+ import ballConfig from '../ballConfig';
4
5
  import { useDefaults } from './Play/Options';
5
6
 
6
7
  import {
@@ -44,6 +45,7 @@ export type PlayStaticData = {
44
45
  watermark: typeof PlayModel.watermark;
45
46
  playerHats: readonly ImageConfigItem[];
46
47
  shapes: readonly ImageConfigItem[];
48
+ balls: readonly ImageConfigItem[];
47
49
  playerHeadshots: typeof PlayModel.playerHeadshots;
48
50
  teamPlayers: typeof PlayModel.teamPlayers;
49
51
  };
@@ -88,6 +90,10 @@ export type PlayModelOptions = {
88
90
  playersMap: PlayersMapItem[];
89
91
  labelsOverrideType: 'Initials' | 'Jersey number' | 'Headshot' | null;
90
92
  inDrawingState: boolean;
93
+ showBallMode: boolean;
94
+ // sub-options for showBallMode
95
+ highlightPlayerPuck: boolean;
96
+ showPassLinesDuringPlayback: boolean;
91
97
  };
92
98
 
93
99
  export default class PlayModel {
@@ -98,6 +104,7 @@ export default class PlayModel {
98
104
  public static playerHeadshots: PlayerHeadshotItem[] = [];
99
105
  public static playerHats: readonly ImageConfigItem[];
100
106
  public static shapes: readonly ImageConfigItem[];
107
+ public static balls: readonly ImageConfigItem[];
101
108
  public static watermark: { LuceoSports: HTMLImageElement; TeamLogo: HTMLImageElement | null };
102
109
  public static backgroundOptions: Record<SportType, HTMLImageElement> & {
103
110
  Hardwood: HTMLImageElement;
@@ -113,6 +120,7 @@ export default class PlayModel {
113
120
  static async init({ teamLogoPath = '' } = {}) {
114
121
  PlayModel.playerHats = await playerHatsConfig();
115
122
  PlayModel.shapes = await shapesConfig();
123
+ PlayModel.balls = await ballConfig();
116
124
 
117
125
  const hardwoodImage = await loadImage(hardwoodImageData);
118
126
  const grassImage = await loadImage(grassImageData);
@@ -190,6 +198,7 @@ export default class PlayModel {
190
198
  watermark: PlayModel.watermark,
191
199
  playerHats: PlayModel.playerHats,
192
200
  shapes: PlayModel.shapes,
201
+ balls: PlayModel.balls,
193
202
  playerHeadshots: PlayModel.playerHeadshots,
194
203
  teamPlayers: PlayModel.teamPlayers
195
204
  };