Merge pull request #199 from ClusterCockpit/197_apply_chartjs_update

197 apply chartjs update
This commit is contained in:
Jan Eitzinger
2023-08-11 11:44:22 +02:00
committed by GitHub
8 changed files with 374 additions and 325 deletions

View File

@@ -0,0 +1,79 @@
<script context="module">
// http://tsitsul.in/blog/coloropt/ : 12 colors normal
export const colors = [
'rgb(235,172,35)',
'rgb(184,0,88)',
'rgb(0,140,249)',
'rgb(0,110,0)',
'rgb(0,187,173)',
'rgb(209,99,230)',
'rgb(178,69,2)',
'rgb(255,146,135)',
'rgb(89,84,214)',
'rgb(0,198,248)',
'rgb(135,133,0)',
'rgb(0,167,108)',
'rgb(189,189,189)'
]
</script>
<script>
import { Pie } from 'svelte-chartjs';
import {
Chart as ChartJS,
Title,
Tooltip,
Legend,
ArcElement,
CategoryScale
} from 'chart.js';
ChartJS.register(
Title,
Tooltip,
Legend,
ArcElement,
CategoryScale
);
export let size
export let sliceLabel
export let quantities
export let entities
export let displayLegend = false
const data = {
labels: entities,
datasets: [
{
label: sliceLabel,
data: quantities,
fill: 1,
backgroundColor: colors.slice(0, quantities.length),
}
]
}
const options = {
maintainAspectRatio: false,
animation: false,
plugins: {
legend: {
display: displayLegend
}
}
}
</script>
<div class="chart-container" style="--container-width: {size}; --container-height: {size}">
<Pie {data} {options}/>
</div>
<style>
.chart-container {
position: relative;
margin: auto;
height: var(--container-height);
width: var(--container-width);
}
</style>

View File

@@ -1,22 +1,34 @@
<div>
<canvas bind:this={canvasElement} width="{width}" height="{height}"></canvas>
</div>
<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 size
export let metrics
export let width
export let height
export let cluster
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')
let ctx, canvasElement
const labels = metrics.filter(name => {
if (!jobMetrics.find(m => m.name == name && m.scope == "node")) {
console.warn(`PolarPlot: No metric data for '${name}'`)
@@ -46,145 +58,49 @@
return avg / metric.series.length
}
const data = [
{
name: 'Max',
values: getValuesForStat(getMax),
color: 'rgb(0, 102, 255)',
areaColor: 'rgba(0, 102, 255, 0.25)'
},
{
name: 'Avg',
values: getValuesForStat(getAvg),
color: 'rgb(255, 153, 0)',
areaColor: 'rgba(255, 153, 0, 0.25)'
}
]
function render() {
if (!width || Number.isNaN(width))
return
const centerX = width / 2
const centerY = height / 2 - 15
const radius = (Math.min(width, height) / 2) - 50
// Draw circles
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 })
const data = {
labels: labels,
datasets: [
{
label: 'Max',
data: getValuesForStat(getMax),
fill: 1,
backgroundColor: 'rgba(0, 102, 255, 0.25)',
borderColor: 'rgb(0, 102, 255)',
pointBackgroundColor: 'rgb(0, 102, 255)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgb(0, 102, 255)'
},
{
label: 'Avg',
data: getValuesForStat(getAvg),
fill: true,
backgroundColor: 'rgba(255, 153, 0, 0.25)',
borderColor: 'rgb(255, 153, 0)',
pointBackgroundColor: 'rgb(255, 153, 0)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgb(255, 153, 0)'
}
// "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
onMount(() => {
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)
// No custom defined options but keep for clarity
const options = {
maintainAspectRatio: false,
animation: false
}
$: sizeChanged(width, height)
</script>
<div class="chart-container">
<Radar {data} {options} width={size} height={size}/>
</div>
<style>
.chart-container {
margin: auto;
position: relative;
}
</style>