diff --git a/pbiviz.json b/pbiviz.json index 42c259a..f8448f4 100644 --- a/pbiviz.json +++ b/pbiviz.json @@ -4,7 +4,7 @@ "displayName":"SPC Charts", "guid":"PBISPC", "visualClassName":"Visual", - "version":"1.3.3.5", + "version":"1.3.3.6", "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" diff --git a/src/Classes/chartObject.ts b/src/Classes/chartObject.ts deleted file mode 100644 index 3b86f36..0000000 --- a/src/Classes/chartObject.ts +++ /dev/null @@ -1,95 +0,0 @@ -import * as limitFunctions from "../Limit Calculations" -import dataObject from "./dataObject"; -import settingsObject from "./settingsObject"; -import controlLimits from "./controlLimits"; -import { multiply } from "../Functions/BinaryFunctions"; -import truncate from "../Functions/truncate" -import rep from "../Functions/rep" - -type chartObjectConstructor = { - inputData: dataObject; - inputSettings: settingsObject; - splitIndexes: number[]; -} - -type LimitArgs = { - inputData: dataObject; - inputSettings: settingsObject; -} - -class chartObject { - inputData: dataObject; - inputSettings: settingsObject; - splitIndexes: number[]; - limitFunction: (x: LimitArgs) => controlLimits; - - getLimits(): controlLimits { - let calcLimits: controlLimits; - - if (this.splitIndexes.length > 0) { - const indexes: number[] = this.splitIndexes - .concat([this.inputData.keys.length - 1]) - .sort((a,b) => a - b); - const groupedData: dataObject[] = indexes.map((d, idx) => { - // Force a deep copy - const data: dataObject = JSON.parse(JSON.stringify(this.inputData)); - if(idx === 0) { - data.denominators = data.denominators.slice(0, d + 1) - data.numerators = data.numerators.slice(0, d + 1) - data.keys = data.keys.slice(0, d + 1) - } else { - data.denominators = data.denominators.slice(indexes[idx - 1] + 1, d + 1) - data.numerators = data.numerators.slice(indexes[idx - 1] + 1, d + 1) - data.keys = data.keys.slice(indexes[idx - 1] + 1, d + 1) - } - return data; - }) - - 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) => { - if (this.inputSettings.spc.chart_type !== "run" || !["ll99", "ll95", "ul95", "ul99"].includes(entry[0])) { - allInner[entry[0] as keyof controlLimits] = entry[1].concat(Object.entries(curr)[idx][1]); - } - }) - return allInner; - }) - } 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 = { - 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); - } - return calcLimits; - } - - constructor(args: chartObjectConstructor) { - this.inputData = args.inputData; - this.inputSettings = args.inputSettings; - this.splitIndexes = args.splitIndexes; - - this.limitFunction = limitFunctions[args.inputSettings.spc.chart_type as keyof typeof limitFunctions] - } -} - -export { LimitArgs } -export default chartObject; diff --git a/src/Classes/controlLimits.ts b/src/Classes/controlLimitsClass.ts similarity index 91% rename from src/Classes/controlLimits.ts rename to src/Classes/controlLimitsClass.ts index 9ac4051..7b03568 100644 --- a/src/Classes/controlLimits.ts +++ b/src/Classes/controlLimitsClass.ts @@ -1,14 +1,13 @@ import rep from "../Functions/rep"; -import plotKey from "./plotKey" import astronomical from "../Outlier Flagging/astronomical" import trend from "../Outlier Flagging/trend" import two_in_three from "../Outlier Flagging/two_in_three" import shift from "../Outlier Flagging/shift" -import settingsObject from "./settingsObject"; +import settingsClass from "./settingsClass"; import checkFlagDirection from "../Functions/checkFlagDirection" type controlLimitsArgs = { - keys: plotKey[]; + keys: { x: number, id: number, label: string }[]; values: number[]; numerators?: number[]; denominators?: number[]; @@ -21,9 +20,9 @@ type controlLimitsArgs = { count?: number[]; } -class controlLimits { +class controlLimitsClass { [key: string] : any; - keys: plotKey[]; + keys: { x: number, id: number, label: string }[]; values: number[]; numerators?: number[]; denominators?: number[]; @@ -39,7 +38,7 @@ class controlLimits { two_in_three: string[]; shift: string[]; - flagOutliers(inputSettings: settingsObject) { + flagOutliers(inputSettings: settingsClass) { const process_flag_type: string = inputSettings.outliers.process_flag_type; const improvement_direction: string = inputSettings.outliers.improvement_direction; if (inputSettings.spc.chart_type !== "run") { @@ -86,4 +85,4 @@ class controlLimits { } } -export default controlLimits +export default controlLimitsClass diff --git a/src/Classes/dataObject.ts b/src/Classes/dataClass.ts similarity index 90% rename from src/Classes/dataObject.ts rename to src/Classes/dataClass.ts index a30d0d9..67d245b 100644 --- a/src/Classes/dataObject.ts +++ b/src/Classes/dataClass.ts @@ -3,15 +3,14 @@ import DataViewCategoryColumn = powerbi.DataViewCategoryColumn; import PrimitiveValue = powerbi.PrimitiveValue; import DataViewCategorical = powerbi.DataViewCategorical; import extractDataColumn from "../Functions/extractDataColumn" -import settingsObject from "./settingsObject" +import settingsClass from "./settingsClass" import checkValidInput from "../Functions/checkValidInput" import extractValues from "../Functions/extractValues" -import plotKey from "./plotKey" import extractConditionalFormatting from "../Functions/extractConditionalFormatting" import { defaultSettingsType } from "./defaultSettings"; -class dataObject { - keys: plotKey[]; +class dataClass { + keys: { x: number, id: number, label: string }[]; numerators: number[]; denominators: number[]; xbar_sds: number[]; @@ -21,7 +20,7 @@ class dataObject { categories: DataViewCategoryColumn; scatter_formatting: defaultSettingsType["scatter"][]; - constructor(inputView: DataViewCategorical, inputSettings: settingsObject) { + constructor(inputView: DataViewCategorical, inputSettings: settingsClass) { const numerators: number[] = extractDataColumn(inputView, "numerators"); const denominators: number[] = extractDataColumn(inputView, "denominators"); const xbar_sds: number[] = extractDataColumn(inputView, "xbar_sds"); @@ -29,7 +28,7 @@ class dataObject { const scatter_cond = extractConditionalFormatting(inputView, "scatter", inputSettings) const valid_ids: number[] = new Array(); - const valid_keys: plotKey[] = new Array(); + const valid_keys: { x: number, id: number, label: string }[] = new Array<{ x: number, id: number, label: string }>(); for (let i: number = 0; i < numerators.length; i++) { if (checkValidInput(numerators[i], @@ -61,4 +60,4 @@ class dataObject { } } -export default dataObject; +export default dataClass; diff --git a/src/Classes/plotKey.ts b/src/Classes/plotKey.ts deleted file mode 100644 index 4aa9b19..0000000 --- a/src/Classes/plotKey.ts +++ /dev/null @@ -1,3 +0,0 @@ -type plotKey = { x: number, id: number, label: string } - -export default plotKey; diff --git a/src/Classes/plotProperties.ts b/src/Classes/plotPropertiesClass.ts similarity index 89% rename from src/Classes/plotProperties.ts rename to src/Classes/plotPropertiesClass.ts index a9cc18d..67dd288 100644 --- a/src/Classes/plotProperties.ts +++ b/src/Classes/plotPropertiesClass.ts @@ -2,10 +2,10 @@ import * as d3 from "d3"; import powerbi from "powerbi-visuals-api"; import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions; import truncate from "../Functions/truncate"; -import { plotData } from "./viewModel" -import settingsObject from "./settingsObject"; -import dataObject from "./dataObject"; -import controlLimits from "./controlLimits"; +import { plotData } from "./viewModelClass" +import settingsClass from "./settingsClass"; +import dataClass from "./dataClass"; +import controlLimitsClass from "./controlLimitsClass"; import { pixelConverter } from "powerbi-visuals-utils-typeutils"; type axisProperties = { @@ -51,9 +51,9 @@ class plotPropertiesClass { update(args: { options: VisualUpdateOptions, plotPoints: plotData[], - calculatedLimits: controlLimits, - inputData: dataObject, - inputSettings: settingsObject }) { + controlLimits: controlLimitsClass, + inputData: dataClass, + inputSettings: settingsClass }) { // Get the width and height of plotting space this.width = args.options.viewport.width; @@ -69,18 +69,18 @@ class plotPropertiesClass { let yUpperLimit: number = args.inputSettings.y_axis.ylimit_u; // Only update data-/settings-dependent plot aesthetics if they have changed - if (args.inputData && args.calculatedLimits) { - xUpperLimit = xUpperLimit !== null ? xUpperLimit : d3.max(args.calculatedLimits.keys.map(d => d.x)) + if (args.inputData && args.controlLimits) { + xUpperLimit = xUpperLimit !== null ? xUpperLimit : d3.max(args.controlLimits.keys.map(d => d.x)) const limitMultiplier: number = args.inputSettings.y_axis.limit_multiplier; const chart_type: string = args.inputSettings.spc.chart_type; - const values: number[] = args.calculatedLimits.values; - const ul99: number[] = args.calculatedLimits.ul99; - const ll99: number[] = args.calculatedLimits.ll99; + const values: number[] = args.controlLimits.values; + const ul99: number[] = args.controlLimits.ul99; + const ll99: number[] = args.controlLimits.ll99; const maxValueOrLimit: number = d3.max(values.concat(ul99)); const minValueOrLimit: number = d3.min(values.concat(ll99)); - const maxTarget: number = d3.max(args.calculatedLimits.targets); - const minTarget: number = d3.min(args.calculatedLimits.targets); + const maxTarget: number = d3.max(args.controlLimits.targets); + const minTarget: number = d3.min(args.controlLimits.targets); const upperLimitRaw: number = maxTarget + (maxValueOrLimit - maxTarget) * limitMultiplier; const lowerLimitRaw: number = minTarget - (minTarget - minValueOrLimit) * limitMultiplier; diff --git a/src/Classes/settingsObject.ts b/src/Classes/settingsClass.ts similarity index 97% rename from src/Classes/settingsObject.ts rename to src/Classes/settingsClass.ts index 82a7641..1a9cc57 100644 --- a/src/Classes/settingsObject.ts +++ b/src/Classes/settingsClass.ts @@ -15,7 +15,7 @@ import { defaultSettingsType, defaultSettingsKey } from "./defaultSettings"; * * These are defined in the settingsGroups.ts file */ -class settingsObject implements defaultSettingsType { +class settingsClass implements defaultSettingsType { canvas: defaultSettingsType["canvas"]; spc: defaultSettingsType["spc"]; outliers: defaultSettingsType["outliers"]; @@ -84,4 +84,4 @@ class settingsObject implements defaultSettingsType { }); } } -export default settingsObject; +export default settingsClass; diff --git a/src/Classes/svgDotsClass.ts b/src/Classes/svgDotsClass.ts index c0651ba..c490de2 100644 --- a/src/Classes/svgDotsClass.ts +++ b/src/Classes/svgDotsClass.ts @@ -1,7 +1,7 @@ import * as d3 from "d3"; import powerbi from "powerbi-visuals-api"; -import viewModelObject from "./viewModel"; -import { plotData } from "./viewModel"; +import viewModelClass from "./viewModelClass"; +import { plotData } from "./viewModelClass"; import between from "../Functions/between"; import ISelectionId = powerbi.visuals.ISelectionId; type SelectionBase = d3.Selection; @@ -9,7 +9,7 @@ type SelectionBase = d3.Selection; class svgDotsClass { dotsGroup: SelectionBase; - draw(viewModel: viewModelObject): void { + draw(viewModel: viewModelClass): void { this.dotsGroup.selectAll(".dotsgroup").remove() if (!(viewModel.plotPoints)) { return; diff --git a/src/Classes/svgIconClass.ts b/src/Classes/svgIconClass.ts index 4385037..c5b1baa 100644 --- a/src/Classes/svgIconClass.ts +++ b/src/Classes/svgIconClass.ts @@ -1,7 +1,7 @@ import * as d3 from "d3"; import * as iconSVG from "../Icons" -import viewModelObject from "./viewModel"; -import controlLimits from "./controlLimits"; +import viewModelClass from "./viewModelClass"; +import controlLimitsClass from "./controlLimitsClass"; type SelectionBase = d3.Selection; class svgIconClass { @@ -92,8 +92,8 @@ class svgIconClass { return icon_svg } - variationIconsToDraw(viewModel: viewModelObject): string[] { - const currLimits: controlLimits = viewModel.calculatedLimits; + variationIconsToDraw(viewModel: viewModelClass): string[] { + const currLimits: controlLimitsClass = viewModel.controlLimits; const imp_direction: string = viewModel.inputSettings.outliers.improvement_direction; const suffix_map: Record = { "increase" : "High", @@ -138,7 +138,7 @@ class svgIconClass { return iconsPresent; } - drawIcons(viewModel: viewModelObject): void { + drawIcons(viewModel: viewModelClass): void { d3.selectAll(".icongroup").remove() const draw_variation: boolean = viewModel.inputSettings.nhs_icons.show_variation_icons; if (!draw_variation) { diff --git a/src/Classes/svgLinesClass.ts b/src/Classes/svgLinesClass.ts index c7135c0..1a452d8 100644 --- a/src/Classes/svgLinesClass.ts +++ b/src/Classes/svgLinesClass.ts @@ -1,7 +1,7 @@ import * as d3 from "d3"; import powerbi from "powerbi-visuals-api"; -import viewModelObject from "./viewModel"; -import { lineData } from "./viewModel"; +import viewModelClass from "./viewModelClass"; +import { lineData } from "./viewModelClass"; import between from "../Functions/between"; import getAesthetic from "../Functions/getAesthetic"; import ISelectionId = powerbi.visuals.ISelectionId; @@ -10,7 +10,7 @@ type SelectionBase = d3.Selection; class svgLinesClass { linesGroup: SelectionBase; - draw(viewModel: viewModelObject): void { + draw(viewModel: viewModelClass): void { this.linesGroup.selectAll(".linesgroup").remove() if (!(viewModel.groupedLines)) { return; diff --git a/src/Classes/svgSelectionClass.ts b/src/Classes/svgSelectionClass.ts index 409a696..7dbc64c 100644 --- a/src/Classes/svgSelectionClass.ts +++ b/src/Classes/svgSelectionClass.ts @@ -1,6 +1,6 @@ import * as d3 from "d3"; import svgObjectClass from "./svgObjectClass" -import viewModel from "./viewModel" +import viewModel from "./viewModelClass" type SelectionAny = d3.Selection; diff --git a/src/Classes/viewModel.ts b/src/Classes/viewModel.ts deleted file mode 100644 index cce6b59..0000000 --- a/src/Classes/viewModel.ts +++ /dev/null @@ -1,183 +0,0 @@ -import * as d3 from "d3"; -import powerbi from "powerbi-visuals-api"; -import IVisualHost = powerbi.extensibility.visual.IVisualHost; -import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions; -import DataViewPropertyValue = powerbi.DataViewPropertyValue; -import DataViewObject = powerbi.DataViewObject; -import VisualTooltipDataItem = powerbi.extensibility.VisualTooltipDataItem; -import ISelectionId = powerbi.visuals.ISelectionId; -import chartObject from "./chartObject" -import settingsObject from "./settingsObject"; -import dataObject from "./dataObject"; -import controlLimits from "./controlLimits"; -import checkInvalidDataView from "../Functions/checkInvalidDataView" -import buildTooltip from "../Functions/buildTooltip" -import plotPropertiesClass from "./plotProperties" -import getAesthetic from "../Functions/getAesthetic" -import { defaultSettingsType } from "./defaultSettings"; - -class lineData { - x: number; - line_value: number; - group: string; -} - -class plotData { - x: number; - value: number; - aesthetics: defaultSettingsType["scatter"]; - // ISelectionId allows the visual to report the selection choice to PowerBI - identity: ISelectionId; - // Flag for whether dot should be highlighted by selections in other charts - highlighted: boolean; - // Tooltip data to print - tooltip: VisualTooltipDataItem[]; -} - -class viewModelObject { - inputData: dataObject; - inputSettings: settingsObject; - chartBase: chartObject; - calculatedLimits: controlLimits; - plotPoints: plotData[]; - groupedLines: [string, lineData[]][]; - tickLabels: { x: number; label: string; }[]; - plotProperties: plotPropertiesClass; - splitIndexes: number[]; - firstRun: boolean; - - initialisePlotData(host: IVisualHost): void { - this.plotPoints = new Array(); - this.tickLabels = new Array<{ x: number; label: string; }>(); - - for (let i: number = 0; i < this.calculatedLimits.keys.length; i++) { - const index: number = this.calculatedLimits.keys[i].x; - const aesthetics: defaultSettingsType["scatter"] = this.inputData.scatter_formatting[i] - if (this.calculatedLimits.shift[i] !== "none") { - aesthetics.colour = getAesthetic(this.calculatedLimits.shift[i], "outliers", - "shift_colour", this.inputSettings) as string; - } - if (this.calculatedLimits.trend[i] !== "none") { - aesthetics.colour = getAesthetic(this.calculatedLimits.trend[i], "outliers", - "trend_colour", this.inputSettings) as string; - } - if (this.calculatedLimits.two_in_three[i] !== "none") { - aesthetics.colour = getAesthetic(this.calculatedLimits.two_in_three[i], "outliers", - "two_in_three_colour", this.inputSettings) as string; - } - if (this.calculatedLimits.astpoint[i] !== "none") { - aesthetics.colour = getAesthetic(this.calculatedLimits.astpoint[i], "outliers", - "ast_colour", this.inputSettings) as string; - } - - this.plotPoints.push({ - x: index, - value: this.calculatedLimits.values[i], - aesthetics: aesthetics, - identity: host.createSelectionIdBuilder() - .withCategory(this.inputData.categories, - this.inputData.keys[i].id) - .createSelectionId(), - highlighted: this.inputData.highlights ? (this.inputData.highlights[index] ? true : false) : false, - tooltip: buildTooltip(i, this.calculatedLimits, this.inputData, this.inputSettings) - }) - this.tickLabels.push({x: index, label: this.calculatedLimits.keys[i].label}); - } - } - - initialiseGroupedLines(): void { - const labels: string[] = ["ll99", "ll95", "ul95", "ul99", "targets", "values", "alt_targets"]; - - const formattedLines: lineData[] = new Array(); - const nLimits = this.calculatedLimits.keys.length; - - for (let i: number = 0; i < nLimits; i++) { - labels.forEach(label => { - // By adding an additional null line value at each re-baseline point - // we avoid rendering a line joining each segment - if (this.chartBase.splitIndexes.includes(i - 1)) { - formattedLines.push({ - x: this.calculatedLimits.keys[i].x, - line_value: null, - group: label - }) - } - formattedLines.push({ - x: this.calculatedLimits.keys[i].x, - line_value: this.calculatedLimits[label] ? this.calculatedLimits[label][i] : null, - group: label - }) - }) - } - this.groupedLines = d3.groups(formattedLines, d => d.group); - } - - update(args: { options: VisualUpdateOptions; host: IVisualHost; }) { - if (this.firstRun) { - this.inputSettings = new settingsObject(); - } - 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((split_indexes)) : new Array(); - // 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 = null; - this.chartBase = null; - this.calculatedLimits = null; - this.plotPoints = null; - this.groupedLines = <[string, lineData[]][]>null; - this.splitIndexes = new Array(); - } 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 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, - splitIndexes: this.splitIndexes ? this.splitIndexes : new Array() }); - - // Use initialised chartObject to calculate control limits - this.calculatedLimits = this.chartBase.getLimits(); - console.log("calculatedLimits: ", this.calculatedLimits) - - // 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, - calculatedLimits: this.calculatedLimits, - inputData: this.inputData, - inputSettings: this.inputSettings - }) - this.firstRun = false; - } - - constructor() { - this.inputData = null; - this.inputSettings = null; - this.chartBase = null; - this.calculatedLimits = null; - this.plotPoints = null; - this.groupedLines = <[string, lineData[]][]>null; - this.plotProperties = null; - this.firstRun = true - this.splitIndexes = new Array(); - } -} - -export { lineData, plotData } -export default viewModelObject diff --git a/src/Classes/viewModelClass.ts b/src/Classes/viewModelClass.ts new file mode 100644 index 0000000..b667801 --- /dev/null +++ b/src/Classes/viewModelClass.ts @@ -0,0 +1,245 @@ +import * as d3 from "d3"; +import powerbi from "powerbi-visuals-api"; +import IVisualHost = powerbi.extensibility.visual.IVisualHost; +import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions; +import DataViewPropertyValue = powerbi.DataViewPropertyValue; +import DataViewObject = powerbi.DataViewObject; +import VisualTooltipDataItem = powerbi.extensibility.VisualTooltipDataItem; +import ISelectionId = powerbi.visuals.ISelectionId; +import settingsClass from "./settingsClass"; +import dataClass from "./dataClass"; +import controlLimitsClass from "./controlLimitsClass"; +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 * as limitFunctions from "../Limit Calculations" + +class lineData { + x: number; + line_value: number; + group: string; +} + +class plotData { + x: number; + value: number; + aesthetics: defaultSettingsType["scatter"]; + // ISelectionId allows the visual to report the selection choice to PowerBI + identity: ISelectionId; + // Flag for whether dot should be highlighted by selections in other charts + highlighted: boolean; + // Tooltip data to print + tooltip: VisualTooltipDataItem[]; +} + +type LimitArgs = { inputData: dataClass; inputSettings: settingsClass; } + +class viewModelClass { + inputData: dataClass; + inputSettings: settingsClass; + controlLimits: controlLimitsClass; + plotPoints: plotData[]; + groupedLines: [string, lineData[]][]; + tickLabels: { x: number; label: string; }[]; + plotProperties: plotPropertiesClass; + splitIndexes: number[]; + firstRun: boolean; + limitFunction: (x: LimitArgs) => controlLimitsClass; + + getLimits(): controlLimitsClass { + let calcLimits: controlLimitsClass; + + if (this.splitIndexes.length > 0) { + const indexes: number[] = this.splitIndexes + .concat([this.inputData.keys.length - 1]) + .sort((a,b) => a - b); + const groupedData: dataClass[] = indexes.map((d, idx) => { + // Force a deep copy + const data: dataClass = JSON.parse(JSON.stringify(this.inputData)); + if(idx === 0) { + data.denominators = data.denominators.slice(0, d + 1) + data.numerators = data.numerators.slice(0, d + 1) + data.keys = data.keys.slice(0, d + 1) + } else { + data.denominators = data.denominators.slice(indexes[idx - 1] + 1, d + 1) + data.numerators = data.numerators.slice(indexes[idx - 1] + 1, d + 1) + data.keys = data.keys.slice(indexes[idx - 1] + 1, d + 1) + } + return data; + }) + + const calcLimitsGrouped: controlLimitsClass[] = groupedData.map(d => this.limitFunction({inputData: d, inputSettings: this.inputSettings})); + calcLimits = 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])) { + allInner[entry[0] as keyof controlLimitsClass] = entry[1].concat(Object.entries(curr)[idx][1]); + } + }) + return allInner; + }) + } 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 = { + 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); + } + return calcLimits; + } + + initialisePlotData(host: IVisualHost): void { + this.plotPoints = new Array(); + this.tickLabels = new Array<{ x: number; label: string; }>(); + + for (let i: number = 0; i < this.controlLimits.keys.length; i++) { + const index: number = this.controlLimits.keys[i].x; + const aesthetics: defaultSettingsType["scatter"] = this.inputData.scatter_formatting[i] + if (this.controlLimits.shift[i] !== "none") { + aesthetics.colour = getAesthetic(this.controlLimits.shift[i], "outliers", + "shift_colour", this.inputSettings) as string; + } + if (this.controlLimits.trend[i] !== "none") { + aesthetics.colour = getAesthetic(this.controlLimits.trend[i], "outliers", + "trend_colour", this.inputSettings) as string; + } + if (this.controlLimits.two_in_three[i] !== "none") { + aesthetics.colour = getAesthetic(this.controlLimits.two_in_three[i], "outliers", + "two_in_three_colour", this.inputSettings) as string; + } + if (this.controlLimits.astpoint[i] !== "none") { + aesthetics.colour = getAesthetic(this.controlLimits.astpoint[i], "outliers", + "ast_colour", this.inputSettings) as string; + } + + this.plotPoints.push({ + x: index, + value: this.controlLimits.values[i], + aesthetics: aesthetics, + identity: host.createSelectionIdBuilder() + .withCategory(this.inputData.categories, + this.inputData.keys[i].id) + .createSelectionId(), + highlighted: this.inputData.highlights ? (this.inputData.highlights[index] ? true : false) : false, + tooltip: buildTooltip(i, this.controlLimits, this.inputData, this.inputSettings) + }) + this.tickLabels.push({x: index, label: this.controlLimits.keys[i].label}); + } + } + + initialiseGroupedLines(): void { + const labels: string[] = ["ll99", "ll95", "ul95", "ul99", "targets", "values", "alt_targets"]; + + const formattedLines: lineData[] = new Array(); + const nLimits = this.controlLimits.keys.length; + + for (let i: number = 0; i < nLimits; i++) { + labels.forEach(label => { + // By adding an additional null line value at each re-baseline point + // we avoid rendering a line joining each segment + if (this.splitIndexes.includes(i - 1)) { + formattedLines.push({ + x: this.controlLimits.keys[i].x, + line_value: null, + group: label + }) + } + formattedLines.push({ + x: this.controlLimits.keys[i].x, + line_value: this.controlLimits[label] ? this.controlLimits[label][i] : null, + group: label + }) + }) + } + 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((split_indexes)) : new Array(); + // 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 = null; + this.limitFunction = null; + this.controlLimits = null; + this.plotPoints = null; + this.groupedLines = <[string, lineData[]][]>null; + this.splitIndexes = new Array(); + } 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() { + this.inputData = null; + this.inputSettings = null; + this.limitFunction = null; + this.controlLimits = null; + this.plotPoints = null; + this.groupedLines = <[string, lineData[]][]>null; + this.plotProperties = null; + this.firstRun = true + this.splitIndexes = new Array(); + } +} + +export { lineData, plotData, LimitArgs } +export default viewModelClass diff --git a/src/Functions/buildTooltip.ts b/src/Functions/buildTooltip.ts index 8e460ea..59e6edf 100644 --- a/src/Functions/buildTooltip.ts +++ b/src/Functions/buildTooltip.ts @@ -1,8 +1,8 @@ import powerbi from "powerbi-visuals-api"; import VisualTooltipDataItem = powerbi.extensibility.VisualTooltipDataItem; -import controlLimits from "../Classes/controlLimits"; -import dataObject from "../Classes/dataObject"; -import settingsObject from "../Classes/settingsObject"; +import controlLimitsClass from "../Classes/controlLimitsClass"; +import dataClass from "../Classes/dataClass"; +import settingsClass from "../Classes/settingsClass"; const valueNames: Record = { "i": "Observation", @@ -21,24 +21,24 @@ const valueNames: Record = { const integerParams: string[] = ["c", "p", "pp"]; -function buildTooltip(index: number, calculatedLimits: controlLimits, - inputData: dataObject, inputSettings: settingsObject): VisualTooltipDataItem[] { +function buildTooltip(index: number, controlLimits: controlLimitsClass, + inputData: dataClass, inputSettings: settingsClass): VisualTooltipDataItem[] { - const date: string = calculatedLimits.keys[index].label; - const value: number = calculatedLimits.values[index]; - const numerator: number = calculatedLimits.numerators ? calculatedLimits.numerators[index] : null; - const denominator: number = calculatedLimits.denominators ? calculatedLimits.denominators[index] : null; - const target: number = calculatedLimits.targets[index]; + const date: string = controlLimits.keys[index].label; + const value: number = controlLimits.values[index]; + const numerator: number = controlLimits.numerators ? controlLimits.numerators[index] : null; + const denominator: number = controlLimits.denominators ? controlLimits.denominators[index] : null; + const target: number = controlLimits.targets[index]; const limits = { - ll99: calculatedLimits.ll99 ? calculatedLimits.ll99[index] : null, - ul99: calculatedLimits.ll99 ? calculatedLimits.ul99[index] : null + ll99: controlLimits.ll99 ? controlLimits.ll99[index] : null, + ul99: controlLimits.ll99 ? controlLimits.ul99[index] : null }; const chart_type: string = inputSettings.spc.chart_type; const prop_labels: boolean = inputData.percentLabels; - const astpoint: string = calculatedLimits.astpoint[index]; - const trend: string = calculatedLimits.trend[index]; - const shift: string = calculatedLimits.shift[index]; - const two_in_three: string = calculatedLimits.two_in_three[index]; + const astpoint: string = controlLimits.astpoint[index]; + const trend: string = controlLimits.trend[index]; + const shift: string = controlLimits.shift[index]; + const two_in_three: string = controlLimits.two_in_three[index]; let multiplier: number = inputSettings.spc.multiplier; if (prop_labels && (multiplier === 1)) { multiplier = 100; diff --git a/src/Functions/extractConditionalFormatting.ts b/src/Functions/extractConditionalFormatting.ts index 3691e83..4be758e 100644 --- a/src/Functions/extractConditionalFormatting.ts +++ b/src/Functions/extractConditionalFormatting.ts @@ -1,12 +1,12 @@ import powerbi from "powerbi-visuals-api" import DataViewCategoryColumn = powerbi.DataViewCategoryColumn; import DataViewCategorical = powerbi.DataViewCategorical; -import settingsObject from "../Classes/settingsObject"; +import settingsClass from "../Classes/settingsClass"; import { defaultSettingsType, defaultSettingsKey } from "../Classes/defaultSettings"; import extractSetting from "./extractSetting"; -function extractConditionalFormatting(inputView: DataViewCategorical, name: string, inputSettings: settingsObject): SettingsT[] { +function extractConditionalFormatting(inputView: DataViewCategorical, name: string, inputSettings: settingsClass): SettingsT[] { const inputCategories: DataViewCategoryColumn = (inputView.categories as DataViewCategoryColumn[])[0]; const settingNames = Object.getOwnPropertyNames(inputSettings[name]) diff --git a/src/Functions/extractDataColumn.ts b/src/Functions/extractDataColumn.ts index b88c9bb..75105c3 100644 --- a/src/Functions/extractDataColumn.ts +++ b/src/Functions/extractDataColumn.ts @@ -3,13 +3,13 @@ import DataViewValueColumn = powerbi.DataViewValueColumn; import DataViewValueColumns = powerbi.DataViewValueColumns; import DataViewCategorical = powerbi.DataViewCategorical; import DataViewCategoryColumn = powerbi.DataViewCategoryColumn; -import settingsObject from "../Classes/settingsObject"; +import settingsClass from "../Classes/settingsClass"; import dateToFormattedString from "./dateToFormattedString"; type TargetT = number[] | string[] | number | string; function extractDataColumn(inputView: DataViewCategorical, name: string, - inputSettings?: settingsObject): T { + inputSettings?: settingsClass): T { let columnRaw: DataViewValueColumn; if (name === "key") { const columnRawTmp: DataViewValueColumn[] = (inputView.categories as DataViewCategoryColumn[]).filter(viewColumn => { diff --git a/src/Functions/getAesthetic.ts b/src/Functions/getAesthetic.ts index f9c2f54..efb6cb2 100644 --- a/src/Functions/getAesthetic.ts +++ b/src/Functions/getAesthetic.ts @@ -1,4 +1,4 @@ -import settingsObject from "../Classes/settingsObject" +import settingsClass from "../Classes/settingsClass" const lineNameMap: Record = { "ll99" : "99", @@ -10,7 +10,7 @@ const lineNameMap: Record = { "alt_targets" : "alt_target" } -function getAesthetic(type: string, group: string, aesthetic: string, inputSettings: settingsObject): string | number { +function getAesthetic(type: string, group: string, aesthetic: string, inputSettings: settingsClass): string | number { const mapName: string = group.includes("line") ? lineNameMap[type] : type; const settingName: string = aesthetic + "_" + mapName; return inputSettings[group][settingName]; diff --git a/src/Limit Calculations/c.ts b/src/Limit Calculations/c.ts index 31d3615..7e28054 100644 --- a/src/Limit Calculations/c.ts +++ b/src/Limit Calculations/c.ts @@ -1,15 +1,15 @@ 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"; +import dataClass from "../Classes/dataClass" +import controlLimitsClass from "../Classes/controlLimitsClass" +import { LimitArgs } from "../Classes/viewModelClass"; -function cLimits(args: LimitArgs): controlLimits { - const inputData: dataObject = args.inputData; +function cLimits(args: LimitArgs): controlLimitsClass { + const inputData: dataClass = args.inputData; const cl: number = d3.mean(inputData.numerators); const sigma: number = Math.sqrt(cl); - return new controlLimits({ + return new controlLimitsClass({ keys: inputData.keys, values: inputData.numerators, targets: rep(cl, inputData.keys.length), diff --git a/src/Limit Calculations/g.ts b/src/Limit Calculations/g.ts index 513c1d7..2340498 100644 --- a/src/Limit Calculations/g.ts +++ b/src/Limit Calculations/g.ts @@ -1,16 +1,16 @@ import * as d3 from "d3"; 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"; +import dataClass from "../Classes/dataClass"; +import controlLimitsClass from "../Classes/controlLimitsClass"; +import { LimitArgs } from "../Classes/viewModelClass"; -function gLimits(args: LimitArgs): controlLimits { - const inputData: dataObject = args.inputData; +function gLimits(args: LimitArgs): controlLimitsClass { + const inputData: dataClass = args.inputData; const cl: number = d3.mean(inputData.numerators); const sigma: number = sqrt(cl * (cl + 1)); - return new controlLimits({ + return new controlLimitsClass({ keys: inputData.keys, values: inputData.numerators, targets: rep(d3.median(inputData.numerators), inputData.keys.length), diff --git a/src/Limit Calculations/i.ts b/src/Limit Calculations/i.ts index 3fdc645..fac5772 100644 --- a/src/Limit Calculations/i.ts +++ b/src/Limit Calculations/i.ts @@ -3,12 +3,12 @@ import diff from "../Functions/diff" import rep from "../Functions/rep"; 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"; +import dataClass from "../Classes/dataClass"; +import controlLimitsClass from "../Classes/controlLimitsClass"; +import { LimitArgs } from "../Classes/viewModelClass"; -function iLimits(args: LimitArgs): controlLimits { - const inputData: dataObject = args.inputData; +function iLimits(args: LimitArgs): controlLimitsClass { + const inputData: dataClass = args.inputData; const useRatio: boolean = (inputData.denominators && inputData.denominators.length > 0); const ratio: number[] = useRatio ? divide(inputData.numerators, inputData.denominators) @@ -23,7 +23,7 @@ function iLimits(args: LimitArgs): controlLimits { const sigma: number = d3.mean(consec_diff_valid) / 1.128; - return new controlLimits({ + return new controlLimitsClass({ keys: inputData.keys, values: ratio.map(d => isNaN(d) ? 0 : d), numerators: useRatio ? inputData.numerators : undefined, diff --git a/src/Limit Calculations/mr.ts b/src/Limit Calculations/mr.ts index fdca919..834a50d 100644 --- a/src/Limit Calculations/mr.ts +++ b/src/Limit Calculations/mr.ts @@ -3,12 +3,12 @@ import diff from "../Functions/diff" import rep from "../Functions/rep"; 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"; +import dataClass from "../Classes/dataClass"; +import controlLimitsClass from "../Classes/controlLimitsClass"; +import { LimitArgs } from "../Classes/viewModelClass"; -function mrLimits(args: LimitArgs): controlLimits { - const inputData: dataObject = args.inputData; +function mrLimits(args: LimitArgs): controlLimitsClass { + const inputData: dataClass = args.inputData; const useRatio: boolean = (inputData.denominators && inputData.denominators.length > 0); const ratio: number[] = useRatio ? divide(inputData.numerators, inputData.denominators) @@ -17,7 +17,7 @@ function mrLimits(args: LimitArgs): controlLimits { const consec_diff: number[] = abs(diff(ratio)); const cl: number = d3.mean(consec_diff); - return new controlLimits({ + return new controlLimitsClass({ keys: inputData.keys, values: consec_diff, numerators: useRatio ? inputData.numerators : null, diff --git a/src/Limit Calculations/p.ts b/src/Limit Calculations/p.ts index af7e97f..8cfeb28 100644 --- a/src/Limit Calculations/p.ts +++ b/src/Limit Calculations/p.ts @@ -2,17 +2,17 @@ import * as d3 from "d3"; import rep from "../Functions/rep"; import { sqrt } from "../Functions/UnaryFunctions"; import { subtract, add, divide, multiply } from "../Functions/BinaryFunctions"; -import controlLimits from "../Classes/controlLimits"; -import dataObject from "../Classes/dataObject"; +import controlLimitsClass from "../Classes/controlLimitsClass"; +import dataClass from "../Classes/dataClass"; import truncate from "../Functions/truncate" -import {LimitArgs} from "../Classes/chartObject"; +import { LimitArgs } from "../Classes/viewModelClass"; -function pLimits(args: LimitArgs): controlLimits { - const inputData: dataObject = args.inputData; +function pLimits(args: LimitArgs): controlLimitsClass { + const inputData: dataClass = args.inputData; const cl: number = d3.sum(inputData.numerators) / d3.sum(inputData.denominators); const sigma: number[] = sqrt(divide(cl * (1 - cl), inputData.denominators)); - return new controlLimits({ + return new controlLimitsClass({ keys: inputData.keys, values: divide(inputData.numerators, inputData.denominators), numerators: inputData.numerators, diff --git a/src/Limit Calculations/pprime.ts b/src/Limit Calculations/pprime.ts index 6a7f3b9..016dfd1 100644 --- a/src/Limit Calculations/pprime.ts +++ b/src/Limit Calculations/pprime.ts @@ -3,13 +3,13 @@ import rep from "../Functions/rep"; import diff from "../Functions/diff"; import { sqrt, abs } from "../Functions/UnaryFunctions"; import { subtract, add, divide, multiply } from "../Functions/BinaryFunctions"; -import controlLimits from "../Classes/controlLimits"; -import dataObject from "../Classes/dataObject"; +import controlLimitsClass from "../Classes/controlLimitsClass"; +import dataClass from "../Classes/dataClass"; import truncate from "../Functions/truncate"; -import {LimitArgs} from "../Classes/chartObject"; +import { LimitArgs } from "../Classes/viewModelClass"; -function pprimeLimits(args: LimitArgs): controlLimits { - const inputData: dataObject = args.inputData; +function pprimeLimits(args: LimitArgs): controlLimitsClass { + const inputData: dataClass = 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)); @@ -21,7 +21,7 @@ function pprimeLimits(args: LimitArgs): controlLimits { 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({ + return new controlLimitsClass({ keys: inputData.keys, values: val, numerators: inputData.numerators, diff --git a/src/Limit Calculations/run.ts b/src/Limit Calculations/run.ts index 053c2ed..17067e5 100644 --- a/src/Limit Calculations/run.ts +++ b/src/Limit Calculations/run.ts @@ -1,12 +1,12 @@ import * as d3 from "d3"; 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"; +import controlLimits from "../Classes/controlLimitsClass"; +import dataClass from "../Classes/dataClass"; +import { LimitArgs } from "../Classes/viewModelClass"; function runLimits(args: LimitArgs): controlLimits { - const inputData: dataObject = args.inputData; + const inputData: dataClass = 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 5580be7..2aad1fd 100644 --- a/src/Limit Calculations/s.ts +++ b/src/Limit Calculations/s.ts @@ -3,12 +3,12 @@ import { b3, b4 } from "../Functions/Constants"; import rep from "../Functions/rep"; 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"; +import controlLimitsClass from "../Classes/controlLimitsClass"; +import dataClass from "../Classes/dataClass"; +import { LimitArgs } from "../Classes/viewModelClass"; -function sLimits(args: LimitArgs): controlLimits { - const inputData: dataObject = args.inputData; +function sLimits(args: LimitArgs): controlLimitsClass { + const inputData: dataClass = args.inputData; const group_sd: number[] = inputData.numerators; const count_per_group: number[] = inputData.denominators; @@ -24,7 +24,7 @@ function sLimits(args: LimitArgs): controlLimits { const B4: number[] = b4(count_per_group, false); const B495: number[] = b4(count_per_group, true); - return new controlLimits({ + return new controlLimitsClass({ keys: inputData.keys, values: group_sd, targets: rep(cl, inputData.keys.length), diff --git a/src/Limit Calculations/t.ts b/src/Limit Calculations/t.ts index d910640..6235e3f 100644 --- a/src/Limit Calculations/t.ts +++ b/src/Limit Calculations/t.ts @@ -1,17 +1,17 @@ import iLimits from "./i" import { pow } from "../Functions/BinaryFunctions"; -import controlLimits from "../Classes/controlLimits"; -import dataObject from "../Classes/dataObject"; +import controlLimitsClass from "../Classes/controlLimitsClass"; +import dataClass from "../Classes/dataClass"; import truncate from "../Functions/truncate"; -import {LimitArgs} from "../Classes/chartObject"; +import { LimitArgs } from "../Classes/viewModelClass"; -function tLimits(args: LimitArgs): controlLimits { - const inputData: dataObject = args.inputData; +function tLimits(args: LimitArgs): controlLimitsClass { + const inputData: dataClass = args.inputData; const val: number[] = pow(inputData.numerators, 1 / 3.6); const argsDataCopy: LimitArgs = args; argsDataCopy.inputData.numerators = val; argsDataCopy.inputData.denominators = null; - const limits: controlLimits = iLimits(argsDataCopy); + const limits: controlLimitsClass = 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 5c09170..8fbfeef 100644 --- a/src/Limit Calculations/u.ts +++ b/src/Limit Calculations/u.ts @@ -2,17 +2,17 @@ import * as d3 from "d3"; import rep from "../Functions/rep"; import { sqrt } from "../Functions/UnaryFunctions"; import { subtract, add, divide, multiply } from "../Functions/BinaryFunctions"; -import controlLimits from "../Classes/controlLimits"; -import dataObject from "../Classes/dataObject"; +import controlLimitsClass from "../Classes/controlLimitsClass"; +import dataClass from "../Classes/dataClass"; import truncate from "../Functions/truncate"; -import {LimitArgs} from "../Classes/chartObject"; +import { LimitArgs } from "../Classes/viewModelClass"; -function uLimits(args: LimitArgs): controlLimits { - const inputData: dataObject = args.inputData; +function uLimits(args: LimitArgs): controlLimitsClass { + const inputData: dataClass = args.inputData; const cl: number = divide(d3.sum(inputData.numerators),d3.sum(inputData.denominators)); const sigma: number[] = sqrt(divide(cl,inputData.denominators)); - return new controlLimits({ + return new controlLimitsClass({ keys: inputData.keys, values: divide(inputData.numerators, inputData.denominators), numerators: inputData.numerators, diff --git a/src/Limit Calculations/uprime.ts b/src/Limit Calculations/uprime.ts index d73a320..51045e0 100644 --- a/src/Limit Calculations/uprime.ts +++ b/src/Limit Calculations/uprime.ts @@ -3,13 +3,13 @@ import rep from "../Functions/rep"; import diff from "../Functions/diff"; import { abs, sqrt } from "../Functions/UnaryFunctions"; import { subtract, add, divide, multiply } from "../Functions/BinaryFunctions"; -import controlLimits from "../Classes/controlLimits"; -import dataObject from "../Classes/dataObject"; +import controlLimitsClass from "../Classes/controlLimitsClass"; +import dataClass from "../Classes/dataClass"; import truncate from "../Functions/truncate"; -import {LimitArgs} from "../Classes/chartObject"; +import { LimitArgs } from "../Classes/viewModelClass"; -function uprimeLimits(args: LimitArgs): controlLimits { - const inputData: dataObject = args.inputData; +function uprimeLimits(args: LimitArgs): controlLimitsClass { + const inputData: dataClass = 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)); @@ -21,7 +21,7 @@ function uprimeLimits(args: LimitArgs): controlLimits { 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({ + return new controlLimitsClass({ keys: inputData.keys, values: val, numerators: inputData.numerators, diff --git a/src/Limit Calculations/xbar.ts b/src/Limit Calculations/xbar.ts index e005a79..b1bb167 100644 --- a/src/Limit Calculations/xbar.ts +++ b/src/Limit Calculations/xbar.ts @@ -3,12 +3,12 @@ import { a3 } from "../Functions/Constants"; import rep from "../Functions/rep"; 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"; +import controlLimitsClass from "../Classes/controlLimitsClass"; +import dataClass from "../Classes/dataClass"; +import { LimitArgs } from "../Classes/viewModelClass"; -function xbarLimits(args: LimitArgs): controlLimits { - const inputData: dataObject = args.inputData; +function xbarLimits(args: LimitArgs): controlLimitsClass { + const inputData: dataClass = args.inputData; // Calculate number of observations in each group const count_per_group: number[] = inputData.denominators; @@ -30,7 +30,7 @@ function xbarLimits(args: LimitArgs): controlLimits { // Sample-size dependent constant const A3: number[] = a3(count_per_group); - return new controlLimits({ + return new controlLimitsClass({ keys: inputData.keys, values: group_means, targets: rep(cl, inputData.keys.length), diff --git a/src/visual.ts b/src/visual.ts index 4967f2f..23afe92 100644 --- a/src/visual.ts +++ b/src/visual.ts @@ -16,13 +16,13 @@ import IVisualHost = ex_visual.IVisualHost; import ISelectionManager = extensibility.ISelectionManager; import ISelectionId = visuals.ISelectionId; import IVisualEventService = extensibility.IVisualEventService; -import viewModelObject from "./Classes/viewModel" -import { plotData } from "./Classes/viewModel"; +import viewModelClass from "./Classes/viewModelClass" +import { plotData } from "./Classes/viewModelClass"; import * as d3 from "d3"; import svgObjectClass from "./Classes/svgObjectClass" import svgIconClass from "./Classes/svgIconClass" import svgSelectionClass from "./Classes/svgSelectionClass" -import { axisProperties } from "./Classes/plotProperties" +import { axisProperties } from "./Classes/plotPropertiesClass" import svgLinesClass from "./Classes/svgLinesClass"; import svgDotsClass from "./Classes/svgDotsClass"; @@ -37,7 +37,7 @@ export class Visual implements IVisual { private svgLines: svgLinesClass; private svgDots: svgDotsClass private svgSelections: svgSelectionClass; - private viewModel: viewModelObject; + private viewModel: viewModelClass; private selectionManager: ISelectionManager; // Service for notifying external clients (export to powerpoint/pdf) of rendering status private events: IVisualEventService; @@ -56,7 +56,7 @@ export class Visual implements IVisual { this.svgLines = new svgLinesClass(this.svg); this.svgDots = new svgDotsClass(this.svg); this.svgSelections = new svgSelectionClass(); - this.viewModel = new viewModelObject(); + this.viewModel = new viewModelClass(); this.viewModel.firstRun = true; this.selectionManager = this.host.createSelectionManager();