* Version 1.0.3 HTM-based

New guid, many changes in R code, visual.ts, supports source to R code

* small change to metadata

updated dependencies and github link

* Solved small bugs

1) forecastLength < 12000
2) empty data => empty plot

* removed "_HTML" from name

* changed PBIX

* Ver 1.0.4

Changed "qua(r)ter" word . English typo

* commented out DEBUG code

* api 1.7.0 + changes due to Ignat

* try to ignore

* gitignore

* remove .R*

* removed out_files

* removed out.html.tmp

* remove out.html_files

* Ver 1.0.4 almost ready

PBIX, export , conf limits in GUI like ARIMA

* Ver 1.0.4

* small

* removed commented

* package.json revert partially

* ignore

* small

* ignore

* whitespaces and package-lock.json

* tslint

* "powerbi-visuals-utils-dataviewutils": "1.2.0"

* ignore pbiviz

* _work remove

* tslint
This commit is contained in:
Boris Efraty 2018-04-26 12:09:33 +03:00 коммит произвёл GitHub
Родитель 92d48d2e62
Коммит 3d7a5a6d39
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 566 добавлений и 477 удалений

8
.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
out.html_files
dist/PowerBI-visuals-forcasting-exp.pbiviz
assets/Presentation1.pptx
assets/*_testing.pbix
dist/PowerBI-visuals-forcasting-exp.pbiviz

13
.vscode/launch.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,13 @@
{
"version": "0.1.0",
"configurations": [
{
"name": "Debugger",
"type": "chrome",
"request": "attach",
"port": 9222,
"sourceMaps": true,
"webRoot": "${cwd}/"
}
]
}

37
.vscode/settings.json поставляемый Normal file
Просмотреть файл

@ -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"
}
]
}

Двоичные данные
assets/CaptureSettings.PNG Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 29 KiB

Двоичные данные
assets/forecast_custom_RV_HTML-based.pbix

Двоичный файл не отображается.

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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"
}
]

Двоичные данные
dist/PowerBI-visuals-forcasting-exp.pbiviz поставляемый

Двоичный файл не отображается.

Просмотреть файл

@ -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"
}
}
}

Просмотреть файл

@ -4,12 +4,12 @@
"displayName": "Forecasting",
"guid": "PBI_CV_8EDDC07B_EE79_4418_A84C_D73897C0E21F_HTML",
"visualClassName": "Visual",
"version": "1.0.3",
"description": "<span>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.<br/><br/><span style='font-style:italic'>Service prerequisites:</span> R-powered custom visual is used in service seamlessly<br/><br /><span style='font-style:italic'>Desktop prerequisites:</span> To run R scripts in Power BI Desktop, you must separately install R on your local computer.<br />You can download and install R for free from the <a href='https://mran.revolutionanalytics.com/download/'>Revolution Open download page</a> or the <a href='https://cran.r-project.org/bin/windows/base/'>CRAN Repository</a><br /><br /> <span style='font-style:italic'> R package dependencies(auto-installed): </span> graphics, scales, forecast, zoo, ggplot2, htmlWidgets, XML, plotly <br /><br /> <span style='font-style:italic'> Supports R versions: </span> R 3.3.1, R 3.3.0, MRO 3.3.1, MRO 3.3.0, MRO 3.2.2 <br /></span>",
"version": "1.0.4",
"description": "<span>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.<br/><br/><span style='font-style:italic'>Service prerequisites:</span> R-powered custom visual is used in service seamlessly<br/><br /><span style='font-style:italic'>Desktop prerequisites:</span> To run R scripts in Power BI Desktop, you must separately install R on your local computer.<br />You can download and install R for free from the <a href='https://mran.revolutionanalytics.com/download/'>Revolution Open download page</a> or the <a href='https://cran.r-project.org/bin/windows/base/'>CRAN Repository</a><br /><br /> <span style='font-style:italic'> R package dependencies(auto-installed): </span> graphics, scales, forecast, zoo, ggplot2, htmlWidgets, XML, plotly <br /><br /> <span style='font-style:italic'> Supports R versions: </span> R 3.3.1, R 3.3.0, R 3.4.1, MRO 3.3.1, MRO 3.3.0, MRO 3.2.2 <br /></span>",
"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": []
}

Просмотреть файл

@ -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)
}
#################################################

158
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 = '\n<a href="" download="data.csv" style="position: absolute; top:0px; left: 0px; z-index: 20000;" id = "mydataURL">export</a>\n'
updateLinkElem = paste('<script>\n link_element = document.getElementById("mydataURL");link_element.href = outDataString64href;', '\n</script> ', sep =' ')
var64 = paste('<script> outDataString64 ="', outDataString64, '"; </script>', sep ="")
var64href = paste('<script> outDataString64href ="data:;base64,', outDataString64, '"; </script>', sep ="")
buttonElem = '<button style="position: absolute; top:0px; left: 0px; z-index: 20000;" onclick="myFunctionCopy(1)">copy to clipboard</button>'
funcScript = '<script>
function myFunctionCopy(is64)
{
const el = document.createElement("textarea");
if(is64)
{
el.value = atob(outDataString64);
}
else
{
el.value = outDataStringPlane;
}
document.body.appendChild(el);
el.select();
document.execCommand("copy");
document.body.removeChild(el);};
</script>'
if(exportMethod == "copy")
endOfBody = paste(var64,funcScript, buttonElem,'\n</body>',sep ="")
else#"download"
endOfBody = paste(linkElem,var64, var64href,updateLinkElem,'\n</body>',sep ="")
ReadFullFileReplaceString('out.html', 'out.html', '</body>', 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)
}

Просмотреть файл

@ -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; i<nodes.length; i++)
{
let tempNode: HTMLElement;
if (nodes.item(i).nodeName.toLowerCase() === 'script'){
if (!el || !el.hasChildNodes()) {
return;
}
let nodes: HTMLCollection = el.children;
for (let i: number = 0; i < nodes.length; i++) {
let tempNode: HTMLElement;
if (nodes.item(i).nodeName.toLowerCase() === "script") {
tempNode = createScriptNode(nodes.item(i));
}
else
{
} else {
tempNode = <HTMLElement>nodes.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<attr.length; i++)
{
script.setAttribute(attr[i].name, attr[i].textContent);
if (attr[i].name.toLowerCase() === 'src') {
// waiting only for src to finish loading
function createScriptNode(refNode: Element): HTMLElement {
let script: HTMLScriptElement = document.createElement("script");
let attr: NamedNodeMap = refNode.attributes;
for (let i: number = 0; i < attr.length; i++) {
script.setAttribute(attr[i].name, attr[i].textContent);
if (attr[i].name.toLowerCase() === "src") {
// waiting only for src to finish loading - async opetation
injectorCounter++;
script.onload = function() {
injectorCounter--;
script.onload = () => {
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);

Просмотреть файл

@ -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<T>(objects: DataViewObjects, objectName: string, propertyName: string, defaultValue: T ): T {
if (objects) {
let object = objects[objectName];
if (object) {
let property: T = <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<T>(objects: DataViewObjects, objectName: string, propertyName: string, defaultValue: T, minVal: T, maxVal: T ): T {
if (objects) {
let object = objects[objectName];
if (object) {
let property: T = <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<T>(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 = <T>object[propertyName];
if (property !== undefined) {
return property;
}
}
}
}
return defaultValue;
}
}

71
src/settings.ts Normal file
Просмотреть файл

@ -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";
}
}

Просмотреть файл

@ -24,153 +24,72 @@
* THE SOFTWARE.
*/
module powerbi.extensibility.visual {
"use strict";
// in order to improve the performance, one can update the <head> only in the initial rendering.
// in order to improve the performance, one can update the <head> 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 = <VisualSettingsForecastPlotParams>{
forecastLength: 10,
// forecastDate: "9/25/2010 11:00:00 PM",
seasonType: "Automatic",
errorType: "Automatic",
trendType: "Automatic",
dampingType: "Automatic",
targetSeason: "Automatic"
};
this.settings_conf_params = <VisualSettingsConfParams>{
show: true,
percentile: 80,
upperConfIntervalFactor: "0.5",
};
this.settings_graph_params = <VisualGraphParams>{
dataCol: "orange",
forecastCol: "red",
percentile: 40,
weight: 10
};
this.settings_additional_params = <VisualAdditionalParams>{
// 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<HTMLHeadElement> = el.getElementsByTagName('head');
let headList: NodeListOf<HTMLHeadElement> = 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<HTMLBodyElement> = el.getElementsByTagName('body');
let bodyList: NodeListOf<HTMLBodyElement> = 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 = <VisualSettings>{
lineColor: getFillValue(object, 'settings', 'lineColor', "#333333")
};*/
this.settings_forecastPlot_params = <VisualSettingsForecastPlotParams>{
//show: getValue<boolean>(dataView.metadata.objects, 'settings_forecastPlot_params', 'show', false),
forecastLength: getValue<number>(objects, 'settings_forecastPlot_params', 'forecastLength', 10),
// forecastDate: getValue<string>(dataView.metadata.objects, 'settings_forecastPlot_params', 'forecastDate', "9/25/2010 11:00:00 PM"),
seasonType: getValue<string>(objects, 'settings_forecastPlot_params', 'seasonType', "Automatic"),
errorType: getValue<string>(objects, 'settings_forecastPlot_params', 'errorType', "Automatic"),
trendType: getValue<string>(objects, 'settings_forecastPlot_params', 'trendType', "Automatic"),
dampingType: getValue<string>(objects, 'settings_forecastPlot_params', 'dampingType', "Automatic"),
targetSeason: getValue<string>(objects, 'settings_forecastPlot_params', 'targetSeason', "Automatic")
};
this.settings_conf_params = <VisualSettingsConfParams>{
show: getValue<boolean>( objects, 'settings_conf_params', 'show', true),
percentile: getValue<number>( objects, 'settings_conf_params', 'percentile', 80),
upperConfIntervalFactor: getValue<string>( objects, 'settings_conf_params', 'upperConfIntervalFactor', "0.5"),
};
this.settings_graph_params = <VisualGraphParams>{
// show: getValue<boolean>( objects, 'settings_graph_params', 'show', false),
dataCol: getValue<string>( objects, 'settings_graph_params', 'dataCol', "orange"),
forecastCol: getValue<string>( objects, 'settings_graph_params', 'forecastCol', "red"),
percentile: getValue<number>( objects, 'settings_graph_params', 'percentile', 40),
weight: getValue<number>( objects, 'settings_graph_params', 'weight', 10),
};
this.settings_additional_params = <VisualAdditionalParams>{
//show: getValue<boolean>( objects, 'settings_additional_params', 'show', false),
// showWarnings: getValue<boolean>( objects, 'settings_additional_params', 'showWarnings', false),
showInfo: getValue<boolean>( objects, 'settings_additional_params', 'showInfo', true),
textSize: getValue<number>( 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;
}
}

Просмотреть файл

@ -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"
]
}

Просмотреть файл

@ -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
}
}
}