mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-11-10 08:57:25 +01:00
feat: add hover-legend to histograms & metricplots
This commit is contained in:
parent
742c2e399e
commit
423e800d9e
@ -318,6 +318,7 @@
|
||||
scopes={item.data.map((x) => x.scope)}
|
||||
{width}
|
||||
isShared={$initq.data.job.exclusive != 1}
|
||||
resources={$initq.data.job.resources}
|
||||
/>
|
||||
{:else}
|
||||
<Card body color="warning"
|
||||
|
@ -89,6 +89,7 @@
|
||||
timestep={data.timestep}
|
||||
scope={selectedScope} metric={metricName}
|
||||
series={series}
|
||||
isShared={isShared} />
|
||||
isShared={isShared}
|
||||
resources={job.resources} />
|
||||
{/if}
|
||||
{/key}
|
||||
|
@ -203,6 +203,7 @@
|
||||
subCluster={$nodeMetricsData.data.nodeMetrics[0]
|
||||
.subCluster}
|
||||
series={item.metric.series}
|
||||
resources={[{hostname: hostname}]}
|
||||
/>
|
||||
{:else if item.disabled === true && item.metric}
|
||||
<Card
|
||||
|
@ -136,7 +136,8 @@
|
||||
series={item.data.metric.series}
|
||||
metric={item.data.name}
|
||||
cluster={clusters.find(c => c.name == cluster)}
|
||||
subCluster={item.subCluster} />
|
||||
subCluster={item.subCluster}
|
||||
resources={[{hostname: item.host}]}/>
|
||||
{:else if item.disabled === true && item.data}
|
||||
<Card style="margin-left: 2rem;margin-right: 2rem;" body color="info">Metric disabled for subcluster <code>{selectedMetric}:{item.subCluster}</code></Card>
|
||||
{:else}
|
||||
|
@ -136,6 +136,7 @@
|
||||
{cluster}
|
||||
subCluster={job.subCluster}
|
||||
isShared={(job.exclusive != 1)}
|
||||
resources={job.resources}
|
||||
/>
|
||||
{:else if metric.disabled == true && metric.data}
|
||||
<Card body color="info">Metric disabled for subcluster <code>{metric.data.name}:{job.subCluster}</code></Card>
|
||||
|
@ -39,8 +39,62 @@
|
||||
return renderer(u, seriesIdx, idx0, idx1, extendGap, buildClip);
|
||||
}
|
||||
|
||||
// converts the legend into a simple tooltip
|
||||
function legendAsTooltipPlugin({ className, style = { backgroundColor:"rgba(255, 249, 196, 0.92)", color: "black" } } = {}) {
|
||||
let legendEl;
|
||||
|
||||
function init(u, opts) {
|
||||
legendEl = u.root.querySelector(".u-legend");
|
||||
|
||||
legendEl.classList.remove("u-inline");
|
||||
className && legendEl.classList.add(className);
|
||||
|
||||
uPlot.assign(legendEl.style, {
|
||||
textAlign: "left",
|
||||
pointerEvents: "none",
|
||||
display: "none",
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 0,
|
||||
zIndex: 100,
|
||||
boxShadow: "2px 2px 10px rgba(0,0,0,0.5)",
|
||||
...style
|
||||
});
|
||||
|
||||
// hide series color markers
|
||||
const idents = legendEl.querySelectorAll(".u-marker");
|
||||
|
||||
for (let i = 0; i < idents.length; i++)
|
||||
idents[i].style.display = "none";
|
||||
|
||||
const overEl = u.over;
|
||||
overEl.style.overflow = "visible";
|
||||
|
||||
// move legend into plot bounds
|
||||
overEl.appendChild(legendEl);
|
||||
|
||||
// show/hide tooltip on enter/exit
|
||||
overEl.addEventListener("mouseenter", () => {legendEl.style.display = null;});
|
||||
overEl.addEventListener("mouseleave", () => {legendEl.style.display = "none";});
|
||||
|
||||
// let tooltip exit plot
|
||||
// overEl.style.overflow = "visible";
|
||||
}
|
||||
|
||||
function update(u) {
|
||||
const { left, top } = u.cursor;
|
||||
legendEl.style.transform = "translate(" + (left + 15) + "px, " + (top + 15) + "px)";
|
||||
}
|
||||
|
||||
return {
|
||||
hooks: {
|
||||
init: init,
|
||||
setCursor: update,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let plotWrapper = null
|
||||
let legendWrapper = null
|
||||
let uplot = null
|
||||
let timeoutId = null
|
||||
|
||||
@ -48,6 +102,9 @@
|
||||
let opts = {
|
||||
width: width,
|
||||
height: height,
|
||||
plugins: [
|
||||
legendAsTooltipPlugin()
|
||||
],
|
||||
cursor: {
|
||||
points: {
|
||||
size: (u, seriesIdx) => u.series[seriesIdx].points.size * 2.5,
|
||||
@ -98,15 +155,6 @@
|
||||
values: (_, t) => t.map(v => formatNumber(v)),
|
||||
},
|
||||
],
|
||||
legend : {
|
||||
mount: (self, legend) => {
|
||||
legendWrapper.appendChild(legend)
|
||||
},
|
||||
markers: {
|
||||
show: false,
|
||||
stroke: "#000000"
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
label: xunit,
|
||||
@ -158,9 +206,7 @@
|
||||
</script>
|
||||
|
||||
{#if data.length > 0}
|
||||
<div bind:this={plotWrapper}>
|
||||
<div bind:this={legendWrapper}/>
|
||||
</div>
|
||||
<div bind:this={plotWrapper}/>
|
||||
{:else}
|
||||
<Card class="mx-4" body color="warning">Cannot render histogram: No data!</Card>
|
||||
{/if}
|
||||
|
@ -26,16 +26,17 @@
|
||||
import { getContext, onMount, onDestroy } from 'svelte'
|
||||
import { Card } from 'sveltestrap'
|
||||
|
||||
export let metric
|
||||
export let scope = 'node'
|
||||
export let resources = []
|
||||
export let width
|
||||
export let height
|
||||
export let timestep
|
||||
export let series
|
||||
export let useStatsSeries = null
|
||||
export let statisticsSeries = null
|
||||
export let cluster
|
||||
export let subCluster
|
||||
export let metric
|
||||
export let useStatsSeries = null
|
||||
export let scope = 'node'
|
||||
export let isShared = false
|
||||
|
||||
if (useStatsSeries == null)
|
||||
@ -53,6 +54,62 @@
|
||||
const backgroundColors = { normal: 'rgba(255, 255, 255, 1.0)', caution: 'rgba(255, 128, 0, 0.3)', alert: 'rgba(255, 0, 0, 0.3)' }
|
||||
const thresholds = findThresholds(metricConfig, scope, typeof subCluster == 'string' ? cluster.subClusters.find(sc => sc.name == subCluster) : subCluster)
|
||||
|
||||
// converts the legend into a simple tooltip
|
||||
function legendAsTooltipPlugin({ className, style = { backgroundColor:"rgba(255, 249, 196, 0.92)", color: "black" } } = {}) {
|
||||
let legendEl;
|
||||
|
||||
function init(u, opts) {
|
||||
legendEl = u.root.querySelector(".u-legend");
|
||||
|
||||
legendEl.classList.remove("u-inline");
|
||||
className && legendEl.classList.add(className);
|
||||
|
||||
uPlot.assign(legendEl.style, {
|
||||
textAlign: "left",
|
||||
pointerEvents: "none",
|
||||
display: "none",
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 0,
|
||||
zIndex: 100,
|
||||
boxShadow: "2px 2px 10px rgba(0,0,0,0.5)",
|
||||
...style
|
||||
});
|
||||
|
||||
// hide series color markers
|
||||
const idents = legendEl.querySelectorAll(".u-marker");
|
||||
|
||||
for (let i = 0; i < idents.length; i++)
|
||||
idents[i].style.display = "none";
|
||||
|
||||
const overEl = u.over;
|
||||
overEl.style.overflow = "visible";
|
||||
|
||||
// move legend into plot bounds
|
||||
overEl.appendChild(legendEl);
|
||||
|
||||
// show/hide tooltip on enter/exit
|
||||
overEl.addEventListener("mouseenter", () => {legendEl.style.display = null;});
|
||||
overEl.addEventListener("mouseleave", () => {legendEl.style.display = "none";});
|
||||
|
||||
// let tooltip exit plot
|
||||
// overEl.style.overflow = "visible";
|
||||
}
|
||||
|
||||
function update(u) {
|
||||
const { left, top } = u.cursor;
|
||||
const width = u.over.querySelector(".u-legend").offsetWidth;
|
||||
legendEl.style.transform = "translate(" + (left - width - 15) + "px, " + (top + 15) + "px)";
|
||||
}
|
||||
|
||||
return {
|
||||
hooks: {
|
||||
init: init,
|
||||
setCursor: update,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function backgroundColor() {
|
||||
if (clusterCockpitConfig.plot_general_colorBackground == false
|
||||
|| !thresholds
|
||||
@ -93,7 +150,7 @@
|
||||
? (statisticsSeries.max.reduce((max, x) => Math.max(max, x), thresholds.normal) || thresholds.normal)
|
||||
: (series.reduce((max, series) => Math.max(max, series.statistics?.max), thresholds.normal) || thresholds.normal)
|
||||
: null
|
||||
const plotSeries = [{}]
|
||||
const plotSeries = [{label: 'Runtime', value: (u, ts, sidx, didx) => didx == null ? null : formatTime(ts)}]
|
||||
const plotData = [new Array(longestSeries)]
|
||||
for (let i = 0; i < longestSeries; i++) // TODO: Cache/Reuse this array?
|
||||
plotData[0][i] = i * timestep
|
||||
@ -103,9 +160,9 @@
|
||||
plotData.push(statisticsSeries.min)
|
||||
plotData.push(statisticsSeries.max)
|
||||
plotData.push(statisticsSeries.mean)
|
||||
plotSeries.push({ scale: 'y', width: lineWidth, stroke: 'red' })
|
||||
plotSeries.push({ scale: 'y', width: lineWidth, stroke: 'green' })
|
||||
plotSeries.push({ scale: 'y', width: lineWidth, stroke: 'black' })
|
||||
plotSeries.push({ label: 'min', scale: 'y', width: lineWidth, stroke: 'red' })
|
||||
plotSeries.push({ label: 'max', scale: 'y', width: lineWidth, stroke: 'green' })
|
||||
plotSeries.push({ label: 'mean', scale: 'y', width: lineWidth, stroke: 'black' })
|
||||
plotBands = [
|
||||
{ series: [2,3], fill: 'rgba(0,255,0,0.1)' },
|
||||
{ series: [3,1], fill: 'rgba(255,0,0,0.1)' }
|
||||
@ -114,6 +171,9 @@
|
||||
for (let i = 0; i < series.length; i++) {
|
||||
plotData.push(series[i].data)
|
||||
plotSeries.push({
|
||||
label: scope === 'node' ? resources[i].hostname :
|
||||
// scope === 'accelerator' ? resources[0].accelerators[i] :
|
||||
scope + ' #' + (i+1),
|
||||
scale: 'y',
|
||||
width: lineWidth,
|
||||
stroke: lineColor(i, series.length)
|
||||
@ -124,6 +184,9 @@
|
||||
const opts = {
|
||||
width,
|
||||
height,
|
||||
plugins: [
|
||||
legendAsTooltipPlugin()
|
||||
],
|
||||
series: plotSeries,
|
||||
axes: [
|
||||
{
|
||||
@ -177,8 +240,7 @@
|
||||
x: { time: false },
|
||||
y: maxY ? { range: [0., maxY * 1.1] } : {}
|
||||
},
|
||||
cursor: { drag: { x: true, y: true } },
|
||||
legend: { show: false, live: false }
|
||||
cursor: { drag: { x: true, y: true } }
|
||||
}
|
||||
|
||||
// console.log(opts)
|
||||
@ -249,16 +311,21 @@
|
||||
}
|
||||
</script>
|
||||
<script context="module">
|
||||
|
||||
export function formatTime(t) {
|
||||
let h = Math.floor(t / 3600)
|
||||
let m = Math.floor((t % 3600) / 60)
|
||||
if (h == 0)
|
||||
return `${m}m`
|
||||
else if (m == 0)
|
||||
return `${h}h`
|
||||
else
|
||||
return `${h}:${m}h`
|
||||
if (t !== null) {
|
||||
if (isNaN(t)) {
|
||||
return t
|
||||
} else {
|
||||
let h = Math.floor(t / 3600)
|
||||
let m = Math.floor((t % 3600) / 60)
|
||||
if (h == 0)
|
||||
return `${m}m`
|
||||
else if (m == 0)
|
||||
return `${h}h`
|
||||
else
|
||||
return `${h}:${m}h`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function timeIncrs(timestep, maxX) {
|
||||
|
Loading…
Reference in New Issue
Block a user