Files
ToneAPI_webclient/src/components/PlayerChart.vue
T

250 lines
7.8 KiB
Vue

<template>
<div class="playerChart" ref="container">
<LoadingBar v-if="progress!==1" :value="progress"></LoadingBar>
<Scatter :data="chart" :options="chartOptions" v-if="progress===1" />
</div>
</template>
<script lang="ts">
import {
Chart as ChartJS,
LinearScale,
PointElement,
LineElement,
Tooltip,
Legend,
CoreChartOptions,
PluginChartOptions,
ScaleChartOptions
} from 'chart.js'
import annotationPlugin, { AnnotationPluginOptions } from 'chartjs-plugin-annotation'
import dataLabel from 'chartjs-plugin-datalabels'
import { useKillStore, Player, Filter } from '@/stores/kill'
import { Scatter } from 'vue-chartjs'
import { defineComponent, PropType, toRaw, unref } from 'vue'
import { ChartEvent } from 'chart.js/dist/core/core.plugins'
import { _DeepPartialObject } from 'chart.js/dist/types/utils'
import LoadingBar from './LoadingBar.vue'
ChartJS.register(LinearScale, PointElement, LineElement, Tooltip, Legend, annotationPlugin, dataLabel)
export default defineComponent({
name: 'PlayerChart',
props: {
filters: Object as PropType<Filter>,
playerHighlighted: String
},
data: () => {
return {
store: useKillStore(),
refreshColors: 0,
playerData: []
}
},
emits: ['highlightPlayer'],
components: {
Scatter, LoadingBar
},
mounted () {
this.refreshColors++
},
computed: {
progress () {
if (this.filters) {
const filter = new Filter(this.filters)
delete filter.player
return this.store.getList('players', filter)?.value.progress.value
}
return 0
},
playerList (): (Player & {id:string})[] {
const filter = new Filter(this.filters)
delete filter.player
const data = unref(this.store.getList('players', filter))?.data
if (!data) return Object.entries(this.store.fetch('players', filter).value.data).map(e => ({ id: e[0], ...toRaw(e[1].value) }))
const cut = Object.entries(data).map(e => ({ id: e[0], ...toRaw(e[1].value) }))
cut.sort((a, b) => {
return b.kills - a.kills
})
return cut.slice(0, 200)
},
colors () {
// eslint-disable-next-line no-unused-expressions
this.refreshColors
const colors = {} as { [key: string]: string }
if (this.$refs?.container) {
const style = getComputedStyle(this.$refs.container as Element)
colors.fg = style.getPropertyValue('--foreground')
colors.bg = style.getPropertyValue('--bg-color')
colors.orange = style.getPropertyValue('--orange')
colors.cyan = style.getPropertyValue('--cyan')
colors.currentLine = style.getPropertyValue('--current-line')
} else {
colors.fg = '#ffffff'
}
return colors
},
chart () {
const playerIndex = this.playerList.findIndex(e => e.id === this.$props.playerHighlighted)
const defaultColor = this.colors.cyan
const borderColor = new Array(this.playerList.length).fill(defaultColor)
borderColor[playerIndex] = this.colors.orange
const backgroundColor = borderColor
const pointRadius = new Array(this.playerList.length).fill(1)
pointRadius[playerIndex] = 20
const hoverRadius = new Array(this.playerList.length).fill(4)
hoverRadius[playerIndex] = 30
const pointStyle = new Array(this.playerList.length).fill('circle')
pointStyle[playerIndex] = 'crossRot'
return {
datasets: [
{
label: 'Players',
labels: this.playerList.map(e => e.id) || [] as string[],
borderColor,
backgroundColor,
pointRadius,
pointStyle,
hoverRadius,
data: this.playerList.map((player) => ({ y: player.kills, x: player.deaths }))
}
]
}
},
chartStaticOptions () {
return {
responsive: true,
maintainAspectRatio: false,
animation: { duration: 500 },
layout: { autoPadding: false },
scales: {
y: {
title: {
text: 'Kills',
display: true,
color: this.colors.fg
},
ticks: {
color: this.colors.fg
}
},
x: {
title: {
text: 'Deaths',
display: true,
color: this.colors.fg
},
ticks: {
color: this.colors.fg
}
}
},
onClick: (e: ChartEvent, element: any) => {
this.$emit('highlightPlayer', element.length > 0 ? this.playerList[element[0].index].id : undefined)
},
onHover: (e: any, element: any) => {
if (!e.native.target) return
(e.native.target as HTMLElement).style.cursor = element[0] ? 'pointer' : 'default'
}
}
},
chartStaticPlugins () {
let endX = 0
let endY = 0
if (this.playerList) {
const maxX = Math.max(...this.playerList.map(e => e.deaths))
const maxY = Math.max(...this.playerList.map(e => e.kills))
if (maxY > maxX) {
endX = maxX
endY = (maxX / maxY) * maxY
} else {
endY = maxY
endX = (maxY / maxX) * maxX
}
}
return {
tooltip: {
callbacks: {
label: (ctx: any) => {
let label: string
if (!this.playerList) {
label = ctx.dataset.labels[ctx.dataIndex]
} else {
label = this.playerList[ctx.dataIndex].username
}
label += ' (' + ctx.parsed.y + ' kills ' + ctx.parsed.x + ' deaths)'
return label
}
}
},
annotation: {
annotations: {
line: {
type: 'line',
yMin: 0,
xMin: 0,
yMax: endY,
xMax: endX,
borderWidth: 2,
borderColor: this.colors.orange,
borderDash: [1, 5]
}
}
} as _DeepPartialObject<AnnotationPluginOptions>,
legend: { display: false }
}
},
chartOptions (): _DeepPartialObject<CoreChartOptions<'scatter'> & PluginChartOptions<'scatter'> & ScaleChartOptions<'scatter'>> {
// eslint-disable-next-line no-unused-expressions
this.$props.playerHighlighted
return {
...this.chartStaticOptions,
plugins: {
...this.chartStaticPlugins,
datalabels: {
formatter: (value: any, context: any) => {
return this.playerList[context.dataIndex].username
},
backgroundColor: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerList[context.dataIndex].id) ? this.colors.orange : null,
borderColor: this.colors.fg,
borderWidth: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerList[context.dataIndex].id) ? 1 : 0,
borderRadius: 5,
display: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerList[context.dataIndex].id) ? 'true' : 'auto',
align: -45,
anchor: 'end',
clamp: true,
color: (context: any) => (this.$props.playerHighlighted && this.$props.playerHighlighted === this.playerList[context.dataIndex].id) ? this.colors.bg : this.colors.fg
}
}
}
}
}
/* methods: {
refreshChart () {
if (this.store.getPlayerList(this.filters || {}) !== undefined) triggerRef(this.store.getPlayerList(this.filters || {}) as unknown as Ref)
}
} */
})
</script>
<style scoped>
canvas {
background: var(--accent);
}
.playerChart{
position:relative;
}
#refreshChart{
position:absolute;
top: 0;
left:0;
}
@media only screen and (max-width: 922px) {
canvas {
display: none !important
}
}
</style>