This commit is contained in:
boefraty74 2017-01-18 23:42:35 +02:00
Родитель 1ebc78a83b
Коммит 8b5efa2caa
14 изменённых файлов: 1917 добавлений и 0 удалений

Двоичные данные
assets/icon.png Normal file

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

После

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

Двоичные данные
assets/screen.png Normal file

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

После

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

Двоичные данные
assets/thumb.png Normal file

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

После

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

699
capabilities.json Normal file
Просмотреть файл

@ -0,0 +1,699 @@
{
"dataRoles": [
{
"displayName": "Date",
"description": "Equally spaced date values",
"kind": "Grouping",
"name": "Date"
},
{
"displayName": "Value",
"description": "Numeric variable",
"kind": "Measure",
"name": "Value"
}
],
"dataViewMappings": [
{
"conditions": [
{
"Date": {
"max": 1
},
"Value": {
"max": 1
}
}
],
"scriptResult": {
"dataInput": {
"table": {
"rows": {
"select": [
{
"for": {
"in": "Date"
}
},
{
"for": {
"in": "Value"
}
}
],
"dataReductionAlgorithm": {
"top": {}
}
}
}
},
"script": {
"scriptProviderDefault": "R",
"scriptOutputType": "png",
"source": {
"objectName": "rcv_script",
"propertyName": "source"
},
"provider": {
"objectName": "rcv_script",
"propertyName": "provider"
}
}
}
}
],
"objects": {
"rcv_script": {
"properties": {
"provider": {
"type": {
"text": true
}
},
"source": {
"type": {
"scripting": {
"source": true
}
}
}
}
},
"settings_forecastPlot_params": {
"displayName": "Forecasting settings",
"description": "All common ARIMA models are provided; if one of the seasonal models is selected, it will be used instead of the non-seasonal model",
"properties": {
"forecastLength": {
"displayName": "Forecast length",
"description": "Number of data points to predict",
"type": {
"numeric": true
}
},
"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"
}
]
}
}
}
},
"settings_seasonality_params": {
"displayName": "Seasonality",
"properties": {
"show": {
"type": {
"bool": true
}
},
"targetSeason": {
"displayName": "Target seasonal factor",
"description": "Specify, if time series is influenced by known seasonal factors. The the number of observations per season is limited by 24. Non compatible seasonality will be ignored",
"type": {
"enumeration": [
{
"displayName": "automatic",
"value": "automatic"
},
{
"displayName": "manual",
"value": "manual"
},
{
"displayName": "hour",
"value": "hour"
},
{
"displayName": "day",
"value": "day"
},
{
"displayName": "week",
"value": "week"
},
{
"displayName": "month",
"value": "month"
},
{
"displayName": "quater",
"value": "quater"
},
{
"displayName": "year",
"value": "year"
}
]
}
},
"knownFrequency": {
"displayName": "Seasonality frequency",
"description": "Specify, if time series is influenced by known seasonal factors of known length. The units are in number of observations",
"type": {
"numeric": true
}
}
}
},
"settings_model_params": {
"displayName": "Model Customization",
"properties": {
"maxp": {
"displayName": "Maximal p",
"description": "The maximal order of the autoregressive component",
"type": {
"enumeration": [
{
"displayName": "0",
"value": "0"
},
{
"displayName": "1",
"value": "1"
},
{
"displayName": "2",
"value": "2"
},
{
"displayName": "3",
"value": "3"
}
]
}
},
"maxq": {
"displayName": "Maximal q",
"description": "The maximal order of the moving average component",
"type": {
"enumeration": [
{
"displayName": "0",
"value": "0"
},
{
"displayName": "1",
"value": "1"
},
{
"displayName": "2",
"value": "2"
},
{
"displayName": "3",
"value": "3"
}
]
}
},
"maxP": {
"displayName": "Maximal P",
"description": "The maximal order of the seasonal autoregressive component",
"type": {
"enumeration": [
{
"displayName": "0",
"value": "0"
},
{
"displayName": "1",
"value": "1"
},
{
"displayName": "2",
"value": "2"
},
{
"displayName": "3",
"value": "3"
}
]
}
},
"maxQ": {
"displayName": "Maximal Q",
"description": "The maximal order of the seasonal moving average component",
"type": {
"enumeration": [
{
"displayName": "0",
"value": "0"
},
{
"displayName": "1",
"value": "1"
},
{
"displayName": "2",
"value": "2"
},
{
"displayName": "3",
"value": "3"
}
]
}
},
"maxd": {
"displayName": "Maximal d",
"description": "The maximal degree of differencing",
"type": {
"enumeration": [
{
"displayName": "0",
"value": "0"
},
{
"displayName": "1",
"value": "1"
},
{
"displayName": "2",
"value": "2"
}
]
}
},
"maxD": {
"displayName": "Maximal D",
"description": "The maximal degree of seasonal differencing",
"type": {
"enumeration": [
{
"displayName": "0",
"value": "0"
},
{
"displayName": "1",
"value": "1"
},
{
"displayName": "2",
"value": "2"
}
]
}
},
"allowDrift": {
"displayName": "Allow drift",
"description": "Should the ARIMA model include a linear drift term?",
"type": {
"bool": true
}
},
"allowMean": {
"displayName": "Allow mean",
"description": "Should the ARIMA model include a mean term?",
"type": {
"bool": true
}
},
"fullEnumeration": {
"displayName": "Full enumeration (slow)",
"description": "If FALSE, will do stepwise selection (faster). Otherwise, it searches over all models (can be very slow).",
"type": {
"bool": true
}
},
"boxCoxTransform": {
"displayName": "Box-Cox transformation",
"description": "A family of transformations that includes logarithms and power transformations. Depends on the parameter `lambda`. Recommended if the data show variation that increases or decreases with the level of the series",
"type": {
"enumeration": [
{
"displayName": "off",
"value": "off"
},
{
"displayName": "automatic",
"value": "automatic"
},
{
"displayName": "manual",
"value": "manual"
}
]
}
},
"lambda": {
"displayName": "lambda",
"description": "Box-Cox transformation parameter",
"type": {
"numeric": true
}
}
}
},
"settings_userModel_params": {
"displayName": "User defined model",
"properties": {
"show": {
"type": {
"bool": true
}
},
"p": {
"displayName": "p",
"description": "The order of the autoregressive component",
"type": {
"enumeration": [
{
"displayName": "0",
"value": "0"
},
{
"displayName": "1",
"value": "1"
},
{
"displayName": "2",
"value": "2"
},
{
"displayName": "3",
"value": "3"
}
]
}
},
"q": {
"displayName": "q",
"description": "The order of the moving average component",
"type": {
"enumeration": [
{
"displayName": "0",
"value": "0"
},
{
"displayName": "1",
"value": "1"
},
{
"displayName": "2",
"value": "2"
},
{
"displayName": "3",
"value": "3"
}
]
}
},
"P": {
"displayName": "P",
"description": "The order of the seasonal autoregressive component",
"type": {
"enumeration": [
{
"displayName": "0",
"value": "0"
},
{
"displayName": "1",
"value": "1"
},
{
"displayName": "2",
"value": "2"
},
{
"displayName": "3",
"value": "3"
}
]
}
},
"Q": {
"displayName": "Q",
"description": "The order of the seasonal moving average component",
"type": {
"enumeration": [
{
"displayName": "0",
"value": "0"
},
{
"displayName": "1",
"value": "1"
},
{
"displayName": "2",
"value": "2"
},
{
"displayName": "3",
"value": "3"
}
]
}
},
"d": {
"displayName": "d",
"description": "The degree of differencing",
"type": {
"enumeration": [
{
"displayName": "0",
"value": "0"
},
{
"displayName": "1",
"value": "1"
},
{
"displayName": "2",
"value": "2"
}
]
}
},
"D": {
"displayName": "D",
"description": "The degree of seasonal differencing",
"type": {
"enumeration": [
{
"displayName": "0",
"value": "0"
},
{
"displayName": "1",
"value": "1"
},
{
"displayName": "2",
"value": "2"
}
]
}
}
}
},
"settings_graph_params": {
"displayName": "Graphical parameters",
"properties": {
"dataCol": {
"displayName": "History data color",
"type": {
"fill": {
"solid": {
"color": true
}
}
}
},
"forecastCol": {
"displayName": "Forecast data color",
"type": {
"fill": {
"solid": {
"color": true
}
}
}
},
"percentile": {
"displayName": "Opacity",
"type": {
"numeric": true
}
},
"weight": {
"displayName": "Line width",
"type": {
"numeric": true
}
}
}
},
"settings_additional_params": {
"displayName": "Info and warnings",
"properties": {
"show": {
"type": {
"bool": true
}
},
"textSize": {
"displayName": "Font size",
"description": "Font size used to show information",
"type": {
"numeric": true
}
},
"textColor": {
"displayName": "Text color",
"description": "Color used to show information",
"type": {
"fill": {
"solid": {
"color": true
}
}
}
},
"infoCriteria": {
"displayName": "Output information criteria",
"description": "Used only for info, does not influence the results. Select one option: Akaike's Information Criterion (AIC), Corrected Akaike's Information Criterion (AICc), or Schwarz's Bayesian Information Criterion (BIC) ",
"type": {
"enumeration": [
{
"displayName": "none",
"value": "none"
},
{
"displayName": "AIC",
"value": "AIC"
},
{
"displayName": "BIC",
"value": "BIC"
},
{
"displayName": "AICc",
"value": "AICc"
}
]
}
}
}
}
},
"suppressDefaultTitle": true
}

25
dependencies.json Normal file
Просмотреть файл

@ -0,0 +1,25 @@
{
"cranPackages": [
{
"name": "zoo",
"displayName": "zoo: S3 Infrastructure for Regular and Irregular Time Series",
"url": "https://cran.r-project.org/web/packages/zoo/index.html"
},
{
"name": "scales",
"displayName": "scales: Scale Functions for Visualization",
"url": "https://cran.r-project.org/web/packages/scales/index.html"
},
{
"name": "reshape2",
"displayName": "reshape2: Flexibly Reshape Data: A Reboot of the Reshape Package",
"url": "https://cran.r-project.org/web/packages/reshape2/index.html"
},
{
"name": "forecast",
"displayName": "forecast: Forecasting Functions for Time Series and Linear Models",
"url": "https://cran.r-project.org/web/packages/forecast/index.html"
}
]
}

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

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

Двоичные данные
dist/hTree1.pbix поставляемый Normal file

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

3
package.json Normal file
Просмотреть файл

@ -0,0 +1,3 @@
{
"name": "visual"
}

24
pbiviz.json Normal file
Просмотреть файл

@ -0,0 +1,24 @@
{
"visual": {
"name": "PowerBI-visuals-forcasting-ARIMA_WORK",
"displayName": "Forecasting with ARIMA_WORK",
"guid": "PBI_CV_AD348DC9_41C4_4867_B19O_53F9RDC6IEAS_WORK",
"visualClassName": "Visual",
"version": "1.0.0",
"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 Autoregressive Integrated Moving Average (ARIMA) method for the forecasting. Both seasonal and non-seasonal modeling is supported. 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 <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>",
"supportUrl": "http://community.powerbi.com/",
"gitHubUrl": "https://github.com/microsoft/PowerBI-visuals-forcasting-ARIMA"
},
"apiVersion": "1.2.0",
"author": {
"name": "Microsoft",
"email": "pbicvsupport@microsoft.com"
},
"assets": {
"icon": "assets/icon.png"
},
"externalJS": [],
"style": "style/visual.less",
"capabilities": "capabilities.json",
"dependencies": "dependencies.json"
}

604
script.r Normal file
Просмотреть файл

@ -0,0 +1,604 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Third Party Programs. This software enables you to obtain software applications from other sources.
# Those applications are offered and distributed by third parties under their own license terms.
# Microsoft is not developing, distributing or licensing those applications to you, but instead,
# as a convenience, enables you to use this software to obtain those applications directly from
# the application providers.
# By using the software, you acknowledge and agree that you are obtaining the applications directly
# from the third party providers and under separate license terms, and that it is your responsibility to locate,
# understand and comply with those license terms.
# Microsoft grants you no license rights for third-party software or applications that is obtained using this software.
#
# WARNINGS:
#
# CREATION DATE: 24/7/2016
#
# LAST UPDATE: 18/01/2017
#
# VERSION: 1.0.0
#
# R VERSION TESTED: 3.3.1
#
# AUTHOR: pbicvsupport@microsoft.com
#
# REFERENCES: https://en.wikipedia.org/wiki/Autoregressive_integrated_moving_average, https://www.otexts.org/fpp/8
Sys.setlocale("LC_ALL","English") # internationalization
#For DEBUG uses:
#save(list = ls(all.names = TRUE), file='c:/Users/boefraty/Temp/tempData.Rda')
load(file='c:/Users/boefraty/Temp/tempData.Rda')
############ User Parameters #########
##PBI_PARAM: Should warnings text be displayed?
#Type:logical, Default:TRUE, Range:NA, PossibleValues:NA, Remarks: NA
showWarnings = TRUE
##PBI_PARAM: Should additional info about the forcasting method be displayed?
#Type:logical, Default:TRUE, Range:NA, PossibleValues:NA, Remarks: NA
showInfo=TRUE
if(exists("settings_additional_params_show"))
showInfo = settings_additional_params_show
infoCriteria = "none"
if(exists("settings_additional_params_infoCriteria"))
infoCriteria = settings_additional_params_infoCriteria
##PBI_PARAM: Forecast length
#Type:integer, Default:NULL, Range:NA, PossibleValues:NA, Remarks: NULL means choose forecast length automatically
forecastLength=10
if(exists("settings_forecastPlot_params_forecastLength"))
{
forecastLength = as.numeric(settings_forecastPlot_params_forecastLength)
if(is.na(forecastLength))
forecastLength = 10
forecastLength = round(max(min(forecastLength,1e+6),1))
}
confInterval1 = 0.85
if(exists("settings_forecastPlot_params_confInterval1"))
{
confInterval1 = as.numeric(settings_forecastPlot_params_confInterval1)
}
confInterval2 = 0.95
if(exists("settings_forecastPlot_params_confInterval2"))
{
confInterval2 = as.numeric(settings_forecastPlot_params_confInterval2)
}
if(confInterval1 > confInterval2)
{#switch
temp = confInterval1
confInterval1 = confInterval2
confInterval2 = temp
}
withSeasonality = TRUE
if(exists("settings_seasonality_params_show"))
withSeasonality = settings_seasonality_params_show
##PBI_PARAM target Season
#Type: string, Default:"Automatic", Range:NA, PossibleValues:"Automatic","Hour","Day","Week", ...
targetSeason = "automatic"
if(exists("settings_seasonality_params_targetSeason"))
targetSeason = settings_seasonality_params_targetSeason
knownFrequency = 12
if(exists("settings_seasonality_params_knownFrequency"))
knownFrequency = min(1000000,max(2,settings_seasonality_params_knownFrequency))
maxp = 3
if(exists("settings_model_params_maxp"))
maxp = as.numeric(settings_model_params_maxp)
maxq = 3
if(exists("settings_model_params_maxq"))
maxq = as.numeric(settings_model_params_maxq)
maxd = 2
if(exists("settings_model_params_maxd"))
maxd = as.numeric(settings_model_params_maxd)
maxP = 3
if(exists("settings_model_params_maxP"))
maxP = as.numeric(settings_model_params_maxP)
maxQ = 3
if(exists("settings_model_params_maxQ"))
maxQ = as.numeric(settings_model_params_maxQ)
maxD = 2
if(exists("settings_model_params_maxD"))
maxD = as.numeric(settings_model_params_maxD)
allowDrift = FALSE
if(exists("settings_model_params_allowDrift"))
allowDrift = (settings_model_params_allowDrift)
allowMean = FALSE
if(exists("settings_model_params_allowMean"))
allowMean = settings_model_params_allowMean
fullEnumeration = FALSE
if(exists("settings_model_params_fullEnumeration"))
fullEnumeration = settings_model_params_fullEnumeration
boxCoxTransform = "off"
if(exists("settings_model_params_boxCoxTransform"))
boxCoxTransform = settings_model_params_boxCoxTransform
lambda = 0
if(exists("settings_model_params_lambda"))
lambda = max(-1,min(settings_model_params_lambda,2))
userModel = FALSE
if(exists("settings_userModel_params_show"))
userModel = settings_userModel_params_show
p = 3
if(exists("settings_userModel_params_p"))
p = as.numeric(settings_userModel_params_p)
q = 3
if(exists("settings_userModel_params_q"))
q = as.numeric(settings_userModel_params_q)
d = 2
if(exists("settings_userModel_params_d"))
d = as.numeric(settings_userModel_params_d)
P = 3
if(exists("settings_userModel_params_P"))
P = as.numeric(settings_userModel_params_P)
Q = 3
if(exists("settings_userModel_params_Q"))
Q = as.numeric(settings_userModel_params_Q)
D = 2
if(exists("settings_userModel_params_D"))
D = as.numeric(settings_userModel_params_D)
lowerConfInterval = confInterval1
upperConfInterval = confInterval2
###############Library Declarations###############
libraryRequireInstall = function(packageName, ...)
{
if(!require(packageName, character.only = TRUE))
warning(paste("*** The package: '", packageName, "' was not installed ***",sep=""))
}
libraryRequireInstall("scales")
libraryRequireInstall("forecast")
libraryRequireInstall("zoo")
###############Internal parameters definitions#################
#PBI_PARAM Minimal number of points
#Type:integer, Default:10, Range:[0,], PossibleValues:NA, Remarks: NA
minPoints = 10
##PBI_PARAM Color of time series line
#Type:string, Default:"orange", Range:NA, PossibleValues:"orange","blue","green","black"
pointsCol = "blue"
if(exists("settings_graph_params_dataCol"))
pointsCol = settings_graph_params_dataCol
##PBI_PARAM Color of forecast line
#Type:string, Default:"red", Range:NA, PossibleValues:"red","blue","green","black"
forecastCol = "orange"
if(exists("settings_graph_params_forecastCol"))
forecastCol = settings_graph_params_forecastCol
#PBI_PARAM Transparency of scatterplot points
#Type:numeric, Default:0.4, Range:[0,1], PossibleValues:NA, Remarks: NA
transparency = 1
if(exists("settings_graph_params_percentile"))
transparency = as.numeric(settings_graph_params_percentile)/100
#PBI_PARAM Shaded band for confidence interval
#Type:logical, Default:TRUE, Range:NA, PossibleValues:NA, Remarks: NA
fillConfidenceLevels=TRUE
#PBI_PARAM Size of points on the plot
#Type:numeric, Default: 1 , Range:[0.1,5], PossibleValues:NA, Remarks: NA
pointCex = 1
if(exists("settings_graph_params_weight"))
pointCex = as.numeric(settings_graph_params_weight)/10
#PBI_PARAM Size of subtitle on the plot
#Type:numeric, Default: 0.75 , Range:[0.1,5], PossibleValues:NA, Remarks: NA
cexSub = 0.75
if(exists("settings_additional_params_textSize"))
cexSub = as.numeric(settings_additional_params_textSize)/12
infoTextColor = "gray"
if(exists("settings_additional_params_textColor"))
infoTextColor = settings_additional_params_textColor
###############Internal functions definitions#################
# tiny function to deal with verl long strings on plot
cutStr2Show = function(strText, strCex = 0.8, abbrTo = 100, isH = TRUE, maxChar = 3, partAvailable = 1)
{
# partAvailable, wich portion of window is available, in [0,1]
if(is.null(strText))
return (NULL)
SCL = 0.075*strCex/0.8
pardin = par()$din
gStand = partAvailable*(isH*pardin[1]+(1-isH)*pardin[2]) /SCL
# if very very long abbreviate
if(nchar(strText)>abbrTo && nchar(strText)> 1)
strText = abbreviate(strText, abbrTo)
# if looooooong convert to lo...
if(nchar(strText)>round(gStand) && nchar(strText)> 1)
strText = paste(substring(strText,1,floor(gStand)),"...",sep="")
# if shorter than maxChar remove
if(gStand<=maxChar)
strText = NULL
return(strText)
}
# Find number of ticks on X axis
FindTicksNum = function(n,f)
{
tn = 10 # default minimum
D = 2 # tick/inch
numCircles = n/f
xSize = par()$din[1]
tn = max(round(xSize*D),tn)
return(tn)
}
#format labels on X-axis automatically
flexFormat = function(dates, orig_dates, freq = 1, myformat = NULL)
{
days=(as.numeric(difftime(dates[length(dates)],dates[1]),units="days"))
months = days/30
years = days/365.25
constHour = length(unique(orig_dates$hour))==1
constMin = length(unique(orig_dates$min))==1
constSec = length(unique(orig_dates$sec))==1
constMon = length(unique(orig_dates$mon))==1
timeChange = any(!constHour,!constMin,!constSec)
if(is.null(myformat))
{
if(years > 10){
if(constMon)
{
myformat = "%Y" #many years => only year :2001
}else{
myformat = "%m/%y" #many years + months :12/01
}
}else{
if(years > 1 && N < 50){
myformat = "%b %d, %Y" #several years, few samples:Jan 01, 2010
}else{
if(years > 1){
myformat = "%m/%d/%y" #several years, many samples: 01/20/10
}else{
if(years <= 1 && !timeChange)
myformat = "%b %d" #1 year,no time: Jan 01
}
}
}
}
if(is.null(myformat) && timeChange)
if(years>1){
myformat = "%m/%d/%y %H:%M" # 01/20/10 12:00
}else{
if(days>1){
myformat = "%b %d, %H:%M" # Jan 01 12:00
}else{
if(days<=1){
myformat = "%H:%M" # Jan 01 12:00
}
}
}
if(!is.null(myformat)){
if(myformat == "%Y,Q%q")
dates = as.yearqtr(dates)
dates1= format(dates, myformat)
}else{
dates1 = as.character(1:length(dates)) # just id
}
return(dates1)
}
# verify if "perSeason" is good for "frequency" parameter
freqSeason1 = function(seasons,perSeason)
{
if((seasons > 5 && perSeason > 3) || (seasons > 2 && perSeason > 7))
return (perSeason)
return(1)
}
# find frequency using the dates, targetS is a "recommended" seasonality
findFreqFromDates1 = function(dates, targetS = "Automatic")
{
freq = 1
N = length(dates)
nnn = c("hour", "day", "week", "month", "quater", "year")
seasons = rep(NaN,6)
names(seasons) = nnn
perSeason = seasons
seasons["day"]=round(as.numeric(difftime(dates[length(dates)],dates[1]),units="days"))
seasons["hour"]=round(as.numeric(difftime(dates[length(dates)],dates[1]),units="hours"))
seasons["week"]=round(as.numeric(difftime(dates[length(dates)],dates[1]),units="weeks"))
seasons["month"] = seasons["day"]/30
seasons["year"] = seasons["day"]/365.25
seasons["quater"] = seasons["year"]*4
perSeason = N/seasons
if(targetS!="automatic") # target
freq = perSeason[targetS]
if(freq < 2) # if TRUE, target season factor is not good
freq = 1
for( s in rev(nnn)) # check year --> Quater --> etc
if(freq == 1 )
freq = freqSeason1(seasons[s],perSeason[s])
return(round(freq))
}
#get valid frequency parameter, based on input from user
getFrequency1 = function(parsed_dates, values, tS, f)
{
myFreq = f
grp = c("automatic","none","manual")
if(!(tS %in% c("autodetect from value","none","manual"))) #detect from date
{
myFreq = findFreqFromDates1(parsed_dates, targetS = tS)
}else{
if(tS == "none")
{ myFreq = 1}
else
{# NOT YET IMPLEMENTED
# if(tS == "autodetect from value")
# myFreq = freqFromValue1(values)
}
}
numPeriods = floor(length(values)/myFreq)
if(numPeriods< 2)
myFreq = findFreqFromDates1(parsed_dates, targetS = "automatic")
return(myFreq)
}
#format info string
GetFitMethodString = function(fit,withSeasonality, infoCriteria = "none")
{
arma = fit$arma
resString = as.character(arimaorder(fit))
skey = c("p","d","q","P","D","Q", "m")
skey = skey[1:length(resString)]
skey = paste(skey, collapse = ",")
resString = paste("ARIMA: (",skey,") = (",paste(resString,collapse = ","),")",sep ="")
# add more info
if(infoCriteria != "none")
{
if(infoCriteria == "AIC")
resString = paste(resString, "; AIC = ", as.character(round(fit$aic,2)), sep ="")
else
if(infoCriteria == "AICc")
resString = paste(resString, "; AICc = ", as.character(round(fit$aicc,2)), sep ="")
else
if(infoCriteria == "BIC")
resString = paste(resString, "; BIC = ", as.character(round(fit$bic,2)), sep ="")
}
return(resString)
}
# find lambda for Box-Cox transform
FindBoxCoxLambda = function(timeSeries, boxCoxTransform, lambda = NULL, mymethod = "loglik")
{
if(boxCoxTransform == "off")
return(NULL)
if(boxCoxTransform == "manual")
return(lambda)
if(boxCoxTransform == "automatic")
lambda=BoxCox.lambda(timeSeries, method = mymethod, lower = -1, upper = 2)
return (lambda)
}
###############Upfront input correctness validations (where possible)#################
pbiWarning = NULL
if(!exists("Date") || !exists("Value"))
{
dataset=data.frame()
pbiWarning = cutStr2Show("Both 'Date' and 'Value' fields are required.", strCex = 0.85)
timeSeries=ts()
showWarnings=TRUE
}else{
dataset= cbind(Date,Value)
dataset<-dataset[complete.cases(dataset),] #remove corrupted rows
labTime = "Time"
labValue=names(dataset)[ncol(dataset)]
N=nrow(dataset)
if(N==0 && exists("Date") && nrow(Date)>0 && exists("Value")){
pbiWarning1 = cutStr2Show("Wrong date type. Only 'Date', 'Time', 'Date/Time' are allowed without hierarchy", strCex = 0.85)
pbiWarning = paste(pbiWarning1, pbiWarning, sep ="\n")
timeSeries=ts()
showWarnings=TRUE
}else {
dataset = dataset[order(dataset[,1]),]
parsed_dates=strptime(dataset[,1],"%Y-%m-%dT%H:%M:%S",tz="UTC")
labTime = names(Date)[1]
if((any(is.na(parsed_dates))))
{
pbiWarning1 = cutStr2Show("Wrong or corrupted 'Date'.", strCex = 0.85)
pbiWarning2 = cutStr2Show("Only 'Date', 'Time', 'Date/Time' types are allowed without hierarchy", strCex = 0.85)
pbiWarning = paste(pbiWarning1, pbiWarning2, pbiWarning, sep ="\n")
timeSeries=ts()
showWarnings=TRUE
}
else
{
interval = difftime(parsed_dates[length(parsed_dates)],parsed_dates[1])/(length(parsed_dates)-1) # force equal spacing
if(withSeasonality==FALSE)
targetSeason = "none"
myFreq = getFrequency1(parsed_dates, values = dataset[,2], tS = targetSeason, f = knownFrequency)
if(myFreq < 2)
withSeasonality = FALSE
if(withSeasonality == FALSE)
{
maxP = maxQ = maxD = P = Q = D = 0
}
timeSeries=ts(data = dataset[,2], start=1, frequency = round(myFreq))
}
}
}
##############Main Visualization script###########
pbiInfo = NULL
if(length(timeSeries)>=minPoints) {
lambda = FindBoxCoxLambda(timeSeries, boxCoxTransform, lambda)
fit = NULL
#if user model fails do auto.arima
if(userModel)
{
result <- tryCatch({
fit = suppressWarnings(Arima(timeSeries, order = c(p,d,q), seasonal = c(P,D,Q),
include.mean=allowMean, include.drift=allowDrift,
method = "ML", lambda=lambda))
}, warning = function(war) {
# warning handler picks up where error was generated
print(paste("MY_WARNING: ",war))
return("Arima_warning")
}, error = function(err) {
# error handler picks up where error was generated
print(paste("MY_ERROR: ",err))
return("Arima_error")
}, finally = {
}) # END tryCatch
}
if(is.null(fit))
fit = auto.arima(timeSeries, max.p = maxp, max.q = maxq, max.d = maxd,
max.P = maxP, max.Q = maxQ, max.D = maxD,
seasonal= withSeasonality,allowdrift=allowDrift, allowmean=allowMean, lambda=lambda,
max.order=4, parallel = FALSE, stepwise = !fullEnumeration)
fit$method = GetFitMethodString(fit,withSeasonality, infoCriteria)
if(lowerConfInterval==0)
lowerConfInterval = NULL;
prediction = forecast(fit, level=c(lowerConfInterval,upperConfInterval), h=forecastLength)
lastValue = tail(prediction$x,1)
prediction$mean=ts(c(lastValue,prediction$mean),
frequency = frequency(prediction$mean),
end=end(prediction$mean))
#
prediction$upper=rbind(c(lastValue,lastValue),prediction$upper)
#
prediction$lower=rbind(c(lastValue,lastValue),prediction$lower)
if(showInfo)
{
pbiInfo=paste(pbiInfo,"", fit$method, sep="")
pbiInfo = cutStr2Show(pbiInfo,strCex = cexSub, isH = TRUE, maxChar = 20)
}
labTime = cutStr2Show(labTime, strCex =1.1, isH = TRUE)
labValue = cutStr2Show(labValue, strCex =1.1, isH = FALSE)
plot.forecast(prediction, lwd=pointCex, col=alpha(pointsCol,transparency), fcol=alpha(forecastCol,transparency), flwd = pointCex, shaded=fillConfidenceLevels,
main = "", sub = pbiInfo, col.sub = infoTextColor, cex.sub = cexSub, xlab = labTime, ylab = labValue, xaxt = "n")
NpF = (length(parsed_dates))+forecastLength
freq = frequency(timeSeries)
#format x_with_f
numTicks = FindTicksNum(NpF,freq) # find based on plot size
x_with_f = as.POSIXlt(seq(from=parsed_dates[1], to = (parsed_dates[1]+interval*(length(parsed_dates)+forecastLength)), length.out = numTicks))
x_with_forcast_formatted = flexFormat(dates = x_with_f, orig_dates = parsed_dates, freq = freq)
correction = (NpF-1)/(numTicks-1) # needed due to subsampling of ticks
axis(1, at = 1+correction*((0:(numTicks-1))/freq), labels = x_with_forcast_formatted)
} else{ #empty plot
plot.new()
showWarnings = TRUE
pbiWarning<-paste(pbiWarning, "Not enough data points", sep="\n")
}
#add warning as subtitle
if(showWarnings)
title(main=NULL, sub=pbiWarning, outer=FALSE, col.sub = infoTextColor, cex.sub=cexSub)

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

@ -0,0 +1,156 @@
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;
}
}

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

@ -0,0 +1,377 @@
/*
* Power BI Visual CLI
*
* 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 {
interface VisualSettingsForecastPlotParams {
show: boolean;
forecastLength: number;
confInterval1: string;
confInterval2: string;
}
interface VisualSettingsSeasonalityParams {
show: boolean;
targetSeason: string;
knownFrequency: number;
}
interface VisualSettingsModelParams {
maxp: string;
maxq: string;
maxP: string;
maxQ: string;
maxd: string;
maxD: string;
allowDrift: boolean;
allowMean: boolean;
fullEnumeration: boolean;
boxCoxTransform: string;
lambda: number;
}
interface VisualSettingsUserModelParams {
show: boolean;
p: string;
q: string;
P: string;
Q: string;
d: string;
D: string;
}
interface VisualGraphParams {
show: boolean;
dataCol: string;
forecastCol: string;
percentile: number;
weight: number;
}
interface VisualAdditionalParams {
show: boolean;
textSize: number;
textColor: string;
infoCriteria: string;
}
export class Visual implements IVisual {
private imageDiv: HTMLDivElement;
private imageElement: HTMLImageElement;
private settings_forecastPlot_params: VisualSettingsForecastPlotParams;
private settings_seasonality_params: VisualSettingsSeasonalityParams;
private settings_model_params: VisualSettingsModelParams;
private settings_userModel_params: VisualSettingsUserModelParams;
private settings_graph_params: VisualGraphParams;
private settings_additional_params: VisualAdditionalParams;
public constructor(options: VisualConstructorOptions) {
this.imageDiv = document.createElement('div');
this.imageDiv.className = 'rcv_autoScaleImageContainer';
options.element.appendChild(this.imageDiv);
this.imageElement = document.createElement('img');
this.imageElement.className = 'rcv_autoScaleImage';
this.imageDiv.appendChild(this.imageElement);
this.settings_forecastPlot_params = <VisualSettingsForecastPlotParams>{
forecastLength: 10,
confInterval1: "0.85",
confInterval2: "0.95"
};
this.settings_seasonality_params = <VisualSettingsSeasonalityParams>{
show: true,
targetSeason: "automatic",
knownFrequency: 12,
};
this.settings_model_params = <VisualSettingsModelParams>{
maxp: "3",
maxq: "3",
maxP: "3",
maxQ: "3",
maxd: "2",
maxD: "2",
allowDrift: true,
allowMean: false,
fullEnumeration: false,
boxCoxTransform: "off",
lambda: 0.1,
}
this.settings_userModel_params = <VisualSettingsUserModelParams>{
show: false,
p: "3",
q: "3",
P: "3",
Q: "3",
d: "2",
D: "2",
}
this.settings_graph_params = <VisualGraphParams>{
dataCol: "blue",
forecastCol: "orange",
percentile: 40,
weight: 10
};
this.settings_additional_params = <VisualAdditionalParams>{
show: true,
textSize: 10,
textColor: "gray",
infoCriteria: "none"
};
}
public update(options: VisualUpdateOptions) {
let dataViews: DataView[] = options.dataViews;
if (!dataViews || dataViews.length === 0)
return;
let dataView: DataView = dataViews[0];
if (!dataView || !dataView.metadata)
return;
this.settings_forecastPlot_params = <VisualSettingsForecastPlotParams>{
forecastLength: getValue<number>(dataView.metadata.objects, 'settings_forecastPlot_params', 'forecastLength', 10),
confInterval1: getValue<string>(dataView.metadata.objects, 'settings_forecastPlot_params', 'confInterval1', "0.85"),
confInterval2: getValue<string>(dataView.metadata.objects, 'settings_forecastPlot_params', 'confInterval2', "0.95")
};
this.settings_seasonality_params = <VisualSettingsSeasonalityParams>{
show: getValue<boolean>(dataView.metadata.objects, 'settings_seasonality_params', 'show', true),
targetSeason: getValue<string>(dataView.metadata.objects, 'settings_seasonality_params', 'targetSeason', "year"),
knownFrequency: getValue<number>(dataView.metadata.objects, 'settings_seasonality_params', 'knownFrequency', 12),
}
this.settings_model_params = <VisualSettingsModelParams>{
maxp: getValue<string>(dataView.metadata.objects, 'settings_model_params', 'maxp', "3"),
maxq: getValue<string>(dataView.metadata.objects, 'settings_model_params', 'maxq', "3"),
maxP: getValue<string>(dataView.metadata.objects, 'settings_model_params', 'maxP', "3"),
maxQ: getValue<string>(dataView.metadata.objects, 'settings_model_params', 'maxQ', "3"),
maxd: getValue<string>(dataView.metadata.objects, 'settings_model_params', 'maxd', "2"),
maxD: getValue<string>(dataView.metadata.objects, 'settings_model_params', 'maxD', "2"),
allowDrift: getValue<boolean>(dataView.metadata.objects, 'settings_model_params', 'allowDrift', true),
allowMean: getValue<boolean>(dataView.metadata.objects, 'settings_model_params', 'allowMean', false),
fullEnumeration: getValue<boolean>(dataView.metadata.objects, 'settings_model_params', 'fullEnumeration', false),
boxCoxTransform: getValue<string>(dataView.metadata.objects, 'settings_model_params', 'boxCoxTransform', "off"),
lambda: getValue<number>(dataView.metadata.objects, 'settings_model_params', 'lambda', 0.1),
}
this.settings_userModel_params = <VisualSettingsUserModelParams>{
show: getValue<boolean>(dataView.metadata.objects, 'settings_userModel_params', 'show', false),
p: getValue<string>(dataView.metadata.objects, 'settings_userModel_params', 'p', "3"),
q: getValue<string>(dataView.metadata.objects, 'settings_userModel_params', 'q', "3"),
P: getValue<string>(dataView.metadata.objects, 'settings_userModel_params', 'P', "3"),
Q: getValue<string>(dataView.metadata.objects, 'settings_userModel_params', 'Q', "3"),
d: getValue<string>(dataView.metadata.objects, 'settings_userModel_params', 'd', "2"),
D: getValue<string>(dataView.metadata.objects, 'settings_userModel_params', 'D', "2"),
}
this.settings_graph_params = <VisualGraphParams>{
dataCol: getValue<string>(dataView.metadata.objects, 'settings_graph_params', 'dataCol', "blue"),
forecastCol: getValue<string>(dataView.metadata.objects, 'settings_graph_params', 'forecastCol', "orange"),
percentile: getValue<number>(dataView.metadata.objects, 'settings_graph_params', 'percentile', 40),
weight: getValue<number>(dataView.metadata.objects, 'settings_graph_params', 'weight', 10),
}
this.settings_additional_params = <VisualAdditionalParams>{
show: getValue<boolean>(dataView.metadata.objects, 'settings_additional_params', 'show', true),
textSize: getValue<number>(dataView.metadata.objects, 'settings_additional_params', 'textSize', 10),
textColor: getValue<string>(dataView.metadata.objects, 'settings_additional_params', 'textColor', "gray"),
infoCriteria: getValue<string>(dataView.metadata.objects, 'settings_additional_params', 'infoCriteria', "none")
}
let imageUrl: string = null;
if (dataView.scriptResult && dataView.scriptResult.payloadBase64) {
imageUrl = "data:image/png;base64," + dataView.scriptResult.payloadBase64;
}
if (imageUrl) {
this.imageElement.src = imageUrl;
} else {
this.imageElement.src = null;
}
this.onResizing(options.viewport);
}
public onResizing(finalViewport: IViewport): void {
this.imageDiv.style.height = finalViewport.height + 'px';
this.imageDiv.style.width = finalViewport.width + 'px';
}
public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {
let objectName = options.objectName;
let objectEnumeration = [];
switch (objectName) {
case 'settings_forecastPlot_params':
objectEnumeration.push({
objectName: objectName,
properties: {
forecastLength: Math.round(inMinMax(this.settings_forecastPlot_params.forecastLength, 1, 1000000)),
confInterval1: this.settings_forecastPlot_params.confInterval1,
confInterval2: this.settings_forecastPlot_params.confInterval2
},
selector: null
});
break;
case 'settings_seasonality_params':
objectEnumeration.push({
objectName: objectName,
properties: {
show: this.settings_seasonality_params.show,
targetSeason: this.settings_seasonality_params.targetSeason,
},
selector: null
});
if (this.settings_seasonality_params.targetSeason == "manual") {
objectEnumeration.push({
objectName: objectName,
properties: {
knownFrequency: inMinMax(this.settings_seasonality_params.knownFrequency, 2, 1000000)
},
selector: null
});
}
break;
case 'settings_model_params':
objectEnumeration.push({
objectName: objectName,
properties: {
maxp: this.settings_model_params.maxp,
maxd: this.settings_model_params.maxd,
maxq: this.settings_model_params.maxq
},
});
if (this.settings_seasonality_params.show) {
objectEnumeration.push({
objectName: objectName,
properties: {
maxP: this.settings_model_params.maxP,
maxD: this.settings_model_params.maxD,
maxQ: this.settings_model_params.maxQ
},
});
}
objectEnumeration.push({
objectName: objectName,
properties: {
allowDrift: this.settings_model_params.allowDrift,
allowMean: this.settings_model_params.allowMean,
boxCoxTransform: this.settings_model_params.boxCoxTransform,
},
});
if (this.settings_model_params.boxCoxTransform == "manual") {
objectEnumeration.push({
objectName: objectName,
properties: {
lambda: inMinMax(this.settings_model_params.lambda, -1, 2)
},
});
}
objectEnumeration.push({
objectName: objectName,
properties: {
fullEnumeration: this.settings_model_params.fullEnumeration
},
selector: null
});
break;
case 'settings_userModel_params':
objectEnumeration.push({
objectName: objectName,
properties: {
show: this.settings_userModel_params.show,
p: this.settings_userModel_params.p,
d: this.settings_userModel_params.d,
q: this.settings_userModel_params.q
},
selector: null
});
if (this.settings_seasonality_params.show) {
objectEnumeration.push({
objectName: objectName,
properties: {
P: this.settings_userModel_params.P,
D: this.settings_userModel_params.D,
Q: this.settings_userModel_params.Q
},
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: this.settings_graph_params.weight
},
selector: null
});
break;
case 'settings_additional_params':
objectEnumeration.push({
objectName: objectName,
properties: {
show: this.settings_additional_params.show,
textSize: this.settings_additional_params.textSize,
textColor: this.settings_additional_params.textColor,
infoCriteria: this.settings_additional_params.infoCriteria
},
selector: null
});
break;
};
return objectEnumeration;
}
}
}

14
style/visual.less Normal file
Просмотреть файл

@ -0,0 +1,14 @@
.rcv_autoScaleImageContainer {
position: relative;
.rcv_autoScaleImage {
max-width: 100%;
max-height: 100%;
position: absolute;
top: 50%;
/* @noflip */
left: 50%;
transform: translateY(-50%) translateX(-50%);
-webkit-transform: translateY(-50%) translateX(-50%);
}
}

15
tsconfig.json Normal file
Просмотреть файл

@ -0,0 +1,15 @@
{
"compilerOptions": {
"allowJs": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "ES5",
"sourceMap": true,
"out": "./.tmp/build/visual.js"
},
"files": [
".api/v1.2.0/PowerBI-visuals.d.ts",
"src/objectEnumerationUtility.ts",
"src/visual.ts"
]
}