replace plotTable with new bootstrap plotGrid component

- helps with narrow window sizes
- plotTable kept for now
This commit is contained in:
Christoph Kluge 2024-10-02 17:48:46 +02:00
parent 0ff5c4bedd
commit 7243dbe763
12 changed files with 305 additions and 224 deletions

View File

@ -28,7 +28,7 @@
} from "./generic/utils.js"; } from "./generic/utils.js";
import PlotSelection from "./analysis/PlotSelection.svelte"; import PlotSelection from "./analysis/PlotSelection.svelte";
import Filters from "./generic/Filters.svelte"; import Filters from "./generic/Filters.svelte";
import PlotTable from "./generic/PlotTable.svelte"; import PlotGrid from "./generic/PlotGrid.svelte";
import Histogram from "./generic/plots/Histogram.svelte"; import Histogram from "./generic/plots/Histogram.svelte";
import Pie, { colors } from "./generic/plots/Pie.svelte"; import Pie, { colors } from "./generic/plots/Pie.svelte";
import ScatterPlot from "./generic/plots/Scatter.svelte"; import ScatterPlot from "./generic/plots/Scatter.svelte";
@ -70,6 +70,8 @@
...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()]), ...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()]),
]; ];
$: console.log(">>> CLUSTER", cluster)
const sortOptions = [ const sortOptions = [
{ key: "totalWalltime", label: "Walltime" }, { key: "totalWalltime", label: "Walltime" },
{ key: "totalNodeHours", label: "Node Hours" }, { key: "totalNodeHours", label: "Node Hours" },
@ -523,7 +525,7 @@
</Row> </Row>
<Row> <Row>
<Col> <Col>
<PlotTable <PlotGrid
let:item let:item
let:width let:width
renderFor="analysis" renderFor="analysis"
@ -551,7 +553,7 @@
ylabel="Normalized Hours" ylabel="Normalized Hours"
yunit="Hours" yunit="Hours"
/> />
</PlotTable> </PlotGrid>
</Col> </Col>
</Row> </Row>
<br /> <br />
@ -569,7 +571,7 @@
</Row> </Row>
<Row> <Row>
<Col> <Col>
<PlotTable <PlotGrid
let:item let:item
let:width let:width
renderFor="analysis" renderFor="analysis"
@ -595,7 +597,7 @@
Y={item.f2} Y={item.f2}
S={$footprintsQuery.data.footprints.timeWeights.nodeHours} S={$footprintsQuery.data.footprints.timeWeights.nodeHours}
/> />
</PlotTable> </PlotGrid>
</Col> </Col>
</Row> </Row>
{/if} {/if}

View File

@ -38,7 +38,7 @@
import JobSummary from "./job/JobSummary.svelte"; import JobSummary from "./job/JobSummary.svelte";
import EnergySummary from "./job/EnergySummary.svelte"; import EnergySummary from "./job/EnergySummary.svelte";
import ConcurrentJobs from "./generic/helper/ConcurrentJobs.svelte"; import ConcurrentJobs from "./generic/helper/ConcurrentJobs.svelte";
import PlotTable from "./generic/PlotTable.svelte"; import PlotGrid from "./generic/PlotGrid.svelte";
import Roofline from "./generic/plots/Roofline.svelte"; import Roofline from "./generic/plots/Roofline.svelte";
import JobInfo from "./generic/joblist/JobInfo.svelte"; import JobInfo from "./generic/joblist/JobInfo.svelte";
import MetricSelection from "./generic/select/MetricSelection.svelte"; import MetricSelection from "./generic/select/MetricSelection.svelte";
@ -330,50 +330,55 @@
</Col> </Col>
{/if} {/if}
</Row> </Row>
<hr/> <hr class="mb-2"/>
<Row>
<Col> {#if $jobMetrics.error}
{#if $jobMetrics.error} <Row class="mt-2">
<Col>
{#if $initq.data.job.monitoringStatus == 0 || $initq.data.job.monitoringStatus == 2} {#if $initq.data.job.monitoringStatus == 0 || $initq.data.job.monitoringStatus == 2}
<Card body color="warning">Not monitored or archiving failed</Card> <Card body color="warning">Not monitored or archiving failed</Card>
<br /> <br />
{/if} {/if}
<Card body color="danger">{$jobMetrics.error.message}</Card> <Card body color="danger">{$jobMetrics.error.message}</Card>
{:else if $jobMetrics.fetching} </Col>
</Row>
{:else if $jobMetrics.fetching}
<Row class="mt-2">
<Col>
<Spinner secondary /> <Spinner secondary />
{:else if $initq?.data && $jobMetrics?.data?.jobMetrics} </Col>
<PlotTable </Row>
let:item {:else if $initq?.data && $jobMetrics?.data?.jobMetrics}
let:width <PlotGrid
renderFor="job" let:item
items={orderAndMap( let:width
groupByScope($jobMetrics.data.jobMetrics), renderFor="job"
selectedMetrics, items={orderAndMap(
)} groupByScope($jobMetrics.data.jobMetrics),
itemsPerRow={ccconfig.plot_view_plotsPerRow} selectedMetrics,
)}
itemsPerRow={ccconfig.plot_view_plotsPerRow}
>
{#if item.data}
<Metric
bind:this={plots[item.metric]}
on:more-loaded={({ detail }) => statsTable.moreLoaded(detail)}
job={$initq.data.job}
metricName={item.metric}
metricUnit={$initq.data.globalMetrics.find((gm) => gm.name == item.metric)?.unit}
nativeScope={$initq.data.globalMetrics.find((gm) => gm.name == item.metric)?.scope}
rawData={item.data.map((x) => x.metric)}
scopes={item.data.map((x) => x.scope)}
{width}
isShared={$initq.data.job.exclusive != 1}
/>
{:else}
<Card body color="warning" class="mt-2"
>No dataset returned for <code>{item.metric}</code></Card
> >
{#if item.data}
<Metric
bind:this={plots[item.metric]}
on:more-loaded={({ detail }) => statsTable.moreLoaded(detail)}
job={$initq.data.job}
metricName={item.metric}
metricUnit={$initq.data.globalMetrics.find((gm) => gm.name == item.metric)?.unit}
nativeScope={$initq.data.globalMetrics.find((gm) => gm.name == item.metric)?.scope}
rawData={item.data.map((x) => x.metric)}
scopes={item.data.map((x) => x.scope)}
{width}
isShared={$initq.data.job.exclusive != 1}
/>
{:else}
<Card body color="warning"
>No dataset returned for <code>{item.metric}</code></Card
>
{/if}
</PlotTable>
{/if} {/if}
</Col> </PlotGrid>
</Row> {/if}
</CardBody> </CardBody>
</Card> </Card>

View File

@ -29,7 +29,7 @@
init, init,
checkMetricDisabled, checkMetricDisabled,
} from "./generic/utils.js"; } from "./generic/utils.js";
import PlotTable from "./generic/PlotTable.svelte"; import PlotGrid from "./generic/PlotGrid.svelte";
import MetricPlot from "./generic/plots/MetricPlot.svelte"; import MetricPlot from "./generic/plots/MetricPlot.svelte";
import TimeSelection from "./generic/select/TimeSelection.svelte"; import TimeSelection from "./generic/select/TimeSelection.svelte";
import Refresher from "./generic/helper/Refresher.svelte"; import Refresher from "./generic/helper/Refresher.svelte";
@ -187,7 +187,7 @@
{:else if $nodeMetricsData.fetching || $initq.fetching} {:else if $nodeMetricsData.fetching || $initq.fetching}
<Spinner /> <Spinner />
{:else} {:else}
<PlotTable <PlotGrid
let:item let:item
let:width let:width
renderFor="node" renderFor="node"
@ -233,7 +233,7 @@
>No dataset returned for <code>{item.name}</code></Card >No dataset returned for <code>{item.name}</code></Card
> >
{/if} {/if}
</PlotTable> </PlotGrid>
{/if} {/if}
</Col> </Col>
</Row> </Row>

View File

@ -32,7 +32,7 @@
transformPerNodeDataForRoofline, transformPerNodeDataForRoofline,
} from "./generic/utils.js"; } from "./generic/utils.js";
import { scaleNumbers } from "./generic/units.js"; import { scaleNumbers } from "./generic/units.js";
import PlotTable from "./generic/PlotTable.svelte"; import PlotGrid from "./generic/PlotGrid.svelte";
import Roofline from "./generic/plots/Roofline.svelte"; import Roofline from "./generic/plots/Roofline.svelte";
import Pie, { colors } from "./generic/plots/Pie.svelte"; import Pie, { colors } from "./generic/plots/Pie.svelte";
import Histogram from "./generic/plots/Histogram.svelte"; import Histogram from "./generic/plots/Histogram.svelte";
@ -651,31 +651,27 @@
</Row> </Row>
<hr class="my-2" /> <hr class="my-2" />
{#if metricsInHistograms} {#if metricsInHistograms}
<Row cols={1}> {#key $mainQuery.data.stats[0].histMetrics}
<Col> <PlotGrid
{#key $mainQuery.data.stats[0].histMetrics} let:item
<PlotTable let:width
let:item renderFor="user"
let:width items={$mainQuery.data.stats[0].histMetrics}
renderFor="user" itemsPerRow={2}
items={$mainQuery.data.stats[0].histMetrics} >
itemsPerRow={2} <Histogram
> data={convert2uplot(item.data)}
<Histogram usesBins={true}
data={convert2uplot(item.data)} {width}
usesBins={true} height={250}
{width} title="Distribution of '{item.metric}' averages"
height={250} xlabel={`${item.metric} bin maximum ${item?.unit ? `[${item.unit}]` : ``}`}
title="Distribution of '{item.metric}' averages" xunit={item.unit}
xlabel={`${item.metric} bin maximum ${item?.unit ? `[${item.unit}]` : ``}`} ylabel="Number of Jobs"
xunit={item.unit} yunit="Jobs"
ylabel="Number of Jobs" />
yunit="Jobs" </PlotGrid>
/> {/key}
</PlotTable>
{/key}
</Col>
</Row>
{/if} {/if}
{/if} {/if}

View File

@ -28,7 +28,7 @@
init, init,
checkMetricDisabled, checkMetricDisabled,
} from "./generic/utils.js"; } from "./generic/utils.js";
import PlotTable from "./generic/PlotTable.svelte"; import PlotGrid from "./generic/PlotGrid.svelte";
import MetricPlot from "./generic/plots/MetricPlot.svelte"; import MetricPlot from "./generic/plots/MetricPlot.svelte";
import TimeSelection from "./generic/select/TimeSelection.svelte"; import TimeSelection from "./generic/select/TimeSelection.svelte";
import Refresher from "./generic/helper/Refresher.svelte"; import Refresher from "./generic/helper/Refresher.svelte";
@ -160,73 +160,77 @@
{/if} {/if}
</Row> </Row>
<br /> <br />
<Row> {#if $nodesQuery.error}
<Col> <Row>
{#if $nodesQuery.error} <Col>
<Card body color="danger">{$nodesQuery.error.message}</Card> <Card body color="danger">{$nodesQuery.error.message}</Card>
{:else if $nodesQuery.fetching || $initq.fetching} </Col>
</Row>
{:else if $nodesQuery.fetching || $initq.fetching}
<Row>
<Col>
<Spinner /> <Spinner />
{:else} </Col>
<PlotTable </Row>
let:item {:else}
let:width <PlotGrid
renderFor="systems" let:item
itemsPerRow={ccconfig.plot_view_plotsPerRow} let:width
items={$nodesQuery.data.nodeMetrics renderFor="systems"
.filter( itemsPerRow={ccconfig.plot_view_plotsPerRow}
(h) => items={$nodesQuery.data.nodeMetrics
h.host.includes(hostnameFilter) && .filter(
h.metrics.some( (h) =>
(m) => m.name == selectedMetric && m.scope == "node", h.host.includes(hostnameFilter) &&
), h.metrics.some(
) (m) => m.name == selectedMetric && m.scope == "node",
.map((h) => ({ ),
host: h.host, )
subCluster: h.subCluster, .map((h) => ({
data: h.metrics.find( host: h.host,
(m) => m.name == selectedMetric && m.scope == "node", subCluster: h.subCluster,
), data: h.metrics.find(
disabled: checkMetricDisabled( (m) => m.name == selectedMetric && m.scope == "node",
selectedMetric, ),
cluster, disabled: checkMetricDisabled(
h.subCluster, selectedMetric,
), cluster,
})) h.subCluster,
.sort((a, b) => a.host.localeCompare(b.host))} ),
}))
.sort((a, b) => a.host.localeCompare(b.host))}
>
<h4 style="width: 100%; text-align: center;">
<a
style="display: block;padding-top: 15px;"
href="/monitoring/node/{cluster}/{item.host}"
>{item.host} ({item.subCluster})</a
>
</h4>
{#if item.disabled === false && item.data}
<MetricPlot
{width}
height={plotHeight}
timestep={item.data.metric.timestep}
series={item.data.metric.series}
metric={item.data.name}
cluster={clusters.find((c) => c.name == cluster)}
subCluster={item.subCluster}
forNode={true}
/>
{: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}
<Card
style="margin-left: 2rem;margin-right: 2rem;"
body
color="warning"
>No dataset returned for <code>{selectedMetric}</code></Card
> >
<h4 style="width: 100%; text-align: center;">
<a
style="display: block;padding-top: 15px;"
href="/monitoring/node/{cluster}/{item.host}"
>{item.host} ({item.subCluster})</a
>
</h4>
{#if item.disabled === false && item.data}
<MetricPlot
{width}
height={plotHeight}
timestep={item.data.metric.timestep}
series={item.data.metric.series}
metric={item.data.name}
cluster={clusters.find((c) => c.name == cluster)}
subCluster={item.subCluster}
forNode={true}
/>
{: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}
<Card
style="margin-left: 2rem;margin-right: 2rem;"
body
color="warning"
>No dataset returned for <code>{selectedMetric}</code></Card
>
{/if}
</PlotTable>
{/if} {/if}
</Col> </PlotGrid>
</Row> {/if}

View File

@ -30,7 +30,7 @@
} from "./generic/utils.js"; } from "./generic/utils.js";
import JobList from "./generic/JobList.svelte"; import JobList from "./generic/JobList.svelte";
import Filters from "./generic/Filters.svelte"; import Filters from "./generic/Filters.svelte";
import PlotTable from "./generic/PlotTable.svelte"; import PlotGrid from "./generic/PlotGrid.svelte";
import Histogram from "./generic/plots/Histogram.svelte"; import Histogram from "./generic/plots/Histogram.svelte";
import MetricSelection from "./generic/select/MetricSelection.svelte"; import MetricSelection from "./generic/select/MetricSelection.svelte";
import HistogramSelection from "./generic/select/HistogramSelection.svelte"; import HistogramSelection from "./generic/select/HistogramSelection.svelte";
@ -162,7 +162,7 @@
</Col> </Col>
</Row> </Row>
<br /> <br />
<Row> <Row cols={{ xs: 1, md: 3}}>
{#if $stats.error} {#if $stats.error}
<Col> <Col>
<Card body color="danger">{$stats.error.message}</Card> <Card body color="danger">{$stats.error.message}</Card>
@ -172,7 +172,7 @@
<Spinner secondary /> <Spinner secondary />
</Col> </Col>
{:else} {:else}
<Col xs="4"> <Col>
<Table> <Table>
<tbody> <tbody>
<tr> <tr>
@ -210,72 +210,77 @@
</tbody> </tbody>
</Table> </Table>
</Col> </Col>
<div class="col-4 text-center" bind:clientWidth={w1}> <Col class="text-center">
{#key $stats.data.jobsStatistics[0].histDuration} <div bind:clientWidth={w1}>
<Histogram {#key $stats.data.jobsStatistics[0].histDuration}
data={convert2uplot($stats.data.jobsStatistics[0].histDuration)} <Histogram
width={w1 - 25} data={convert2uplot($stats.data.jobsStatistics[0].histDuration)}
height={histogramHeight} width={w1 - 25}
title="Duration Distribution" height={histogramHeight}
xlabel="Current Runtimes" title="Duration Distribution"
xunit="Hours" xlabel="Current Runtimes"
ylabel="Number of Jobs" xunit="Hours"
yunit="Jobs" ylabel="Number of Jobs"
/> yunit="Jobs"
{/key} />
</div> {/key}
<div class="col-4 text-center" bind:clientWidth={w2}> </div>
{#key $stats.data.jobsStatistics[0].histNumNodes} </Col>
<Histogram <Col class="text-center">
data={convert2uplot($stats.data.jobsStatistics[0].histNumNodes)} <div bind:clientWidth={w2}>
width={w2 - 25} {#key $stats.data.jobsStatistics[0].histNumNodes}
height={histogramHeight} <Histogram
title="Number of Nodes Distribution" data={convert2uplot($stats.data.jobsStatistics[0].histNumNodes)}
xlabel="Allocated Nodes" width={w2 - 25}
xunit="Nodes" height={histogramHeight}
ylabel="Number of Jobs" title="Number of Nodes Distribution"
yunit="Jobs" xlabel="Allocated Nodes"
/> xunit="Nodes"
{/key} ylabel="Number of Jobs"
</div> yunit="Jobs"
/>
{/key}
</div>
</Col>
{/if} {/if}
</Row> </Row>
{#if metricsInHistograms} {#if metricsInHistograms}
<Row> {#if $stats.error}
{#if $stats.error} <Row>
<Col> <Col>
<Card body color="danger">{$stats.error.message}</Card> <Card body color="danger">{$stats.error.message}</Card>
</Col> </Col>
{:else if !$stats.data} </Row>
{:else if !$stats.data}
<Row>
<Col> <Col>
<Spinner secondary /> <Spinner secondary />
</Col> </Col>
{:else} </Row>
<Col> {:else}
{#key $stats.data.jobsStatistics[0].histMetrics} {#key $stats.data.jobsStatistics[0].histMetrics}
<PlotTable <PlotGrid
let:item let:item
let:width let:width
renderFor="user" renderFor="user"
items={$stats.data.jobsStatistics[0].histMetrics} items={$stats.data.jobsStatistics[0].histMetrics}
itemsPerRow={3} itemsPerRow={3}
> >
<Histogram <Histogram
data={convert2uplot(item.data)} data={convert2uplot(item.data)}
usesBins={true} usesBins={true}
{width} {width}
height={250} height={250}
title="Distribution of '{item.metric} ({item.stat})' footprints" title="Distribution of '{item.metric} ({item.stat})' footprints"
xlabel={`${item.metric} bin maximum ${item?.unit ? `[${item.unit}]` : ``}`} xlabel={`${item.metric} bin maximum ${item?.unit ? `[${item.unit}]` : ``}`}
xunit={item.unit} xunit={item.unit}
ylabel="Number of Jobs" ylabel="Number of Jobs"
yunit="Jobs" yunit="Jobs"
/> />
</PlotTable> </PlotGrid>
{/key} {/key}
</Col> {/if}
{/if}
</Row>
{/if} {/if}
<br /> <br />
<Row> <Row>

View File

@ -6,7 +6,8 @@ filterPresets.cluster = cluster
new Analysis({ new Analysis({
target: document.getElementById('svelte-app'), target: document.getElementById('svelte-app'),
props: { props: {
filterPresets: filterPresets filterPresets: filterPresets,
cluster: cluster
}, },
context: new Map([ context: new Map([
['cc-config', clusterCockpitConfig] ['cc-config', clusterCockpitConfig]

View File

@ -0,0 +1,60 @@
<!--
@component Organized display of plots as bootstrap (sveltestrap) grid
Properties:
- `itemsPerRow Number`: Elements to render per row
- `items [Any]`: List of plot components to render
- `renderFor String`: If 'job', filter disabled metrics
-->
<script>
import {
Row,
Col,
} from "@sveltestrap/sveltestrap";
export let itemsPerRow
export let items
export let renderFor
let rows = []
let colWidth;
const isPlaceholder = x => x._is_placeholder === true
function tile(items, itemsPerRow) {
const rows = []
for (let ri = 0; ri < items.length; ri += itemsPerRow) {
const row = []
for (let ci = 0; ci < itemsPerRow; ci += 1) {
if (ri + ci < items.length)
row.push(items[ri + ci])
else
row.push({ _is_placeholder: true, ri, ci })
}
rows.push(row)
}
return rows
}
$: if (renderFor === 'job') {
rows = tile(items.filter(item => item.disabled === false), itemsPerRow)
} else {
rows = tile(items, itemsPerRow)
}
</script>
{#each rows as row}
<Row cols={{ xs: 1, sm: 1, md: 2, lg: itemsPerRow}}>
{#each row as item (item)}
<Col class="px-1">
<div bind:clientWidth={colWidth}>
{#if !isPlaceholder(item)}
<slot item={item} width={colWidth}/>
{/if}
</div>
</Col>
{/each}
</Row>
{/each}

View File

@ -549,6 +549,10 @@
onMount(() => { onMount(() => {
if (series[0].data.length > 0) { if (series[0].data.length > 0) {
if (forNode) {
plotWrapper.style.paddingTop = "0.5rem"
plotWrapper.style.paddingBottom = "0.5rem"
}
plotWrapper.style.backgroundColor = backgroundColor(); plotWrapper.style.backgroundColor = backgroundColor();
render(); render();
} }
@ -562,7 +566,7 @@
</script> </script>
{#if series[0].data.length > 0} {#if series[0].data.length > 0}
<div bind:this={plotWrapper} class="cc-plot"></div> <div bind:this={plotWrapper} class="cc-plot"/>
{:else} {:else}
<Card class="mx-4" body color="warning" <Card class="mx-4" body color="warning"
>Cannot render plot: No series data returned for <code>{metric}</code></Card >Cannot render plot: No series data returned for <code>{metric}</code></Card

View File

@ -46,8 +46,8 @@
return; return;
const [minX, minY] = [0., 0.]; const [minX, minY] = [0., 0.];
let maxX = X.reduce((maxX, x) => Math.max(maxX, x), minX); let maxX = X ? X.reduce((maxX, x) => Math.max(maxX, x), minX) : 1.0;
let maxY = Y.reduce((maxY, y) => Math.max(maxY, y), minY); let maxY = Y ? Y.reduce((maxY, y) => Math.max(maxY, y), minY) : 1.0;
const w = width - paddingLeft - paddingRight; const w = width - paddingLeft - paddingRight;
const h = height - paddingTop - paddingBottom; const h = height - paddingTop - paddingBottom;
@ -68,24 +68,26 @@
// Draw Data // Draw Data
let size = 3 let size = 3
if (S) { if (S && X && Y) {
let max = S.reduce((max, s, i) => (X[i] == null || Y[i] == null || Number.isNaN(X[i]) || Number.isNaN(Y[i])) ? max : Math.max(max, s), 0) let max = S.reduce((max, s, i) => (X[i] == null || Y[i] == null || Number.isNaN(X[i]) || Number.isNaN(Y[i])) ? max : Math.max(max, s), 0)
size = (w / 15) / max size = (w / 15) / max
} }
ctx.fillStyle = color; ctx.fillStyle = color;
for (let i = 0; i < X.length; i++) { if (X?.length > 0) {
let x = X[i], y = Y[i]; for (let i = 0; i < X.length; i++) {
if (x == null || y == null || Number.isNaN(x) || Number.isNaN(y)) let x = X[i], y = Y[i];
continue; if (x == null || y == null || Number.isNaN(x) || Number.isNaN(y))
continue;
const s = S ? S[i] * size : size; const s = S ? S[i] * size : size;
const px = getCanvasX(x); const px = getCanvasX(x);
const py = getCanvasY(y); const py = getCanvasY(y);
ctx.beginPath(); ctx.beginPath();
ctx.arc(px, py, s, 0, Math.PI * 2, false); ctx.arc(px, py, s, 0, Math.PI * 2, false);
ctx.fill(); ctx.fill();
}
} }
// Axes // Axes

View File

@ -397,16 +397,18 @@ function getMetricConfigDeep(metric, cluster, subCluster) {
export function convert2uplot(canvasData) { export function convert2uplot(canvasData) {
// Prep: Uplot Data Structure // Prep: Uplot Data Structure
let uplotData = [[],[]] // [X, Y1, Y2, ...] let uplotData = [[],[]] // [X, Y1, Y2, ...]
// Iterate // Iterate if exists
canvasData.forEach( cd => { if (canvasData) {
if (Object.keys(cd).length == 4) { // MetricHisto Datafromat canvasData.forEach( cd => {
uplotData[0].push(cd?.max ? cd.max : 0) if (Object.keys(cd).length == 4) { // MetricHisto Datafromat
uplotData[1].push(cd.count) uplotData[0].push(cd?.max ? cd.max : 0)
} else { // Default uplotData[1].push(cd.count)
uplotData[0].push(cd.value) } else { // Default
uplotData[1].push(cd.count) uplotData[0].push(cd.value)
} uplotData[1].push(cd.count)
}) }
})
}
return uplotData return uplotData
} }

View File

@ -171,7 +171,7 @@
); );
</script> </script>
<InputGroup> <InputGroup class="mt-2">
<InputGroupText style="min-width: 150px;"> <InputGroupText style="min-width: 150px;">
{metricName} ({unit}) {metricName} ({unit})
</InputGroupText> </InputGroupText>