add chart

This commit is contained in:
2023-03-22 20:06:13 +01:00
parent aa42772a52
commit 3f13e2933c
5 changed files with 241 additions and 64 deletions
+27
View File
@@ -8,8 +8,10 @@
"name": "toneapi_webclient", "name": "toneapi_webclient",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"chart.js": "^4.2.1",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"vue": "^3.2.13", "vue": "^3.2.13",
"vue-chartjs": "^5.2.0",
"vue-class-component": "^8.0.0-0", "vue-class-component": "^8.0.0-0",
"vue-router": "^4.0.3", "vue-router": "^4.0.3",
"vuex": "^4.0.0" "vuex": "^4.0.0"
@@ -1917,6 +1919,11 @@
"@jridgewell/sourcemap-codec": "1.4.14" "@jridgewell/sourcemap-codec": "1.4.14"
} }
}, },
"node_modules/@kurkle/color": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
},
"node_modules/@leichtgewicht/ip-codec": { "node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
@@ -4358,6 +4365,17 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/chart.js": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.2.1.tgz",
"integrity": "sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw==",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": "^7.0.0"
}
},
"node_modules/chokidar": { "node_modules/chokidar": {
"version": "3.5.3", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -12237,6 +12255,15 @@
"@vue/shared": "3.2.47" "@vue/shared": "3.2.47"
} }
}, },
"node_modules/vue-chartjs": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.2.0.tgz",
"integrity": "sha512-d3zpKmGZr2OWHQ1xmxBcAn5ShTG917+/UCLaSpaCDDqT0U7DBsvFzTs69ZnHCgKoXT55GZDW8YEj9Av+dlONLA==",
"peerDependencies": {
"chart.js": "^4.1.1",
"vue": "^3.0.0-0 || ^2.7.0"
}
},
"node_modules/vue-class-component": { "node_modules/vue-class-component": {
"version": "8.0.0-rc.1", "version": "8.0.0-rc.1",
"resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-8.0.0-rc.1.tgz", "resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-8.0.0-rc.1.tgz",
+2
View File
@@ -8,8 +8,10 @@
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"chart.js": "^4.2.1",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"vue": "^3.2.13", "vue": "^3.2.13",
"vue-chartjs": "^5.2.0",
"vue-class-component": "^8.0.0-0", "vue-class-component": "^8.0.0-0",
"vue-router": "^4.0.3", "vue-router": "^4.0.3",
"vuex": "^4.0.0" "vuex": "^4.0.0"
+88
View File
@@ -0,0 +1,88 @@
<template>
<div class="playerChart">
<Scatter :data="chart" :options="options" />
</div>
</template>
<script lang="ts">
import {
Chart as ChartJS,
LinearScale,
PointElement,
LineElement,
Tooltip,
Legend
} from 'chart.js'
import { Scatter } from 'vue-chartjs'
import { defineComponent } from 'vue'
ChartJS.register(LinearScale, PointElement, LineElement, Tooltip, Legend)
export default defineComponent({
name: 'PlayerChart',
props: {
players: Object
},
components: {
Scatter
},
computed: {
chart () {
const chartData = {
datasets: [
{
label: 'Players',
labels: [] as string[],
borderColor: '#f87979',
backgroundColor: '#f87979',
data: [] as {x:number, y:number}[]
}
]
}
if (!this.$props.players) {
return chartData
}
chartData.datasets[0].labels = Object.keys(this.$props.players)
chartData.datasets[0].data = Object.values(this.$props.players).map((e:any) => ({ x: e.deaths, y: e.kills }))
return chartData
}
},
/* watch: {
players: function (newval: any, oldval: any): void {
}
}, */
data () {
return {
options: {
responsive: true,
plugins: {
tooltip: {
callbacks: {
label: (ctx:any) => {
let label:string
if (!this.$props.players) {
label = ctx.dataset.labels[ctx.dataIndex]
} else {
label = this.$props.players[ctx.dataset.labels[ctx.dataIndex]].username
}
label += ' (' + ctx.parsed.y + ' kills ' + ctx.parsed.x + ' deaths)'
return label
}
}
}
}
}
}
}
})
</script>
<style scoped>
.playerChart {
position:sticky;
height:fit-content;
top:0
}
</style>
+64 -59
View File
@@ -1,86 +1,91 @@
<template> <template>
<div class="playerList"> <div class="playerList">
<select v-on:change="weaponFilter">
<option>global</option>
<option v-for="(weaponData, weaponId) in weapons" v-bind:key="weaponId">{{weaponId}}</option>
</select>
<div class="player"> <div class="player">
<span>username</span> <span v-on:click="sortPlayerList('username')">username</span>
<span>kills</span> <span v-on:click="sortPlayerList('kills')">kills</span>
<span>deaths</span> <span v-on:click="sortPlayerList('deaths')">deaths</span>
<span>k/d</span> <span v-on:click="sortPlayerList('k/d')">k/d</span>
<span>max kill distance</span> <span v-on:click="sortPlayerList('max_kill_distance')">max kill distance</span>
<span>average kill distance</span> <span v-on:click="sortPlayerList('avg_kill_distance')">average kill distance</span>
</div> </div>
<div class="player" v-for="(playerData, playerId) in players" v-bind:key="playerId"> <div class="player" v-for="playerId in playerIdList" v-bind:key="playerId">
<span>{{ playerData.username }}</span> <span>{{ $props.players[playerId].username }}</span>
<span>{{ playerData.kills }}</span> <span>{{ players[playerId].kills }}</span>
<span>{{ playerData.deaths }}</span> <span>{{ players[playerId].deaths }}</span>
<span>{{ Math.round(playerData.kills/playerData.deaths*100)/100 }}</span> <span>{{ Math.round(players[playerId].kills / players[playerId].deaths * 100) / 100 }}</span>
<span>{{ playerData.max_kill_distance }}</span> <span>{{ players[playerId].max_kill_distance }}</span>
<span>{{ Math.round(playerData.avg_kill_distance * 100) / 100}}</span> <span>{{ Math.round(players[playerId].avg_kill_distance * 100) / 100 }}</span>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Options, Vue } from 'vue-class-component' import { defineComponent } from 'vue'
@Options({ export default defineComponent({
props: { props: {
msg: String players: Object
} },
}) data () {
export default class HelloWorld extends Vue { return {
msg!: string; sortingData: { direction: -1, argument: '' },
players: any = {} playerIdList: [] as string[]
weapons: any = {} }
created () { },
this.fetchPlayers({}) watch: {
this.fetchWeapons({}) players: function (newval: any, oldval: any): void {
} this.playerIdList = []
this.sortingData.direction *= -1
this.playerIdList = Object.keys(newval)
this.sortPlayerList(this.sortingData.argument)
}
},
methods: {
sortPlayerList (arg: string) {
if (arg === this.sortingData.argument) {
this.sortingData.direction *= -1
} else {
if (arg === 'username') {
this.sortingData.direction = 1
}
}
this.sortingData.argument = arg
async fetchPlayers ({ weapon, server }:{weapon?:string, server?:string}) { this.playerIdList.sort((a:any, b:any) => {
const searchParams:Record<string, string> = {} if (!this.$props.players) {
if (weapon) { searchParams.weapon = weapon } return 0
if (server) { searchParams.server = server } }
let varA = this.$props.players[a][arg]
let varB = this.$props.players[b][arg]
if (arg === 'k/d') {
varA = this.$props.players[a].kills / this.$props.players[a].deaths
varB = this.$props.players[b].kills / this.$props.players[b].deaths
}
const response = await fetch( if (varA < varB) {
'https://tone.sleepycat.date/v1/client/players?' + new URLSearchParams(searchParams) return -1 * this.sortingData.direction
) }
const data = await response.json() if (varA > varB) {
this.players = data return 1 * this.sortingData.direction
} }
// names must be equal
async fetchWeapons ({ server }:{server?:string}) { return 0
const searchParams:Record<string, string> = {} })
if (server) { searchParams.server = server }
const response = await fetch(
'https://tone.sleepycat.date/v1/client/weapons?' + new URLSearchParams(searchParams)
)
const data = await response.json()
this.weapons = data
console.log(data)
}
weaponFilter (e:Event) {
const weapon = (e.target as HTMLInputElement).value
if (weapon === 'global') {
return this.fetchPlayers({ })
} }
this.fetchPlayers({ weapon })
} }
}
})
</script> </script>
<style scoped> <style scoped>
.player { .player {
display: grid; display: grid;
grid-template-columns: 200px 100px 100px 100px 100px 100px; grid-template-columns: 200px 75px 75px 75px 100px 100px;
background: lightgray; background: lightgray;
margin: 10px; margin: 10px;
text-align: left; text-align: left;
} }
.player span { .player span {
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
+60 -5
View File
@@ -1,23 +1,78 @@
<template> <template>
<PlayerList></PlayerList> <select v-on:change="weaponFilter">
<option>global</option>
<option v-for="(weaponData, weaponId) in weapons" v-bind:key="weaponId">{{ weaponId }}</option>
</select>
<div class="playersData">
<PlayerList :players="players"></PlayerList>
<PlayerChart :players="players"></PlayerChart>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Options, Vue } from 'vue-class-component' import { Options, Vue } from 'vue-class-component'
import PlayerList from '@/components/PlayerList.vue' import PlayerList from '@/components/PlayerList.vue'
import PlayerChart from '@/components/PlayerChart.vue'
@Options({ @Options({
components: { components: {
PlayerList PlayerList, PlayerChart
} }
}) })
export default class PlayerView extends Vue { export default class PlayerView extends Vue {
mounted = function () { players: any = {}
console.log('q') weapons: any = {}
}; created () {
this.fetchPlayers({})
this.fetchWeapons({})
}
async fetchWeapons ({ server }: { server?: string }) {
const searchParams: Record<string, string> = {}
if (server) { searchParams.server = server }
const response = await fetch(
'https://tone.sleepycat.date/v1/client/weapons?' + new URLSearchParams(searchParams)
)
const data = await response.json()
this.weapons = data
}
data () { data () {
return { a: 1 } return { a: 1 }
} }
weaponFilter (e: Event) {
const weapon = (e.target as HTMLInputElement).value
if (weapon === 'global') {
return this.fetchPlayers({})
}
this.fetchPlayers({ weapon })
}
async fetchPlayers ({ weapon, server }: { weapon?: string, server?: string }) {
const searchParams: Record<string, string> = {}
if (weapon) { searchParams.weapon = weapon }
if (server) { searchParams.server = server }
const response = await fetch(
'https://tone.sleepycat.date/v1/client/players?' + new URLSearchParams(searchParams)
)
const data = await response.json()
Object.keys(data).forEach(e => {
data[e].kills = Number(data[e].kills)
data[e].deaths = Number(data[e].deaths)
data[e].max_kill_distance = Number(data[e].max_kill_distance)
data[e].avg_kill_distance = Number(data[e].avg_kill_distance)
})
this.players = data
}
} }
</script> </script>
<style scoped>
.playersData {
display: grid;
grid-template-columns: 50% 50%;
}
</style>