Skip to content

Commit

Permalink
Save chart and data
Browse files Browse the repository at this point in the history
  • Loading branch information
Levminer committed Dec 23, 2024
1 parent 10854e8 commit 04ab828
Show file tree
Hide file tree
Showing 5 changed files with 407 additions and 22 deletions.
13 changes: 11 additions & 2 deletions platforms/interface/ui/charts/LineChart.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Line {data} {options} />
<Line {data} {options} id={props.id} />

<script lang="ts">
import { colors } from "ui/utils/colors.ts"
Expand All @@ -7,6 +7,7 @@
import { Line } from "svelte-chartjs"
interface Props {
id?: string
statistics: {
label?: string
data?: number[]
Expand All @@ -21,6 +22,7 @@
}
export let props: Props = {
id: "",
statistics: [{}],
unit: "",
time: "",
Expand Down Expand Up @@ -81,8 +83,15 @@
},
x: {
ticks: {
display: false,
callback: function (val, index) {
// @ts-ignore ticks if they're divisible by 10
return index % 10 === 0 ? this.getLabelForValue(val) : ""
},
maxRotation: 0,
minRotation: 0,
color: "#969696",
},
// TODO: adjust grid lines
},
},
plugins: {
Expand Down
149 changes: 149 additions & 0 deletions platforms/interface/ui/components/saveDataButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<ModularDialog title={"Save data"} description={"You can save your data to a .csv file or save the graph as an image."}>
<slot slot="openButton">
<Dialog.Trigger
class="transparent-800 group m-1 inline-flex flex-shrink-0 flex-grow flex-col items-center justify-center rounded-lg p-1 px-3 text-gray-200 duration-200 ease-in-out"
>
<p class="text-xl">Save</p>
</Dialog.Trigger>
</slot>
<div class="w-full">
<Tabs.Root value="file" class="">
<Tabs.List class="grid w-full grid-cols-2 gap-1 rounded-xl border-2 p-1 text-sm font-semibold leading-[0.01em]">
<Tabs.Trigger
value="file"
class="h-10 rounded-xl bg-transparent py-2 text-base data-[state=active]:bg-white data-[state=active]:text-black"
>
File
</Tabs.Trigger>
<Tabs.Trigger
value="image"
class="h-10 rounded-xl bg-transparent py-2 text-base data-[state=active]:bg-white data-[state=active]:text-black"
>
Image
</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="file" class="pt-10">
<div>
<button on:click={saveFile} class="button w-full">Save file</button>
</div>
</Tabs.Content>
<Tabs.Content value="image" class="pt-10">
<div>
<button on:click={saveImage} class="button w-full">Save image</button>
</div>
</Tabs.Content>
</Tabs.Root>
</div>
</ModularDialog>

<script lang="ts">
import { Dialog, Tabs } from "bits-ui"
import ModularDialog from "./modularDialog.svelte"
import { settings } from "../stores/settings.ts"
interface Props {
id: string
statistics: {
label: string
data: number[]
}[]
}
export let props: Props = {
id: "",
statistics: [],
}
const saveFile = () => {
if (($settings.licenseKey === "" || $settings.licenseKey === "free") && import.meta.env.VITE_CORES_MODE === "host") {
return (location.href = "/onboarding")
}
const headers = ["Time", ...props.statistics.map((stat) => stat.label)]
// Create CSV content
const csvRows = [headers.join(",")]
// Use the length of the first statistics array
const dataLength = props.statistics[0].data.length
for (let i = 0; i < dataLength; i++) {
const timestamp = new Date(Date.now() - $settings.interval * 1000 * (dataLength - 1 - i))
const row = [timestamp.toISOString(), ...props.statistics.map((stat) => stat.data[i])]
csvRows.push(row.join(","))
}
// Join rows with newlines and create download
const csv = csvRows.join("\n")
const blob = new Blob([csv], { type: "text/csv" })
const url = window.URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = `${props.id.toLowerCase()}.csv`
a.click()
window.URL.revokeObjectURL(url)
}
const saveImage = () => {
if (($settings.licenseKey === "" || $settings.licenseKey === "free") && import.meta.env.VITE_CORES_MODE === "host") {
return (location.href = "/onboarding")
}
const label = props.id.replaceAll("_", " ")
const canvas = document.getElementById(props.id) as HTMLCanvasElement
// Create a temporary canvas to add background and rounded corners
const tempCanvas = document.createElement("canvas")
const tempCtx = tempCanvas.getContext("2d")
const margin = 20
const targetWidth = canvas.width
const targetHeight = canvas.height
// Make temp canvas larger to accommodate margin
tempCanvas.width = targetWidth + margin * 2
tempCanvas.height = targetHeight + margin * 2
// Draw black background with rounded corners
tempCtx.fillStyle = "rgb(30,30,30)"
const radius = 20
tempCtx.beginPath()
tempCtx.roundRect(0, 0, tempCanvas.width, tempCanvas.height, radius)
tempCtx.fill()
// Calculate centered position
const x = Math.floor((tempCanvas.width - targetWidth) / 2)
const y = Math.floor((tempCanvas.height - targetHeight) / 2)
// Draw original canvas content centered with margin
tempCtx.drawImage(canvas, x, y, targetWidth, targetHeight)
// Add text at bottom
tempCtx.fillStyle = "rgb(100,100,100)"
tempCtx.font = "12px Arial"
const bText = "Generated by: Cores (coresmonitor.com)"
const bTextMetrics = tempCtx.measureText(bText)
const bTextX = (tempCanvas.width - bTextMetrics.width) / 2
const bTextY = tempCanvas.height - margin / 2
tempCtx.fillText(bText, bTextX, bTextY)
// Add text at top
tempCtx.fillStyle = "rgb(100,100,100)"
tempCtx.font = "12px Arial"
const tText = `${label} Sensor Data (${new Date().toLocaleString()})`
const tTextMetrics = tempCtx.measureText(tText)
const tTextX = (tempCanvas.width - tTextMetrics.width) / 2
const tTextY = margin + 3
tempCtx.fillText(tText, tTextX, tTextY)
// Get the final image
const image = tempCanvas.toDataURL("image/png")
const a = document.createElement("a")
a.href = image
a.download = `${props.id.toLowerCase()}.png`
a.click()
}
</script>
110 changes: 101 additions & 9 deletions platforms/interface/ui/pages/cpu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<div class="select-text">
<h3>Vendor: {$hardwareInfo.cpu.info[0].manufacturerName.replaceAll("(R)", "").replaceAll("Corporation", "")}</h3>
<h3>Name: {$hardwareInfo.cpu.name}</h3>
<h3>Base speed: {($hardwareInfo.cpu.info[0].currentSpeed / 1000).toFixed(1)} GHz</h3>
<h3>Base speed: {($hardwareInfo.cpu.info[0].currentSpeed / 1000).toFixed(1)} GHz</h3>
<h3>Cores/Threads: {$hardwareInfo.cpu.info[0].coreCount} C/{$hardwareInfo.cpu.info[0].threadCount} T</h3>
</div>
</div>
Expand All @@ -26,14 +26,40 @@
</div>
<h2>Average CPU Temperature</h2>
</div>
<div>
<div class="flex flex-row">
<SaveDataButton
props={{
id: "CPU_Temperature",
statistics: [
{
label: "Max Temperature",
data: minutes
? $hardwareStatistics.minutes.map((value) => value.cpu.temperature.max)
: $hardwareStatistics.seconds.map((value) => value.cpu.temperature.max),
},
{
label: "Current Temperature",
data: minutes
? $hardwareStatistics.minutes.map((value) => value.cpu.temperature.value)
: $hardwareStatistics.seconds.map((value) => value.cpu.temperature.value),
},
{
label: "Min Temperature",
data: minutes
? $hardwareStatistics.minutes.map((value) => value.cpu.temperature.min)
: $hardwareStatistics.seconds.map((value) => value.cpu.temperature.min),
},
],
}}
/>
<ToggleButton selected={minutes} on:click={() => (minutes = !minutes)} />
</div>
</div>

<div>
<LineChart
props={{
id: "CPU_Temperature",
statistics: [
{
label: "Max Temperature",
Expand Down Expand Up @@ -73,32 +99,55 @@
</div>
<h2>Average Clock speed</h2>
</div>
<div>
<div class="flex flex-row">
<SaveDataButton
props={{
id: "CPU_Clock_Speed",
statistics: [
{
label: "Max Clock Speed",
data: minutes
? $hardwareStatistics.minutes.map((value) => value.cpu.clock.max)
: $hardwareStatistics.seconds.map((value) => value.cpu.clock.max),
},
{
label: "Current Clock Speed",
data: minutes
? $hardwareStatistics.minutes.map((value) => value.cpu.clock.value)
: $hardwareStatistics.seconds.map((value) => value.cpu.clock.value),
},
{
label: "Min Clock Speed",
data: minutes
? $hardwareStatistics.minutes.map((value) => value.cpu.clock.min)
: $hardwareStatistics.seconds.map((value) => value.cpu.clock.min),
},
],
}}
/>
<ToggleButton selected={minutes} on:click={() => (minutes = !minutes)} />
</div>
</div>

<div>
<LineChart
props={{
id: "CPU_Clock_Speed",
statistics: [
{
label: "Max Clock Speed",
color: "max",
data: minutes
? $hardwareStatistics.minutes.map((value) => value.cpu.clock.max)
: $hardwareStatistics.seconds.map((value) => value.cpu.clock.max),
},
{
label: "Current Clock Speed",
color: "current",
data: minutes
? $hardwareStatistics.minutes.map((value) => value.cpu.clock.value)
: $hardwareStatistics.seconds.map((value) => value.cpu.clock.value),
},
{
label: "Min Clock Speed",
color: "min",
data: minutes
? $hardwareStatistics.minutes.map((value) => value.cpu.clock.min)
: $hardwareStatistics.seconds.map((value) => value.cpu.clock.min),
Expand All @@ -123,14 +172,28 @@
</div>
<h2>CPU Power Usage</h2>
</div>
<div>
<div class="flex flex-row">
<SaveDataButton
props={{
id: "CPU_Power_Usage",
statistics: [
{
label: "Power Usage",
data: minutes
? $hardwareStatistics.minutes.map((value) => value.cpu.power)
: $hardwareStatistics.seconds.map((value) => value.cpu.power),
},
],
}}
/>
<ToggleButton selected={minutes} on:click={() => (minutes = !minutes)} />
</div>
</div>

<div>
<LineChart
props={{
id: "CPU_Power_Usage",
statistics: [
{
label: "Power Usage",
Expand All @@ -157,14 +220,28 @@
</div>
<h2>Average CPU Load</h2>
</div>
<div>
<div class="flex flex-row">
<SaveDataButton
props={{
id: "CPU_Load",
statistics: [
{
label: "Load",
data: minutes
? $hardwareStatistics.minutes.map((value) => value.cpu.load)
: $hardwareStatistics.seconds.map((value) => value.cpu.load),
},
],
}}
/>
<ToggleButton selected={minutes} on:click={() => (minutes = !minutes)} />
</div>
</div>

<div>
<LineChart
props={{
id: "CPU_Load",
statistics: [
{
label: "Load",
Expand Down Expand Up @@ -194,14 +271,28 @@
</div>
<h2>CPU Voltage</h2>
</div>
<div>
<div class="flex flex-row">
<SaveDataButton
props={{
id: "CPU_Voltage",
statistics: [
{
label: "Voltage",
data: minutes
? $hardwareStatistics.minutes.map((value) => value.cpu.voltage)
: $hardwareStatistics.seconds.map((value) => value.cpu.voltage),
},
],
}}
/>
<ToggleButton selected={minutes} on:click={() => (minutes = !minutes)} />
</div>
</div>

<div>
<LineChart
props={{
id: "CPU_Voltage",
statistics: [
{
label: "Voltage",
Expand All @@ -228,6 +319,7 @@
import { hardwareInfo } from "ui/stores/hardwareInfo.ts"
import { Clock, Cpu, Gauge, Plug, Thermometer, Zap } from "lucide-svelte"
import ToggleButton from "ui/components/toggleButton.svelte"
import SaveDataButton from "../components/saveDataButton.svelte"
let minutes = false
</script>
Loading

0 comments on commit 04ab828

Please sign in to comment.