bullpen pitch progress

This commit is contained in:
Sascha Kühl 2025-04-08 16:05:30 +02:00
parent ea885ae56f
commit da83b0b123
8 changed files with 161 additions and 113 deletions

View File

@ -23,6 +23,10 @@ const routes: Array<RouteRecordRaw> = [
path: '/pitchers', path: '/pitchers',
name: 'Pitchers', name: 'Pitchers',
component: PitcherList component: PitcherList
}, {
path: '/bullpen',
name: 'PreparePitch',
component: PreparePitch
}, { }, {
path: '/prepare', path: '/prepare',
name: 'PreparePitch', name: 'PreparePitch',

View File

@ -1,89 +1,32 @@
import Pitch from "@/types/Pitch"; import Pitch from "@/types/Pitch";
import User from "@/types/User"; import User from "@/types/User";
import PitchType from "@/types/PitchType"; import Bullpen from '@/types/Bullpen';
import PitchTypeService from '@/services/PitchTypeService';
// import api from './Api'; // import api from './Api';
export class BullpenSessionService { export class BullpenSessionService {
private static nullPitcher: User = { public create(user: User): Bullpen {
id: 0, return {
firstName: "", id: null,
lastName: "", startedAt: new Date(),
gender: "male", finishedAt: null,
handedness: "right", pitcherId: user.id,
height: 0.0, pitches: []
weight: 0.0, }
dateOfBirth: new Date(0),
createdAt: new Date(0),
updatedAt: new Date(0),
};
private static nullPitchType: PitchType = {
id: 0,
name: "",
abbreviation: ""
} }
public constructor() { public finish(): void {}
this.currentPitch = this.createPitch();
}
public saveBullpen(): void {} public createPitch(): Pitch {
public startSession(pitcher: User): void {
this.pitcher = pitcher;
this.pitches = [];
this.currentPitch = this.createPitch();
}
public clear(): void {
this.pitcher = BullpenSessionService.nullPitcher;
this.pitches = [];
}
public finishSession(): void {
// Todo: save to backend
}
public cancelSession(): void {
this.pitches = [];
this.pitcher = BullpenSessionService.nullPitcher;
}
public addBullpenPitch( bullpenPitch: Pitch ): void {
this.pitches.push( bullpenPitch );
}
public getBullpenPitches(): Pitch[] {
return this.pitches;
}
public currentBullpenPitch(): Pitch {
return this.currentPitch;
}
public nextPitch(): void {
this.pitches.push(this.currentBullpenPitch());
this.currentPitch = this.createPitch();
}
public getPitcher(): User {
return this.pitcher;
}
public isSessionStarted(): boolean {
return this.pitcher.id === 0;
}
private createPitch(): Pitch {
return { return {
id: 0, id: 0,
date: new Date(), date: new Date(),
pitchType: BullpenSessionService.nullPitchType, pitchType: PitchTypeService.getLocalPitchTypes()[0],
plannedPitchArea: 0, plannedPitchArea: 0,
realPitchArea: 0, realPitchArea: 0,
realPitchSubArea: 0 realPitchSubArea: 0
} }
} }
private pitches: Pitch[] = [];
private pitcher: User = BullpenSessionService.nullPitcher;
private currentPitch: Pitch;
} }
export default new BullpenSessionService(); export default new BullpenSessionService();

View File

@ -84,6 +84,9 @@ const auth: Module<AuthState, RootState> = {
registerFailure(state) { registerFailure(state) {
state.isAuthenticated = false; state.isAuthenticated = false;
} }
},
getters: {
getUser: (state) => state.user
} }
}; };

29
app/src/store/bullpen.ts Normal file
View File

@ -0,0 +1,29 @@
import { Module } from 'vuex';
import { RootState } from './index';
import Bullpen from '@/types/Bullpen';
import User from '@/types/User';
import BullpenSessionService from '@/services/BullpenSessionService';
import Pitch from '@/types/Pitch';
export interface BullpenState {
bullpen: Bullpen | null;
}
const bullpen: Module<BullpenState, RootState> = {
namespaced: true,
state: { bullpen: null },
mutations: {
start(state, user: User) {
state.bullpen = BullpenSessionService.create(user);
},
addPitch(state, pitch: Pitch) {
state.bullpen?.pitches.push({ ...pitch });
},
finish(state) {
state.bullpen = null;
}
}
};
export default bullpen;

View File

@ -1,11 +1,13 @@
import { InjectionKey } from 'vue' import { InjectionKey } from 'vue'
import { createStore, Store } from 'vuex'; import { createStore, Store } from 'vuex';
import auth, {AuthState} from './auth'; import auth, {AuthState} from './auth';
import bullpen, {BullpenState} from './bullpen';
import pitchTypes, {PitchTypeState} from './pitchType'; import pitchTypes, {PitchTypeState} from './pitchType';
// Root state type // Root state type
export interface RootState { export interface RootState {
auth: AuthState; auth: AuthState;
bullpen: BullpenState;
pitchTypes: PitchTypeState; pitchTypes: PitchTypeState;
} }
@ -14,6 +16,7 @@ export const key: InjectionKey<Store<RootState>> = Symbol()
const store = createStore<RootState>({ const store = createStore<RootState>({
modules: { modules: {
auth, auth,
bullpen,
pitchTypes pitchTypes
} }
}); });

View File

@ -1,9 +1,9 @@
import Pitch from "@/types/Pitch"; import Pitch from "@/types/Pitch";
export default interface Bullpen { export default interface Bullpen {
id: number, id: number | null,
startedAt: Date, startedAt: Date,
finishedAt: Date, finishedAt: Date | null,
pitcherId: number, pitcherId: number,
pitches: Pitch[] pitches: Pitch[]
} }

View File

@ -0,0 +1,4 @@
export enum BullpenStep {
Prepare = 1,
Finish = 2
}

View File

@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import {onMounted, ref} from "vue"; import {computed, onMounted, ref} from "vue";
import PitchTypeService from "@/services/PitchTypeService";
import { import {
IonContent, IonContent,
IonHeader, IonHeader,
@ -13,39 +12,73 @@ import {
IonCard, IonCard,
IonCardContent, IonCardContent,
IonCardHeader, IonCardHeader,
IonCardTitle, IonCardTitle, IonRow, IonGrid, IonCol,
} from "@ionic/vue"; } from "@ionic/vue";
import {BullpenStep} from "@/types/BullpenStep";
import PitchType from "@/types/PitchType"; import PitchType from "@/types/PitchType";
import Pitch from "@/types/Pitch";
import BullpenSessionService from "@/services/BullpenSessionService"; import BullpenSessionService from "@/services/BullpenSessionService";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
import {useStore} from 'vuex';
const router = useRouter(); const router = useRouter();
const pitchTypes = ref<PitchType[]>([]); const store = useStore();
const bps = ref(BullpenSessionService);
const pitch = ref<Pitch>(BullpenSessionService.createPitch());
const currentStep = ref<BullpenStep>(BullpenStep.Prepare);
const pitcher = computed(() => store.state.auth.user);
const pitchTypes = computed(() => store.state.pitchTypes.pitchTypes);
onMounted(async () => { onMounted(async () => {
pitchTypes.value = PitchTypeService.getLocalPitchTypes(); store.commit("bullpen/start", pitcher);
bps.value.currentBullpenPitch().pitchType = pitchTypes.value[0];
}); });
const setPitchType = (pitchType: PitchType) => { const setPitchType = (pitchType: PitchType) => {
bps.value.currentBullpenPitch().pitchType = pitchType; pitch.value.pitchType = pitchType;
} }
const gotoFinalizePitch = () => { const gotoFinalizePitch = () => {
router.push({ name: 'FinalizePitch' }); currentStep.value = BullpenStep.Finish;
} }
const finalizeAndNextPitch = () => {
store.commit("bullpen/addPitch", pitch.value);
currentStep.value = BullpenStep.Prepare;
}
const finalizeAndEndBullpen = () => {
store.commit("bullpen/addPitch", pitch.value);
router.push({ name: 'BullpenStats' });
}
const setPlannedPitchArea = (hitArea: number) => { const setPlannedPitchArea = (hitArea: number) => {
bps.value.currentBullpenPitch().plannedPitchArea = hitArea; if (currentStep.value === BullpenStep.Prepare) {
pitch.value.plannedPitchArea = hitArea;
} else {
pitch.value.realPitchArea = hitArea;
}
}; };
const pitchAreaCssClasses = (area: number, name: string) => {
const classes = [];
if (pitch.value.plannedPitchArea === area) {
classes.push(name + '-aim-selected');
}
if (pitch.value.realPitchArea === area) {
classes.push(name + '-hit-selected');
}
return classes;
}
</script> </script>
<template> <template>
<ion-page> <ion-page>
<ion-header :translucent="true"> <ion-header :translucent="true">
<ion-toolbar> <ion-toolbar>
<ion-title>Prepare Pitch for {{ bps.getPitcher().firstName }} {{ bps.getPitcher().lastName }}</ion-title> <ion-title>Prepare Pitch for {{ pitcher.firstName }} {{ pitcher.lastName }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content> <ion-content>
@ -54,7 +87,7 @@ const setPlannedPitchArea = (hitArea: number) => {
<ion-card-title>Select Pitch type</ion-card-title> <ion-card-title>Select Pitch type</ion-card-title>
</ion-card-header> </ion-card-header>
<ion-card-content> <ion-card-content>
<ion-button v-for="(pitchType, index) in pitchTypes" :key="index" :color="pitchType.id !== bps.currentBullpenPitch().pitchType.id ? 'primary' : 'warning'" @click="setPitchType(pitchType)"> <ion-button v-for="(pitchType, index) in pitchTypes" :key="index" :color="pitchType.id !== pitch.pitchType.id ? 'primary' : 'warning'" @click="setPitchType(pitchType)">
<ion-label>{{pitchType.abbreviation}}</ion-label> <ion-label>{{pitchType.abbreviation}}</ion-label>
</ion-button> </ion-button>
</ion-card-content> </ion-card-content>
@ -65,14 +98,14 @@ const setPlannedPitchArea = (hitArea: number) => {
</ion-card-header> </ion-card-header>
<ion-card-content> <ion-card-content>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 360"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 360">
<polygon :class="{'wasted-selected': bps.currentBullpenPitch().plannedPitchArea === 21}" class="wasted" points="0,0 105,0 105,60 60,60 60,105 0,105" @click="setPlannedPitchArea(21)" /> <polygon :class="pitchAreaCssClasses(21, 'wasted')" class="wasted" points="0,0 105,0 105,60 60,60 60,105 0,105" @click="setPlannedPitchArea(21)" />
<rect :class="{'wasted-selected': bps.currentBullpenPitch().plannedPitchArea === 22}" class="wasted" x="105" y="0" width="150" height="60" @click="setPlannedPitchArea(22)" /> <rect :class="pitchAreaCssClasses(22, 'wasted')" class="wasted" x="105" y="0" width="150" height="60" @click="setPlannedPitchArea(22)" />
<polygon :class="{'wasted-selected': bps.currentBullpenPitch().plannedPitchArea === 23}" class="wasted" points="255,0 360,0 360,105 300,105 300,60, 255,60" @click="setPlannedPitchArea(23)" /> <polygon :class="pitchAreaCssClasses(23, 'wasted')" class="wasted" points="255,0 360,0 360,105 300,105 300,60, 255,60" @click="setPlannedPitchArea(23)" />
<rect :class="{'wasted-selected': bps.currentBullpenPitch().plannedPitchArea === 24}" class="wasted" x="0" y="105" width="60" height="150" @click="setPlannedPitchArea(24)" /> <rect :class="pitchAreaCssClasses(24, 'wasted')" class="wasted" x="0" y="105" width="60" height="150" @click="setPlannedPitchArea(24)" />
<rect :class="{'wasted-selected': bps.currentBullpenPitch().plannedPitchArea === 25}" class="wasted" x="300" y="105" width="60" height="150" @click="setPlannedPitchArea(25)" /> <rect :class="pitchAreaCssClasses(25, 'wasted')" class="wasted" x="300" y="105" width="60" height="150" @click="setPlannedPitchArea(25)" />
<polygon :class="{'wasted-selected': bps.currentBullpenPitch().plannedPitchArea === 26}" class="wasted" points="0,255 60,255 60,300 105,300 105,360 0,360" @click="setPlannedPitchArea(26)" /> <polygon :class="pitchAreaCssClasses(26, 'wasted')" class="wasted" points="0,255 60,255 60,300 105,300 105,360 0,360" @click="setPlannedPitchArea(26)" />
<rect :class="{'wasted-selected': bps.currentBullpenPitch().plannedPitchArea === 27}" class="wasted" x="105" y="300" width="150" height="60" @click="setPlannedPitchArea(27)" /> <rect :class="pitchAreaCssClasses(27, 'wasted')" class="wasted" x="105" y="300" width="150" height="60" @click="setPlannedPitchArea(27)" />
<polygon :class="{'wasted-selected': bps.currentBullpenPitch().plannedPitchArea === 28}" class="wasted" points="255,300 300,300 300,255 360,255 360,360 255,360" @click="setPlannedPitchArea(28)" /> <polygon :class="pitchAreaCssClasses(28, 'wasted')" class="wasted" points="255,300 300,300 300,255 360,255 360,360 255,360" @click="setPlannedPitchArea(28)" />
<text x="30" y="40" class="number" @click="setPlannedPitchArea(21)" >21</text> <text x="30" y="40" class="number" @click="setPlannedPitchArea(21)" >21</text>
<text x="180" y="40" class="number" @click="setPlannedPitchArea(22)" >22</text> <text x="180" y="40" class="number" @click="setPlannedPitchArea(22)" >22</text>
@ -83,14 +116,14 @@ const setPlannedPitchArea = (hitArea: number) => {
<text x="180" y="330" class="number" @click="setPlannedPitchArea(27)" >27</text> <text x="180" y="330" class="number" @click="setPlannedPitchArea(27)" >27</text>
<text x="330" y="330" class="number" @click="setPlannedPitchArea(28)" >28</text> <text x="330" y="330" class="number" @click="setPlannedPitchArea(28)" >28</text>
<polygon :class="{'edge-selected': bps.currentBullpenPitch().plannedPitchArea === 11}" class="edge" points="60,60 155,60 155,105 105,105 105,155, 60,155" @click="setPlannedPitchArea(11)" /> <polygon :class="pitchAreaCssClasses(11, 'edge')" class="edge" points="60,60 155,60 155,105 105,105 105,155, 60,155" @click="setPlannedPitchArea(11)" />
<rect :class="{'edge-selected': bps.currentBullpenPitch().plannedPitchArea === 12}" class="edge" x="155" y="60" width="50" height="45" @click="setPlannedPitchArea(12)" /> <rect :class="pitchAreaCssClasses(12, 'edge')" class="edge" x="155" y="60" width="50" height="45" @click="setPlannedPitchArea(12)" />
<polygon :class="{'edge-selected': bps.currentBullpenPitch().plannedPitchArea === 13}" class="edge" points="205,60 300,60 300,155 255,155 255,105, 205,105" @click="setPlannedPitchArea(13)" /> <polygon :class="pitchAreaCssClasses(13, 'edge')" class="edge" points="205,60 300,60 300,155 255,155 255,105, 205,105" @click="setPlannedPitchArea(13)" />
<rect :class="{'edge-selected': bps.currentBullpenPitch().plannedPitchArea === 14}" class="edge" x="60" y="155" width="45" height="50" @click="setPlannedPitchArea(14)" /> <rect :class="pitchAreaCssClasses(14, 'edge')" class="edge" x="60" y="155" width="45" height="50" @click="setPlannedPitchArea(14)" />
<rect :class="{'edge-selected': bps.currentBullpenPitch().plannedPitchArea === 15}" class="edge" x="255" y="155" width="45" height="50" @click="setPlannedPitchArea(15)" /> <rect :class="pitchAreaCssClasses(15, 'edge')" class="edge" x="255" y="155" width="45" height="50" @click="setPlannedPitchArea(15)" />
<polygon :class="{'edge-selected': bps.currentBullpenPitch().plannedPitchArea === 16}" class="edge" points="60,205 105,205 105,255 155,255 155,300, 60,300" @click="setPlannedPitchArea(16)" /> <polygon :class="pitchAreaCssClasses(16, 'edge')" class="edge" points="60,205 105,205 105,255 155,255 155,300, 60,300" @click="setPlannedPitchArea(16)" />
<rect :class="{'edge-selected': bps.currentBullpenPitch().plannedPitchArea === 17}" class="edge" x="155" y="255" width="50" height="45" @click="setPlannedPitchArea(17)" /> <rect :class="pitchAreaCssClasses(17, 'edge')" class="edge" x="155" y="255" width="50" height="45" @click="setPlannedPitchArea(17)" />
<polygon :class="{'edge-selected': bps.currentBullpenPitch().plannedPitchArea === 18}" class="edge" points="205,255 255,255 255,205 300,205 300,300 205,300" @click="setPlannedPitchArea(18)" /> <polygon :class="pitchAreaCssClasses(18, 'edge')" class="edge" points="205,255 255,255 255,205 300,205 300,300 205,300" @click="setPlannedPitchArea(18)" />
<text x="80" y="90" class="number" @click="setPlannedPitchArea(11)">11</text> <text x="80" y="90" class="number" @click="setPlannedPitchArea(11)">11</text>
<text x="180" y="90" class="number" @click="setPlannedPitchArea(12)">12</text> <text x="180" y="90" class="number" @click="setPlannedPitchArea(12)">12</text>
@ -101,15 +134,15 @@ const setPlannedPitchArea = (hitArea: number) => {
<text x="180" y="285" class="number" @click="setPlannedPitchArea(17)">17</text> <text x="180" y="285" class="number" @click="setPlannedPitchArea(17)">17</text>
<text x="275" y="285" class="number" @click="setPlannedPitchArea(18)">18</text> <text x="275" y="285" class="number" @click="setPlannedPitchArea(18)">18</text>
<rect :class="{'inside-selected': bps.currentBullpenPitch().plannedPitchArea === 1}" class="inside" x="105" y="105" width="50" height="50" @click="setPlannedPitchArea(1)"/> <rect :class="pitchAreaCssClasses(1, 'inside')" class="inside" x="105" y="105" width="50" height="50" @click="setPlannedPitchArea(1)"/>
<rect :class="{'inside-selected': bps.currentBullpenPitch().plannedPitchArea === 2}" class="inside" x="155" y="105" width="50" height="50" @click="setPlannedPitchArea(2)"/> <rect :class="pitchAreaCssClasses(2, 'inside')" class="inside" x="155" y="105" width="50" height="50" @click="setPlannedPitchArea(2)"/>
<rect :class="{'inside-selected': bps.currentBullpenPitch().plannedPitchArea === 3}" class="inside" x="205" y="105" width="50" height="50" @click="setPlannedPitchArea(3)"/> <rect :class="pitchAreaCssClasses(3, 'inside')" class="inside" x="205" y="105" width="50" height="50" @click="setPlannedPitchArea(3)"/>
<rect :class="{'inside-selected': bps.currentBullpenPitch().plannedPitchArea === 4}" class="inside" x="105" y="155" width="50" height="50" @click="setPlannedPitchArea(4)"/> <rect :class="pitchAreaCssClasses(4, 'inside')" class="inside" x="105" y="155" width="50" height="50" @click="setPlannedPitchArea(4)"/>
<rect :class="{'inside-selected': bps.currentBullpenPitch().plannedPitchArea === 5}" class="inside" x="155" y="155" width="50" height="50" @click="setPlannedPitchArea(5)"/> <rect :class="pitchAreaCssClasses(5, 'inside')" class="inside" x="155" y="155" width="50" height="50" @click="setPlannedPitchArea(5)"/>
<rect :class="{'inside-selected': bps.currentBullpenPitch().plannedPitchArea === 6}" class="inside" x="205" y="155" width="50" height="50" @click="setPlannedPitchArea(6)"/> <rect :class="pitchAreaCssClasses(6, 'inside')" class="inside" x="205" y="155" width="50" height="50" @click="setPlannedPitchArea(6)"/>
<rect :class="{'inside-selected': bps.currentBullpenPitch().plannedPitchArea === 7}" class="inside" x="105" y="205" width="50" height="50" @click="setPlannedPitchArea(7)"/> <rect :class="pitchAreaCssClasses(7, 'inside')" class="inside" x="105" y="205" width="50" height="50" @click="setPlannedPitchArea(7)"/>
<rect :class="{'inside-selected': bps.currentBullpenPitch().plannedPitchArea === 8}" class="inside" x="155" y="205" width="50" height="50" @click="setPlannedPitchArea(8)"/> <rect :class="pitchAreaCssClasses(8, 'inside')" class="inside" x="155" y="205" width="50" height="50" @click="setPlannedPitchArea(8)"/>
<rect :class="{'inside-selected': bps.currentBullpenPitch().plannedPitchArea === 9}" class="inside" x="205" y="205" width="50" height="50" @click="setPlannedPitchArea(9)"/> <rect :class="pitchAreaCssClasses(9, 'inside')" class="inside" x="205" y="205" width="50" height="50" @click="setPlannedPitchArea(9)"/>
<text x="130" y="140" class="number" @click="setPlannedPitchArea(1)">1</text> <text x="130" y="140" class="number" @click="setPlannedPitchArea(1)">1</text>
<text x="180" y="140" class="number" @click="setPlannedPitchArea(2)">2</text> <text x="180" y="140" class="number" @click="setPlannedPitchArea(2)">2</text>
@ -125,42 +158,71 @@ const setPlannedPitchArea = (hitArea: number) => {
</ion-card> </ion-card>
</ion-content> </ion-content>
<ion-footer> <ion-footer>
<ion-button color="warning" expand="full" size="large" :disabled="bps.currentBullpenPitch().pitchType.id === 0 || bps.currentBullpenPitch().plannedPitchArea === 0" @click="gotoFinalizePitch">Pitch</ion-button> <ion-button color="warning" expand="full" size="large" :disabled="pitch.pitchType.id === 0 || pitch.plannedPitchArea === 0" @click="gotoFinalizePitch">Pitch</ion-button>
</ion-footer> </ion-footer>
</ion-page> </ion-page>
</template> </template>
<!--<ion-grid>-->
<!-- <div v-if="currentStep === BullpenStep.Prepare">-->
<!--<ion-row>-->
<!-- <ion-col><ion-button color="warning" expand="full" size="large" :disabled="pitch.pitchType.id === 0 || pitch.plannedPitchArea === 0" @click="gotoFinalizePitch">Pitch</ion-button></ion-col>-->
<!--</ion-row>-->
<!-- </div>-->
<!-- <div v-if="currentStep === BullpenStep.Finish">-->
<!--<ion-row>-->
<!-- <ion-col><ion-button color="success" expand="full" @click="finalizeAndNextPitch" :disabled="pitch.realPitchArea === 0">Save Pitch<br/>&<br/>Next Pitch</ion-button></ion-col>-->
<!-- <ion-col><ion-button color="success" expand="full" @click="finalizeAndEndBullpen" :disabled="pitch.realPitchArea === 0">Save Pitch<br/>&<br/>End Session</ion-button></ion-col>-->
<!--</ion-row>-->
<!-- </div>-->
<!--</ion-grid>-->
<style scoped> <style scoped>
.wasted { .wasted {
fill: firebrick; fill: firebrick;
stroke: black; stroke: black;
stroke-width: 1; stroke-width: 1;
} }
.wasted-selected { .wasted-aim-selected {
fill: orangered; fill: orangered;
stroke: black; stroke: black;
stroke-width: 1; stroke-width: 1;
} }
.wasted-hit-selected {
fill: orange;
stroke: black;
stroke-width: 1;
}
.edge { .edge {
fill: lightgreen; fill: lightgreen;
stroke: black; stroke: black;
stroke-width: 1; stroke-width: 1;
} }
.edge-selected { .edge-aim-selected {
fill: aquamarine; fill: aquamarine;
stroke: black; stroke: black;
stroke-width: 1; stroke-width: 1;
} }
.edge-hit-selected {
fill: lightblue;
stroke: black;
stroke-width: 1;
}
.inside { .inside {
fill: green; fill: green;
stroke: black; stroke: black;
stroke-width: 1; stroke-width: 1;
} }
.inside-selected { .inside-aim-selected {
fill: darkolivegreen; fill: darkolivegreen;
stroke: black; stroke: black;
stroke-width: 1; stroke-width: 1;
} }
.inside-hit-selected {
fill: darkseagreen;
stroke: black;
stroke-width: 1;
}
.number { .number {
text-anchor: middle; text-anchor: middle;
fill: white; fill: white;