File size: 11,784 Bytes
2964111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
<template>

  <div class="football-field">

    <svg viewBox="-7 -2 119 72" preserveAspectRatio="xMidYMid meet" @click="handleBackgroundClick">

      <g>

        <!-- Base field -->

        <rect x="0" y="0" width="105" height="68" fill="none" stroke="#333" stroke-width="0.3"/>

        

        <!-- Clickable lines -->

        <g class="lines">

          <line v-for="(line, name) in lineCoordinates" 

                :key="name"

                class="field-line"

                :stroke="getLineColor(name)"

                :x1="line.x1"

                :y1="line.y1"

                :x2="line.x2"

                :y2="line.y2"

                @click="selectLine(name)" />

        </g>



        <!-- Key points -->

        <circle v-for="(point, index) in keypoints" 

                :key="index"

                :cx="point[0]" 

                :cy="point[1]" 

                r="2"

                :fill="getPointColor(index)"

                class="keypoint"

                @click="selectPoint(index)" />

        

        <!-- Center circle -->

        <circle 

          cx="52.5" 

          cy="34" 

          r="9.15" 

          fill="none" 

          :stroke="getLineColor('Circle central')"

          class="field-line"

          @click="selectLine('Circle central')" />

          

        

        <!-- Penalty area arcs -->

        <path 

          v-for="arc in circle_left_right" 

          :key="'Circle ' + arc.side"

          :d="getPenaltyArc(arc)"

          fill="none"

          :stroke="getLineColor('Circle ' + arc.side)"

          class="field-line"

          @click="selectLine('Circle ' + arc.side)" />

      </g>

    </svg>

    

    <!-- Selected line or point info -->

    <div v-if="selectedLine && LINES[selectedLine]" class="info-overlay">

      {{ LINES[selectedLine].name }}

    </div>

    <div v-if="selectedPointIndex !== null" class="info-overlay">

      {{ POINTS[selectedPointIndex].name }}

    </div>

  </div>

</template>



<script>

// Définition exacte des classes de lignes comme dans SoccerNet

const LINES = {

  'Big rect. left bottom': { name: 'Big rect. left bottom', description: 'Left penalty area - bottom line' },

  'Big rect. left main': { name: 'Big rect. left main', description: 'Left penalty area - parallel line' },

  'Big rect. left top': { name: 'Big rect. left top', description: 'Left penalty area - top line' },

  'Big rect. right bottom': { name: 'Big rect. right bottom', description: 'Right penalty area - bottom line' },

  'Big rect. right main': { name: 'Big rect. right main', description: 'Right penalty area - parallel line' },

  'Big rect. right top': { name: 'Big rect. right top', description: 'Right penalty area - top line' },

  'Circle central': { name: 'Center circle', description: 'Center circle' },

  'Circle left': { name: 'Left circle', description: 'Left arc' },

  'Circle right': { name: 'Right circle', description: 'Right arc' },

  'Goal left crossbar': { name: 'Goal left crossbar', description: 'Left goal crossbar' },

  'Goal left post left': { name: 'Goal left post left', description: 'Left goal - left post' },

  'Goal left post right': { name: 'Goal left post right', description: 'Left goal - right post' },

  'Goal right crossbar': { name: 'Goal right crossbar', description: 'Right goal crossbar' },

  'Goal right post left': { name: 'Goal right post left', description: 'Right goal - left post' },

  'Goal right post right': { name: 'Goal right post right', description: 'Right goal - right post' },

  'Goal unknown': { name: 'Goal unknown', description: 'Unidentified goal' },

  'Line unknown': { name: 'Line unknown', description: 'Unidentified line' },

  'Middle line': { name: 'Middle line', description: 'Center line' },

  'Side line bottom': { name: 'Side line bottom', description: 'Bottom goal line' },

  'Side line left': { name: 'Side line left', description: 'Left touch line' },

  'Side line right': { name: 'Side line right', description: 'Right touch line' },

  'Side line top': { name: 'Side line top', description: 'Top goal line' },

  'Small rect. left bottom': { name: 'Small rect. left bottom', description: 'Left goal area - bottom line' },

  'Small rect. left main': { name: 'Small rect. left main', description: 'Left goal area - parallel line' },

  'Small rect. left top': { name: 'Small rect. left top', description: 'Left goal area - top line' },

  'Small rect. right bottom': { name: 'Small rect. right bottom', description: 'Right goal area - bottom line' },

  'Small rect. right main': { name: 'Small rect. right main', description: 'Right goal area - parallel line' },

  'Small rect. right top': { name: 'Small rect. right top', description: 'Right goal area - top line' },

  center_circle: {

    name: "Center circle",

    type: "circle",

    color: "#00FF15"

  },

  circle_left: {

    name: "Left circle",

    type: "arc",

    color: "#00FF15"

  },

  circle_right: {

    name: "Right circle",

    type: "arc",

    color: "#00FF15"

  }

};



// Définition des dimensions standard d'un terrain de football

const FIELD_DIMENSIONS = {

  PITCH_LENGTH: 105,

  PITCH_WIDTH: 68,

  GOAL_LINE_TO_PENALTY_MARK: 11.0,

  PENALTY_AREA_WIDTH: 40.32,

  PENALTY_AREA_LENGTH: 16.5,

  GOAL_AREA_WIDTH: 18.32,

  GOAL_AREA_LENGTH: 5.5,

  CENTER_CIRCLE_RADIUS: 9.15,

  GOAL_HEIGHT: 2.44,

  GOAL_LENGTH: 7.32

};



const POINTS = {

  0: { name: "Center point" },

  1: { name: "Left penalty point" },

  2: { name: "Right penalty point" }

};



export default {

  name: 'FootballField',

  props: {

    positionedLines: {

      type: Object,

      default: () => ({})

    },

    positionedPoints: {

      type: Object,

      default: () => ({})

    }

  },

  data() {

    return {

      selectedPointIndex: null,

      selectedLine: null,

      LINES,

      FIELD_DIMENSIONS,

      POINTS,

      keypoints: [

        [52.5, 34],    // Center point

        [11, 34],      // Left penalty point

        [94, 34],      // Right penalty point

      ],

      lineCoordinates: {

        'Side line top': { x1: 0, y1: 0, x2: 105, y2: 0 },

        'Side line bottom': { x1: 0, y1: 68, x2: 105, y2: 68 },

        'Side line left': { x1: 0, y1: 0, x2: 0, y2: 68 },

        'Side line right': { x1: 105, y1: 0, x2: 105, y2: 68 },

        'Middle line': { x1: 52.5, y1: 0, x2: 52.5, y2: 68 },

        

        // Penalty areas

        'Big rect. left bottom': { x1: 0, y1: 54.16, x2: 16.5, y2: 54.16 },

        'Big rect. left main': { x1: 16.5, y1: 13.84, x2: 16.5, y2: 54.16 },

        'Big rect. left top': { x1: 0, y1: 13.84, x2: 16.5, y2: 13.84 },

        'Big rect. right bottom': { x1: 88.5, y1: 54.16, x2: 105, y2: 54.16 },

        'Big rect. right main': { x1: 88.5, y1: 13.84, x2: 88.5, y2: 54.16 },

        'Big rect. right top': { x1: 88.5, y1: 13.84, x2: 105, y2: 13.84 },

        

        // Goal areas

        'Small rect. left bottom': { x1: 0, y1: 43.16, x2: 5.5, y2: 43.16 },

        'Small rect. left main': { x1: 5.5, y1: 24.84, x2: 5.5, y2: 43.16 },

        'Small rect. left top': { x1: 0, y1: 24.84, x2: 5.5, y2: 24.84 },

        'Small rect. right bottom': { x1: 99.5, y1: 43.16, x2: 105, y2: 43.16 },

        'Small rect. right main': { x1: 99.5, y1: 24.84, x2: 99.5, y2: 43.16 },

        'Small rect. right top': { x1: 99.5, y1: 24.84, x2: 105, y2: 24.84 },

        

        // Goals

        'Goal left post left': { x1: -5, y1: 37.66, x2: 0, y2: 37.66 },

        'Goal left crossbar': { x1: -5, y1: 30.34, x2: -5, y2: 37.66 },

        'Goal left post right': { x1: -5, y1: 30.34, x2: 0, y2: 30.34 },

        'Goal right post left': { x1: 105, y1: 30.34, x2: 110, y2: 30.34 },

        'Goal right crossbar': { x1: 110, y1: 30.34, x2: 110, y2: 37.66 },

        'Goal right post right': { x1: 105, y1: 37.66, x2: 110, y2: 37.66 },

      },

      lastSelected: null, // 'point' or 'line'

      circle_left_right: [

        { x: 11, y: 34, side: 'left' },

        { x: 94, y: 34, side: 'right' }

      ]

    }

  },

  methods: {

    handleBackgroundClick(event) {

      // Vérifie si le clic vient directement du SVG (pas d'un enfant)

      if (event.target.tagName === 'svg') {

        if (this.selectedPointIndex !== null) {

          this.selectedPointIndex = null;

          this.$emit('point-selected', null);

        }

        if (this.selectedLine) {

          this.selectedLine = null;

          this.$emit('line-selected', null);

        }

        this.lastSelected = null;

      }

    },

    selectPoint(index, event) {

      if (event) {

        event.stopPropagation();

      }

      if (this.selectedLine) this.selectedLine = null;

      this.selectedPointIndex = index;

      this.lastSelected = 'point';

      this.$emit('point-selected', {

        index,

        coordinates: this.keypoints[index],

        name: this.POINTS[index].name

      });

    },

    selectLine(lineName, event) {

      if (event) {

        event.stopPropagation();

      }

      if (this.selectedPointIndex !== null) this.selectedPointIndex = null;

      this.selectedLine = lineName;

      this.lastSelected = 'line';

      if (this.LINES[lineName]) {

        this.$emit('line-selected', {

          id: lineName,

          name: this.LINES[lineName].name,

          description: this.LINES[lineName].description

        });

      }

    },

    getPointColor(index) {

      if (this.selectedPointIndex === index && this.lastSelected === 'point') {

        return index in this.positionedPoints ? '#FFFF00' : 'red';

      }

      return index in this.positionedPoints ? '#00FF15' : 'white';

    },

    getLineColor(lineName) {

      if (this.selectedLine === lineName && this.lastSelected === 'line') {

        return this.positionedLines[lineName] ? '#FFFF00' : 'red';

      }

      return this.positionedLines[lineName] ? '#00FF15' : 'white';

    },  

    

    getPenaltyArc(arc) {

      const radius = 9.15;

      const startAngle = arc.side === 'left' ? -53 : -127;

      const endAngle = arc.side === 'left' ? 53 : 127;

      

      const start = {

        x: arc.x + radius * Math.cos(startAngle * Math.PI / 180),

        y: arc.y + radius * Math.sin(startAngle * Math.PI / 180)

      };

      

      const end = {

        x: arc.x + radius * Math.cos(endAngle * Math.PI / 180),

        y: arc.y + radius * Math.sin(endAngle * Math.PI / 180)

      };

      

      const largeArc = 0;

      const sweep = arc.side === 'left' ? 1 : 0;

      

      return `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArc} ${sweep} ${end.x} ${end.y}`;

    }

  }

}

</script>



<style scoped>

.football-field {

  position: relative;

  width: 100%;

  height: 100%;

}



.field-line {

  stroke-width: 0.8;

  cursor: pointer;

}



/* Specific style for goal lines */

.field-line[class*="Goal"] {

  stroke-width: 1;  /* Thicker line for goals */

}



.field-line:hover {

  stroke-width: 1.2;  /* Even thicker on hover */

  opacity: 0.8;

}



.line-info {

  position: absolute;

  bottom: 10px;

  left: 50%;

  transform: translateX(-50%);

  background-color: rgba(0, 0, 0, 0.7);

  color: white;

  padding: 5px 10px;

  border-radius: 4px;

  font-size: 0.9rem;

}



.keypoint {

  filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.5)); /* Glow effect for points */

  cursor: pointer;

}



.keypoint:hover {

  filter: drop-shadow(0 0 4px rgba(255, 0, 0, 0.8));

}



.info-overlay {

  position: absolute;

  bottom: 10px;

  left: 50%;

  transform: translateX(-50%);

  background-color: rgba(0, 0, 0, 0.7);

  color: white;

  padding: 5px 10px;

  border-radius: 4px;

  font-size: 0.9rem;

}

</style>