Skip to content

Commit

Permalink
ui: fixed on bar chart component
Browse files Browse the repository at this point in the history
This commit:
- Updates z-index of legend, so it doesn't get on top of tooltip
- Fixes the case when there is a single bar (previously not loading)
- Tooltip shows the value with unit
- The tooltip fixes to 1 decimal points when the value is a decimal
- Adds new stories for single bar and chart with scale of `Duration`
and `Bytes`

Release note: None
  • Loading branch information
maryliag committed Jun 13, 2022
1 parent 48954b5 commit 09d201e
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,15 @@ function genValuesInRange(range: [number, number], length: number): number[] {
const mockData: AlignedData = [
generateTimestampsMillis(1546300800, 20),
genValuesInRange([0, 100], 20),
genValuesInRange([0, 100], 20),
genValuesInRange([0, 100], 20),
genValuesInRange([0, 10000], 20),
genValuesInRange([0, 100000], 20),
];
const mockDataSingle: AlignedData = [[1654115121], [0], [1], [2]];
const mockDataDuration: AlignedData = [
generateTimestampsMillis(1546300800, 20),
genValuesInRange([0, 1e7], 20),
genValuesInRange([0, 1e7], 20),
genValuesInRange([0, 1e7], 20),
];

const mockOpts: Partial<Options> = {
Expand All @@ -58,7 +65,7 @@ storiesOf("BarGraphTimeSeries", module)
alignedData={mockData}
uPlotOptions={mockOpts}
tooltip={
<div>This is an example stacked bar graph axis unit = count.</div>
<div>This is an example stacked bar graph axis unit = Count.</div>
}
yAxisUnits={AxisUnits.Count}
/>
Expand All @@ -85,4 +92,43 @@ storiesOf("BarGraphTimeSeries", module)
yAxisUnits={AxisUnits.Percentage}
/>
);
})
.add("with single stacked multi-series", () => {
return (
<BarGraphTimeSeries
title="Example one Stacked - Count"
alignedData={mockDataSingle}
uPlotOptions={mockOpts}
tooltip={
<div>This is an example stacked bar graph axis unit = Count.</div>
}
yAxisUnits={AxisUnits.Count}
/>
);
})
.add("with duration stacked multi-series", () => {
return (
<BarGraphTimeSeries
title="Example one Stacked - Duration"
alignedData={mockDataDuration}
uPlotOptions={mockOpts}
tooltip={
<div>This is an example stacked bar graph axis unit = Duration.</div>
}
yAxisUnits={AxisUnits.Duration}
/>
);
})
.add("with bytes stacked multi-series", () => {
return (
<BarGraphTimeSeries
title="Example one Stacked - Bytes"
alignedData={mockDataDuration}
uPlotOptions={mockOpts}
tooltip={
<div>This is an example stacked bar graph axis unit = Bytes.</div>
}
yAxisUnits={AxisUnits.Bytes}
/>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
text-align: left;
font-size: 10px;
margin-top: 20px;
z-index: 100;
z-index: 1;
width: fit-content;
padding: 10px;
}
Expand Down
10 changes: 5 additions & 5 deletions pkg/ui/workspaces/cluster-ui/src/graphs/bargraph/bars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@

import { merge } from "lodash";
import uPlot, { Options, Band, AlignedData } from "uplot";
import {
// AxisUnits,
AxisDomain,
} from "../utils/domain";
import { AxisUnits, AxisDomain } from "../utils/domain";
import { barTooltipPlugin } from "./plugins";

const seriesPalette = [
Expand Down Expand Up @@ -89,12 +86,14 @@ export const getStackedBarOpts = (
userOptions: Partial<Options>,
xAxisDomain: AxisDomain,
yAxisDomain: AxisDomain,
yyAxisUnits: AxisUnits,
colourPalette = seriesPalette,
): Options => {
const options = getBarChartOpts(
userOptions,
xAxisDomain,
yAxisDomain,
yyAxisUnits,
colourPalette,
);

Expand Down Expand Up @@ -140,6 +139,7 @@ export const getBarChartOpts = (
userOptions: Partial<Options>,
xAxisDomain: AxisDomain,
yAxisDomain: AxisDomain,
yAxisUnits: AxisUnits,
colourPalette = seriesPalette,
): Options => {
const { series, ...providedOpts } = userOptions;
Expand Down Expand Up @@ -188,7 +188,7 @@ export const getBarChartOpts = (
...s,
})),
],
plugins: [barTooltipPlugin()],
plugins: [barTooltipPlugin(yAxisUnits)],
};

const combinedOpts = merge(opts, providedOpts);
Expand Down
6 changes: 3 additions & 3 deletions pkg/ui/workspaces/cluster-ui/src/graphs/bargraph/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,8 @@ export const BarGraphTimeSeries: React.FC<BarGraphTimeSeriesProps> = ({
yAxisUnits,
}) => {
const graphRef = useRef<HTMLDivElement>(null);
const samplingIntervalMillis = alignedData[0].length
? alignedData[0][1] - alignedData[0][0]
: 0;
const samplingIntervalMillis =
alignedData[0].length > 1 ? alignedData[0][1] - alignedData[0][0] : 1e3;

useEffect(() => {
if (!alignedData) return;
Expand All @@ -68,6 +67,7 @@ export const BarGraphTimeSeries: React.FC<BarGraphTimeSeriesProps> = ({
uPlotOptions,
xAxisDomain,
yAxisDomain,
yAxisUnits,
colourPalette,
);

Expand Down
34 changes: 28 additions & 6 deletions pkg/ui/workspaces/cluster-ui/src/graphs/bargraph/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@
// licenses/APL.txt.

import uPlot, { Plugin } from "uplot";
import { formatTimeStamp } from "../utils/domain";
import { AxisUnits, formatTimeStamp } from "../utils/domain";
import { Bytes, Duration, Percentage, Count } from "../../util";

// Fallback color for series stroke if one is not defined.
const DEFAULT_STROKE = "#7e89a9";

// Generate a series legend within the provided div showing the data points
// relative to the cursor position.
const generateSeriesLegend = (uPlot: uPlot, seriesLegend: HTMLDivElement) => {
const generateSeriesLegend = (
uPlot: uPlot,
seriesLegend: HTMLDivElement,
yAxisUnits: AxisUnits,
) => {
// idx is the closest data index to the cursor position.
const { idx } = uPlot.cursor;

Expand Down Expand Up @@ -62,8 +67,11 @@ const generateSeriesLegend = (uPlot: uPlot, seriesLegend: HTMLDivElement) => {
value.style.fontFamily = "'Source Sans Pro', sans-serif";
value.textContent =
series.value instanceof Function && dataValue
? String(series.value(uPlot, dataValue, index, idx))
: String(dataValue);
? getFormattedValue(
Number(series.value(uPlot, dataValue, index, idx)),
yAxisUnits,
)
: getFormattedValue(dataValue, yAxisUnits);

container.appendChild(colorBox);
container.appendChild(label);
Expand All @@ -73,8 +81,22 @@ const generateSeriesLegend = (uPlot: uPlot, seriesLegend: HTMLDivElement) => {
});
};

// Formats the value according to its unit.
function getFormattedValue(value: number, yAxisUnits: AxisUnits): string {
switch (yAxisUnits) {
case AxisUnits.Bytes:
return Bytes(value);
case AxisUnits.Duration:
return Duration(value);
case AxisUnits.Percentage:
return Percentage(value, 1);
default:
return Count(value);
}
}

// Tooltip legend plugin for bar charts.
export function barTooltipPlugin(): Plugin {
export function barTooltipPlugin(yAxis: AxisUnits): Plugin {
const cursorToolTip = {
tooltip: document.createElement("div"),
timeStamp: document.createElement("div"),
Expand All @@ -91,7 +113,7 @@ export function barTooltipPlugin(): Plugin {
timeStamp.textContent = formatTimeStamp(closestDataPointTimeMillis);

// Generating the series legend based on current state of µPlot
generateSeriesLegend(u, seriesLegend);
generateSeriesLegend(u, seriesLegend, yAxis);

// set the position of the Tooltip. Adjusting the tooltip away from the
// cursor for readability.
Expand Down
33 changes: 29 additions & 4 deletions pkg/ui/workspaces/cluster-ui/src/util/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const byteUnits: string[] = [
"YiB",
];
export const durationUnits: string[] = ["ns", "µs", "ms", "s"];
export const countUnits: string[] = ["", "k", "m", "b"];

interface UnitValue {
value: number;
Expand All @@ -38,7 +39,7 @@ export function ComputePrefixExponent(
value: number,
prefixMultiple: number,
prefixList: string[],
) {
): number {
// Compute the metric prefix that will be used to label the axis.
let maxUnits = Math.abs(value);
let prefixScale: number;
Expand Down Expand Up @@ -102,7 +103,7 @@ export function Bytes(bytes: number): string {
* Cast bytes to provided scale units
*/
// tslint:disable-next-line: variable-name
export const BytesFitScale = (scale: string) => (bytes: number) => {
export const BytesFitScale = (scale: string) => (bytes: number): string => {
if (!bytes) {
return `0.00 ${scale}`;
}
Expand Down Expand Up @@ -156,7 +157,9 @@ export function Duration(nanoseconds: number): string {
* Cast nanoseconds to provided scale units
*/
// tslint:disable-next-line: variable-name
export const DurationFitScale = (scale: string) => (nanoseconds: number) => {
export const DurationFitScale = (scale: string) => (
nanoseconds: number,
): string => {
if (!nanoseconds) {
return `0.00 ${scale}`;
}
Expand All @@ -166,7 +169,7 @@ export const DurationFitScale = (scale: string) => (nanoseconds: number) => {

export const DATE_FORMAT = "MMM DD, YYYY [at] H:mm";

export function RenderCount(yesCount: Long, totalCount: Long) {
export function RenderCount(yesCount: Long, totalCount: Long): string {
if (longToInt(yesCount) == 0) {
return "No";
}
Expand All @@ -176,3 +179,25 @@ export function RenderCount(yesCount: Long, totalCount: Long) {
const noCount = longToInt(totalCount) - longToInt(yesCount);
return `${longToInt(yesCount)} Yes / ${noCount} No`;
}

/**
* ComputeCountScale calculates an appropriate scale factor and unit to use
* to display a given count value, without actually converting the value.
*/
function ComputeCountScale(count: number): UnitValue {
const scale = ComputePrefixExponent(count, 1000, countUnits);
return {
value: Math.pow(1000, scale),
units: countUnits[scale],
};
}

/**
* Count creates a string representation for a count.
*/
export function Count(count: number): string {
const scale = ComputeCountScale(count);
const unitVal = count / scale.value;
const fractionDigits = Number.isInteger(unitVal) ? 0 : 1;
return unitVal.toFixed(fractionDigits) + " " + scale.units;
}

0 comments on commit 09d201e

Please sign in to comment.