diff --git a/.gitignore b/.gitignore index e016ea6..3a1a973 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules +node_modules/* .tmp dist .api @@ -7,7 +8,6 @@ dist .tmp/* dist/* .tmp/drop/pbiviz.json -.tmp/drop/pbiviz.json .tmp/drop/visual.js .tmp/precompile/src/visual.ts .tmp/drop/pbiviz.json @@ -25,4 +25,8 @@ dist/PowerBI-visuals-forcasting-exp.pbiviz out.html.tmp out.html.* out.html_files/* -out.html_files \ No newline at end of file +out.html_files +dist/PowerBI-visuals-forcasting-exp.pbiviz +assets/Presentation1.pptx +assets/*_testing.pbix +dist/PowerBI-visuals-forcasting-exp.pbiviz \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d4e749d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.1.0", + "configurations": [ + { + "name": "Debugger", + "type": "chrome", + "request": "attach", + "port": 9222, + "sourceMaps": true, + "webRoot": "${cwd}/" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..040bf4b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,37 @@ +{ + "editor.tabSize": 4, + "editor.insertSpaces": true, + "files.eol": "\n", + "files.watcherExclude": { + "**/.git/objects/**": true, + "**/node_modules/**": true, + ".tmp": true + }, + "files.exclude": { + ".tmp": true + }, + "search.exclude": { + ".tmp": true, + "typings": true + }, + "json.schemas": [ + { + "fileMatch": [ + "/pbiviz.json" + ], + "url": "./.api/v1.10.0/schema.pbiviz.json" + }, + { + "fileMatch": [ + "/capabilities.json" + ], + "url": "./.api/v1.10.0/schema.capabilities.json" + }, + { + "fileMatch": [ + "/dependencies.json" + ], + "url": "./.api/v1.10.0/schema.dependencies.json" + } + ] +} diff --git a/assets/CaptureSettings.PNG b/assets/CaptureSettings.PNG new file mode 100644 index 0000000..7d6173d Binary files /dev/null and b/assets/CaptureSettings.PNG differ diff --git a/assets/forecast_custom_RV_HTML-based.pbix b/assets/forecast_custom_RV_HTML-based.pbix index 2ee69c5..5f10a9d 100644 Binary files a/assets/forecast_custom_RV_HTML-based.pbix and b/assets/forecast_custom_RV_HTML-based.pbix differ diff --git a/capabilities.json b/capabilities.json index d1745fc..c985805 100644 --- a/capabilities.json +++ b/capabilities.json @@ -62,7 +62,7 @@ }, "settings_forecastPlot_params": { "displayName": "Forecasting settings", - "description": "Basic decomposition models are: 1. Additive: x[t] = Trend + Seasonal + Random, 2. Multiplicative: x[t] = Trend * Seasonal * Random. Hybrid models are allowed. Any forbiden model combination will be replaced by `Automatic`", + "description": "Basic decomposition models are: 1. Additive: x[t] = Trend + Seasonal + Remainder, 2. Multiplicative: x[t] = Trend * Seasonal * Remainder. Hybrid models are allowed. Any forbiden model combination will be replaced by `Automatic`", "properties": { "forecastLength": { @@ -72,7 +72,7 @@ }, "errorType": { - "displayName": "Error component", + "displayName": "Remainder component", "type": { "enumeration": [ { @@ -201,34 +201,125 @@ "show": { "type": {"bool": true} }, - "percentile": { "displayName": "Confidence", "type": { "numeric": true } }, - "upperConfIntervalFactor": { - "displayName": "Upper interval factor", - "description": "Upper Confidence = Confidence + (100 - Confidence)*UpperIntervalFactor ", - "type": { - "enumeration": [ - { - "displayName": "0", - "value": "0" - }, - { - "displayName": "0.5", - "value": "0.5" - }, - { - "displayName": "0.75", - "value": "0.75" - }, - { - "displayName": "0.9", - "value": "0.9" - }, - { - "displayName": "0.95", - "value": "0.95" - } - ] - } + "confInterval1": { + "displayName": "Confidence level", + "description": "Select first confidence interval", + "type": { + "enumeration": [ + { + "displayName": "0", + "value": "0" + }, + { + "displayName": "0.2", + "value": "0.2" + }, + { + "displayName": "0.4", + "value": "0.4" + }, + { + "displayName": "0.5", + "value": "0.5" + }, + { + "displayName": "0.75", + "value": "0.75" + }, + { + "displayName": "0.8", + "value": "0.8" + }, + { + "displayName": "0.9", + "value": "0.9" + }, + { + "displayName": "0.95", + "value": "0.95" + }, + { + "displayName": "0.975", + "value": "0.975" + }, + { + "displayName": "0.98", + "value": "0.98" + }, + { + "displayName": "0.99", + "value": "0.99" + }, + { + "displayName": "0.995", + "value": "0.995" + }, + { + "displayName": "0.999", + "value": "0.999" + } + ] + } + }, + "confInterval2": { + "displayName": "Confidence level #2", + "description": "Select additional confidence interval", + "type": { + "enumeration": [ + { + "displayName": "0", + "value": "0" + }, + { + "displayName": "0.2", + "value": "0.2" + }, + { + "displayName": "0.4", + "value": "0.4" + }, + { + "displayName": "0.5", + "value": "0.5" + }, + { + "displayName": "0.75", + "value": "0.75" + }, + { + "displayName": "0.8", + "value": "0.8" + }, + { + "displayName": "0.9", + "value": "0.9" + }, + { + "displayName": "0.95", + "value": "0.95" + }, + { + "displayName": "0.975", + "value": "0.975" + }, + { + "displayName": "0.98", + "value": "0.98" + }, + { + "displayName": "0.99", + "value": "0.99" + }, + { + "displayName": "0.995", + "value": "0.995" + }, + { + "displayName": "0.999", + "value": "0.999" + } + ] + } } } }, @@ -276,10 +367,56 @@ } } } + }, + "settings_export_params": { + "displayName": "Export data", + "description": "Export result of clustering", + "properties": { + "show": { + "type": { + "bool": true + } + }, + "limitExportSize": { + "displayName": "Maximum exported rows", + "description": "Limit number of rows", + "type": { + "enumeration": [{ + "displayName": "1000", + "value": "1000" + }, + { + "displayName": "10000", + "value": "10000" + }, + { + "displayName": "50000", + "value": "50000" + }, + { + "displayName": "unlimited", + "value": "100000" + } + ] + } + }, + "method": { + "displayName": "Method", + "description": "Method", + "type": { + "enumeration": [{ + "displayName": "copy to clipboard", + "value": "copy" + }, + { + "displayName": "download (only service)", + "value": "download" + } + ] + } + } + } } - - - }, "suppressDefaultTitle": true } \ No newline at end of file diff --git a/dependencies.json b/dependencies.json index e710848..70a90ee 100644 --- a/dependencies.json +++ b/dependencies.json @@ -39,6 +39,11 @@ "name": "XML", "displayName": "XML", "url": "https://cran.r-project.org/web/packages/XML/index.html" + }, + { + "name": "caTools", + "displayName": "caTools", + "url": "https://cran.r-project.org/web/packages/caTools/index.html" } ] diff --git a/dist/PowerBI-visuals-forcasting-exp.pbiviz b/dist/PowerBI-visuals-forcasting-exp.pbiviz index b4f8a01..7a45bf7 100644 Binary files a/dist/PowerBI-visuals-forcasting-exp.pbiviz and b/dist/PowerBI-visuals-forcasting-exp.pbiviz differ diff --git a/package.json b/package.json index 128fe46..9ddc23c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "visual", "scripts": { - "postinstall": "pbiviz update 1.7.0", + "postinstall": "pbiviz update 1.10.0", "pbiviz": "pbiviz", "package": "pbiviz package", "lint": "tslint -r \"node_modules/tslint-microsoft-contrib\" \"+(src)/**/*.ts\"", @@ -10,6 +10,7 @@ "devDependencies": { "tslint": "^4.4.2", "tslint-microsoft-contrib": "^4.0.0", - "powerbi-visuals-tools": "1.7.1" + "powerbi-visuals-tools": "1.10.0", + "powerbi-visuals-utils-dataviewutils": "1.2.0" } -} +} \ No newline at end of file diff --git a/pbiviz.json b/pbiviz.json index 3c9e54d..c78b58e 100644 --- a/pbiviz.json +++ b/pbiviz.json @@ -4,12 +4,12 @@ "displayName": "Forecasting", "guid": "PBI_CV_8EDDC07B_EE79_4418_A84C_D73897C0E21F_HTML", "visualClassName": "Visual", - "version": "1.0.3", - "description": "Time series forecasting is the use of a model to predict future values based on previously observed values. Current visual implements well known exponential smoothing method for the forecasting. The prediction is based on trend and seasonality modeling. You can control the algorithm parameters and the visual attributes to suit your needs.

Service prerequisites: R-powered custom visual is used in service seamlessly

Desktop prerequisites: To run R scripts in Power BI Desktop, you must separately install R on your local computer.
You can download and install R for free from the Revolution Open download page or the CRAN Repository

R package dependencies(auto-installed): graphics, scales, forecast, zoo, ggplot2, htmlWidgets, XML, plotly

Supports R versions: R 3.3.1, R 3.3.0, MRO 3.3.1, MRO 3.3.0, MRO 3.2.2
", + "version": "1.0.4", + "description": "Time series forecasting is the use of a model to predict future values based on previously observed values. Current visual implements well known exponential smoothing method for the forecasting. The prediction is based on trend and seasonality modeling. You can control the algorithm parameters and the visual attributes to suit your needs.

Service prerequisites: R-powered custom visual is used in service seamlessly

Desktop prerequisites: To run R scripts in Power BI Desktop, you must separately install R on your local computer.
You can download and install R for free from the Revolution Open download page or the CRAN Repository

R package dependencies(auto-installed): graphics, scales, forecast, zoo, ggplot2, htmlWidgets, XML, plotly

Supports R versions: R 3.3.1, R 3.3.0, R 3.4.1, MRO 3.3.1, MRO 3.3.0, MRO 3.2.2
", "supportUrl": "http://community.powerbi.com/", "gitHubUrl": "https://github.com/microsoft/PowerBI-visuals-forcasting-exp" }, - "apiVersion": "1.7.0", + "apiVersion": "1.10.0", "author": { "name": "Microsoft", "email": "pbicvsupport@microsoft.com" @@ -17,8 +17,11 @@ "assets": { "icon": "assets/icon.png" }, - "externalJS": [], + "externalJS": [ + "node_modules/powerbi-visuals-utils-dataviewutils/lib/index.js" + ], "style": "style/visual.less", "capabilities": "capabilities.json", - "dependencies": "dependencies.json" + "dependencies": "dependencies.json", + "stringResources": [] } diff --git a/r_files/flatten_HTML.r b/r_files/flatten_HTML.r index b0e387f..8e73e56 100644 --- a/r_files/flatten_HTML.r +++ b/r_files/flatten_HTML.r @@ -115,5 +115,14 @@ FindSrcReplacement <- function(str) str = paste('https://cdn.plot.ly/plotly-', verstr,'.min.js', sep='') return(str) } +#ReadFullFileReplaceString +ReadFullFileReplaceString <- function(fnameIn, fnameOut, sourceString,targetString) +{ + if(!file.exists(fnameIn)) + return(NULL) + + tx <- readLines(fnameIn) + tx2 <- gsub(pattern = sourceString, replace = targetString, x = tx) + writeLines(tx2, con = fnameOut) +} ################################################# - diff --git a/script.r b/script.r index ce3c379..84168fa 100644 --- a/script.r +++ b/script.r @@ -29,21 +29,11 @@ source('./r_files/flatten_HTML.r') ############### Library Declarations ############### -libraryRequireInstall("ggplot2"); +libraryRequireInstall("ggplot2") libraryRequireInstall("plotly") +libraryRequireInstall("caTools") #################################################### -#DEBUG -# fileRda = "C:/Users/boefraty/projects/PBI/R/tempData.Rda" -# if(file.exists(dirname(fileRda))) -# { -# if(Sys.getenv("RSTUDIO")!="") -# load(file= fileRda) -# else -# save(list = ls(all.names = TRUE), file=fileRda) -# } - - Sys.setlocale("LC_ALL","English") # Internationalization ############ User Parameters ######### @@ -106,20 +96,34 @@ drawConfidenceLevels = TRUE if(exists("settings_conf_params_show")) drawConfidenceLevels = settings_conf_params_show - -lowerConfInterval = 0.8 -if (exists("settings_conf_params_percentile")) -{ - lowerConfInterval = as.numeric(settings_conf_params_percentile)/100 - if(is.na(lowerConfInterval)) - lowerConfInterval = 0.8 - - lowerConfInterval = max(min(lowerConfInterval,0.99),0) +##PBI_PARAM: Confidence level +#Type:enum, Default:"0.8", Range:NA, PossibleValues:0, 0.5 etc, Remarks: NA +confInterval1 = 0.8 +if(exists("settings_conf_params_confInterval1")) +{ + confInterval1 = as.numeric(settings_conf_params_confInterval1) } -upperConfInterval = 0.98 -if (exists("settings_conf_params_upperConfIntervalFactor")) -{ upperConfInterval = lowerConfInterval+(1-lowerConfInterval)*as.numeric(settings_conf_params_upperConfIntervalFactor)} + +##PBI_PARAM: Confidence level +#Type:enum, Default:"0.95", Range:NA, PossibleValues:0, 0.5 etc, Remarks: NA +confInterval2 = 0.95 +if(exists("ssettings_conf_params_confInterval2")) +{ + confInterval2 = as.numeric(settings_conf_params_confInterval2) +} + + +if(confInterval1 > confInterval2) +{#switch places + temp = confInterval1 + confInterval1 = confInterval2 + confInterval2 = temp +} + +lowerConfInterval = confInterval1 +upperConfInterval = confInterval2 + if(drawConfidenceLevels==FALSE) lowerConfInterval=upperConfInterval=0 @@ -164,6 +168,23 @@ cexSub = 0.75 if(exists("settings_additional_params_textSize")) cexSub = as.numeric(settings_additional_params_textSize)/12 +##PBI_PARAM: export out data to HTML? +#Type:logical, Default:FALSE, Range:NA, PossibleValues:NA, Remarks: NA +keepOutData = FALSE +if(exists("settings_export_params_show")) + keepOutData = settings_export_params_show + +##PBI_PARAM: method of export interface +#Type: string , Default:"copy", Range:NA, PossibleValues:"copy", "download", Remarks: NA +exportMethod = "copy" +if(exists("settings_export_params_method")) + exportMethod = settings_export_params_method + +##PBI_PARAM: limit the out table exported +#Type: string , Default:1000, Range:NA, PossibleValues:"1000", "10000", Inf, Remarks: NA +limitExportSize = 1000 +if(exists("settings_export_params_limitExportSize")) + limitExportSize = as.numeric(settings_export_params_limitExportSize) ###############Internal parameters definitions################# @@ -376,6 +397,65 @@ getAngleXlabels = function(mylabels) } +ConvertDF64encoding = function (df, withoutEncoding = FALSE) +{ + header_row <- paste(names(df), collapse=", ") + tab <- apply(df, 1, function(x)paste(x, collapse=", ")) + + if(withoutEncoding){ + text <- paste(c(header_row, tab), collapse="\n") + x <- text + } + else + { + text <- paste(c(header_row, tab), collapse="\n") + x <- caTools::base64encode(text) + } + return(x) +} + + +KeepOutDataInHTML = function(df, htmlFile = 'out.html', exportMethod = "copy", limitExportSize = 1000) +{ + if(nrow(df)>limitExportSize) + df = df[1:limitExportSize,] + + outDataString64 = ConvertDF64encoding(df) + + linkElem = '\nexport\n' + updateLinkElem = paste(' ', sep =' ') + var64 = paste('', sep ="") + var64href = paste('', sep ="") + + buttonElem = '' + funcScript = '' + + if(exportMethod == "copy") + endOfBody = paste(var64,funcScript, buttonElem,'\n',sep ="") + else#"download" + endOfBody = paste(linkElem,var64, var64href,updateLinkElem,'\n',sep ="") + + ReadFullFileReplaceString('out.html', 'out.html', '', endOfBody) + +} + + ###############Upfront input correctness validations (where possible)################# @@ -592,7 +672,29 @@ p <- config(p, staticPlot = FALSE, editable = FALSE, sendData = FALSE, showLink displaylogo = FALSE, collaborate = FALSE, cloud=FALSE) internalSaveWidget(p, 'out.html') -#################################################### -#display in R studio -# if(Sys.getenv("RSTUDIO")!="") -# print(p) + +# resolve bug in plotly (margin of 40 px) +ReadFullFileReplaceString('out.html', 'out.html', ',"padding":40,', ',"padding":0,') + +if(keepOutData) +{ + padNA1 = rep(NA,length(x_full)) + padNA2 = rep(NA,length(f_full)) + if(!exists("lower1")) + lower1 = lower2 = upper1 = upper2 = padNA2; + + + lower1 = c(padNA1,lower1) + lower2 = c(padNA1,lower2) + upper1 = c(padNA1,upper1) + upper2 = c(padNA1,upper2) + + exportDF = data.frame(Date = as.character(c(x_full,f_full)),Value = c(y1,y2), + lower1 = lower1, + lower2 = lower2, + upper1 = upper1, + upper2 = upper2) + colnames(exportDF)[c(1,2)] = c(labTime,labValue) + + KeepOutDataInHTML(df = exportDF, htmlFile = 'out.html', exportMethod = exportMethod, limitExportSize = limitExportSize) +} diff --git a/src/htmlInjectionUtility.ts b/src/htmlInjectionUtility.ts index add10a5..4788b9d 100644 --- a/src/htmlInjectionUtility.ts +++ b/src/htmlInjectionUtility.ts @@ -1,29 +1,24 @@ module powerbi.extensibility.visual { + "use strict"; let injectorCounter: number = 0; - - export function ResetInjector() : void { + export function ResetInjector(): void { injectorCounter = 0; } - export function injectorReady() : boolean { + export function injectorReady(): boolean { return injectorCounter === 0; } - - export function ParseElement(el: HTMLElement , target: HTMLElement) : Node[] - { + export function ParseElement(el: HTMLElement, target: HTMLElement): Node[] { let arr: Node[] = []; - if (!el || !el.hasChildNodes()) - return - - let nodes = el.children; - for (var i=0; inodes.item(i).cloneNode(true); } target.appendChild(tempNode); @@ -31,33 +26,32 @@ module powerbi.extensibility.visual { } return arr; } - - function createScriptNode(refNode: Element): HTMLElement{ - let script = document.createElement('script'); - let attr = refNode.attributes; - for (var i=0; i { + injectorCounter--; }; } } - - script.innerHTML = refNode.innerHTML; + script.innerHTML = refNode.innerHTML; return script; } export function RunHTMLWidgetRenderer(): void { - let intervalVar = window.setInterval(() => { + // rendering HTML which was created by HTMLWidgets package + // wait till all tje script elements are loaded + let intervalVar: number = window.setInterval(() => { if (injectorReady()) { window.clearInterval(intervalVar); - if (window.hasOwnProperty('HTMLWidgets') && window['HTMLWidgets'].staticRender) { - window['HTMLWidgets'].staticRender(); + if (window.hasOwnProperty("HTMLWidgets") && window["HTMLWidgets"].staticRender) { + window["HTMLWidgets"].staticRender(); } } }, 100); diff --git a/src/objectEnumerationUtility.ts b/src/objectEnumerationUtility.ts deleted file mode 100644 index e73f5f2..0000000 --- a/src/objectEnumerationUtility.ts +++ /dev/null @@ -1,149 +0,0 @@ -module powerbi.extensibility.visual { - /** - * Gets property value for a particular object. - * - * @function - * @param {DataViewObjects} objects - Map of defined objects. - * @param {string} objectName - Name of desired object. - * @param {string} propertyName - Name of desired property. - * @param {T} defaultValue - Default value of desired property. - */ - export function getValue(objects: DataViewObjects, objectName: string, propertyName: string, defaultValue: T ): T { - if (objects) { - let object = objects[objectName]; - if (object) { - let property: T = object[propertyName]; - if (property !== undefined) { - return property; - } - } - } - return defaultValue; - } - - /** - * Gets property value for a particular object. - * - * @function - * @param {DataViewObjects} objects - Map of defined objects. - * @param {string} objectName - Name of desired object. - * @param {string} propertyName - Name of desired property. - * @param {T} defaultValue - Default value of desired property. - */ - export function getValueMinMax(objects: DataViewObjects, objectName: string, propertyName: string, defaultValue: T, minVal: T, maxVal: T ): T { - if (objects) { - let object = objects[objectName]; - if (object) { - let property: T = object[propertyName]; - if (property < minVal) { - return minVal; - } - if (property > maxVal) { - return maxVal; - } - if (property !== undefined) { - return property; - } - } - } - return defaultValue; - } - - - /** - * Gets property value for a particular object. - * - * @function - * @param {DataViewObjects} objects - Map of defined objects. - * @param {string} objectName - Name of desired object. - * @param {string} propertyName - Name of desired property. - * @param {T} defaultValue - Default value of desired property. - */ - export function getValueNumberMinMax(objects: DataViewObjects, objectName: string, propertyName: string, defaultValue: number, minValue: number, maxValue: number ) { - if (objects) { - let object = objects[objectName]; - if (object) { - let property = object[propertyName]; - if (property !== undefined) { - if (property > maxValue) { - return maxValue; - } - if (property < minValue) { - return minValue; - } - return property; - } - } - } - return defaultValue; - } - - -/** - * Gets conditional property value for a particular object of type string - * - * @function - * @param {string} inVal - current value of parameter - * @param {string} contrVal - control value - * @param {string} contrVal2Compare - specific string to be compared with contrVal - * @param {boolean} logic - true / false "logic" - * @param {string} outValIfCondTrue - output value if comparison (contrVal == contrVal2Compare) comes out as "logic" - */ - export function ifStringReturnString(inVal: string, contrVal: string, contrVal2Compare: string, outValIfCondTrue: string, logic: boolean, applyNow: boolean) { - if (applyNow && contrVal === contrVal2Compare && logic === true) - return outValIfCondTrue; - - if (applyNow && contrVal !== contrVal2Compare && logic === false) - return outValIfCondTrue; - - return inVal; - } - - export function ifStringReturnStringClustersMethod(numClustersMethods: string, numOfClusters: string) { - if (numOfClusters !== "auto") - return "None"; - - if (numOfClusters === "auto" && numClustersMethods === "None") - return "fast"; - - return numClustersMethods; - } - - export function inMinMax(a: number, mi: number, ma: number) { - if (a < mi) - return mi; - if (a > ma) - return ma; - return a; - } - - - - /** - * Gets property value for a particular object in a category. - * - * @function - * @param {DataViewCategoryColumn} category - List of category objects. - * @param {number} index - Index of category object. - * @param {string} objectName - Name of desired object. - * @param {string} propertyName - Name of desired property. - * @param {T} defaultValue - Default value of desired property. - */ - export function getCategoricalObjectValue(category: DataViewCategoryColumn, index: number, objectName: string, propertyName: string, defaultValue: T): T { - let categoryObjects = category.objects; - - if (categoryObjects) { - let categoryObject: DataViewObject = categoryObjects[index]; - if (categoryObject) { - let object = categoryObject[objectName]; - if (object) { - let property: T = object[propertyName]; - if (property !== undefined) { - return property; - } - } - } - } - return defaultValue; - } -} \ No newline at end of file diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..225a447 --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,71 @@ +/* + * Power BI Visualizations + * + * Copyright (c) Microsoft Corporation + * All rights reserved. + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the ""Software""), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +module powerbi.extensibility.visual { + "use strict"; + import DataViewObjectsParser = powerbi.extensibility.utils.dataview.DataViewObjectsParser; + export function inMinMax(a: number, mi: number, ma: number) { + if (a < mi) + return mi; + if (a > ma) + return ma; + return a; + } + export class VisualSettings extends DataViewObjectsParser { + public settings_forecastPlot_params: SettingsForecastPlotParams = new SettingsForecastPlotParams(); + public settings_conf_params: SettingsConfParams = new SettingsConfParams(); + public settings_graph_params: SettingsGraphParams = new SettingsGraphParams(); + public settings_additional_params: SettingsAdditionalParams = new SettingsAdditionalParams(); + public settings_export_params: SettingsExportParams = new SettingsExportParams(); + } + export class SettingsForecastPlotParams { + public forecastLength: number = 10; + public seasonType: string = "Automatic"; + public errorType: string = "Automatic"; + public trendType: string = "Automatic"; + public dampingType: string = "Automatic"; + public targetSeason: string = "Automatic"; + } + export class SettingsConfParams { + public show: boolean = true; + public confInterval1: string = "0.8"; + public confInterval2: string = "0.95"; + } + export class SettingsGraphParams { + public dataCol: string = "orange"; + public forecastCol: string = "red"; + public percentile: number = 40; + public weight: number = 10; + } + export class SettingsAdditionalParams { + public showInfo: boolean = true; + public textSize: number = 10; + } + export class SettingsExportParams { + public show: boolean = false; + public limitExportSize: string = "10000"; + public method: string = "copy"; + } +} diff --git a/src/visual.ts b/src/visual.ts index bae830c..978dd9d 100644 --- a/src/visual.ts +++ b/src/visual.ts @@ -24,153 +24,72 @@ * THE SOFTWARE. */ module powerbi.extensibility.visual { + "use strict"; - // in order to improve the performance, one can update the only in the initial rendering. + // in order to improve the performance, one can update the only in the initial rendering. // set to 'true' if you are using different packages to create the widgets const updateHTMLHead: boolean = false; - const renderVisualUpdateType: number[] = [VisualUpdateType.Resize, VisualUpdateType.ResizeEnd, VisualUpdateType.Resize + VisualUpdateType.ResizeEnd]; - - - interface VisualSettingsForecastPlotParams { - show: boolean; - forecastLength: number; - seasonType: string; - errorType: string; - trendType: string; - dampingType: string; - targetSeason: string; - } - - interface VisualSettingsConfParams { - show: boolean; - percentile: number; - upperConfIntervalFactor: string; - } - interface VisualGraphParams { - show: boolean; - dataCol: string; - forecastCol: string; - percentile: number; - weight: number; - } - interface VisualAdditionalParams { - show: boolean; - // showWarnings: boolean; - showInfo: boolean; - textSize: number; - } - + const renderVisualUpdateType: number[] = [ + VisualUpdateType.Resize, + VisualUpdateType.ResizeEnd, + VisualUpdateType.Resize + VisualUpdateType.ResizeEnd + ]; export class Visual implements IVisual { - // private imageDiv: HTMLDivElement; - // private imageElement: HTMLImageElement; - //HTML private rootElement: HTMLElement; private headNodes: Node[]; private bodyNodes: Node[]; - - - - - private settings_forecastPlot_params: VisualSettingsForecastPlotParams; - private settings_conf_params: VisualSettingsConfParams; - private settings_graph_params: VisualGraphParams; - private settings_additional_params: VisualAdditionalParams; + private settings: VisualSettings; public constructor(options: VisualConstructorOptions) { - // HTML - if(options && options.element) + if (options && options.element) { this.rootElement = options.element; - + } this.headNodes = []; this.bodyNodes = []; - - // default parameters - this.settings_forecastPlot_params = { - - forecastLength: 10, - // forecastDate: "9/25/2010 11:00:00 PM", - seasonType: "Automatic", - errorType: "Automatic", - trendType: "Automatic", - dampingType: "Automatic", - targetSeason: "Automatic" - }; - - this.settings_conf_params = { - show: true, - percentile: 80, - upperConfIntervalFactor: "0.5", - }; - - this.settings_graph_params = { - - dataCol: "orange", - forecastCol: "red", - percentile: 40, - weight: 10 - - }; - - this.settings_additional_params = { - - // showWarnings: false, - showInfo: true, - textSize: 10 - }; } - public update(options: VisualUpdateOptions) { - if (!options || !options.type || !options.viewport) + public update(options: VisualUpdateOptions): void { + if (!options || + !options.type || + !options.viewport || + !options.dataViews || + options.dataViews.length === 0 || + !options.dataViews[0]) { return; - - let dataViews: DataView[] = options.dataViews; - if (!dataViews || dataViews.length === 0) - return; - - let dataView: DataView = dataViews[0]; - if (!dataView || !dataView.metadata) - return; - - this.updateObjects(dataView.metadata.objects); - + } + const dataView: DataView = options.dataViews[0]; + this.settings = Visual.parseSettings(dataView); let payloadBase64: string = null; if (dataView.scriptResult && dataView.scriptResult.payloadBase64) { payloadBase64 = dataView.scriptResult.payloadBase64; } - if (renderVisualUpdateType.indexOf(options.type) === -1) { if (payloadBase64) { this.injectCodeFromPayload(payloadBase64); } + } else { + this.onResizing(options.viewport); } - - this.onResizing(options.viewport); } - -// HTML - public onResizing(finalViewport: IViewport): void { + public onResizing(finalViewport: IViewport): void { /* add code to handle resizing of the view port */ } - - private injectCodeFromPayload(payloadBase64: string): void { - // Inject HTML from payload, created in R + private injectCodeFromPayload(payloadBase64: string): void { + // inject HTML from payload, created in R // the code is injected to the 'head' and 'body' sections. // if the visual was already rendered, the previous DOM elements are cleared - ResetInjector(); - - if (!payloadBase64) - return - + if (!payloadBase64) { + return; + } // create 'virtual' HTML, so parsing is easier - let el: HTMLHtmlElement = document.createElement('html'); + let el: HTMLHtmlElement = document.createElement("html"); try { el.innerHTML = window.atob(payloadBase64); } catch (err) { return; } - // if 'updateHTMLHead == false', then the code updates the header data only on the 1st rendering // this option allows loading and parsing of large and recurring scripts only once. if (updateHTMLHead || this.headNodes.length === 0) { @@ -178,154 +97,94 @@ module powerbi.extensibility.visual { let tempNode: Node = this.headNodes.pop(); document.head.removeChild(tempNode); } - let headList: NodeListOf = el.getElementsByTagName('head'); + let headList: NodeListOf = el.getElementsByTagName("head"); if (headList && headList.length > 0) { let head: HTMLHeadElement = headList[0]; this.headNodes = ParseElement(head, document.head); } } - // update 'body' nodes, under the rootElement while (this.bodyNodes.length > 0) { let tempNode: Node = this.bodyNodes.pop(); this.rootElement.removeChild(tempNode); } - let bodyList: NodeListOf = el.getElementsByTagName('body'); + let bodyList: NodeListOf = el.getElementsByTagName("body"); if (bodyList && bodyList.length > 0) { let body: HTMLBodyElement = bodyList[0]; this.bodyNodes = ParseElement(body, this.rootElement); } - RunHTMLWidgetRenderer(); } - - - - /** - * This function gets called by the update function above. You should read the new values of the properties into - * your settings object so you can use the new value in the enumerateObjectInstances function below. - * - * Below is a code snippet demonstrating how to expose a single property called "lineColor" from the object called "settings" - * This object and property should be first defined in the capabilities.json file in the objects section. - * In this code we get the property value from the objects (and have a default value in case the property is undefined) - */ - public updateObjects(objects: DataViewObjects) { - /*this.settings = { - lineColor: getFillValue(object, 'settings', 'lineColor', "#333333") - };*/ - - this.settings_forecastPlot_params = { - //show: getValue(dataView.metadata.objects, 'settings_forecastPlot_params', 'show', false), - forecastLength: getValue(objects, 'settings_forecastPlot_params', 'forecastLength', 10), - // forecastDate: getValue(dataView.metadata.objects, 'settings_forecastPlot_params', 'forecastDate', "9/25/2010 11:00:00 PM"), - seasonType: getValue(objects, 'settings_forecastPlot_params', 'seasonType', "Automatic"), - errorType: getValue(objects, 'settings_forecastPlot_params', 'errorType', "Automatic"), - trendType: getValue(objects, 'settings_forecastPlot_params', 'trendType', "Automatic"), - dampingType: getValue(objects, 'settings_forecastPlot_params', 'dampingType', "Automatic"), - targetSeason: getValue(objects, 'settings_forecastPlot_params', 'targetSeason', "Automatic") - }; - - - this.settings_conf_params = { - show: getValue( objects, 'settings_conf_params', 'show', true), - percentile: getValue( objects, 'settings_conf_params', 'percentile', 80), - upperConfIntervalFactor: getValue( objects, 'settings_conf_params', 'upperConfIntervalFactor', "0.5"), - - }; - this.settings_graph_params = { - // show: getValue( objects, 'settings_graph_params', 'show', false), - dataCol: getValue( objects, 'settings_graph_params', 'dataCol', "orange"), - forecastCol: getValue( objects, 'settings_graph_params', 'forecastCol', "red"), - percentile: getValue( objects, 'settings_graph_params', 'percentile', 40), - weight: getValue( objects, 'settings_graph_params', 'weight', 10), - - }; - this.settings_additional_params = { - - //show: getValue( objects, 'settings_additional_params', 'show', false), - // showWarnings: getValue( objects, 'settings_additional_params', 'showWarnings', false), - showInfo: getValue( objects, 'settings_additional_params', 'showInfo', true), - textSize: getValue( objects, 'settings_additional_params', 'textSize', 10) - } + private static parseSettings(dataView: DataView): VisualSettings { + return VisualSettings.parse(dataView) as VisualSettings; } - - + //This function gets called for each of the objects defined in the capabilities files and allows you to select which of the + //objects and properties you want to expose to the users in the property pane. public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration { let objectName = options.objectName; let objectEnumeration = []; - switch (objectName) { case 'settings_forecastPlot_params': - if (this.settings_forecastPlot_params.trendType !== "None") { - objectEnumeration.push({ - objectName: objectName, - properties: { - - - forecastLength: Math.round(inMinMax(this.settings_forecastPlot_params.forecastLength,1,12000)), - trendType: this.settings_forecastPlot_params.trendType, - dampingType: this.settings_forecastPlot_params.dampingType, - errorType: this.settings_forecastPlot_params.errorType, - seasonType: this.settings_forecastPlot_params.seasonType, - targetSeason: this.settings_forecastPlot_params.targetSeason - }, - selector: null - }); + if (this.settings.settings_forecastPlot_params.trendType !== "None") { + objectEnumeration.push({ + objectName: objectName, + properties: { + forecastLength: Math.round(inMinMax(this.settings.settings_forecastPlot_params.forecastLength, 1, 12000)), + trendType: this.settings.settings_forecastPlot_params.trendType, + dampingType: this.settings.settings_forecastPlot_params.dampingType, + errorType: this.settings.settings_forecastPlot_params.errorType, + seasonType: this.settings.settings_forecastPlot_params.seasonType, + targetSeason: this.settings.settings_forecastPlot_params.targetSeason + }, + selector: null + }); } else { objectEnumeration.push({ - objectName: objectName, - properties: { - - - forecastLength: Math.round(inMinMax(this.settings_forecastPlot_params.forecastLength,1,100000)), - trendType: this.settings_forecastPlot_params.trendType, - errorType: this.settings_forecastPlot_params.errorType, - seasonType: this.settings_forecastPlot_params.seasonType, - }, - selector: null - }); + objectName: objectName, + properties: { + forecastLength: Math.round(inMinMax(this.settings.settings_forecastPlot_params.forecastLength, 1, 100000)), + trendType: this.settings.settings_forecastPlot_params.trendType, + errorType: this.settings.settings_forecastPlot_params.errorType, + seasonType: this.settings.settings_forecastPlot_params.seasonType, + }, + selector: null + }); } break; - case 'settings_conf_params': objectEnumeration.push({ objectName: objectName, properties: { - show: this.settings_conf_params.show, - percentile: inMinMax(this.settings_conf_params.percentile, 0, 99), - upperConfIntervalFactor: this.settings_conf_params.upperConfIntervalFactor + show: this.settings.settings_conf_params.show, + confInterval1: this.settings.settings_conf_params.confInterval1, + confInterval2: this.settings.settings_conf_params.confInterval2 }, selector: null }); break; - case 'settings_graph_params': objectEnumeration.push({ objectName: objectName, properties: { - - dataCol: this.settings_graph_params.dataCol, - forecastCol: this.settings_graph_params.forecastCol, - percentile: this.settings_graph_params.percentile, - weight: inMinMax(this.settings_graph_params.weight, 1,50) + + dataCol: this.settings.settings_graph_params.dataCol, + forecastCol: this.settings.settings_graph_params.forecastCol, + percentile: this.settings.settings_graph_params.percentile, + weight: inMinMax(this.settings.settings_graph_params.weight, 1, 50) }, selector: null }); break; - case 'settings_additional_params': - if (this.settings_additional_params.showInfo === true) { + if (this.settings.settings_additional_params.showInfo === true) { objectEnumeration.push({ - objectName: objectName, properties: { - - // showWarnings: this.settings_additional_params.showWarnings, - showInfo: this.settings_additional_params.showInfo, - textSize: this.settings_additional_params.textSize + showInfo: this.settings.settings_additional_params.showInfo, + textSize: this.settings.settings_additional_params.textSize }, selector: null }); @@ -335,18 +194,24 @@ module powerbi.extensibility.visual { objectName: objectName, properties: { - - // showWarnings: this.settings_additional_params.showWarnings, - showInfo: this.settings_additional_params.showInfo, - + showInfo: this.settings.settings_additional_params.showInfo, }, selector: null }); - } break; + case 'settings_export_params': + objectEnumeration.push({ + objectName: objectName, + properties: { + show: this.settings.settings_export_params.show, + limitExportSize: this.settings.settings_export_params.limitExportSize, + method: this.settings.settings_export_params.method + }, + selector: null + }); + break; }; - return objectEnumeration; } } diff --git a/tsconfig.json b/tsconfig.json index a6d418c..e6f9957 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,9 +8,10 @@ "out": "./.tmp/build/visual.js" }, "files": [ - ".api/v1.7.0/PowerBI-visuals.d.ts", + ".api/v1.10.0/PowerBI-visuals.d.ts", "src/htmlInjectionUtility.ts", - "src/objectEnumerationUtility.ts", + "node_modules/powerbi-visuals-utils-dataviewutils/lib/index.d.ts", + "src/settings.ts", "src/visual.ts" ] } diff --git a/tslint.json b/tslint.json index 08414b0..8eb111c 100644 --- a/tslint.json +++ b/tslint.json @@ -63,11 +63,7 @@ "no-document-write": true, "no-exec-script": true, "no-function-constructor-with-string-args": true, - "no-http-string": [ - true, - "http://www.example.com/?.*", - "http://www.examples.com/?.*" - ], + "no-http-string": [true, "http://www.example.com/?.*", "http://www.examples.com/?.*"], "no-inner-html": true, "no-octal-literal": true, "no-reserved-keywords": true, @@ -80,4 +76,4 @@ "react-iframe-missing-sandbox": true, "react-no-dangerous-html": true } -} \ No newline at end of file +}