diff --git a/capabilities.json b/capabilities.json index 87c033a..61a564d 100644 --- a/capabilities.json +++ b/capabilities.json @@ -100,6 +100,10 @@ ] } }, + "outliers_in_limits": { + "displayName": "Keep Outliers in Limit Calcs.", + "type": { "bool": true } + }, "multiplier": { "displayName": "Multiplier", "type": { @@ -293,6 +297,10 @@ "displayName": "Show Variation Icons", "type" : { "bool" : true } }, + "flag_variation_last": { + "displayName": "Flag Only Last Point", + "type" : { "bool" : true } + }, "variation_icons_locations": { "displayName": "Variation Icon Locations", "type": { diff --git a/src/Classes/chartObject.ts b/src/Classes/chartObject.ts index bc5643e..13b6f73 100644 --- a/src/Classes/chartObject.ts +++ b/src/Classes/chartObject.ts @@ -12,11 +12,16 @@ type chartObjectConstructor = { splitIndexes: number[]; } +type LimitArgs = { + inputData: dataObject; + inputSettings: settingsObject; +} + class chartObject { inputData: dataObject; inputSettings: settingsObject; splitIndexes: number[]; - limitFunction: (x: dataObject) => controlLimits; + limitFunction: (x: LimitArgs) => controlLimits; getLimits(): controlLimits { let calcLimits: controlLimits; @@ -40,7 +45,7 @@ class chartObject { return data; }) - const calcLimitsGrouped: controlLimits[] = groupedData.map(d => this.limitFunction(d)); + const calcLimitsGrouped: controlLimits[] = groupedData.map(d => this.limitFunction({inputData: d, inputSettings: this.inputSettings})); calcLimits = calcLimitsGrouped.reduce((all: controlLimits, curr: controlLimits) => { const allInner: controlLimits = all; Object.entries(all).forEach((entry, idx) => { @@ -52,7 +57,7 @@ class chartObject { }) } else { // Calculate control limits using user-specified type - calcLimits = this.limitFunction(this.inputData); + calcLimits = this.limitFunction({inputData: this.inputData, inputSettings: this.inputSettings}); } calcLimits.flagOutliers(this.inputSettings); @@ -86,4 +91,5 @@ class chartObject { } } +export { LimitArgs } export default chartObject; diff --git a/src/Classes/settingsGroups.ts b/src/Classes/settingsGroups.ts index dcdd0ca..1b206af 100644 --- a/src/Classes/settingsGroups.ts +++ b/src/Classes/settingsGroups.ts @@ -49,6 +49,7 @@ class datesSettings { class spcSettings { chart_type: settingsPair; + outliers_in_limits: settingsPair; multiplier: settingsPair; sig_figs: settingsPair; perc_labels: settingsPair; @@ -59,6 +60,7 @@ class spcSettings { constructor() { this.chart_type = new settingsPair("i"); + this.outliers_in_limits = new settingsPair(false); this.perc_labels = new settingsPair("Automatic"); this.multiplier = new settingsPair(1); this.sig_figs = new settingsPair(2); @@ -243,11 +245,13 @@ class outliersSettings { class nhsIconSettings { show_variation_icons: settingsPair; + flag_variation_last: settingsPair; variation_icons_locations: settingsPair; variation_icons_scaling: settingsPair; constructor() { this.show_variation_icons = new settingsPair(false); + this.flag_variation_last = new settingsPair(true); this.variation_icons_locations = new settingsPair("Top Right"); this.variation_icons_scaling = new settingsPair(1.0); } diff --git a/src/Classes/svgIconClass.ts b/src/Classes/svgIconClass.ts index b8a00ab..40f99fb 100644 --- a/src/Classes/svgIconClass.ts +++ b/src/Classes/svgIconClass.ts @@ -100,9 +100,20 @@ class svgIconClass { "decrease" : "Low", "neutral" : "" } + const invert_suffix_map: Record = { + "High" : "Low", + "Low" : "High", + "" : "" + } const suffix: string = suffix_map[imp_direction]; - const allFlags: string[] - = currLimits.astpoint.concat(currLimits.shift, currLimits.trend, currLimits.two_in_three); + const flag_last: boolean = viewModel.inputSettings.nhs_icons.flag_variation_last.value; + let allFlags: string[]; + if (flag_last) { + const N: number = currLimits.astpoint.length - 1; + allFlags = [currLimits.astpoint[N], currLimits.shift[N], currLimits.trend[N], currLimits.two_in_three[N]]; + } else { + allFlags = currLimits.astpoint.concat(currLimits.shift, currLimits.trend, currLimits.two_in_three); + } const iconsPresent: string[] = new Array(); @@ -110,7 +121,7 @@ class svgIconClass { iconsPresent.push("improvement" + suffix) } if (allFlags.includes("deterioration")) { - iconsPresent.push("concern" + suffix) + iconsPresent.push("concern" + invert_suffix_map[suffix]) } if (allFlags.includes("neutral_low")) { iconsPresent.push("neutralLow") diff --git a/src/Classes/viewModel.ts b/src/Classes/viewModel.ts index cbc8258..b23fe56 100644 --- a/src/Classes/viewModel.ts +++ b/src/Classes/viewModel.ts @@ -120,7 +120,6 @@ class viewModelObject { // Extract input data, filter out invalid values, and identify any settings passed as data this.inputData = new dataObject(dv[0].categorical, this.inputSettings) - // Initialise a new chartObject class which can be used to calculate the control limits this.chartBase = new chartObject({ inputData: this.inputData, inputSettings: this.inputSettings, diff --git a/src/Limit Calculations/c.ts b/src/Limit Calculations/c.ts index 072ab34..31d3615 100644 --- a/src/Limit Calculations/c.ts +++ b/src/Limit Calculations/c.ts @@ -2,8 +2,10 @@ import * as d3 from "d3"; import rep from "../Functions/rep"; import dataObject from "../Classes/dataObject" import controlLimits from "../Classes/controlLimits" +import { LimitArgs } from "../Classes/chartObject"; -function cLimits(inputData: dataObject): controlLimits { +function cLimits(args: LimitArgs): controlLimits { + const inputData: dataObject = args.inputData; const cl: number = d3.mean(inputData.numerators); const sigma: number = Math.sqrt(cl); diff --git a/src/Limit Calculations/g.ts b/src/Limit Calculations/g.ts index 586ca01..513c1d7 100644 --- a/src/Limit Calculations/g.ts +++ b/src/Limit Calculations/g.ts @@ -3,8 +3,10 @@ import { sqrt } from "../Functions/UnaryFunctions"; import rep from "../Functions/rep"; import dataObject from "../Classes/dataObject"; import controlLimits from "../Classes/controlLimits"; +import { LimitArgs } from "../Classes/chartObject"; -function gLimits(inputData: dataObject): controlLimits { +function gLimits(args: LimitArgs): controlLimits { + const inputData: dataObject = args.inputData; const cl: number = d3.mean(inputData.numerators); const sigma: number = sqrt(cl * (cl + 1)); diff --git a/src/Limit Calculations/i.ts b/src/Limit Calculations/i.ts index 37bf6b8..6d81947 100644 --- a/src/Limit Calculations/i.ts +++ b/src/Limit Calculations/i.ts @@ -5,8 +5,10 @@ import { abs } from "../Functions/UnaryFunctions" import { divide } from "../Functions/BinaryFunctions"; import dataObject from "../Classes/dataObject"; import controlLimits from "../Classes/controlLimits"; +import { LimitArgs } from "../Classes/chartObject"; -function iLimits(inputData: dataObject): controlLimits { +function iLimits(args: LimitArgs): controlLimits { + const inputData: dataObject = args.inputData; const useRatio: boolean = (inputData.denominators && inputData.denominators.length > 0); const ratio: number[] = useRatio ? divide(inputData.numerators, inputData.denominators) @@ -16,7 +18,8 @@ function iLimits(inputData: dataObject): controlLimits { const consec_diff: number[] = abs(diff(ratio)); const consec_diff_ulim: number = d3.mean(consec_diff) * 3.267; - const consec_diff_valid: number[] = consec_diff.filter(d => d < consec_diff_ulim); + const outliers_in_limits: boolean = args.inputSettings.spc.outliers_in_limits.value; + const consec_diff_valid: number[] = outliers_in_limits ? consec_diff : consec_diff.filter(d => d < consec_diff_ulim); const sigma: number = d3.mean(consec_diff_valid) / 1.128; diff --git a/src/Limit Calculations/mr.ts b/src/Limit Calculations/mr.ts index c883b17..fdca919 100644 --- a/src/Limit Calculations/mr.ts +++ b/src/Limit Calculations/mr.ts @@ -5,8 +5,10 @@ import { abs } from "../Functions/UnaryFunctions" import { divide } from "../Functions/BinaryFunctions"; import dataObject from "../Classes/dataObject"; import controlLimits from "../Classes/controlLimits"; +import { LimitArgs } from "../Classes/chartObject"; -function mrLimits(inputData: dataObject): controlLimits { +function mrLimits(args: LimitArgs): controlLimits { + const inputData: dataObject = args.inputData; const useRatio: boolean = (inputData.denominators && inputData.denominators.length > 0); const ratio: number[] = useRatio ? divide(inputData.numerators, inputData.denominators) diff --git a/src/Limit Calculations/p.ts b/src/Limit Calculations/p.ts index a8099dc..af7e97f 100644 --- a/src/Limit Calculations/p.ts +++ b/src/Limit Calculations/p.ts @@ -5,8 +5,10 @@ import { subtract, add, divide, multiply } from "../Functions/BinaryFunctions"; import controlLimits from "../Classes/controlLimits"; import dataObject from "../Classes/dataObject"; import truncate from "../Functions/truncate" +import {LimitArgs} from "../Classes/chartObject"; -function pLimits(inputData: dataObject): controlLimits { +function pLimits(args: LimitArgs): controlLimits { + const inputData: dataObject = args.inputData; const cl: number = d3.sum(inputData.numerators) / d3.sum(inputData.denominators); const sigma: number[] = sqrt(divide(cl * (1 - cl), inputData.denominators)); diff --git a/src/Limit Calculations/pprime.ts b/src/Limit Calculations/pprime.ts index 6be8301..bf83724 100644 --- a/src/Limit Calculations/pprime.ts +++ b/src/Limit Calculations/pprime.ts @@ -6,8 +6,10 @@ import { subtract, add, divide, multiply } from "../Functions/BinaryFunctions"; import controlLimits from "../Classes/controlLimits"; import dataObject from "../Classes/dataObject"; import truncate from "../Functions/truncate"; +import {LimitArgs} from "../Classes/chartObject"; -function pprimeLimits(inputData: dataObject): controlLimits { +function pprimeLimits(args: LimitArgs): controlLimits { + const inputData: dataObject = args.inputData; const val: number[] = divide(inputData.numerators, inputData.denominators); const cl: number = d3.sum(inputData.numerators) / d3.sum(inputData.denominators); const sd: number[] = sqrt(divide(cl * (1 - cl), inputData.denominators)); @@ -15,7 +17,8 @@ function pprimeLimits(inputData: dataObject): controlLimits { const consec_diff: number[] = abs(diff(zscore)); const consec_diff_ulim: number = d3.mean(consec_diff) * 3.267; - const consec_diff_valid: number[] = consec_diff.filter(d => d < consec_diff_ulim); + const outliers_in_limits: boolean = args.inputSettings.spc.outliers_in_limits.value; + const consec_diff_valid: number[] = outliers_in_limits ? consec_diff : consec_diff.filter(d => d < consec_diff_ulim); const sigma: number[] = multiply(sd, d3.mean(consec_diff_valid) / 1.128); return new controlLimits({ diff --git a/src/Limit Calculations/run.ts b/src/Limit Calculations/run.ts index f81653d..053c2ed 100644 --- a/src/Limit Calculations/run.ts +++ b/src/Limit Calculations/run.ts @@ -3,8 +3,10 @@ import rep from "../Functions/rep"; import { divide } from "../Functions/BinaryFunctions"; import controlLimits from "../Classes/controlLimits"; import dataObject from "../Classes/dataObject"; +import {LimitArgs} from "../Classes/chartObject"; -function runLimits(inputData: dataObject): controlLimits { +function runLimits(args: LimitArgs): controlLimits { + const inputData: dataObject = args.inputData; const useRatio: boolean = (inputData.denominators && inputData.denominators.length > 0); const ratio: number[] = useRatio ? divide(inputData.numerators, inputData.denominators) diff --git a/src/Limit Calculations/s.ts b/src/Limit Calculations/s.ts index e979882..5580be7 100644 --- a/src/Limit Calculations/s.ts +++ b/src/Limit Calculations/s.ts @@ -5,8 +5,10 @@ import { sqrt } from "../Functions/UnaryFunctions"; import { subtract, pow, multiply } from "../Functions/BinaryFunctions"; import controlLimits from "../Classes/controlLimits"; import dataObject from "../Classes/dataObject"; +import {LimitArgs} from "../Classes/chartObject"; -function sLimits(inputData: dataObject): controlLimits { +function sLimits(args: LimitArgs): controlLimits { + const inputData: dataObject = args.inputData; const group_sd: number[] = inputData.numerators; const count_per_group: number[] = inputData.denominators; diff --git a/src/Limit Calculations/t.ts b/src/Limit Calculations/t.ts index a42945b..d910640 100644 --- a/src/Limit Calculations/t.ts +++ b/src/Limit Calculations/t.ts @@ -3,13 +3,15 @@ import { pow } from "../Functions/BinaryFunctions"; import controlLimits from "../Classes/controlLimits"; import dataObject from "../Classes/dataObject"; import truncate from "../Functions/truncate"; +import {LimitArgs} from "../Classes/chartObject"; -function tLimits(inputData: dataObject): controlLimits { +function tLimits(args: LimitArgs): controlLimits { + const inputData: dataObject = args.inputData; const val: number[] = pow(inputData.numerators, 1 / 3.6); - const inputDataCopy = inputData; - inputDataCopy.numerators = val; - inputDataCopy.denominators = null; - const limits: controlLimits = iLimits(inputDataCopy); + const argsDataCopy: LimitArgs = args; + argsDataCopy.inputData.numerators = val; + argsDataCopy.inputData.denominators = null; + const limits: controlLimits = iLimits(argsDataCopy); limits.targets = pow(limits.targets, 3.6); limits.values = pow(limits.values, 3.6); limits.ll99 = truncate(pow(limits.ll99, 3.6), {lower: 0}); diff --git a/src/Limit Calculations/u.ts b/src/Limit Calculations/u.ts index 4c9cc4f..5c09170 100644 --- a/src/Limit Calculations/u.ts +++ b/src/Limit Calculations/u.ts @@ -5,8 +5,10 @@ import { subtract, add, divide, multiply } from "../Functions/BinaryFunctions"; import controlLimits from "../Classes/controlLimits"; import dataObject from "../Classes/dataObject"; import truncate from "../Functions/truncate"; +import {LimitArgs} from "../Classes/chartObject"; -function uLimits(inputData: dataObject): controlLimits { +function uLimits(args: LimitArgs): controlLimits { + const inputData: dataObject = args.inputData; const cl: number = divide(d3.sum(inputData.numerators),d3.sum(inputData.denominators)); const sigma: number[] = sqrt(divide(cl,inputData.denominators)); diff --git a/src/Limit Calculations/uprime.ts b/src/Limit Calculations/uprime.ts index 7947911..7cef9d6 100644 --- a/src/Limit Calculations/uprime.ts +++ b/src/Limit Calculations/uprime.ts @@ -6,8 +6,10 @@ import { subtract, add, divide, multiply } from "../Functions/BinaryFunctions"; import controlLimits from "../Classes/controlLimits"; import dataObject from "../Classes/dataObject"; import truncate from "../Functions/truncate"; +import {LimitArgs} from "../Classes/chartObject"; -function uprimeLimits(inputData: dataObject): controlLimits { +function uprimeLimits(args: LimitArgs): controlLimits { + const inputData: dataObject = args.inputData; const val: number[] = divide(inputData.numerators, inputData.denominators); const cl: number = d3.sum(inputData.numerators) / d3.sum(inputData.denominators); const sd: number[] = sqrt(divide(cl,inputData.denominators)); @@ -15,7 +17,8 @@ function uprimeLimits(inputData: dataObject): controlLimits { const consec_diff: number[] = abs(diff(zscore)); const consec_diff_ulim: number = d3.mean(consec_diff) * 3.267; - const consec_diff_valid: number[] = consec_diff.filter(d => d < consec_diff_ulim); + const outliers_in_limits: boolean = args.inputSettings.spc.outliers_in_limits.value; + const consec_diff_valid: number[] = outliers_in_limits ? consec_diff : consec_diff.filter(d => d < consec_diff_ulim); const sigma: number[] = multiply(sd, d3.mean(consec_diff_valid) / 1.128); return new controlLimits({ diff --git a/src/Limit Calculations/xbar.ts b/src/Limit Calculations/xbar.ts index 2db833d..e005a79 100644 --- a/src/Limit Calculations/xbar.ts +++ b/src/Limit Calculations/xbar.ts @@ -5,8 +5,10 @@ import { sqrt } from "../Functions/UnaryFunctions" import { pow, subtract, add, multiply, divide } from "../Functions/BinaryFunctions"; import controlLimits from "../Classes/controlLimits"; import dataObject from "../Classes/dataObject"; +import {LimitArgs} from "../Classes/chartObject"; -function xbarLimits(inputData: dataObject): controlLimits { +function xbarLimits(args: LimitArgs): controlLimits { + const inputData: dataObject = args.inputData; // Calculate number of observations in each group const count_per_group: number[] = inputData.denominators; diff --git a/src/visual.ts b/src/visual.ts index 5e11651..5f307d4 100644 --- a/src/visual.ts +++ b/src/visual.ts @@ -385,7 +385,7 @@ export class Visual implements IVisual { objectName: "split_indexes_storage", selector: undefined, properties: { - split_indexes: JSON.stringify(this.viewModel.splitIndexes) + split_indexes: this.viewModel.splitIndexes ? JSON.stringify(this.viewModel.splitIndexes) : JSON.stringify(new Array()) } }