Skip to content

Commit

Permalink
Add alerts visualization for line graph
Browse files Browse the repository at this point in the history
  • Loading branch information
jwbonner committed Nov 2, 2024
1 parent f7740f7 commit 05ee084
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 10 deletions.
Binary file modified docsSite/docs/tab-reference/img/line-graph-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docsSite/docs/tab-reference/img/line-graph-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docsSite/docs/tab-reference/img/line-graph-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 9 additions & 3 deletions docsSite/docs/tab-reference/line-graph.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
sidebar_position: 1
---

import Image2 from './img/line-graph-2.png';
import Image3 from './img/line-graph-3.png';
import Image4 from './img/line-graph-4.png';

# 📉 Line Graph

Expand All @@ -29,11 +29,17 @@ To get started, drag a field to one of the three sections (left, right, or discr
The color and line style of each field can be customized by clicking the colored icon or right-clicking on the field name.
:::

:::tip
Data from the WPILib [persistent alerts](https://docs.wpilib.org/en/latest/docs/software/telemetry/persistent-alerts.html) API can be visualized by adding the alerts group as a discrete field. An example visualization is shown below.

![Alerts visualization](./img/line-graph-2.png)
:::

### Adjusting Axes

By default, each axis adjusts its range based on the visible data. To disable auto-ranging and lock the range to its current min and max, click the three dots near the axis title and then `Lock Axis`. To manually adjust the range, choose `Edit Range...` and enter the desired values.

<img src={Image2} alt="Editing axis range" height="250" />
<img src={Image3} alt="Editing axis range" height="250" />

### Unit Conversion

Expand All @@ -43,7 +49,7 @@ To adjust the units for an axis, click the three dots near the axis title and th
To quickly enable or disable unit conversion, click the three dots near the axis title and choose `Recent Presets` or `Reset Units`.
:::

<img src={Image3} alt="Editing unit conversion" height="250" />
<img src={Image4} alt="Editing unit conversion" height="250" />

### Integration & Differentiation

Expand Down
2 changes: 2 additions & 0 deletions docsSite/docs/whats-new/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ New **integration** and **differentiation** options, along with a **streamlined

Want to visualize even more data at once? Try **popping out the line graph** to a separate window!

Users of the WPILib [persistent alerts](https://docs.wpilib.org/en/latest/docs/software/telemetry/persistent-alerts.html) API can now visualize errors, warnings, and info messages as a compact waterfall chart.

![Line graph styles](./img/line-graph-styles.png)

### 📊 Redesigned Statistics View
Expand Down
38 changes: 36 additions & 2 deletions src/hub/SourceList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -996,10 +996,10 @@ export default class SourceList {
let logType = window.log.getType(state.logKey);
let structuredType = window.log.getStructuredType(state.logKey);
if (logType !== null) {
let value: any;
let value: any = null;
if (logType === LoggableType.Number && this.getNumberPreview !== undefined) {
value = this.getNumberPreview(state.logKey, time);
} else {
} else if (logType !== LoggableType.Empty) {
value = getOrDefault(window.log, state.logKey, logType, time, null);
}
if (value !== null || logType === LoggableType.Empty) {
Expand Down Expand Up @@ -1159,6 +1159,40 @@ export default class SourceList {
let count = mechanismState.lines.length;
text = count.toString() + " segment" + (count === 1 ? "" : "s");
}
} else if (structuredType === "Alerts") {
let errorCount: number = getOrDefault(
window.log,
state.logKey + "/errors",
LoggableType.StringArray,
time,
[]
).length;
let warningCount: number = getOrDefault(
window.log,
state.logKey + "/warnings",
LoggableType.StringArray,
time,
[]
).length;
let infoCount: number = getOrDefault(
window.log,
state.logKey + "/infos",
LoggableType.StringArray,
time,
[]
).length;
text =
errorCount.toString() +
" error" +
(errorCount === 1 ? "" : "s") +
", " +
warningCount.toString() +
" warning" +
(warningCount === 1 ? "" : "s") +
", " +
infoCount.toString() +
" info" +
(infoCount === 1 ? "" : "s");
} else if (
logType === LoggableType.BooleanArray ||
logType === LoggableType.NumberArray ||
Expand Down
77 changes: 75 additions & 2 deletions src/hub/controllers/LineGraphController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { AKIT_TIMESTAMP_KEYS, getEnabledKey, getLogValueText } from "../../share
import { LogValueSetNumber } from "../../shared/log/LogValueSets";
import {
LineGraphRendererCommand,
LineGraphRendererCommand_Alert,
LineGraphRendererCommand_AlertSet,
LineGraphRendererCommand_DiscreteField,
LineGraphRendererCommand_NumericField
} from "../../shared/renderers/LineGraphRenderer";
Expand Down Expand Up @@ -468,7 +470,7 @@ export default class LineGraphController implements TabController {

// Add discrete fields
this.discreteSourceList.getState().forEach((fieldItem) => {
if (!fieldItem.visible) return;
if (!fieldItem.visible || fieldItem.type === "alerts") return;

let data = window.log.getRange(fieldItem.logKey, timeRange[0], timeRange[1]);
if (data === undefined) return;
Expand Down Expand Up @@ -504,6 +506,76 @@ export default class LineGraphController implements TabController {
});
});

// Process alerts
let alerts: LineGraphRendererCommand_AlertSet = [];
(["error", "warning", "info"] as const).forEach((alertType) => {
this.discreteSourceList.getState().forEach((fieldItem) => {
if (!fieldItem.visible || fieldItem.type !== "alerts") return;

let valueSet = window.log.getStringArray(fieldItem.logKey + "/" + alertType + "s", -Infinity, Infinity);
if (valueSet === undefined) return;

let allAlerts: LineGraphRendererCommand_Alert[] = [];
for (let i = 0; i < valueSet.values.length; i++) {
// Add new alerts
new Set(valueSet.values[i]).forEach((alertText) => {
let currentCount = valueSet!.values[i].filter((x) => x === alertText).length;
let activeCount = allAlerts.filter((x) => x.text === alertText && !isFinite(x.range[1])).length;
if (currentCount > activeCount) {
for (let count = 0; count < currentCount - activeCount; count++) {
allAlerts.push({
type: alertType,
text: alertText,
range: [valueSet!.timestamps[i], Infinity]
});
}
}
});

// Clear inactive alerts
new Set(allAlerts.map((x) => x.text)).forEach((alertText) => {
let currentCount = valueSet!.values[i].filter((x) => x === alertText).length;
let activeCount = allAlerts.filter((x) => x.text === alertText && !isFinite(x.range[1])).length;
if (activeCount > currentCount) {
for (let count = 0; count < activeCount - currentCount; count++) {
allAlerts.find((alert) => alert.text === alertText && !isFinite(alert.range[1]))!.range[1] =
valueSet!.timestamps[i];
}
}
});
}

// Clear all remaining active alerts
allAlerts.forEach((alert) => {
if (alert.range[1] === Infinity) {
alert.range[1] = timeRange[1];
}
});

// Add alerts to main set
allAlerts.forEach((alert) => {
let row = -1;
do {
row++;
while (alerts.length <= row) {
alerts.push([]);
}
} while (!alerts[row].every((other) => other.range[1] <= alert.range[0] || other.range[0] >= alert.range[1]));
alerts[row].push(alert);
});
});
});

// Remove offscreen alerts
alerts = alerts.map((row) =>
row.filter(
(alert) =>
!(alert.range[0] > timeRange[1] && alert.range[1] > timeRange[1]) &&
!(alert.range[0] < timeRange[0] && alert.range[1] < timeRange[0])
)
);
alerts = alerts.filter((row) => row.length > 0);

// Get numeric ranges
let calcRange = (dataRange: [number, number], lockedRange: [number, number] | null): [number, number] => {
let range: [number, number];
Expand Down Expand Up @@ -546,7 +618,8 @@ export default class LineGraphController implements TabController {
: "left",
leftFields: leftFieldsCommand,
rightFields: rightFieldsCommand,
discreteFields: discreteFieldsCommand
discreteFields: discreteFieldsCommand,
alerts: alerts
};
}

Expand Down
10 changes: 10 additions & 0 deletions src/hub/controllers/LineGraphController_Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,16 @@ export const LineGraphController_DiscreteConfig: SourceListConfig = {
values: GraphColors
}
]
},
{
key: "alerts",
display: "Alerts",
symbol: "list.bullet",
showInTypeName: false,
color: "#ffaa00",
sourceTypes: ["Alerts"],
showDocs: true,
options: []
}
]
};
46 changes: 43 additions & 3 deletions src/shared/renderers/LineGraphRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import ScrollSensor from "../../hub/ScrollSensor";
import { ensureThemeContrast } from "../Colors";
import { SelectionMode } from "../Selection";
import { ValueScaler, calcAxisStepSize, clampValue, cleanFloat, scaleValue, shiftColor } from "../util";
import { calcAxisStepSize, clampValue, cleanFloat, scaleValue, shiftColor, ValueScaler } from "../util";
import TabRenderer from "./TabRenderer";

export default class LineGraphRenderer implements TabRenderer {
Expand Down Expand Up @@ -123,8 +124,8 @@ export default class LineGraphRenderer implements TabRenderer {
this.SCROLL_OVERLAY.style.top = graphTop.toString() + "px";
let graphHeight = height - graphTop - 35;
if (graphHeight < 1) graphHeight = 1;
let graphHeightOpen =
graphHeight - command.discreteFields.length * 20 - (command.discreteFields.length > 0 ? 5 : 0);
let discreteRowCount = command.discreteFields.length + command.alerts.length;
let graphHeightOpen = graphHeight - discreteRowCount * 20 - (discreteRowCount > 0 ? 5 : 0);
if (graphHeightOpen < 1) graphHeightOpen = 1;

// Calculate Y step sizes
Expand Down Expand Up @@ -225,6 +226,36 @@ export default class LineGraphRenderer implements TabRenderer {
}
});

// Render alerts
command.alerts.forEach((alertsRow, rowIndex) => {
let topY = graphTop + graphHeight - 20 - (command.discreteFields.length + rowIndex) * 20;
alertsRow.forEach((alert) => {
let startX = scaleValue(alert.range[0], timeRange, [graphLeft, graphLeft + graphWidth]);
let endX = scaleValue(alert.range[1], timeRange, [graphLeft, graphLeft + graphWidth]);

// Draw shape
switch (alert.type) {
case "error":
context.fillStyle = ensureThemeContrast("#ff0000");
break;
case "warning":
context.fillStyle = ensureThemeContrast("#ffaa00");
break;
case "info":
context.fillStyle = ensureThemeContrast("#00ff00");
break;
}
context.fillRect(startX, topY, endX - startX, 15);

// Draw text
let adjustedStartX = startX < graphLeft ? graphLeft : startX;
if (endX - adjustedStartX > 10) {
context.fillStyle = "black";
context.fillText(alert.text, adjustedStartX + 5, topY + 15 / 2, endX - adjustedStartX - 10);
}
});
});

// Render continuous data
const xScaler = new ValueScaler(timeRange, [graphLeft, graphLeft + graphWidth]);
let drawNumericFields = (fields: LineGraphRendererCommand_NumericField[], yRange: [number, number]) => {
Expand Down Expand Up @@ -607,6 +638,7 @@ export type LineGraphRendererCommand = {
leftFields: LineGraphRendererCommand_NumericField[];
rightFields: LineGraphRendererCommand_NumericField[];
discreteFields: LineGraphRendererCommand_DiscreteField[];
alerts: LineGraphRendererCommand_AlertSet;
};

export type LineGraphRendererCommand_NumericField = {
Expand All @@ -624,3 +656,11 @@ export type LineGraphRendererCommand_DiscreteField = {
type: "stripes" | "graph";
toggleReference: boolean;
};

export type LineGraphRendererCommand_AlertSet = LineGraphRendererCommand_Alert[][];

export type LineGraphRendererCommand_Alert = {
type: "error" | "warning" | "info";
text: string;
range: [number, number];
};
16 changes: 16 additions & 0 deletions www/symbols/sourceList/list.bullet.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 05ee084

Please sign in to comment.