From 1f945ba14fc1dcba24130ff76dc56afe84e040c7 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 5 Apr 2024 11:40:13 +0300 Subject: [PATCH] Centralise determination and management of chart type properties --- src/Classes/derivedSettingsClass.ts | 17 +++++++++++++++++ src/Classes/plotPropertiesClass.ts | 10 ++++++---- src/Classes/viewModelClass.ts | 10 ++++------ src/Functions/buildTooltip.ts | 4 ++-- src/Functions/extractInputData.ts | 5 ++--- src/Functions/validateDataView.ts | 11 +++++------ src/Functions/validateInputData.ts | 20 +++++++------------- src/Outlier Flagging/assuranceIconToDraw.ts | 2 +- src/visual.ts | 2 +- 9 files changed, 45 insertions(+), 36 deletions(-) diff --git a/src/Classes/derivedSettingsClass.ts b/src/Classes/derivedSettingsClass.ts index 27a754a..8e40ec1 100644 --- a/src/Classes/derivedSettingsClass.ts +++ b/src/Classes/derivedSettingsClass.ts @@ -3,6 +3,14 @@ import {defaultSettingsType} from "./settingsClass" export default class derivedSettingsClass { multiplier: number percentLabels: boolean + chart_type_props: { + needs_denominator: boolean, + denominator_optional: boolean, + numerator_non_negative: boolean, + numerator_leq_denominator: boolean, + has_control_limits: boolean, + needs_sd: boolean + } update(inputSettings: defaultSettingsType) { const chartType: string = inputSettings.spc.chart_type; @@ -25,6 +33,15 @@ export default class derivedSettingsClass { percentLabels = percentSettingString === "Yes"; } + this.chart_type_props = { + needs_denominator: ["p", "pp", "u", "up", "xbar", "s"].includes(chartType), + denominator_optional: ["i", "run", "mr"].includes(chartType), + numerator_non_negative: ["p", "pp", "u", "up", "s", "c", "g", "t"].includes(chartType), + numerator_leq_denominator: ["p", "pp", "u", "up"].includes(chartType), + has_control_limits: !(["run"].includes(chartType)), + needs_sd: ["xbar"].includes(chartType) + } + this.multiplier = multiplier this.percentLabels = percentLabels } diff --git a/src/Classes/plotPropertiesClass.ts b/src/Classes/plotPropertiesClass.ts index 6dbfc8e..8aa275a 100644 --- a/src/Classes/plotPropertiesClass.ts +++ b/src/Classes/plotPropertiesClass.ts @@ -72,12 +72,14 @@ export default class plotPropertiesClass { const limitMultiplier: number = inputSettings.y_axis.limit_multiplier; const values: number[] = controlLimits.values; - const upper_limits: number[] = controlLimits.ul99.concat(controlLimits?.speclimits_upper); - const lower_limits: number[] = controlLimits.ll99.concat(controlLimits?.speclimits_lower); + const ul99: number[] = controlLimits?.ul99 + const speclimits_upper: number[] = controlLimits?.speclimits_upper; + const ll99: number[] = controlLimits?.ll99; + const speclimits_lower: number[] = controlLimits?.speclimits_lower; const alt_targets: number[] = controlLimits.alt_targets; const maxValue: number = max(values); - const maxValueOrLimit: number = max(values.concat(upper_limits).concat(alt_targets)); - const minValueOrLimit: number = min(values.concat(lower_limits).concat(alt_targets)); + const maxValueOrLimit: number = max(values.concat(ul99).concat(speclimits_upper).concat(alt_targets)); + const minValueOrLimit: number = min(values.concat(ll99).concat(speclimits_lower).concat(alt_targets)); const maxTarget: number = max(controlLimits.targets); const minTarget: number = min(controlLimits.targets); diff --git a/src/Classes/viewModelClass.ts b/src/Classes/viewModelClass.ts index d2eac1c..ddb90cc 100644 --- a/src/Classes/viewModelClass.ts +++ b/src/Classes/viewModelClass.ts @@ -164,9 +164,7 @@ export default class viewModelClass { this.controlLimits = calcLimitsGrouped.reduce((all: controlLimitsObject, curr: controlLimitsObject) => { const allInner: controlLimitsObject = all; Object.entries(all).forEach((entry, idx) => { - if (this.inputSettings.settings.spc.chart_type !== "run" || !["ll99", "ll95", "ll68", "ul68", "ul95", "ul99"].includes(entry[0])) { - allInner[entry[0]] = entry[1]?.concat(Object.entries(curr)[idx][1]); - } + allInner[entry[0]] = entry[1]?.concat(Object.entries(curr)[idx][1]); }) return allInner; }) @@ -236,7 +234,7 @@ export default class viewModelClass { if (this.inputSettings.settings.lines.show_specification) { labels.push("speclimits_lower", "speclimits_upper"); } - if (this.inputSettings.settings.spc.chart_type !== "run") { + if (this.inputSettings.derivedSettings.chart_type_props.has_control_limits) { if (this.inputSettings.settings.lines.show_99) { labels.push("ll99", "ul99"); } @@ -292,7 +290,7 @@ export default class viewModelClass { const multiplier: number = this.inputSettings.derivedSettings.multiplier; let lines_to_scale: string[] = ["values", "targets"]; - if (this.inputSettings.settings.spc.chart_type !== "run") { + if (this.inputSettings.derivedSettings.chart_type_props.has_control_limits) { lines_to_scale = lines_to_scale.concat(["ll99", "ll95", "ll68", "ul68", "ul95", "ul99"]); } @@ -343,7 +341,7 @@ export default class viewModelClass { const group_values: number[] = this.controlLimits.values.slice(start, end); const group_targets: number[] = this.controlLimits.targets.slice(start, end); - if (this.inputSettings.settings.spc.chart_type !== "run" || ast_specification || two_in_three_specification) { + if (this.inputSettings.derivedSettings.chart_type_props.has_control_limits || ast_specification || two_in_three_specification) { const limit_map: Record = { "1 Sigma": "68", "2 Sigma": "95", diff --git a/src/Functions/buildTooltip.ts b/src/Functions/buildTooltip.ts index 5420c38..5b0791d 100644 --- a/src/Functions/buildTooltip.ts +++ b/src/Functions/buildTooltip.ts @@ -102,7 +102,7 @@ export default function buildTooltip(index: number, }) } } - if (chart_type !== "run") { + if (derivedSettings.chart_type_props.has_control_limits) { ["68", "95", "99"].forEach(limit => { if (inputSettings.lines[`ttip_show_${limit}`] && inputSettings.lines[`show_${limit}`]) { tooltip.push({ @@ -124,7 +124,7 @@ export default function buildTooltip(index: number, value: (alt_target).toFixed(sig_figs) + suffix }) } - if (chart_type !== "run") { + if (derivedSettings.chart_type_props.has_control_limits) { ["68", "95", "99"].forEach(limit => { if (inputSettings.lines[`ttip_show_${limit}`] && inputSettings.lines[`show_${limit}`]) { tooltip.push({ diff --git a/src/Functions/extractInputData.ts b/src/Functions/extractInputData.ts index 0129496..d025a68 100644 --- a/src/Functions/extractInputData.ts +++ b/src/Functions/extractInputData.ts @@ -62,7 +62,7 @@ export default function extractInputData(inputView: DataViewCategorical, inputSe .map(d => d.show_specification ? d.specification_upper : null); - const inputValidStatus: ValidationT = validateInputData(keys, numerators, denominators, xbar_sds, groupings, inputSettings.spc.chart_type); + const inputValidStatus: ValidationT = validateInputData(keys, numerators, denominators, xbar_sds, groupings, inputSettingsClass.derivedSettings.chart_type_props); if (inputValidStatus.status !== 0) { return invalidInputData(inputValidStatus); @@ -112,8 +112,7 @@ export default function extractInputData(inputView: DataViewCategorical, inputSe } } - const chart_type: string = inputSettings.spc.chart_type; - if (chart_type === "run") { + if (!inputSettingsClass.derivedSettings.chart_type_props.has_control_limits) { removalMessages.push("NHS Assurance icon requires chart with control limits.") } } diff --git a/src/Functions/validateDataView.ts b/src/Functions/validateDataView.ts index 3ef99b8..8691218 100644 --- a/src/Functions/validateDataView.ts +++ b/src/Functions/validateDataView.ts @@ -1,7 +1,7 @@ import type powerbi from "powerbi-visuals-api"; -import { type defaultSettingsType } from "../Classes"; +import { settingsClass } from "../Classes"; -export default function validateDataView(inputDV: powerbi.DataView[], inputSettings: defaultSettingsType): string { +export default function validateDataView(inputDV: powerbi.DataView[], inputSettingsClass: settingsClass): string { if (!(inputDV?.[0])) { return "No data present"; } @@ -17,9 +17,8 @@ export default function validateDataView(inputDV: powerbi.DataView[], inputSetti if (!numeratorsPresent) { return "No Numerators passed!"; } - const chart_type: string = inputSettings.spc.chart_type; - const denominatorRequired: string[] = ["p", "pp", "u", "up", "xbar", "s"]; - if (denominatorRequired.includes(chart_type)) { + const chart_type: string = inputSettingsClass.settings.spc.chart_type; + if (inputSettingsClass.derivedSettings.chart_type_props.needs_denominator) { const denominatorsPresent: boolean = inputDV[0].categorical ?.values @@ -30,7 +29,7 @@ export default function validateDataView(inputDV: powerbi.DataView[], inputSetti } } - if (chart_type === "xbar") { + if (inputSettingsClass.derivedSettings.chart_type_props.needs_sd) { const xbarSDPresent: boolean = inputDV[0].categorical ?.values diff --git a/src/Functions/validateInputData.ts b/src/Functions/validateInputData.ts index 4c276b1..aba591d 100644 --- a/src/Functions/validateInputData.ts +++ b/src/Functions/validateInputData.ts @@ -1,3 +1,4 @@ +import { derivedSettingsClass } from "../Classes"; import rep from "./rep"; export type ValidationT = { status: number, messages: string[], error?: string }; @@ -9,16 +10,9 @@ export default function validateInputData(keys: string[], denominators: number[], xbar_sds: number[], groupings: string[], - data_type: string): { status: number, messages: string[], error?: string } { - const denominatorConstraintRequired: string[] = ["p", "pp", "u", "up"]; - const denominatorRequired: string[] = ["p", "pp", "u", "up", "xbar", "s"]; - const denominatorOptional: string[] = ["i", "run", "mr"]; + chart_type_props: derivedSettingsClass["chart_type_props"]): { status: number, messages: string[], error?: string } { - const checkOptionalDenominator: boolean = denominatorOptional.includes(data_type) && !(denominators === null || denominators === undefined); - if (checkOptionalDenominator) { - denominatorRequired.push(data_type); - } - const numeratorNonNegativeRequired: string[] = ["p", "pp", "u", "up", "s", "c", "g", "t"]; + const check_optional: boolean = chart_type_props.denominator_optional && !(denominators === null || denominators === undefined); const validationRtn: ValidationT = { status: 0, messages: rep("", keys.length) }; @@ -48,7 +42,7 @@ export default function validateInputData(keys: string[], validationRtn.error = "All numerators are missing or null!"; return validationRtn; } - if (numeratorNonNegativeRequired.includes(data_type)) { + if (chart_type_props.numerator_non_negative) { numerators.forEach((d, idx) => { validationRtn.messages[idx] = validationRtn.messages[idx] === "" ? ((d >= 0) ? "" : "Numerator negative") @@ -60,7 +54,7 @@ export default function validateInputData(keys: string[], } } - if (denominatorRequired.includes(data_type)) { + if (chart_type_props.needs_denominator || check_optional) { denominators.forEach((d, idx) => { validationRtn.messages[idx] = validationRtn.messages[idx] === "" ? ((d != null) ? "" : "Denominator missing") @@ -79,7 +73,7 @@ export default function validateInputData(keys: string[], validationRtn.error = "All denominators are negative!"; return validationRtn; } - if (denominatorConstraintRequired.includes(data_type)) { + if (chart_type_props.numerator_leq_denominator) { denominators.forEach((d, idx) => { validationRtn.messages[idx] = validationRtn.messages[idx] === "" ? ((d >= numerators[idx]) ? "" : "Denominator < numerator") @@ -92,7 +86,7 @@ export default function validateInputData(keys: string[], } } - if (data_type === "xbar") { + if (chart_type_props.needs_sd) { xbar_sds.forEach((d, idx) => { validationRtn.messages[idx] = validationRtn.messages[idx] === "" ? ((d != null) ? "" : "SD missing") diff --git a/src/Outlier Flagging/assuranceIconToDraw.ts b/src/Outlier Flagging/assuranceIconToDraw.ts index dc3de7c..b9a0737 100644 --- a/src/Outlier Flagging/assuranceIconToDraw.ts +++ b/src/Outlier Flagging/assuranceIconToDraw.ts @@ -1,7 +1,7 @@ import type { viewModelClass } from "../Classes"; export default function assuranceIconToDraw(viewModel: viewModelClass): string { - if (viewModel.inputSettings.settings.spc.chart_type === "run") { + if (!(viewModel.inputSettings.derivedSettings.chart_type_props.has_control_limits)) { return "none"; } const imp_direction: string = viewModel.inputSettings.settings.outliers.improvement_direction; diff --git a/src/visual.ts b/src/visual.ts index fb31a85..7b3c576 100644 --- a/src/visual.ts +++ b/src/visual.ts @@ -46,7 +46,7 @@ export class Visual implements powerbi.extensibility.IVisual { } const checkDV: string = validateDataView(options.dataViews, - this.viewModel.inputSettings.settings); + this.viewModel.inputSettings); if (checkDV !== "valid") { if (this.viewModel.inputSettings.settings.canvas.show_errors) { this.svg.call(drawErrors, options, checkDV);