feat: Use chart.js for polarplot n jobview

This commit is contained in:
Christoph Kluge 2023-08-10 12:05:02 +02:00
parent f286872a33
commit b42a11d30e
2 changed files with 55 additions and 151 deletions

View File

@ -20,12 +20,11 @@
} from "sveltestrap"; } from "sveltestrap";
import PlotTable from "./PlotTable.svelte"; import PlotTable from "./PlotTable.svelte";
import Metric from "./Metric.svelte"; import Metric from "./Metric.svelte";
import PolarPlot from "./plots/Polar.svelte"; import Polar from "./plots/Polar.svelte";
import Roofline from "./plots/Roofline.svelte"; import Roofline from "./plots/Roofline.svelte";
import JobInfo from "./joblist/JobInfo.svelte"; import JobInfo from "./joblist/JobInfo.svelte";
import TagManagement from "./TagManagement.svelte"; import TagManagement from "./TagManagement.svelte";
import MetricSelection from "./MetricSelection.svelte"; import MetricSelection from "./MetricSelection.svelte";
import Zoom from "./Zoom.svelte";
import StatsTable from "./StatsTable.svelte"; import StatsTable from "./StatsTable.svelte";
import { getContext } from "svelte"; import { getContext } from "svelte";
@ -233,7 +232,7 @@
{/if} {/if}
{/if} {/if}
<Col> <Col>
<PolarPlot <Polar
width={polarPlotSize} width={polarPlotSize}
height={polarPlotSize} height={polarPlotSize}
metrics={ccconfig[ metrics={ccconfig[
@ -246,7 +245,7 @@
<Col> <Col>
<Roofline <Roofline
width={fullWidth / 3 - 10} width={fullWidth / 3 - 10}
height={polarPlotSize} height={polarPlotSize + 20}
cluster={clusters cluster={clusters
.find((c) => c.name == $initq.data.job.cluster) .find((c) => c.name == $initq.data.job.cluster)
.subClusters.find( .subClusters.find(

View File

@ -1,22 +1,35 @@
<div>
<canvas bind:this={canvasElement} width="{width}" height="{height}"></canvas>
</div>
<script> <script>
import { onMount, getContext } from 'svelte' import { getContext } from 'svelte'
import { Radar } from 'svelte-chartjs';
import {
Chart as ChartJS,
Title,
Tooltip,
Legend,
Filler,
PointElement,
RadialLinearScale,
LineElement
} from 'chart.js';
ChartJS.register(
Title,
Tooltip,
Legend,
Filler,
PointElement,
RadialLinearScale,
LineElement
);
export let metrics
export let width export let width
export let height export let height
export let metrics
export let cluster export let cluster
export let jobMetrics export let jobMetrics
const fontSize = 12
const fontFamily = 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'
const metricConfig = getContext('metrics') const metricConfig = getContext('metrics')
let ctx, canvasElement
const labels = metrics.filter(name => { const labels = metrics.filter(name => {
if (!jobMetrics.find(m => m.name == name && m.scope == "node")) { if (!jobMetrics.find(m => m.name == name && m.scope == "node")) {
console.warn(`PolarPlot: No metric data for '${name}'`) console.warn(`PolarPlot: No metric data for '${name}'`)
@ -46,145 +59,37 @@
return avg / metric.series.length return avg / metric.series.length
} }
const data = [ const data = {
{ labels: labels,
name: 'Max', datasets: [
values: getValuesForStat(getMax), {
color: 'rgb(0, 102, 255)', label: 'Max',
areaColor: 'rgba(0, 102, 255, 0.25)' data: getValuesForStat(getMax),
}, fill: 1,
{ backgroundColor: 'rgba(0, 102, 255, 0.25)',
name: 'Avg', borderColor: 'rgb(0, 102, 255)',
values: getValuesForStat(getAvg), pointBackgroundColor: 'rgb(0, 102, 255)',
color: 'rgb(255, 153, 0)', pointBorderColor: '#fff',
areaColor: 'rgba(255, 153, 0, 0.25)' pointHoverBackgroundColor: '#fff',
} pointHoverBorderColor: 'rgb(0, 102, 255)'
] },
{
function render() { label: 'Avg',
if (!width || Number.isNaN(width)) data: getValuesForStat(getAvg),
return fill: true,
backgroundColor: 'rgba(255, 153, 0, 0.25)',
const centerX = width / 2 borderColor: 'rgb(255, 153, 0)',
const centerY = height / 2 - 15 pointBackgroundColor: 'rgb(255, 153, 0)',
const radius = (Math.min(width, height) / 2) - 50 pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
// Draw circles pointHoverBorderColor: 'rgb(255, 153, 0)'
ctx.lineWidth = 1
ctx.strokeStyle = '#999999'
ctx.beginPath()
ctx.arc(centerX, centerY, radius * 1.0, 0, Math.PI * 2, false)
ctx.stroke()
ctx.beginPath()
ctx.arc(centerX, centerY, radius * 0.666, 0, Math.PI * 2, false)
ctx.stroke()
ctx.beginPath()
ctx.arc(centerX, centerY, radius * 0.333, 0, Math.PI * 2, false)
ctx.stroke()
// Axis
ctx.font = `${fontSize}px ${fontFamily}`
ctx.textAlign = 'center'
ctx.fillText('1/3',
Math.floor(centerX + radius * 0.333),
Math.floor(centerY + 15))
ctx.fillText('2/3',
Math.floor(centerX + radius * 0.666),
Math.floor(centerY + 15))
ctx.fillText('1.0',
Math.floor(centerX + radius * 1.0),
Math.floor(centerY + 15))
// Label text and straight lines from center
for (let i = 0; i < labels.length; i++) {
const angle = 2 * Math.PI * ((i + 1) / labels.length)
const dx = Math.cos(angle) * radius
const dy = Math.sin(angle) * radius
ctx.fillText(labels[i],
Math.floor(centerX + dx * 1.1),
Math.floor(centerY + dy * 1.1))
ctx.beginPath()
ctx.moveTo(centerX, centerY)
ctx.lineTo(centerX + dx, centerY + dy)
ctx.stroke()
}
for (let dataset of data) {
console.assert(dataset.values.length === labels.length, 'this will look confusing')
ctx.fillStyle = dataset.color
ctx.strokeStyle = dataset.color
const points = []
for (let i = 0; i < dataset.values.length; i++) {
const value = dataset.values[i]
const angle = 2 * Math.PI * ((i + 1) / labels.length)
const x = centerX + Math.cos(angle) * radius * value
const y = centerY + Math.sin(angle) * radius * value
ctx.beginPath()
ctx.arc(x, y, 3, 0, Math.PI * 2, false)
ctx.fill()
points.push({ x, y })
} }
]
// "Fill" the shape this dataset has
ctx.fillStyle = dataset.areaColor
ctx.beginPath()
ctx.moveTo(points[0].x, points[0].y)
for (let p of points)
ctx.lineTo(p.x, p.y)
ctx.lineTo(points[0].x, points[0].y)
ctx.stroke()
ctx.fill()
}
// Legend at the bottom left corner
ctx.textAlign = 'left'
let paddingLeft = 0
for (let dataset of data) {
const text = `${dataset.name}: `
const textWidth = ctx.measureText(text).width
ctx.fillStyle = 'black'
ctx.fillText(text, paddingLeft, height - 20)
ctx.fillStyle = dataset.color
ctx.beginPath()
ctx.arc(paddingLeft + textWidth + 5, height - 25, 5, 0, Math.PI * 2, false)
ctx.fill()
paddingLeft += textWidth + 15
}
ctx.fillStyle = 'black'
ctx.fillText(`Values relative to respective peak.`, 0, height - 7)
} }
let mounted = false // No custom defined options but keep for clarity
onMount(() => { const options = {}
canvasElement.width = width
canvasElement.height = height
ctx = canvasElement.getContext('2d')
render(ctx, data, width, height)
mounted = true
})
let timeoutId = null
function sizeChanged() {
if (!mounted)
return;
if (timeoutId != null)
clearTimeout(timeoutId)
timeoutId = setTimeout(() => {
timeoutId = null
canvasElement.width = width
canvasElement.height = height
ctx = canvasElement.getContext('2d')
render(ctx, data, width, height)
}, 250)
}
$: sizeChanged(width, height)
</script> </script>
<Radar {data} {options} {width} {height}/>