Add support for IIDX notes radar graphs.
Some checks failed
Build / build (push) Has been cancelled

This commit is contained in:
Trenton Zimmer
2025-03-21 16:09:19 -04:00
parent 22c81f6227
commit d3af72808b
6 changed files with 576 additions and 245 deletions

545
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@
"@rushstack/eslint-patch": "^1.1.4",
"@vuepic/vue-datepicker": "^11.0.1",
"axios": "^0.27.0",
"chart.js": "^3.6.0",
"chart.js": "^4.1.1",
"crypto-js": "^4.2.0",
"dotenv": "^16.4.5",
"js-cookie": "^3.0.5",
@@ -20,6 +20,7 @@
"particles.js": "^2.0.0",
"pinia": "^2.0.13",
"vue": "^3.0.0",
"vue-chartjs": "^5.3.2",
"vue-router": "^4.0.0",
"vue3-easy-data-table": "^1.5.47"
},

View File

@@ -0,0 +1,191 @@
<script setup>
import { watch, ref, onMounted, computed } from "vue";
import {
Chart as ChartJS,
RadialLinearScale,
PointElement,
LineElement,
Filler,
Tooltip,
Legend,
} from "chart.js";
import { Radar } from "vue-chartjs";
import CardBox from "@/components/CardBox.vue";
import PillTag from "@/components/PillTag.vue";
import { APIGetAchievements } from "@/stores/api/profile";
ChartJS.register(
RadialLinearScale,
PointElement,
LineElement,
Filler,
Tooltip,
Legend
);
const props = defineProps({
profile: {
type: Number,
default: null,
},
game: {
type: String,
default: null,
},
version: {
type: Number,
default: 9,
},
});
const userProfile = ref(props.profile);
const version = ref(props.version);
const loading = ref(true);
const loadingKey = ref(0);
const chartData = ref({
labels: ["Notes", "Peak", "Scratch", "Sof-Lan", "Charge", "Chord"],
datasets: [
{
label: "SP",
backgroundColor: "rgba(22, 95, 250, 0.2)",
borderColor: "rgba(22, 95, 250, 1)",
pointBackgroundColor: "rgba(22, 95, 250, 1)",
pointBorderColor: "#fff",
pointHoverBackgroundColor: "#fff",
pointHoverBorderColor: "rgba(22, 95, 250, 1)",
data: [],
},
{
label: "DP",
backgroundColor: "rgba(250, 170, 22, 0.2)",
borderColor: "rgba(250, 170, 22, 1)",
pointBackgroundColor: "rgba(250, 170, 22, 1)",
pointBorderColor: "#fff",
pointHoverBackgroundColor: "#fff",
pointHoverBorderColor: "rgba(250, 170, 22, 1)",
data: [],
},
],
});
watch(
() => props.version,
() => {
userProfile.value = props.profile;
version.value = props.version;
loadData();
}
);
async function loadData() {
try {
const data = await APIGetAchievements(
props.game,
props.version,
userProfile.value,
[
["notes_radar", 0],
["notes_radar", 1],
]
);
const reorderData = (originalData) => {
const [notes, peak, scratch, sof_lan, charge, chord] = originalData;
return [
notes / 100,
peak / 100,
scratch / 100,
sof_lan / 100,
charge / 100,
chord / 100,
];
};
if (data[0]?.achievementId === 0) {
chartData.value.datasets[0].data = reorderData(data[0].data.radar_score);
}
if (data[1]?.achievementId === 1) {
chartData.value.datasets[1].data = reorderData(data[1].data.radar_score);
}
loading.value = false;
loadingKey.value += 1;
} catch (error) {
console.error("Failed to fetch arcade data:", error);
}
}
onMounted(async () => {
loadData();
});
const chartOptions = {
responsive: false,
maintainAspectRatio: true,
scales: {
r: {
ticks: {
stepSize: 30,
callback: function (value) {
return value;
},
},
pointLabels: {
font: {
size: 15,
},
},
},
},
};
const spTotalScore = computed(() => {
return chartData.value.datasets[0].data
.reduce((sum, value) => sum + value, 0)
.toFixed(2);
});
const dpTotalScore = computed(() => {
return chartData.value.datasets[1].data
.reduce((sum, value) => sum + value, 0)
.toFixed(2);
});
</script>
<template>
<CardBox v-if="chartData.datasets[0]" class="my-6">
<div class="grid lg:grid-cols-2 gap-4">
<CardBox color-prop="bg-slate-800 dark:bg-slate-800">
<PillTag color="info" label="SP Radar" class="mb-2" />
<div class="w-full grid place-content-center text-center">
<Radar
:key="loadingKey"
class="bg-slate-100 p-2 rounded-lg"
:data="{
labels: chartData.labels,
datasets: [chartData.datasets[0]],
}"
:options="chartOptions"
/>
<span class="text-lg">Total score: {{ spTotalScore }}</span>
</div>
</CardBox>
<CardBox color-prop="bg-slate-800 dark:bg-slate-800">
<PillTag color="info" label="DP Radar" class="mb-2" />
<div class="w-full grid place-content-center text-center">
<Radar
:key="loadingKey"
class="bg-slate-100 p-2 rounded-lg"
:data="{
labels: chartData.labels,
datasets: [chartData.datasets[1]],
}"
:options="chartOptions"
/>
<span class="text-lg">Total score: {{ dpTotalScore }}</span>
</div>
</CardBox>
</div>
</CardBox>
</template>

View File

@@ -0,0 +1,21 @@
export const data = {
//labels: ["Notes", "Chord", "Peak", "Charge", "Scratch", "Sof-Lan"],
labels: ["Notes", "Peak", "Scratch", "Sof-Lan", "Charge", "Chord"],
datasets: [
{
label: "SP",
backgroundColor: "rgba(179,181,198,0.2)",
borderColor: "rgba(179,181,198,1)",
pointBackgroundColor: "rgba(179,181,198,1)",
pointBorderColor: "#fff",
pointHoverBackgroundColor: "#fff",
pointHoverBorderColor: "rgba(179,181,198,1)",
data: [65, 59, 90, 81, 56, 55],
},
],
};
export const options = {
responsive: false,
maintainAspectRatio: true,
};

View File

@@ -48,3 +48,42 @@ export async function APIUpdateProfile(game, version, newProfile) {
throw error;
}
}
export async function APIGetAchievements(
game,
version,
userId = null,
achievements = []
) {
if (!userId) {
while (!mainStore.userId) {
await new Promise((resolve) => setTimeout(resolve, 200));
}
userId = mainStore.userId;
}
var formattedAchievements = "";
for (const achievement of achievements) {
const achievementType = achievement[0];
const achievementId = achievement[1];
formattedAchievements += `${achievementType}:${achievementId},`;
}
try {
const data = await mainStore.callApi(
`/profile/${game}/achievements?version=${version}&userId=${userId}`,
"GET",
null,
{
achievements: formattedAchievements.substring(
0,
formattedAchievements.length - 1
),
}
);
return data.data;
} catch (error) {
console.log("Error fetching achievements:", error);
throw error;
}
}

View File

@@ -10,6 +10,7 @@ import {
mdiChartAreasplineVariant,
mdiChartTimeline,
mdiStickerOutline,
mdiChartDonutVariant,
} from "@mdi/js";
import SectionMain from "@/components/SectionMain.vue";
import CardBoxWidget from "@/components/CardBoxWidget.vue";
@@ -22,10 +23,11 @@ import FormControl from "@/components/FormControl.vue";
import PillTag from "@/components/PillTag.vue";
import JubilityTable from "@/components/Tables/JubilityTable.vue";
import UserSticker from "@/components/UserSticker.vue";
import UserNotesRadar from "@/components/Charts/UserNotesRadar.vue";
import { APIGetProfile } from "@/stores/api/profile";
import { APIGetArcade } from "@/stores/api/arcade";
import { getGameInfo } from "@/constants";
import { GameConstants, getGameInfo } from "@/constants";
import { getIIDXDan } from "@/constants/danClass.js";
import { getGitadoraColor, getJubilityColor } from "@/constants/skillColor";
const ASSET_PATH = import.meta.env.VITE_ASSET_PATH;
@@ -540,6 +542,24 @@ async function generateTimeline(myProfile) {
</div>
</template>
<template
v-if="
thisGame.id == GameConstants.IIDX &&
versionForm.currentVersion >= 29
"
>
<SectionTitleLine
:icon="mdiChartDonutVariant"
title="Notes Radar"
main
/>
<UserNotesRadar
:game="thisGame.id"
:version="versionForm.currentVersion"
:profile="profileUserId"
/>
</template>
<template v-if="myProfile?.timeline">
<SectionTitleLine :icon="mdiChartTimeline" title="Timeline" main />