From fee53590ac474e2d2e633e4354752203bdbc7941 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Sat, 6 Apr 2024 21:32:13 +0300 Subject: [PATCH] Initial infrastructure for showing summary table instead of chart --- capabilities.json | 9 +++ src/Classes/viewModelClass.ts | 36 +++++++++++ src/D3 Plotting Functions/drawSummaryTable.ts | 32 ++++++++++ src/D3 Plotting Functions/index.ts | 1 + src/defaultSettings.ts | 3 + src/visual.ts | 61 ++++++++++++------- 6 files changed, 119 insertions(+), 23 deletions(-) create mode 100644 src/D3 Plotting Functions/drawSummaryTable.ts diff --git a/capabilities.json b/capabilities.json index f4ffbd7..ede6308 100644 --- a/capabilities.json +++ b/capabilities.json @@ -20,6 +20,15 @@ { "displayName": "Tooltips", "name": "tooltips", "kind": "Measure" } ], "objects": { + "summary_table": { + "displayName": "Summary Table", + "properties": { + "show_table": { + "displayName": "Show Summary Table", + "type" : { "bool" : true } + } + } + }, "split_indexes_storage" : { "displayName" : "Hidden", "properties": { diff --git a/src/Classes/viewModelClass.ts b/src/Classes/viewModelClass.ts index 1650ec9..6245fb8 100644 --- a/src/Classes/viewModelClass.ts +++ b/src/Classes/viewModelClass.ts @@ -15,10 +15,28 @@ export type lineData = { group: string; } +export type summaryTableRowData = { + date: string; + numerator: number; + denominator: number; + value: number; + target: number; + alt_target: number; + ll99: number; + ll95: number; + ll68: number; + ul68: number; + ul95: number; + ul99: number; + speclimits_lower: number; + speclimits_upper: number; +} + export type plotData = { x: number; value: number; aesthetics: defaultSettingsType["scatter"]; + table_row: summaryTableRowData; // 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 @@ -205,10 +223,28 @@ export default class viewModelClass { "ast_colour", this.inputSettings.settings) as string; } + const table_row: summaryTableRowData = { + date: this.controlLimits.keys[i].label, + numerator: this.controlLimits.numerators?.[i], + denominator: this.controlLimits.denominators?.[i], + value: this.controlLimits.values[i], + target: this.controlLimits.targets[i], + alt_target: this.controlLimits.alt_targets[i], + ll99: this.controlLimits?.ll99?.[i], + ll95: this.controlLimits?.ll95?.[i], + ll68: this.controlLimits?.ll68?.[i], + ul68: this.controlLimits?.ul68?.[i], + ul95: this.controlLimits?.ul95?.[i], + ul99: this.controlLimits?.ul99?.[i], + speclimits_lower: this.controlLimits?.speclimits_lower?.[i], + speclimits_upper: this.controlLimits?.speclimits_upper?.[i] + } + this.plotPoints.push({ x: index, value: this.controlLimits.values[i], aesthetics: aesthetics, + table_row: table_row, identity: host.createSelectionIdBuilder() .withCategory(this.inputData.categories, this.inputData.limitInputArgs.keys[i].id) diff --git a/src/D3 Plotting Functions/drawSummaryTable.ts b/src/D3 Plotting Functions/drawSummaryTable.ts new file mode 100644 index 0000000..dc6d5aa --- /dev/null +++ b/src/D3 Plotting Functions/drawSummaryTable.ts @@ -0,0 +1,32 @@ +import { plotData } from "../Classes/viewModelClass"; +import type { divBaseType, Visual } from "../visual"; + +export default function drawSummaryTable(selection: divBaseType, visualObj: Visual) { + selection.select("svg").attr("width", 0).attr("height", 0); + + if (selection.select(".table-group").empty()) { + const table = selection.append("table").classed("table-group", true); + table.append("thead") + .append("tr") + .classed("table-header", true); + table.append('tbody') + .classed("table-body", true); + } + + const plotPoints: plotData[] = visualObj.viewModel.plotPoints; + + selection.select(".table-header") + .selectAll("th") + .data(Object.keys(plotPoints[0].table_row)) + .join("th") + .text((d) => d); + + selection.select(".table-body") + .selectAll('tr') + .data(plotPoints) + .join('tr') + .selectAll('td') + .data((d) => Object.values(d.table_row)) + .join('td') + .text((d) => d); +} diff --git a/src/D3 Plotting Functions/index.ts b/src/D3 Plotting Functions/index.ts index 5cfa3de..b0cabbc 100644 --- a/src/D3 Plotting Functions/index.ts +++ b/src/D3 Plotting Functions/index.ts @@ -8,3 +8,4 @@ export { default as drawYAxis } from "./drawYAxis" export { default as updateHighlighting } from "./updateHighlighting" export { default as drawErrors } from "./drawErrors" export { default as initialiseSVG } from "./initialiseSVG" +export { default as drawSummaryTable } from "./drawSummaryTable" diff --git a/src/defaultSettings.ts b/src/defaultSettings.ts index 4000e53..35411bb 100644 --- a/src/defaultSettings.ts +++ b/src/defaultSettings.ts @@ -186,6 +186,9 @@ const defaultSettings = { date_format_year: { default: "YYYY", valid: ["YYYY", "YY", "(blank)"]}, date_format_delim: { default: "/", valid: ["/", "-", " "]}, date_format_locale: { default: "en-GB", valid: ["en-GB", "en-US"]} + }, + summary_table: { + show_table: { default: false } } }; diff --git a/src/visual.ts b/src/visual.ts index 7b3c576..855485c 100644 --- a/src/visual.ts +++ b/src/visual.ts @@ -3,23 +3,27 @@ import type powerbi from "powerbi-visuals-api"; type EnumerateVisualObjectInstancesOptions = powerbi.EnumerateVisualObjectInstancesOptions; type VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject; +type VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions; import * as d3 from "./D3 Plotting Functions/D3 Modules"; import { drawXAxis, drawYAxis, drawTooltipLine, drawLines, drawDots, drawIcons, updateHighlighting, addContextMenu, - drawErrors, initialiseSVG } from "./D3 Plotting Functions" + drawErrors, initialiseSVG, drawSummaryTable } from "./D3 Plotting Functions" import { defaultSettingsKey, viewModelClass } from "./Classes" import { validateDataView } from "./Functions"; export type svgBaseType = d3.Selection; +export type divBaseType = d3.Selection; export class Visual implements powerbi.extensibility.IVisual { host: powerbi.extensibility.visual.IVisualHost; + div: divBaseType; svg: svgBaseType; viewModel: viewModelClass; selectionManager: powerbi.extensibility.ISelectionManager; constructor(options: powerbi.extensibility.visual.VisualConstructorOptions) { - this.svg = d3.select(options.element).append("svg"); + this.div = d3.select(options.element).append("div"); + this.svg = this.div.append("svg"); this.host = options.host; this.viewModel = new viewModelClass(); @@ -30,48 +34,48 @@ export class Visual implements powerbi.extensibility.IVisual { this.svg.call(initialiseSVG); } - public update(options: powerbi.extensibility.visual.VisualUpdateOptions) { + public update(options: VisualUpdateOptions) { try { this.host.eventService.renderingStarted(options); // Remove printed error if refreshing after a previous error run this.svg.select(".errormessage").remove(); - this.svg.attr("width", options.viewport.width) - .attr("height", options.viewport.height) this.viewModel.inputSettings.update(options.dataViews[0]); if (this.viewModel.inputSettings.validationStatus.error !== "") { - this.svg.call(drawErrors, options, this.viewModel.inputSettings.validationStatus.error, "settings"); - this.host.eventService.renderingFinished(options); + this.processVisualError(options, + this.viewModel.inputSettings.validationStatus.error, + "settings"); return; } const checkDV: string = validateDataView(options.dataViews, this.viewModel.inputSettings); if (checkDV !== "valid") { - if (this.viewModel.inputSettings.settings.canvas.show_errors) { - this.svg.call(drawErrors, options, checkDV); - } else { - this.svg.call(initialiseSVG, true); - } - this.host.eventService.renderingFinished(options); + this.processVisualError(options, checkDV); return; } this.viewModel.update(options, this.host); if (this.viewModel.inputData.validationStatus.status !== 0) { - this.svg.call(drawErrors, options, this.viewModel.inputData.validationStatus.error); - this.host.eventService.renderingFinished(options); + this.processVisualError(options, + this.viewModel.inputData.validationStatus.error); return; } - this.svg.call(drawXAxis, this) - .call(drawYAxis, this) - .call(drawTooltipLine, this) - .call(drawLines, this) - .call(drawDots, this) - .call(drawIcons, this) - .call(updateHighlighting, this) - .call(addContextMenu, this) + if (this.viewModel.inputSettings.settings.summary_table.show_table) { + this.div.call(drawSummaryTable, this); + } else { + this.svg.attr("width", options.viewport.width) + .attr("height", options.viewport.height) + .call(drawXAxis, this) + .call(drawYAxis, this) + .call(drawTooltipLine, this) + .call(drawLines, this) + .call(drawDots, this) + .call(drawIcons, this) + .call(updateHighlighting, this) + .call(addContextMenu, this) + } if (this.viewModel.inputData.warningMessage !== "") { this.host.displayWarningIcon("Invalid inputs or settings ignored.\n", @@ -86,6 +90,17 @@ export class Visual implements powerbi.extensibility.IVisual { } } + processVisualError(options: VisualUpdateOptions, message: string, type: string = null): void { + this.svg.attr("width", options.viewport.width) + .attr("height", options.viewport.height) + if (this.viewModel.inputSettings.settings.canvas.show_errors) { + this.svg.call(drawErrors, options, message, type); + } else { + this.svg.call(initialiseSVG, true); + } + this.host.eventService.renderingFinished(options); + } + // Function to render the properties specified in capabilities.json to the properties pane public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumerationObject { return this.viewModel.inputSettings.createSettingsEntry(options.objectName as defaultSettingsKey);