Skip to content

Commit

Permalink
Merge pull request #147 from AUS-DOH-Safety-and-Quality/further-tidying
Browse files Browse the repository at this point in the history
Further tidying and consistent structures
  • Loading branch information
andrjohns authored Jul 19, 2023
2 parents 72bb4ac + 9acb37d commit 6f6c4d4
Show file tree
Hide file tree
Showing 61 changed files with 420 additions and 547 deletions.
2 changes: 1 addition & 1 deletion pbiviz.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"displayName":"SPC Charts",
"guid":"PBISPC",
"visualClassName":"Visual",
"version":"1.3.3.7",
"version":"1.3.3.8",
"description":"A PowerBI custom visual for SPC charts",
"supportUrl":"https://github.com/AUS-DOH-Safety-and-Quality/PowerBI-SPC",
"gitHubUrl":"https://github.com/AUS-DOH-Safety-and-Quality/PowerBI-SPC"
Expand Down
32 changes: 29 additions & 3 deletions src/Classes/controlLimitsClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import two_in_three from "../Outlier Flagging/two_in_three"
import shift from "../Outlier Flagging/shift"
import settingsClass from "./settingsClass";
import checkFlagDirection from "../Functions/checkFlagDirection"
import truncate from "../Functions/truncate";
import { truncateInputs } from "../Functions/truncate";
import { multiply } from "../Functions/BinaryFunctions";

type controlLimitsArgs = {
keys: { x: number, id: number, label: string }[];
Expand All @@ -20,7 +23,7 @@ type controlLimitsArgs = {
count?: number[];
}

class controlLimitsClass {
export default class controlLimitsClass {
[key: string] : any;
keys: { x: number, id: number, label: string }[];
values: number[];
Expand All @@ -38,6 +41,31 @@ class controlLimitsClass {
two_in_three: string[];
shift: string[];

scaleLimits(inputSettings: settingsClass): void {
// Scale limits using provided multiplier
const multiplier: number = inputSettings.spc.multiplier;
this.alt_targets = rep(inputSettings.spc.alt_target, this.values.length);

["values", "targets", "alt_targets", "ll99", "ll95", "ul95", "ul99"].forEach(limit => {
this[limit] = multiply(this[limit], multiplier)
})
}

truncateLimits(inputSettings: settingsClass): void {
if (inputSettings.spc.chart_type === "run") {
return;
}

const limits: truncateInputs = {
lower: inputSettings.spc.ll_truncate,
upper: inputSettings.spc.ul_truncate
};

["ll99", "ll95", "ul95", "ul99"].forEach(limit => {
this[limit] = truncate(this[limit], limits);
});
}

flagOutliers(inputSettings: settingsClass) {
const process_flag_type: string = inputSettings.outliers.process_flag_type;
const improvement_direction: string = inputSettings.outliers.improvement_direction;
Expand Down Expand Up @@ -84,5 +112,3 @@ class controlLimitsClass {
}
}
}

export default controlLimitsClass
6 changes: 2 additions & 4 deletions src/Classes/dataClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import settingsClass from "./settingsClass"
import checkValidInput from "../Functions/checkValidInput"
import extractValues from "../Functions/extractValues"
import extractConditionalFormatting from "../Functions/extractConditionalFormatting"
import { defaultSettingsType } from "./defaultSettings";
import { defaultSettingsType } from "../defaultSettings";

class dataClass {
export default class dataClass {
keys: { x: number, id: number, label: string }[];
numerators: number[];
denominators: number[];
Expand Down Expand Up @@ -59,5 +59,3 @@ class dataClass {
this.scatter_formatting = extractValues(scatter_cond, valid_ids)
}
}

export default dataClass;
12 changes: 5 additions & 7 deletions src/Classes/plotPropertiesClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import dataClass from "./dataClass";
import controlLimitsClass from "./controlLimitsClass";
import { pixelConverter } from "powerbi-visuals-utils-typeutils";

type axisProperties = {
export type axisProperties = {
lower: number,
upper: number,
start_padding: number,
Expand All @@ -26,7 +26,7 @@ type axisProperties = {
label_colour: string
};

class plotPropertiesClass {
export default class plotPropertiesClass {
width: number;
height: number;
displayPlot: boolean;
Expand Down Expand Up @@ -134,10 +134,8 @@ class plotPropertiesClass {
label_font: args.inputSettings.y_axis.ylimit_label_font,
label_colour: args.inputSettings.y_axis.ylimit_label_colour
};
this.initialiseScale();
this.firstRun = false;

this.initialiseScale();
this.firstRun = false;
}
}

export default plotPropertiesClass
export { axisProperties }
12 changes: 5 additions & 7 deletions src/Classes/settingsClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import VisualEnumerationInstanceKinds = powerbi.VisualEnumerationInstanceKinds;
import { dataViewWildcard } from "powerbi-visuals-utils-dataviewutils";
import extractSetting from "../Functions/extractSetting";
import extractConditionalFormatting from "../Functions/extractConditionalFormatting";
import defaultSettings from "./defaultSettings"
import { defaultSettingsType, defaultSettingsKey } from "./defaultSettings";
import defaultSettings from "../defaultSettings"
import { defaultSettingsType, defaultSettingsKey } from "../defaultSettings";

/**
* This is the core class which controls the initialisation and
Expand All @@ -15,7 +15,7 @@ import { defaultSettingsType, defaultSettingsKey } from "./defaultSettings";
*
* These are defined in the settingsGroups.ts file
*/
class settingsClass implements defaultSettingsType {
export default class settingsClass implements defaultSettingsType {
canvas: defaultSettingsType["canvas"];
spc: defaultSettingsType["spc"];
outliers: defaultSettingsType["outliers"];
Expand All @@ -38,9 +38,8 @@ class settingsClass implements defaultSettingsType {
const allSettingGroups: string[] = Object.getOwnPropertyNames(this);

allSettingGroups.forEach(settingGroup => {
const condFormatting: defaultSettingsType[defaultSettingsKey] = inputView.categorical.categories
? extractConditionalFormatting(inputView.categorical, settingGroup, this)[0]
: null;
const categoricalView: powerbi.DataViewCategorical = inputView.categorical ? inputView.categorical : null;
const condFormatting: defaultSettingsType[defaultSettingsKey] = extractConditionalFormatting(categoricalView, settingGroup, this)[0];
// Get the names of all settings in a given class and
// use those to extract and update the relevant values
const settingNames: string[] = Object.getOwnPropertyNames(this[settingGroup]);
Expand Down Expand Up @@ -84,4 +83,3 @@ class settingsClass implements defaultSettingsType {
});
}
}
export default settingsClass;
152 changes: 64 additions & 88 deletions src/Classes/viewModelClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,16 @@ import checkInvalidDataView from "../Functions/checkInvalidDataView"
import buildTooltip from "../Functions/buildTooltip"
import plotPropertiesClass from "./plotPropertiesClass"
import getAesthetic from "../Functions/getAesthetic"
import { defaultSettingsType } from "./defaultSettings";
import { multiply } from "../Functions/BinaryFunctions";
import truncate from "../Functions/truncate"
import rep from "../Functions/rep"
import { defaultSettingsType } from "../defaultSettings";
import * as limitFunctions from "../Limit Calculations"

class lineData {
export class lineData {
x: number;
line_value: number;
group: string;
}

class plotData {
export class plotData {
x: number;
value: number;
aesthetics: defaultSettingsType["scatter"];
Expand All @@ -37,9 +34,9 @@ class plotData {
tooltip: VisualTooltipDataItem[];
}

type LimitArgs = { inputData: dataClass; inputSettings: settingsClass; }
export type LimitArgs = { inputData: dataClass; inputSettings: settingsClass; }

class viewModelClass {
export default class viewModelClass {
inputData: dataClass;
inputSettings: settingsClass;
controlLimits: controlLimitsClass;
Expand All @@ -51,9 +48,64 @@ class viewModelClass {
firstRun: boolean;
limitFunction: (x: LimitArgs) => controlLimitsClass;

getLimits(): controlLimitsClass {
let calcLimits: controlLimitsClass;
update(args: { options: VisualUpdateOptions; host: IVisualHost; }) {
if (this.firstRun) {
this.inputSettings = new settingsClass();
}
const dv: powerbi.DataView[] = args.options.dataViews;
this.inputSettings.update(dv[0]);

const split_indexes_storage: DataViewObject = dv[0].metadata.objects ? dv[0].metadata.objects.split_indexes_storage : null;
const split_indexes: DataViewPropertyValue = split_indexes_storage ? split_indexes_storage.split_indexes : null;
this.splitIndexes = split_indexes ? JSON.parse(<string>(split_indexes)) : new Array<number>();

// Make sure that the construction returns early with null members so
// that the visual does not crash when trying to process invalid data
if (checkInvalidDataView(dv)) {
this.inputData = <dataClass>null;
this.limitFunction = null;
this.controlLimits = null;
this.plotPoints = <plotData[]>null;
this.groupedLines = <[string, lineData[]][]>null;
this.splitIndexes = new Array<number>();
} else {

// Only re-construct data and re-calculate limits if they have changed
if (args.options.type === 2 || this.firstRun) {

// Extract input data, filter out invalid values, and identify any settings passed as data
this.inputData = new dataClass(dv[0].categorical, this.inputSettings)

// Initialise a new chartObject class which can be used to calculate the control limits
this.limitFunction = limitFunctions[this.inputSettings.spc.chart_type as keyof typeof limitFunctions]

// Use initialised chartObject to calculate control limits
this.calculateLimits();
this.controlLimits.scaleLimits(this.inputSettings);
this.controlLimits.truncateLimits(this.inputSettings);
this.controlLimits.flagOutliers(this.inputSettings)
console.log("calculatedLimits: ", this.controlLimits)

// Structure the data and calculated limits to the format needed for plotting
this.initialisePlotData(args.host);
this.initialiseGroupedLines();
}
}
if (this.firstRun) {
this.plotProperties = new plotPropertiesClass();
this.plotProperties.firstRun = true;
}
this.plotProperties.update({
options: args.options,
plotPoints: this.plotPoints,
controlLimits: this.controlLimits,
inputData: this.inputData,
inputSettings: this.inputSettings
})
this.firstRun = false;
}

calculateLimits(): void {
if (this.splitIndexes.length > 0) {
const indexes: number[] = this.splitIndexes
.concat([this.inputData.keys.length - 1])
Expand All @@ -74,7 +126,7 @@ class viewModelClass {
})

const calcLimitsGrouped: controlLimitsClass[] = groupedData.map(d => this.limitFunction({inputData: d, inputSettings: this.inputSettings}));
calcLimits = calcLimitsGrouped.reduce((all: controlLimitsClass, curr: controlLimitsClass) => {
this.controlLimits = calcLimitsGrouped.reduce((all: controlLimitsClass, curr: controlLimitsClass) => {
const allInner: controlLimitsClass = all;
Object.entries(all).forEach((entry, idx) => {
if (this.inputSettings.spc.chart_type !== "run" || !["ll99", "ll95", "ul95", "ul99"].includes(entry[0])) {
Expand All @@ -85,29 +137,8 @@ class viewModelClass {
})
} else {
// Calculate control limits using user-specified type
calcLimits = this.limitFunction({inputData: this.inputData, inputSettings: this.inputSettings});
}

calcLimits.flagOutliers(this.inputSettings);

// Scale limits using provided multiplier
const multiplier: number = this.inputSettings.spc.multiplier;

calcLimits.values = multiply(calcLimits.values, multiplier);
calcLimits.targets = multiply(calcLimits.targets, multiplier);
calcLimits.alt_targets = rep(this.inputSettings.spc.alt_target, calcLimits.values.length)

if (this.inputSettings.spc.chart_type !== "run") {
const limits: Record<string, number> = {
lower: this.inputSettings.y_axis.ylimit_l,
upper: this.inputSettings.y_axis.ylimit_u
}
calcLimits.ll99 = truncate(multiply(calcLimits.ll99, multiplier), limits);
calcLimits.ll95 = truncate(multiply(calcLimits.ll95, multiplier), limits);
calcLimits.ul95 = truncate(multiply(calcLimits.ul95, multiplier), limits);
calcLimits.ul99 = truncate(multiply(calcLimits.ul99, multiplier), limits);
this.controlLimits = this.limitFunction({inputData: this.inputData, inputSettings: this.inputSettings});
}
return calcLimits;
}

initialisePlotData(host: IVisualHost): void {
Expand Down Expand Up @@ -176,58 +207,6 @@ class viewModelClass {
this.groupedLines = d3.groups(formattedLines, d => d.group);
}

update(args: { options: VisualUpdateOptions; host: IVisualHost; }) {
if (this.firstRun) {
this.inputSettings = new settingsClass();
}
const dv: powerbi.DataView[] = args.options.dataViews;
this.inputSettings.update(dv[0]);
let split_indexes_storage: DataViewObject = dv[0].metadata.objects ? dv[0].metadata.objects.split_indexes_storage : null;
let split_indexes: DataViewPropertyValue = split_indexes_storage ? split_indexes_storage.split_indexes : null;
this.splitIndexes = split_indexes ? JSON.parse(<string>(split_indexes)) : new Array<number>();
// Make sure that the construction returns early with null members so
// that the visual does not crash when trying to process invalid data
if (checkInvalidDataView(dv)) {
this.inputData = <dataClass>null;
this.limitFunction = null;
this.controlLimits = null;
this.plotPoints = <plotData[]>null;
this.groupedLines = <[string, lineData[]][]>null;
this.splitIndexes = new Array<number>();
} else {

// Only re-construct data and re-calculate limits if they have changed
if (args.options.type === 2 || this.firstRun) {

// Extract input data, filter out invalid values, and identify any settings passed as data
this.inputData = new dataClass(dv[0].categorical, this.inputSettings)

// Initialise a new chartObject class which can be used to calculate the control limits
this.limitFunction = limitFunctions[this.inputSettings.spc.chart_type as keyof typeof limitFunctions]

// Use initialised chartObject to calculate control limits
this.controlLimits = this.getLimits();
console.log("calculatedLimits: ", this.controlLimits)

// Structure the data and calculated limits to the format needed for plotting
this.initialisePlotData(args.host);
this.initialiseGroupedLines();
}
}
if (this.firstRun) {
this.plotProperties = new plotPropertiesClass();
this.plotProperties.firstRun = true;
}
this.plotProperties.update({
options: args.options,
plotPoints: this.plotPoints,
controlLimits: this.controlLimits,
inputData: this.inputData,
inputSettings: this.inputSettings
})
this.firstRun = false;
}

constructor(from?: viewModelClass) {
if (from) {
this.inputData = from.inputData;
Expand All @@ -253,6 +232,3 @@ class viewModelClass {
}
}
}

export { lineData, plotData, LimitArgs }
export default viewModelClass
7 changes: 2 additions & 5 deletions src/D3 Plotting Functions/Icons/commonCause.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as d3 from "d3";
type SelectionBase = d3.Selection<SVGGElement, unknown, null, undefined>;
import { svgBaseType } from "../../visual";

/**
* Inline function to be called by D3 for rendering the Variation - Common Cause icon.
Expand All @@ -10,7 +9,7 @@ type SelectionBase = d3.Selection<SVGGElement, unknown, null, undefined>;
*
* @param selection The D3 parent object to which the icon's SVG code will be added
*/
function commonCause(selection: SelectionBase): void {
export default function commonCause(selection: svgBaseType): void {
selection.append("g")
.attr("clip-path","url(#clip2)")
.append("g")
Expand Down Expand Up @@ -107,5 +106,3 @@ function commonCause(selection: SelectionBase): void {
.attr("fill","#BFBFBF")
.attr("fill-rule","evenodd")
}

export default commonCause
Loading

0 comments on commit 6f6c4d4

Please sign in to comment.