Skip to content

Commit

Permalink
Merge pull request #240 from AUS-DOH-Safety-and-Quality/error-handling
Browse files Browse the repository at this point in the history
Update error handling to allow message toggle
  • Loading branch information
andrjohns authored Jan 31, 2024
2 parents 116671c + 7a2612a commit 982df58
Show file tree
Hide file tree
Showing 13 changed files with 234 additions and 125 deletions.
4 changes: 4 additions & 0 deletions capabilities.json
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@
"canvas": {
"displayName": "Canvas Settings",
"properties": {
"show_errors": {
"displayName": "Show Errors on Canvas",
"type": { "bool": true }
},
"lower_padding":{
"displayName": "Padding Below Plot (pixels):",
"type": { "numeric": true }
Expand Down
2 changes: 1 addition & 1 deletion src/Classes/plotPropertiesClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export default class plotPropertiesClass {
let yUpperLimit: number = inputSettings.y_axis.ylimit_u;

// Only update data-/settings-dependent plot aesthetics if they have changed
if (inputData && controlLimits) {
if (inputData?.validationStatus?.status == 0 && controlLimits) {
xUpperLimit = xUpperLimit !== null ? xUpperLimit : max(controlLimits.keys.map(d => d.x))

const limitMultiplier: number = inputSettings.y_axis.limit_multiplier;
Expand Down
38 changes: 20 additions & 18 deletions src/Classes/viewModelClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,25 +89,27 @@ export default class viewModelClass {
this.splitIndexes = JSON.parse(split_indexes);
this.inputData = extractInputData(options.dataViews[0].categorical, this.inputSettings.settings);

const allIndexes: number[] = this.splitIndexes
.concat([-1])
.concat(this.inputData.groupingIndexes)
.concat([this.inputData.limitInputArgs.keys.length - 1])
.filter((d, idx, arr) => arr.indexOf(d) === idx)
.sort((a,b) => a - b);

this.groupStartEndIndexes = new Array<number[]>();
for (let i: number = 0; i < allIndexes.length - 1; i++) {
this.groupStartEndIndexes.push([allIndexes[i] + 1, allIndexes[i + 1] + 1])
}
if (this.inputData.validationStatus.status === 0) {
const allIndexes: number[] = this.splitIndexes
.concat([-1])
.concat(this.inputData.groupingIndexes)
.concat([this.inputData.limitInputArgs.keys.length - 1])
.filter((d, idx, arr) => arr.indexOf(d) === idx)
.sort((a,b) => a - b);

this.groupStartEndIndexes = new Array<number[]>();
for (let i: number = 0; i < allIndexes.length - 1; i++) {
this.groupStartEndIndexes.push([allIndexes[i] + 1, allIndexes[i + 1] + 1])
}

this.calculateLimits();
this.scaleAndTruncateLimits();
this.flagOutliers();
this.calculateLimits();
this.scaleAndTruncateLimits();
this.flagOutliers();

// Structure the data and calculated limits to the format needed for plotting
this.initialisePlotData(host);
this.initialiseGroupedLines();
// Structure the data and calculated limits to the format needed for plotting
this.initialisePlotData(host);
this.initialiseGroupedLines();
}
}

this.plotProperties.update(
Expand Down Expand Up @@ -244,7 +246,7 @@ export default class viewModelClass {
})
}
}

formattedLines.push({
x: this.controlLimits.keys[i].x,
line_value: this.controlLimits[label]?.[i],
Expand Down
28 changes: 28 additions & 0 deletions src/D3 Plotting Functions/drawErrors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type powerbi from "powerbi-visuals-api";
type VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions;
import type { svgBaseType } from "../visual";
import initialiseSVG from "./initialiseSVG";

export default function drawErrors(selection: svgBaseType,
options: VisualUpdateOptions,
message: string,
internal: boolean = false) {
selection.call(initialiseSVG, true);
const errMessageSVG = selection.append("g").classed("errormessage", true);

if (internal) {
errMessageSVG.append('text')
.attr("x",options.viewport.width / 2)
.attr("y",options.viewport.height / 3)
.style("text-anchor", "middle")
.text("Internal Error! Please file a bug report with the following text:")
.style("font-size", "10px");
}

errMessageSVG.append('text')
.attr("x",options.viewport.width / 2)
.attr("y",options.viewport.height / 2)
.style("text-anchor", "middle")
.text(message)
.style("font-size", "10px");
}
14 changes: 7 additions & 7 deletions src/D3 Plotting Functions/drawTooltipLine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,30 @@ import type { svgBaseType, Visual } from "../visual";
import type { plotData, plotPropertiesClass } from "../Classes";

export default function drawTooltipLine(selection: svgBaseType, visualObj: Visual) {
const plotProperties: plotPropertiesClass = visualObj.viewModel.plotProperties;
const xAxisLine = selection
.select(".ttip-line-x")
.attr("x1", 0)
.attr("x2", 0)
.attr("y1", visualObj.viewModel.plotProperties.yAxis.end_padding)
.attr("y2", visualObj.viewModel.plotProperties.height - visualObj.viewModel.plotProperties.yAxis.start_padding)
.attr("y1", plotProperties.yAxis.end_padding)
.attr("y2", plotProperties.height - plotProperties.yAxis.start_padding)
.attr("stroke-width", "1px")
.attr("stroke", "black")
.style("stroke-opacity", 0);
const yAxisLine = selection
.select(".ttip-line-y")
.attr("x1", visualObj.viewModel.plotProperties.xAxis.start_padding)
.attr("x2", visualObj.viewModel.plotProperties.width - visualObj.viewModel.plotProperties.xAxis.end_padding)
.attr("x1", plotProperties.xAxis.start_padding)
.attr("x2", plotProperties.width - plotProperties.xAxis.end_padding)
.attr("y1", 0)
.attr("y2", 0)
.attr("stroke-width", "1px")
.attr("stroke", "black")
.style("stroke-opacity", 0);

selection.on("mousemove", (event) => {
if (!visualObj.viewModel.plotProperties.displayPlot) {
if (!plotProperties.displayPlot) {
return;
}
const plotProperties: plotPropertiesClass = visualObj.viewModel.plotProperties;
const plotPoints: plotData[] = visualObj.viewModel.plotPoints

const xValue: number = plotProperties.xScale.invert(event.pageX);
Expand All @@ -49,7 +49,7 @@ export default function drawTooltipLine(selection: svgBaseType, visualObj: Visua
.attr("y2", y_coord);
})
.on("mouseleave", () => {
if (!visualObj.viewModel.plotProperties.displayPlot) {
if (!plotProperties.displayPlot) {
return;
}
visualObj.host.tooltipService.hide({ immediately: true, isTouchEvent: false });
Expand Down
2 changes: 2 additions & 0 deletions src/D3 Plotting Functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export { default as drawTooltipLine } from "./drawTooltipLine"
export { default as drawXAxis } from "./drawXAxis"
export { default as drawYAxis } from "./drawYAxis"
export { default as updateHighlighting } from "./updateHighlighting"
export { default as drawErrors } from "./drawErrors"
export { default as initialiseSVG } from "./initialiseSVG"
16 changes: 16 additions & 0 deletions src/D3 Plotting Functions/initialiseSVG.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { svgBaseType } from "../visual";

export default function initialiseSVG(selection: svgBaseType,
removeAll: boolean = false) {
if (removeAll) {
selection.selectChildren().remove();
}
selection.append('line').classed("ttip-line-x", true)
selection.append('line').classed("ttip-line-y", true)
selection.append('g').classed("xaxisgroup", true)
selection.append('text').classed("xaxislabel", true)
selection.append('g').classed("yaxisgroup", true)
selection.append('text').classed("yaxislabel", true)
selection.append('g').classed("linesgroup", true)
selection.append('g').classed("dotsgroup", true)
}
6 changes: 5 additions & 1 deletion src/Functions/extractDataColumn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ function datePartsToRecord(dateParts: Intl.DateTimeFormatPart[]) {
}

function extractKeys(inputView: DataViewCategorical, inputSettings: defaultSettingsType): string[] {
const inputDates = parseInputDates(inputView.categories.filter(viewColumn => viewColumn.source?.roles?.["key"]))
const col: powerbi.DataViewCategoryColumn[] = inputView.categories.filter(viewColumn => viewColumn.source?.roles?.["key"]);
if (col.length === 1 && !(col[0].source.type?.temporal)) {
return col[0].values.map(d => d === null ? null : String(d));
}
const inputDates = parseInputDates(col)
const formatter = new Intl.DateTimeFormat(inputSettings.dates.date_format_locale, dateSettingsToFormatOptions(inputSettings.dates));
const delim: string = inputSettings.dates.date_format_delim;
return inputDates.dates.map((value: Date, idx) => {
Expand Down
31 changes: 25 additions & 6 deletions src/Functions/extractInputData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type DataViewCategorical = powerbi.DataViewCategorical;
type VisualTooltipDataItem = powerbi.extensibility.VisualTooltipDataItem;
import { extractDataColumn, extractValues, extractConditionalFormatting, validateInputData } from "../Functions"
import { type defaultSettingsType, type controlLimitsArgs } from "../Classes";
import type { ValidationT } from "./validateInputData";

export type dataObject = {
limitInputArgs: controlLimitsArgs;
Expand All @@ -17,6 +18,7 @@ export type dataObject = {
tooltips: VisualTooltipDataItem[][];
warningMessage: string;
alt_targets: number[];
validationStatus: ValidationT;
}

export default function extractInputData(inputView: DataViewCategorical, inputSettings: defaultSettingsType): dataObject {
Expand All @@ -31,23 +33,39 @@ export default function extractInputData(inputView: DataViewCategorical, inputSe
const alt_targets: number[] = extractConditionalFormatting<defaultSettingsType["lines"]>(inputView, "lines", inputSettings)
.map(d => inputSettings.lines.show_alt_target ? d.alt_target : null);

const inputValidStatus: string[] = validateInputData(keys, numerators, denominators, xbar_sds, groupings, inputSettings.spc.chart_type);
const inputValidStatus: ValidationT = validateInputData(keys, numerators, denominators, xbar_sds, groupings, inputSettings.spc.chart_type);

if (inputValidStatus.status !== 0) {
return {
limitInputArgs: null,
highlights: null,
anyHighlights: false,
categories: null,
groupings: null,
groupingIndexes: null,
scatter_formatting: null,
tooltips: null,
warningMessage: inputValidStatus.error,
alt_targets: null,
validationStatus: inputValidStatus
}
}

const valid_ids: number[] = new Array<number>();
const valid_keys: { x: number, id: number, label: string }[] = new Array<{ x: number, id: number, label: string }>();
const removalMessages: string[] = new Array<string>();
const groupVarName: string = inputView.categories[0].source.displayName;
let valid_x: number = 0;
for (let i: number = 0; i < numerators.length; i++) {
if (inputValidStatus[i] === "") {
if (inputValidStatus.messages[i] === "") {
valid_ids.push(i);
valid_keys.push({ x: valid_x, id: i, label: keys[i] })
valid_x += 1;
} else {
removalMessages.push(`${groupVarName} ${keys[i]} removed due to: ${inputValidStatus[i]}.`)
removalMessages.push(`${groupVarName} ${keys[i]} removed due to: ${inputValidStatus.messages[i]}.`)
}
}

const valid_groupings: string[] = extractValues(groupings, valid_ids);
const groupingIndexes: number[] = new Array<number>();
let current_grouping: string = valid_groupings[0];
Expand All @@ -57,7 +75,7 @@ export default function extractInputData(inputView: DataViewCategorical, inputSe
current_grouping = d;
}
})

return {
limitInputArgs: {
keys: valid_keys,
Expand All @@ -74,6 +92,7 @@ export default function extractInputData(inputView: DataViewCategorical, inputSe
groupingIndexes: groupingIndexes,
scatter_formatting: extractValues(scatter_cond, valid_ids),
warningMessage: removalMessages.length >0 ? removalMessages.join("\n") : "",
alt_targets: extractValues(alt_targets, valid_ids)
alt_targets: extractValues(alt_targets, valid_ids),
validationStatus: inputValidStatus
}
}
18 changes: 10 additions & 8 deletions src/Functions/validateDataView.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type powerbi from "powerbi-visuals-api";
import { validationErrorClass, type defaultSettingsType } from "../Classes";
import { type defaultSettingsType } from "../Classes";

export default function validateDataView(inputDV: powerbi.DataView[], inputSettings: defaultSettingsType) {
export default function validateDataView(inputDV: powerbi.DataView[], inputSettings: defaultSettingsType): string {
if (!(inputDV?.[0])) {
throw(new validationErrorClass("No data present"));
return "No data present";
}
if (!(inputDV[0]?.categorical?.categories?.[0]?.values?.length > 0)) {
throw(new validationErrorClass("No grouping/ID variable passed!"));
if (!(inputDV[0]?.categorical?.categories)) {
return "No grouping/ID variable passed!";
}

const numeratorsPresent: boolean
Expand All @@ -15,7 +15,7 @@ export default function validateDataView(inputDV: powerbi.DataView[], inputSetti
?.some(d => d.source?.roles?.numerators);

if (!numeratorsPresent) {
throw(new validationErrorClass("No Numerators passed!"));
return "No Numerators passed!";
}
const chart_type: string = inputSettings.spc.chart_type;
const denominatorRequired: string[] = ["p", "pp", "u", "up", "xbar", "s"];
Expand All @@ -26,7 +26,7 @@ export default function validateDataView(inputDV: powerbi.DataView[], inputSetti
?.some(d => d.source?.roles?.denominators);

if (!denominatorsPresent) {
throw(new validationErrorClass(`Chart type '${chart_type}' requires denominators!`));
return `Chart type '${chart_type}' requires denominators!`;
}
}

Expand All @@ -37,7 +37,9 @@ export default function validateDataView(inputDV: powerbi.DataView[], inputSetti
?.some(d => d.source?.roles?.xbar_sds);

if (!xbarSDPresent) {
throw(new validationErrorClass(`Chart type '${chart_type}' requires SDs!`));
return `Chart type '${chart_type}' requires SDs!`;
}
}

return "valid";
}
Loading

0 comments on commit 982df58

Please sign in to comment.