Timeline Slicer 2.0.0 (#58)
* 29222 Update npm dependencies * 29223 [Timeline] Update TS code to use ES2015 modules * 29223 [Timeline] Update TS code to use ES2015 modules. Migrate d3 drag behavior to d3 v5.x.x * 29455 [Timeline] Restore a filter from dataView.jsonFilters instead SemanticFilter * 29454 [Timeline] Migrate UTs to ES2015 modules * 29469 [Timeline] Get rid of Interactive Utils * 29470 [Timeline] Fix the broken UTs after converting to ES2015 * 29456 [Timeline] Integrate TSList via Webpack * 29499 [Timeline] Fix TSList issues. * 29549 Prepare a PR for Timeline v2.0.0 * 29557 Timeline 2.0.0 does not report coverage to coveralls * 29081 Migrate Timeline to API 2.2.0 and PBIVIZ 3.x.x * Turn on babel polyfill for IE11 * 29753 scroll position option breaks slicer
This commit is contained in:
Родитель
98379e1f29
Коммит
2a02083b5b
|
@ -0,0 +1,17 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
|
||||
[*.json]
|
||||
indent_size = 2
|
||||
|
||||
[*.less]
|
||||
indent_size = 4
|
||||
|
||||
[*.ts]
|
||||
indent_size = 4
|
|
@ -5,4 +5,7 @@ dist
|
|||
typings
|
||||
.api
|
||||
*.log
|
||||
coverage
|
||||
coverage
|
||||
webpack.statistics.html
|
||||
webpack.statistics.dev.html
|
||||
webpack.statistics.prod.html
|
||||
|
|
|
@ -10,9 +10,8 @@ addons:
|
|||
install:
|
||||
- npm install
|
||||
script:
|
||||
- npm run lint
|
||||
- npm run test
|
||||
after_success:
|
||||
- node node_modules/coveralls/bin/coveralls.js < coverage/lcov.info
|
||||
notifications:
|
||||
email: false
|
||||
email: false
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
{
|
||||
"editor.tabSize": 4,
|
||||
"editor.insertSpaces": true,
|
||||
"files.eol": "\n",
|
||||
"files.watcherExclude": {
|
||||
"**/.git/objects/**": true,
|
||||
"**/node_modules/**": true,
|
||||
|
@ -11,27 +8,26 @@
|
|||
".tmp": true
|
||||
},
|
||||
"search.exclude": {
|
||||
".tmp": true,
|
||||
"typings": true
|
||||
".tmp": true
|
||||
},
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": [
|
||||
"/pbiviz.json"
|
||||
"pbiviz.json"
|
||||
],
|
||||
"url": "./.api/v1.13.0/schema.pbiviz.json"
|
||||
"url": "./node_modules/powerbi-visuals-api/schema.pbiviz.json"
|
||||
},
|
||||
{
|
||||
"fileMatch": [
|
||||
"/capabilities.json"
|
||||
"capabilities.json"
|
||||
],
|
||||
"url": "./.api/v1.13.0/schema.capabilities.json"
|
||||
"url": "./node_modules/powerbi-visuals-api/schema.capabilities.json"
|
||||
},
|
||||
{
|
||||
"fileMatch": [
|
||||
"/dependencies.json"
|
||||
"dependencies.json"
|
||||
],
|
||||
"url": "./.api/v1.13.0/schema.dependencies.json"
|
||||
"url": "./node_modules/powerbi-visuals-api/schema.dependencies.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -1,3 +1,11 @@
|
|||
## 2.0.0
|
||||
* Updates API version to 2.3.0
|
||||
* Converts code to ES2015 syntax
|
||||
* Uses PBIVIZ 3.x.x
|
||||
* Gets rid of Interactive Utils
|
||||
* Uses jsonFilter to restore a filter
|
||||
* Updates TSLint flow and rules
|
||||
|
||||
## 1.10.3
|
||||
* FIX: unexpected exception if user clears selection
|
||||
|
||||
|
@ -43,7 +51,7 @@
|
|||
* Increased API version to 1.11.0
|
||||
|
||||
## 1.6.4
|
||||
* Fix issue with incorrect selection after granularity change
|
||||
* Fix issue with incorrect selection after granularity change
|
||||
* Increased minimum width of cells for weeks, months, quarters and years
|
||||
* Changed title of week granularity
|
||||
|
||||
|
@ -97,4 +105,4 @@
|
|||
* Fixed cross filtering selection
|
||||
|
||||
## 1.4.1
|
||||
* Fixed selection of "selected range + 1 day" issue
|
||||
* Fixed selection of "selected range + 1 day" issue
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const recursivePathToTests = 'test/**/*.ts';
|
||||
const srcRecursivePath = '.tmp/drop/visual.js';
|
||||
const srcCssRecursivePath = '.tmp/drop/visual.css';
|
||||
const srcOriginalRecursivePath = 'src/**/*.ts';
|
||||
const coverageFolder = 'coverage';
|
||||
|
||||
process.env.CHROME_BIN = require('puppeteer').executablePath();
|
||||
|
||||
module.exports = (config) => {
|
||||
config.set({
|
||||
browsers: ['ChromeHeadless'],
|
||||
colors: true,
|
||||
frameworks: ['jasmine'],
|
||||
reporters: [
|
||||
'progress',
|
||||
'coverage',
|
||||
'karma-remap-istanbul'
|
||||
],
|
||||
singleRun: true,
|
||||
files: [
|
||||
srcCssRecursivePath,
|
||||
srcRecursivePath,
|
||||
'node_modules/lodash/lodash.min.js',
|
||||
'node_modules/jquery/dist/jquery.min.js',
|
||||
'node_modules/powerbi-visuals-utils-testutils/lib/index.js',
|
||||
'node_modules/jasmine-jquery/lib/jasmine-jquery.js',
|
||||
{
|
||||
pattern: './capabilities.json',
|
||||
watched: false,
|
||||
served: true,
|
||||
included: false
|
||||
},
|
||||
recursivePathToTests,
|
||||
{
|
||||
pattern: srcOriginalRecursivePath,
|
||||
included: false,
|
||||
served: true
|
||||
}
|
||||
],
|
||||
preprocessors: {
|
||||
[recursivePathToTests]: ['typescript'],
|
||||
[srcRecursivePath]: ['sourcemap', 'coverage']
|
||||
},
|
||||
typescriptPreprocessor: {
|
||||
options: {
|
||||
sourceMap: false,
|
||||
target: 'ES5',
|
||||
removeComments: false,
|
||||
concatenateOutput: false
|
||||
}
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: coverageFolder,
|
||||
reporters: [{
|
||||
type: 'html'
|
||||
},
|
||||
{
|
||||
type: 'lcov'
|
||||
}
|
||||
]
|
||||
},
|
||||
remapIstanbulReporter: {
|
||||
reports: {
|
||||
lcovonly: coverageFolder + '/lcov.info',
|
||||
html: coverageFolder,
|
||||
'text-summary': null
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
process.env.CHROME_BIN = require("puppeteer").executablePath();
|
||||
|
||||
const path = require("path");
|
||||
|
||||
const webpackConfig = require("./test.webpack.config.js");
|
||||
const tsconfig = require("./tsconfig.json");
|
||||
|
||||
import { Config, ConfigOptions } from "karma";
|
||||
|
||||
const testRecursivePath = "test/*.test.ts";
|
||||
const coverageFolder = "coverage";
|
||||
|
||||
module.exports = (config: Config) => {
|
||||
config.set({
|
||||
browsers: ["ChromeHeadless"],
|
||||
colors: true,
|
||||
coverageIstanbulReporter: {
|
||||
"combineBrowserReports": true,
|
||||
"dir": path.join(__dirname, coverageFolder),
|
||||
"fixWebpackSourcePaths": true,
|
||||
"report-config": {
|
||||
html: {
|
||||
subdir: "html-report",
|
||||
},
|
||||
},
|
||||
"reports": ["html", "lcovonly", "text-summary", "cobertura"],
|
||||
"verbose": false,
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: path.join(__dirname, coverageFolder),
|
||||
reporters: [
|
||||
{ type: "html", subdir: "html-report" },
|
||||
{ type: "lcov", subdir: "lcov" },
|
||||
{ type: "cobertura", subdir: ".", file: "cobertura-coverage.xml" },
|
||||
{ type: "lcovonly", subdir: ".", file: "report-lcovonly.txt" },
|
||||
{ type: "text-summary", subdir: ".", file: "text-summary.txt" },
|
||||
],
|
||||
},
|
||||
files: [
|
||||
testRecursivePath,
|
||||
],
|
||||
frameworks: ["jasmine"],
|
||||
junitReporter: {
|
||||
outputDir: path.join(__dirname, coverageFolder),
|
||||
outputFile: "TESTS-report.xml",
|
||||
useBrowserName: false,
|
||||
},
|
||||
mime: {
|
||||
"text/x-typescript": ["ts", "tsx"],
|
||||
},
|
||||
reporters: [
|
||||
"progress",
|
||||
"junit",
|
||||
"coverage-istanbul",
|
||||
],
|
||||
preprocessors: {
|
||||
[testRecursivePath]: ["webpack", "sourcemap"],
|
||||
},
|
||||
singleRun: true,
|
||||
typescriptPreprocessor: {
|
||||
options: tsconfig.compilerOptions,
|
||||
},
|
||||
webpack: webpackConfig,
|
||||
webpackMiddleware: {
|
||||
noInfo: true,
|
||||
},
|
||||
} as ConfigOptions);
|
||||
};
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
84
package.json
84
package.json
|
@ -1,23 +1,21 @@
|
|||
{
|
||||
"name": "powerbi-visuals-timeline",
|
||||
"version": "1.10.3",
|
||||
"version": "2.0.0",
|
||||
"description": "Timeline slicer is a graphical date range selector used as a filtering component in the report canvas",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Microsoft/powerbi-visuals-timeline.git"
|
||||
},
|
||||
"keywords": [
|
||||
"powerbi-visuals"
|
||||
"powerbi-visuals",
|
||||
"timeline"
|
||||
],
|
||||
"scripts": {
|
||||
"postinstall": "pbiviz update 1.13.0",
|
||||
"pbiviz": "pbiviz",
|
||||
"start": "pbiviz start",
|
||||
"package": "pbiviz package",
|
||||
"lint": "tslint -r \"node_modules/tslint-microsoft-contrib\" \"+(src|test)/**/*.ts\"",
|
||||
"pretest": "pbiviz package --resources --no-minify --no-pbiviz --no-plugin",
|
||||
"test": "karma start",
|
||||
"cert": "pbiviz --create-cert"
|
||||
"watch": "karma start --single-run=false"
|
||||
},
|
||||
"author": {
|
||||
"name": "Microsoft",
|
||||
|
@ -29,38 +27,50 @@
|
|||
},
|
||||
"homepage": "https://github.com/Microsoft/powerbi-visuals-timeline#readme",
|
||||
"devDependencies": {
|
||||
"@types/d3": "3.5.36",
|
||||
"@types/jasmine": "2.5.52",
|
||||
"@types/jasmine-jquery": "1.5.31",
|
||||
"@types/jquery": "2.0.46",
|
||||
"@types/lodash": "4.14.55",
|
||||
"coveralls": "3.0.2",
|
||||
"jasmine": "3.1.0",
|
||||
"jasmine-jquery": "2.1.1",
|
||||
"jquery": "3.2.1",
|
||||
"karma": "2.0.5",
|
||||
"karma-chrome-launcher": "2.2.0",
|
||||
"karma-coverage": "1.1.2",
|
||||
"karma-jasmine": "1.1.2",
|
||||
"karma-remap-istanbul": "0.6.0",
|
||||
"karma-sourcemap-loader": "0.3.7",
|
||||
"karma-typescript-preprocessor": "0.3.1",
|
||||
"lodash": "4.17.4",
|
||||
"powerbi-visuals-tools": "1.13.1",
|
||||
"powerbi-visuals-utils-testutils": "^1.2.1",
|
||||
"puppeteer": "^1.6.2",
|
||||
"tslint": "5.4.3",
|
||||
"tslint-microsoft-contrib": "5.0.0",
|
||||
"typescript": "2.3.4"
|
||||
"@babel/polyfill": "^7.0.0",
|
||||
"@types/d3": "^5.0.1",
|
||||
"@types/jasmine": "^3.3.0",
|
||||
"@types/jasmine-jquery": "^1.5.33",
|
||||
"@types/jquery": "^3.3.22",
|
||||
"@types/karma": "^3.0.0",
|
||||
"@types/lodash": "^4.14.118",
|
||||
"@types/node": "^10.12.8",
|
||||
"@types/puppeteer": "^1.10.0",
|
||||
"coveralls": "^3.0.2",
|
||||
"istanbul-instrumenter-loader": "^3.0.1",
|
||||
"jasmine": "^3.3.0",
|
||||
"jasmine-jquery": "^2.1.1",
|
||||
"jquery": "^3.3.1",
|
||||
"karma": "^3.1.1",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-coverage-istanbul-reporter": "^2.0.4",
|
||||
"karma-jasmine": "^1.1.2",
|
||||
"karma-junit-reporter": "^1.2.0",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-webpack": "^3.0.5",
|
||||
"less": "^3.8.1",
|
||||
"less-loader": "^4.1.0",
|
||||
"lodash": "^4.17.11",
|
||||
"powerbi-visuals-api": "^2.2.2",
|
||||
"powerbi-visuals-tools": "^3.0.9",
|
||||
"powerbi-visuals-utils-testutils": "^2.1.4",
|
||||
"puppeteer": "^1.10.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"ts-loader": "^5.3.0",
|
||||
"ts-node": "^7.0.1",
|
||||
"tslint": "^5.11.0",
|
||||
"tslint-loader": "^3.5.4",
|
||||
"tslint-microsoft-contrib": "^5.2.1",
|
||||
"typescript": "^3.1.6",
|
||||
"webpack": "^4.25.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"d3": "3.5.5",
|
||||
"powerbi-models": "1.0.8",
|
||||
"powerbi-visuals-utils-chartutils": "^1.7.0",
|
||||
"powerbi-visuals-utils-dataviewutils": "^1.4.1",
|
||||
"powerbi-visuals-utils-formattingutils": "^3.0.2",
|
||||
"powerbi-visuals-utils-interactivityutils": "^3.2.0",
|
||||
"powerbi-visuals-utils-svgutils": "^1.1.0",
|
||||
"powerbi-visuals-utils-typeutils": "^1.1.0"
|
||||
"d3": "^5.7.0",
|
||||
"powerbi-models": "^1.0.13",
|
||||
"powerbi-visuals-utils-chartutils": "^2.2.0",
|
||||
"powerbi-visuals-utils-dataviewutils": "^2.1.0",
|
||||
"powerbi-visuals-utils-formattingutils": "^4.1.1",
|
||||
"powerbi-visuals-utils-svgutils": "^2.1.0",
|
||||
"powerbi-visuals-utils-typeutils": "^2.1.0"
|
||||
}
|
||||
}
|
||||
|
|
63
pbiviz.json
63
pbiviz.json
|
@ -1,15 +1,14 @@
|
|||
{
|
||||
"visual": {
|
||||
"name": "Timeline",
|
||||
"displayName": "Timeline 1.10.3",
|
||||
"displayName": "Timeline 2.0.0",
|
||||
"guid": "Timeline1447991079100",
|
||||
"visualClassName": "Timeline",
|
||||
"version": "1.10.3",
|
||||
"version": "2.0.0",
|
||||
"description": "Timeline slicer is a graphical date range selector used as a filtering component in the report canvas",
|
||||
"supportUrl": "https://community.powerbi.com",
|
||||
"gitHubUrl": "https://github.com/Microsoft/powerbi-visuals-timeline"
|
||||
},
|
||||
"apiVersion": "1.13.0",
|
||||
"author": {
|
||||
"name": "Microsoft",
|
||||
"email": "pbicvsupport@microsoft.com"
|
||||
|
@ -17,61 +16,5 @@
|
|||
"assets": {
|
||||
"icon": "assets/icon.png"
|
||||
},
|
||||
"externalJS": [
|
||||
"node_modules/powerbi-models/dist/models.min.js",
|
||||
"node_modules/d3/d3.min.js",
|
||||
"node_modules/globalize/lib/globalize.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.ar-SA.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.bg-BG.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.ca-ES.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.cs-CZ.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.da-DK.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.de-DE.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.el-GR.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.en-US.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.es-ES.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.et-EE.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.eu-ES.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.fi-FI.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.fr-FR.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.gl-ES.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.he-IL.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.hi-IN.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.hr-HR.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.hu-HU.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.id-ID.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.it-IT.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.ja-JP.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.kk-KZ.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.ko-KR.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.lt-LT.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.lv-LV.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.ms-MY.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.nb-NO.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.nl-NL.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.pl-PL.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.pt-BR.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.pt-PT.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.ro-RO.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.ru-RU.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.sk-SK.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.sl-SI.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.sr-Cyrl-RS.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.sr-Latn-RS.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.sv-SE.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.th-TH.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.tr-TR.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.uk-UA.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.vi-VN.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.zh-CN.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.zh-TW.js",
|
||||
"node_modules/powerbi-visuals-utils-typeutils/lib/index.js",
|
||||
"node_modules/powerbi-visuals-utils-svgutils/lib/index.js",
|
||||
"node_modules/powerbi-visuals-utils-dataviewutils/lib/index.js",
|
||||
"node_modules/powerbi-visuals-utils-formattingutils/lib/index.js",
|
||||
"node_modules/powerbi-visuals-utils-interactivityutils/lib/index.js",
|
||||
"node_modules/powerbi-visuals-utils-chartutils/lib/index.js"
|
||||
],
|
||||
"style": "style/visual.less",
|
||||
"capabilities": "capabilities.json"
|
||||
}
|
||||
}
|
||||
|
|
282
src/calendar.ts
282
src/calendar.ts
|
@ -24,169 +24,165 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual {
|
||||
// granularity
|
||||
import TimelineGranularityData = granularity.TimelineGranularityData;
|
||||
import { TimelineGranularityData } from "./granularity/granularityData";
|
||||
|
||||
// settings
|
||||
import WeekDaySettings = settings.WeekDaySettings;
|
||||
import CalendarSettings = settings.CalendarSettings;
|
||||
import { CalendarSettings } from "./settings/calendarSettings";
|
||||
import { WeekDaySettings } from "./settings/weekDaySettings";
|
||||
|
||||
interface DateDictionary {
|
||||
[year: number]: Date;
|
||||
interface IDateDictionary {
|
||||
[year: number]: Date;
|
||||
}
|
||||
|
||||
export interface IPeriodDates {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
}
|
||||
|
||||
export class Calendar {
|
||||
private static QuarterFirstMonths: number[] = [0, 3, 6, 9];
|
||||
|
||||
private firstDayOfWeek: number;
|
||||
private firstMonthOfYear: number;
|
||||
private firstDayOfYear: number;
|
||||
private dateOfFirstWeek: IDateDictionary;
|
||||
private dateOfFirstFullWeek: IDateDictionary;
|
||||
private quarterFirstMonths: number[];
|
||||
private isDaySelection: boolean;
|
||||
|
||||
constructor(
|
||||
calendarFormat: CalendarSettings,
|
||||
weekDaySettings: WeekDaySettings) {
|
||||
|
||||
this.isDaySelection = weekDaySettings.daySelection;
|
||||
this.firstDayOfWeek = weekDaySettings.day;
|
||||
this.firstMonthOfYear = calendarFormat.month;
|
||||
this.firstDayOfYear = calendarFormat.day;
|
||||
|
||||
this.dateOfFirstWeek = {};
|
||||
this.dateOfFirstFullWeek = {};
|
||||
|
||||
this.quarterFirstMonths = Calendar.QuarterFirstMonths.map((monthIndex: number) => {
|
||||
return monthIndex + this.firstMonthOfYear;
|
||||
});
|
||||
}
|
||||
|
||||
export interface PeriodDates {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
public getFirstDayOfWeek(): number {
|
||||
return this.firstDayOfWeek;
|
||||
}
|
||||
|
||||
export class Calendar {
|
||||
private static QuarterFirstMonths: number[] = [0, 3, 6, 9];
|
||||
public getFirstMonthOfYear(): number {
|
||||
return this.firstMonthOfYear;
|
||||
}
|
||||
|
||||
private firstDayOfWeek: number;
|
||||
private firstMonthOfYear: number;
|
||||
private firstDayOfYear: number;
|
||||
private dateOfFirstWeek: DateDictionary;
|
||||
private dateOfFirstFullWeek: DateDictionary;
|
||||
private quarterFirstMonths: number[];
|
||||
private isDaySelection: boolean;
|
||||
public getFirstDayOfYear(): number {
|
||||
return this.firstDayOfYear;
|
||||
}
|
||||
|
||||
public getFirstDayOfWeek(): number {
|
||||
return this.firstDayOfWeek;
|
||||
public getNextDate(date: Date): Date {
|
||||
return TimelineGranularityData.nextDay(date);
|
||||
}
|
||||
|
||||
public getWeekPeriod(date: Date): IPeriodDates {
|
||||
const year: number = date.getFullYear();
|
||||
const month: number = date.getMonth();
|
||||
const dayOfWeek: number = date.getDay();
|
||||
|
||||
const weekDay = this.isDaySelection
|
||||
? this.firstDayOfWeek
|
||||
: new Date(year, this.firstMonthOfYear, this.firstDayOfYear).getDay();
|
||||
|
||||
let deltaDays: number = 0;
|
||||
if (weekDay !== dayOfWeek) {
|
||||
deltaDays = dayOfWeek - weekDay;
|
||||
}
|
||||
|
||||
public getFirstMonthOfYear(): number {
|
||||
return this.firstMonthOfYear;
|
||||
if (deltaDays < 0) {
|
||||
deltaDays = 7 + deltaDays;
|
||||
}
|
||||
|
||||
public getFirstDayOfYear(): number {
|
||||
return this.firstDayOfYear;
|
||||
const daysToWeekEnd = (7 - deltaDays);
|
||||
const startDate = new Date(year, month, date.getDate() - deltaDays);
|
||||
const endDate = new Date(year, month, date.getDate() + daysToWeekEnd);
|
||||
|
||||
return { startDate, endDate };
|
||||
}
|
||||
|
||||
public getQuarterIndex(date: Date): number {
|
||||
return Math.floor(date.getMonth() / 3);
|
||||
}
|
||||
|
||||
public getQuarterStartDate(year: number, quarterIndex: number): Date {
|
||||
return new Date(year, this.quarterFirstMonths[quarterIndex], this.firstDayOfYear);
|
||||
}
|
||||
|
||||
public getQuarterEndDate(date: Date): Date {
|
||||
return new Date(date.getFullYear(), date.getMonth() + 3, this.firstDayOfYear);
|
||||
}
|
||||
|
||||
public getQuarterPeriod(date: Date): IPeriodDates {
|
||||
const quarterIndex = this.getQuarterIndex(date);
|
||||
|
||||
const startDate: Date = this.getQuarterStartDate(date.getFullYear(), quarterIndex);
|
||||
const endDate: Date = this.getQuarterEndDate(startDate);
|
||||
|
||||
return { startDate, endDate };
|
||||
}
|
||||
|
||||
public getMonthPeriod(date: Date): IPeriodDates {
|
||||
const year: number = date.getFullYear();
|
||||
const month: number = date.getMonth();
|
||||
|
||||
const startDate: Date = new Date(year, month, this.firstDayOfYear);
|
||||
const endDate: Date = new Date(year, month + 1, this.firstDayOfYear);
|
||||
|
||||
return { startDate, endDate };
|
||||
}
|
||||
|
||||
public getYearPeriod(date: Date): IPeriodDates {
|
||||
const year: number = date.getFullYear();
|
||||
|
||||
const startDate: Date = new Date(year, this.firstMonthOfYear, this.firstDayOfYear);
|
||||
const endDate: Date = new Date(year + 1, this.firstMonthOfYear, this.firstDayOfYear);
|
||||
|
||||
return { startDate, endDate };
|
||||
}
|
||||
|
||||
public isChanged(
|
||||
calendarSettings: CalendarSettings,
|
||||
weekDaySettings: WeekDaySettings): boolean {
|
||||
|
||||
return this.firstMonthOfYear !== calendarSettings.month
|
||||
|| this.firstDayOfYear !== calendarSettings.day
|
||||
|| this.firstDayOfWeek !== weekDaySettings.day;
|
||||
}
|
||||
|
||||
public getDateOfFirstWeek(year: number): Date {
|
||||
if (!this.dateOfFirstWeek[year]) {
|
||||
this.dateOfFirstWeek[year] = new Date(year, this.firstMonthOfYear, this.firstDayOfYear);
|
||||
}
|
||||
|
||||
public getNextDate(date: Date): Date {
|
||||
return TimelineGranularityData.nextDay(date);
|
||||
return this.dateOfFirstWeek[year];
|
||||
}
|
||||
|
||||
public getDateOfFirstFullWeek(year: number): Date {
|
||||
if (!this.dateOfFirstFullWeek[year]) {
|
||||
this.dateOfFirstFullWeek[year] = this.calculateDateOfFirstFullWeek(year);
|
||||
}
|
||||
|
||||
public getWeekPeriod(date: Date): PeriodDates {
|
||||
const year: number = date.getFullYear();
|
||||
const month: number = date.getMonth();
|
||||
const dayOfWeek: number = date.getDay();
|
||||
return this.dateOfFirstFullWeek[year];
|
||||
}
|
||||
|
||||
let weekDay = this.isDaySelection ?
|
||||
this.firstDayOfWeek :
|
||||
new Date(year, this.firstMonthOfYear, this.firstDayOfYear).getDay();
|
||||
private calculateDateOfFirstFullWeek(year: number): Date {
|
||||
let date: Date = new Date(year, this.firstMonthOfYear, this.firstDayOfYear);
|
||||
|
||||
let deltaDays: number = 0;
|
||||
if (weekDay !== dayOfWeek) {
|
||||
deltaDays = dayOfWeek - weekDay;
|
||||
}
|
||||
const weekDay = this.isDaySelection
|
||||
? this.firstDayOfWeek
|
||||
: new Date(year, this.firstMonthOfYear, this.firstDayOfYear).getDay();
|
||||
|
||||
if (deltaDays < 0) {
|
||||
deltaDays = 7 + deltaDays;
|
||||
}
|
||||
|
||||
const daysToWeekEnd = (7 - deltaDays);
|
||||
const startDate = new Date(year, month, date.getDate() - deltaDays);
|
||||
const endDate = new Date(year, month, date.getDate() + daysToWeekEnd);
|
||||
|
||||
return { startDate, endDate };
|
||||
while (date.getDay() !== weekDay) {
|
||||
date = TimelineGranularityData.nextDay(date);
|
||||
}
|
||||
|
||||
public getQuarterIndex(date: Date): number {
|
||||
return Math.floor(date.getMonth() / 3);
|
||||
}
|
||||
|
||||
public getQuarterStartDate(year: number, quarterIndex: number): Date {
|
||||
return new Date(year, this.quarterFirstMonths[quarterIndex], this.firstDayOfYear);
|
||||
}
|
||||
|
||||
public getQuarterEndDate(date: Date): Date {
|
||||
return new Date(date.getFullYear(), date.getMonth() + 3, this.firstDayOfYear);
|
||||
}
|
||||
|
||||
public getQuarterPeriod(date: Date): PeriodDates {
|
||||
const quarterIndex = this.getQuarterIndex(date);
|
||||
|
||||
let startDate: Date = this.getQuarterStartDate(date.getFullYear(), quarterIndex);
|
||||
let endDate: Date = this.getQuarterEndDate(startDate);
|
||||
|
||||
return { startDate, endDate };
|
||||
}
|
||||
|
||||
public getMonthPeriod(date: Date): PeriodDates {
|
||||
const year: number = date.getFullYear();
|
||||
const month: number = date.getMonth();
|
||||
|
||||
let startDate: Date = new Date(year, month, this.firstDayOfYear);
|
||||
let endDate: Date = new Date(year, month + 1, this.firstDayOfYear);
|
||||
|
||||
return { startDate, endDate };
|
||||
}
|
||||
|
||||
public getYearPeriod(date: Date): PeriodDates {
|
||||
const year: number = date.getFullYear();
|
||||
|
||||
let startDate: Date = new Date(year, this.firstMonthOfYear, this.firstDayOfYear);
|
||||
let endDate: Date = new Date(year + 1, this.firstMonthOfYear, this.firstDayOfYear);
|
||||
|
||||
return { startDate, endDate };
|
||||
}
|
||||
|
||||
public isChanged(
|
||||
calendarSettings: CalendarSettings,
|
||||
weekDaySettings: WeekDaySettings): boolean {
|
||||
|
||||
return this.firstMonthOfYear !== calendarSettings.month
|
||||
|| this.firstDayOfYear !== calendarSettings.day
|
||||
|| this.firstDayOfWeek !== weekDaySettings.day;
|
||||
}
|
||||
|
||||
constructor(
|
||||
calendarFormat: CalendarSettings,
|
||||
weekDaySettings: WeekDaySettings) {
|
||||
|
||||
this.isDaySelection = weekDaySettings.daySelection;
|
||||
this.firstDayOfWeek = weekDaySettings.day;
|
||||
this.firstMonthOfYear = calendarFormat.month;
|
||||
this.firstDayOfYear = calendarFormat.day;
|
||||
|
||||
this.dateOfFirstWeek = {};
|
||||
this.dateOfFirstFullWeek = {};
|
||||
|
||||
this.quarterFirstMonths = Calendar.QuarterFirstMonths.map((monthIndex: number) => {
|
||||
return monthIndex + this.firstMonthOfYear;
|
||||
});
|
||||
}
|
||||
|
||||
private calculateDateOfFirstFullWeek(year: number): Date {
|
||||
let date: Date = new Date(year, this.firstMonthOfYear, this.firstDayOfYear);
|
||||
|
||||
let weekDay = this.isDaySelection ?
|
||||
this.firstDayOfWeek :
|
||||
new Date(year, this.firstMonthOfYear, this.firstDayOfYear).getDay();
|
||||
|
||||
while (date.getDay() !== weekDay) {
|
||||
date = TimelineGranularityData.nextDay(date);
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
public getDateOfFirstWeek(year: number): Date {
|
||||
if (!this.dateOfFirstWeek[year]) {
|
||||
this.dateOfFirstWeek[year] = new Date(year, this.firstMonthOfYear, this.firstDayOfYear);
|
||||
}
|
||||
|
||||
return this.dateOfFirstWeek[year];
|
||||
}
|
||||
|
||||
public getDateOfFirstFullWeek(year: number): Date {
|
||||
if (!this.dateOfFirstFullWeek[year]) {
|
||||
this.dateOfFirstFullWeek[year] = this.calculateDateOfFirstFullWeek(year);
|
||||
}
|
||||
|
||||
return this.dateOfFirstFullWeek[year];
|
||||
}
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,115 +24,111 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual {
|
||||
// powerbi.extensibility.utils.svg
|
||||
import ClassAndSelector = powerbi.extensibility.utils.svg.CssConstants.ClassAndSelector;
|
||||
import { CssConstants } from "powerbi-visuals-utils-svgutils";
|
||||
|
||||
// granularity
|
||||
import Granularity = granularity.Granularity;
|
||||
import { IFilterColumnTarget } from "powerbi-models";
|
||||
|
||||
// datePeriod
|
||||
import TimelineDatePeriod = datePeriod.TimelineDatePeriod;
|
||||
import { ITimelineDatePeriod } from "./datePeriod/datePeriod";
|
||||
import { IGranularity } from "./granularity/granularity";
|
||||
|
||||
export interface TimelineMargins {
|
||||
LeftMargin: number;
|
||||
RightMargin: number;
|
||||
TopMargin: number;
|
||||
BottomMargin: number;
|
||||
CellWidth: number;
|
||||
CellHeight: number;
|
||||
StartXpoint: number;
|
||||
StartYpoint: number;
|
||||
ElementWidth: number;
|
||||
MinCellWidth: number;
|
||||
MinCellHeight: number;
|
||||
MaxCellHeight: number;
|
||||
PeriodSlicerRectWidth: number;
|
||||
PeriodSlicerRectHeight: number;
|
||||
LegendHeight: number;
|
||||
LegendHeightOffset: number;
|
||||
HeightOffset: number;
|
||||
}
|
||||
|
||||
export interface TimelineSelectors {
|
||||
TimelineVisual: ClassAndSelector;
|
||||
TimelineWrapper: ClassAndSelector;
|
||||
SelectionRangeContainer: ClassAndSelector;
|
||||
textLabel: ClassAndSelector;
|
||||
LowerTextCell: ClassAndSelector;
|
||||
UpperTextCell: ClassAndSelector;
|
||||
UpperTextArea: ClassAndSelector;
|
||||
LowerTextArea: ClassAndSelector;
|
||||
RangeTextArea: ClassAndSelector;
|
||||
CellsArea: ClassAndSelector;
|
||||
CursorsArea: ClassAndSelector;
|
||||
MainArea: ClassAndSelector;
|
||||
SelectionCursor: ClassAndSelector;
|
||||
Cell: ClassAndSelector;
|
||||
CellRect: ClassAndSelector;
|
||||
TimelineSlicer: ClassAndSelector;
|
||||
PeriodSlicerGranularities: ClassAndSelector;
|
||||
PeriodSlicerSelection: ClassAndSelector;
|
||||
PeriodSlicerSelectionRect: ClassAndSelector;
|
||||
PeriodSlicerRect: ClassAndSelector;
|
||||
}
|
||||
|
||||
export interface TimelineLabel {
|
||||
title: string;
|
||||
text: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface ExtendedLabel {
|
||||
yearLabels?: TimelineLabel[];
|
||||
quarterLabels?: TimelineLabel[];
|
||||
monthLabels?: TimelineLabel[];
|
||||
weekLabels?: TimelineLabel[];
|
||||
dayLabels?: TimelineLabel[];
|
||||
}
|
||||
|
||||
export interface ITimelineJSONDatePeriod {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
}
|
||||
|
||||
export interface TimelineCursorOverElement {
|
||||
index: number;
|
||||
datapoint: TimelineDatapoint;
|
||||
}
|
||||
|
||||
export interface TimelineProperties {
|
||||
leftMargin: number;
|
||||
rightMargin: number;
|
||||
topMargin: number;
|
||||
bottomMargin: number;
|
||||
textYPosition: number;
|
||||
startXpoint: number;
|
||||
startYpoint: number;
|
||||
elementWidth: number;
|
||||
cellWidth: number;
|
||||
cellHeight: number;
|
||||
cellsYPosition: number;
|
||||
}
|
||||
|
||||
export interface TimelineData {
|
||||
filterColumnTarget?: IFilterColumnTarget;
|
||||
timelineDatapoints?: TimelineDatapoint[];
|
||||
selectionStartIndex?: number;
|
||||
selectionEndIndex?: number;
|
||||
cursorDataPoints?: CursorDatapoint[];
|
||||
currentGranularity?: Granularity;
|
||||
}
|
||||
|
||||
export interface CursorDatapoint {
|
||||
x: number;
|
||||
y: number;
|
||||
cursorIndex: number;
|
||||
selectionIndex: number;
|
||||
}
|
||||
|
||||
export interface TimelineDatapoint {
|
||||
index: number;
|
||||
datePeriod: TimelineDatePeriod;
|
||||
}
|
||||
export interface ITimelineMargins {
|
||||
LeftMargin: number;
|
||||
RightMargin: number;
|
||||
TopMargin: number;
|
||||
BottomMargin: number;
|
||||
CellWidth: number;
|
||||
CellHeight: number;
|
||||
StartXpoint: number;
|
||||
StartYpoint: number;
|
||||
ElementWidth: number;
|
||||
MinCellWidth: number;
|
||||
MinCellHeight: number;
|
||||
MaxCellHeight: number;
|
||||
PeriodSlicerRectWidth: number;
|
||||
PeriodSlicerRectHeight: number;
|
||||
LegendHeight: number;
|
||||
LegendHeightOffset: number;
|
||||
HeightOffset: number;
|
||||
}
|
||||
|
||||
export interface ITimelineSelectors {
|
||||
Cell: CssConstants.ClassAndSelector;
|
||||
CellRect: CssConstants.ClassAndSelector;
|
||||
CellsArea: CssConstants.ClassAndSelector;
|
||||
CursorsArea: CssConstants.ClassAndSelector;
|
||||
LowerTextArea: CssConstants.ClassAndSelector;
|
||||
LowerTextCell: CssConstants.ClassAndSelector;
|
||||
MainArea: CssConstants.ClassAndSelector;
|
||||
PeriodSlicerGranularities: CssConstants.ClassAndSelector;
|
||||
PeriodSlicerRect: CssConstants.ClassAndSelector;
|
||||
PeriodSlicerSelection: CssConstants.ClassAndSelector;
|
||||
PeriodSlicerSelectionRect: CssConstants.ClassAndSelector;
|
||||
RangeTextArea: CssConstants.ClassAndSelector;
|
||||
SelectionCursor: CssConstants.ClassAndSelector;
|
||||
SelectionRangeContainer: CssConstants.ClassAndSelector;
|
||||
TextLabel: CssConstants.ClassAndSelector;
|
||||
TimelineSlicer: CssConstants.ClassAndSelector;
|
||||
TimelineVisual: CssConstants.ClassAndSelector;
|
||||
TimelineWrapper: CssConstants.ClassAndSelector;
|
||||
UpperTextArea: CssConstants.ClassAndSelector;
|
||||
UpperTextCell: CssConstants.ClassAndSelector;
|
||||
}
|
||||
|
||||
export interface ITimelineLabel {
|
||||
title: string;
|
||||
text: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface IExtendedLabel {
|
||||
yearLabels?: ITimelineLabel[];
|
||||
quarterLabels?: ITimelineLabel[];
|
||||
monthLabels?: ITimelineLabel[];
|
||||
weekLabels?: ITimelineLabel[];
|
||||
dayLabels?: ITimelineLabel[];
|
||||
}
|
||||
|
||||
export interface ITimelineJSONDatePeriod {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
}
|
||||
|
||||
export interface ITimelineCursorOverElement {
|
||||
index: number;
|
||||
datapoint: ITimelineDataPoint;
|
||||
}
|
||||
|
||||
export interface ITimelineProperties {
|
||||
leftMargin: number;
|
||||
rightMargin: number;
|
||||
topMargin: number;
|
||||
bottomMargin: number;
|
||||
textYPosition: number;
|
||||
startXpoint: number;
|
||||
startYpoint: number;
|
||||
elementWidth: number;
|
||||
cellWidth: number;
|
||||
cellHeight: number;
|
||||
cellsYPosition: number;
|
||||
}
|
||||
|
||||
export interface ITimelineData {
|
||||
filterColumnTarget?: IFilterColumnTarget;
|
||||
timelineDataPoints?: ITimelineDataPoint[];
|
||||
selectionStartIndex?: number;
|
||||
selectionEndIndex?: number;
|
||||
cursorDataPoints?: ICursorDataPoint[];
|
||||
currentGranularity?: IGranularity;
|
||||
}
|
||||
|
||||
export interface ICursorDataPoint {
|
||||
x: number;
|
||||
y: number;
|
||||
cursorIndex: number;
|
||||
selectionIndex: number;
|
||||
}
|
||||
|
||||
export interface ITimelineDataPoint {
|
||||
index: number;
|
||||
datePeriod: ITimelineDatePeriod;
|
||||
}
|
||||
|
|
|
@ -24,17 +24,15 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual.datePeriod {
|
||||
export interface ITimelineDatePeriod {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
}
|
||||
|
||||
export interface TimelineDatePeriod extends ITimelineDatePeriod {
|
||||
identifierArray: (string | number)[];
|
||||
year: number;
|
||||
week: number[];
|
||||
fraction: number;
|
||||
index: number;
|
||||
}
|
||||
export interface ITimelineDatePeriodBase {
|
||||
endDate: Date;
|
||||
startDate: Date;
|
||||
}
|
||||
|
||||
export interface ITimelineDatePeriod extends ITimelineDatePeriodBase {
|
||||
fraction: number;
|
||||
identifierArray: Array<string | number>;
|
||||
index: number;
|
||||
week: number[];
|
||||
year: number;
|
||||
}
|
||||
|
|
|
@ -24,51 +24,52 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual.datePeriod {
|
||||
// utils
|
||||
import Utils = utils.Utils;
|
||||
import { ITimelineJSONDatePeriod } from "../dataInterfaces";
|
||||
import { Utils } from "../utils";
|
||||
import { ITimelineDatePeriodBase } from "./datePeriod";
|
||||
|
||||
export class TimelineDatePeriodBase implements ITimelineDatePeriod {
|
||||
public startDate: Date = null;
|
||||
public endDate: Date = null;
|
||||
export class TimelineDatePeriodBase implements ITimelineDatePeriodBase {
|
||||
public static parse(jsonString: string): TimelineDatePeriodBase {
|
||||
let datePeriod: ITimelineJSONDatePeriod;
|
||||
let startDate: Date = null;
|
||||
let endDate: Date = null;
|
||||
|
||||
public static parse(jsonString: string): TimelineDatePeriodBase {
|
||||
let datePeriod: ITimelineJSONDatePeriod,
|
||||
startDate: Date = null,
|
||||
endDate: Date = null;
|
||||
|
||||
try {
|
||||
datePeriod = JSON.parse(jsonString);
|
||||
} finally { }
|
||||
|
||||
if (datePeriod) {
|
||||
startDate = Utils.parseDateWithoutTimezone(datePeriod.startDate);
|
||||
endDate = Utils.parseDateWithoutTimezone(datePeriod.endDate);
|
||||
}
|
||||
|
||||
return TimelineDatePeriodBase.create(startDate, endDate);
|
||||
try {
|
||||
datePeriod = JSON.parse(jsonString);
|
||||
} catch (_) {
|
||||
datePeriod = null;
|
||||
}
|
||||
|
||||
public static create(startDate: Date, endDate: Date): TimelineDatePeriodBase {
|
||||
return new TimelineDatePeriodBase(startDate, endDate);
|
||||
if (datePeriod) {
|
||||
startDate = Utils.parseDateWithoutTimezone(datePeriod.startDate);
|
||||
endDate = Utils.parseDateWithoutTimezone(datePeriod.endDate);
|
||||
}
|
||||
|
||||
public static createEmpty(): TimelineDatePeriodBase {
|
||||
return TimelineDatePeriodBase.create(null, null);
|
||||
}
|
||||
return TimelineDatePeriodBase.create(startDate, endDate);
|
||||
}
|
||||
|
||||
constructor(startDate: Date, endDate: Date) {
|
||||
this.startDate = startDate;
|
||||
this.endDate = endDate;
|
||||
}
|
||||
public static create(startDate: Date, endDate: Date): TimelineDatePeriodBase {
|
||||
return new TimelineDatePeriodBase(startDate, endDate);
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
let jsonDatePeriod: ITimelineJSONDatePeriod = {
|
||||
startDate: Utils.toStringDateWithoutTimezone(this.startDate),
|
||||
endDate: Utils.toStringDateWithoutTimezone(this.endDate)
|
||||
};
|
||||
public static createEmpty(): TimelineDatePeriodBase {
|
||||
return TimelineDatePeriodBase.create(null, null);
|
||||
}
|
||||
|
||||
return JSON.stringify(jsonDatePeriod);
|
||||
}
|
||||
public startDate: Date = null;
|
||||
public endDate: Date = null;
|
||||
|
||||
constructor(startDate: Date, endDate: Date) {
|
||||
this.startDate = startDate;
|
||||
this.endDate = endDate;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
const jsonDatePeriod: ITimelineJSONDatePeriod = {
|
||||
endDate: Utils.toStringDateWithoutTimezone(this.endDate),
|
||||
startDate: Utils.toStringDateWithoutTimezone(this.startDate),
|
||||
};
|
||||
|
||||
return JSON.stringify(jsonDatePeriod);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,50 +24,52 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual.granularity {
|
||||
// datePeriod
|
||||
import TimelineDatePeriod = datePeriod.TimelineDatePeriod;
|
||||
// utils
|
||||
import Utils = utils.Utils;
|
||||
import Selection = d3.Selection;
|
||||
import { Selection } from "d3-selection";
|
||||
|
||||
export class DayGranularity extends TimelineGranularityBase {
|
||||
constructor(calendar: Calendar, locale: string) {
|
||||
super(calendar, locale, Utils.getGranularityPropsByMarker("D"));
|
||||
import { Calendar } from "../calendar";
|
||||
import { ITimelineLabel } from "../dataInterfaces";
|
||||
import { ITimelineDatePeriod } from "../datePeriod/datePeriod";
|
||||
import { Utils } from "../utils";
|
||||
import { TimelineGranularityBase } from "./granularityBase";
|
||||
import { IGranularityRenderProps } from "./granularityRenderProps";
|
||||
import { GranularityType } from "./granularityType";
|
||||
|
||||
export class DayGranularity extends TimelineGranularityBase {
|
||||
constructor(calendar: Calendar, locale: string) {
|
||||
super(calendar, locale, Utils.getGranularityPropsByMarker("D"));
|
||||
}
|
||||
|
||||
public render(props: IGranularityRenderProps, isFirst: boolean): Selection<any, any, any, any> {
|
||||
if (!props.granularSettings.granularityDayVisibility) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public render(props: GranularityRenderProps, isFirst: boolean): Selection<any> {
|
||||
if (!props.granularSettings.granularityDayVisibility) {
|
||||
return null;
|
||||
}
|
||||
return super.render(props, isFirst);
|
||||
}
|
||||
|
||||
return super.render(props, isFirst);
|
||||
}
|
||||
public getType(): GranularityType {
|
||||
return GranularityType.day;
|
||||
}
|
||||
|
||||
public getType(): GranularityType {
|
||||
return GranularityType.day;
|
||||
}
|
||||
public splitDate(date: Date): Array<string | number> {
|
||||
return [
|
||||
this.shortMonthName(date),
|
||||
date.getDate(),
|
||||
this.determineYear(date),
|
||||
];
|
||||
}
|
||||
|
||||
public splitDate(date: Date): (string | number)[] {
|
||||
return [
|
||||
this.shortMonthName(date),
|
||||
date.getDate(),
|
||||
this.determineYear(date)
|
||||
];
|
||||
}
|
||||
public sameLabel(firstDatePeriod: ITimelineDatePeriod, secondDatePeriod: ITimelineDatePeriod): boolean {
|
||||
return firstDatePeriod.startDate.getTime() === secondDatePeriod.startDate.getTime();
|
||||
}
|
||||
|
||||
public sameLabel(firstDatePeriod: TimelineDatePeriod, secondDatePeriod: TimelineDatePeriod): boolean {
|
||||
return firstDatePeriod.startDate.getTime() === secondDatePeriod.startDate.getTime();
|
||||
}
|
||||
public generateLabel(datePeriod: ITimelineDatePeriod): ITimelineLabel {
|
||||
const title: string = `${this.shortMonthName(datePeriod.startDate)} ${datePeriod.startDate.getDate()} - ${datePeriod.year}`;
|
||||
|
||||
public generateLabel(datePeriod: TimelineDatePeriod): TimelineLabel {
|
||||
const title: string = `${this.shortMonthName(datePeriod.startDate)} ${datePeriod.startDate.getDate()} - ${datePeriod.year}`;
|
||||
|
||||
return {
|
||||
title,
|
||||
text: datePeriod.startDate.getDate().toLocaleString(),
|
||||
id: datePeriod.index
|
||||
};
|
||||
}
|
||||
return {
|
||||
id: datePeriod.index,
|
||||
text: datePeriod.startDate.getDate().toLocaleString(),
|
||||
title,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,28 +24,33 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual.granularity {
|
||||
// datePeriod
|
||||
import TimelineDatePeriod = datePeriod.TimelineDatePeriod;
|
||||
import Selection = d3.Selection;
|
||||
import { Selection } from "d3-selection";
|
||||
|
||||
export interface Granularity {
|
||||
getType?(): GranularityType;
|
||||
splitDate(date: Date): (string | number)[];
|
||||
getDatePeriods(): TimelineDatePeriod[];
|
||||
resetDatePeriods(): void;
|
||||
getExtendedLabel(): ExtendedLabel;
|
||||
setExtendedLabel(extendedLabel: ExtendedLabel): void;
|
||||
createLabels(granularity: Granularity): TimelineLabel[];
|
||||
sameLabel?(firstDatePeriod: TimelineDatePeriod, secondDatePeriod: TimelineDatePeriod): boolean;
|
||||
generateLabel?(datePeriod: TimelineDatePeriod): TimelineLabel;
|
||||
addDate(date: Date);
|
||||
setNewEndDate(date: Date): void;
|
||||
splitPeriod(index: number, newFraction: number, newDate: Date): void;
|
||||
splitDateForTitle(date: Date): (string | number)[];
|
||||
render(
|
||||
props: GranularityRenderProps,
|
||||
isFirst: boolean
|
||||
): Selection<any>;
|
||||
}
|
||||
import { ITimelineDatePeriod } from "../datePeriod/datePeriod";
|
||||
import { IGranularityRenderProps } from "./granularityRenderProps";
|
||||
import { GranularityType } from "./granularityType";
|
||||
|
||||
import {
|
||||
IExtendedLabel,
|
||||
ITimelineLabel,
|
||||
} from "../dataInterfaces";
|
||||
|
||||
export interface IGranularity {
|
||||
getType?(): GranularityType;
|
||||
splitDate(date: Date): Array<string | number>;
|
||||
getDatePeriods(): ITimelineDatePeriod[];
|
||||
resetDatePeriods(): void;
|
||||
getExtendedLabel(): IExtendedLabel;
|
||||
setExtendedLabel(extendedLabel: IExtendedLabel): void;
|
||||
createLabels(granularity: IGranularity): ITimelineLabel[];
|
||||
sameLabel?(firstDatePeriod: ITimelineDatePeriod, secondDatePeriod: ITimelineDatePeriod): boolean;
|
||||
generateLabel?(datePeriod: ITimelineDatePeriod): ITimelineLabel;
|
||||
addDate(date: Date);
|
||||
setNewEndDate(date: Date): void;
|
||||
splitPeriod(index: number, newFraction: number, newDate: Date): void;
|
||||
splitDateForTitle(date: Date): Array<string | number>;
|
||||
render(
|
||||
props: IGranularityRenderProps,
|
||||
isFirst: boolean,
|
||||
): Selection<any, any, any, any>;
|
||||
}
|
||||
|
|
|
@ -24,290 +24,288 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual.granularity {
|
||||
// datePeriod
|
||||
import TimelineDatePeriod = datePeriod.TimelineDatePeriod;
|
||||
import valueFormatter = powerbi.extensibility.utils.formatting.valueFormatter;
|
||||
import {
|
||||
selectAll,
|
||||
Selection,
|
||||
} from "d3-selection";
|
||||
|
||||
// powerbi.extensibility.utils.svg
|
||||
import translate = powerbi.extensibility.utils.svg.translate;
|
||||
import { valueFormatter } from "powerbi-visuals-utils-formattingutils";
|
||||
import { manipulation as svgManipulation } from "powerbi-visuals-utils-svgutils";
|
||||
import { pixelConverter } from "powerbi-visuals-utils-typeutils";
|
||||
|
||||
// powerbi.extensibility.utils.type
|
||||
import convertToPx = powerbi.extensibility.utils.type.PixelConverter.toString;
|
||||
import { Calendar } from "../calendar";
|
||||
import { ITimelineDatePeriod } from "../datePeriod/datePeriod";
|
||||
import { GranularitySettings } from "../settings/granularitySettings";
|
||||
import { Utils } from "../utils";
|
||||
import { IGranularity } from "./granularity";
|
||||
import { IGranularityName } from "./granularityName";
|
||||
import { IGranularityRenderProps } from "./granularityRenderProps";
|
||||
|
||||
// utils
|
||||
import Utils = utils.Utils;
|
||||
import {
|
||||
IExtendedLabel,
|
||||
ITimelineLabel,
|
||||
} from "../dataInterfaces";
|
||||
|
||||
import Selection = d3.Selection;
|
||||
export class TimelineGranularityBase implements IGranularity {
|
||||
private static DefaultFraction: number = 1;
|
||||
private static EmptyYearOffset: number = 0;
|
||||
private static YearOffset: number = 1;
|
||||
|
||||
import GranularitySettings = settings.GranularitySettings;
|
||||
protected calendar: Calendar;
|
||||
|
||||
export class TimelineGranularityBase implements Granularity {
|
||||
private static DefaultFraction: number = 1;
|
||||
private static EmptyYearOffset: number = 0;
|
||||
private static YearOffset: number = 1;
|
||||
private clickableRectHeight: number = 23;
|
||||
private clickableRectFactor: number = 2;
|
||||
private clickableRectWidth: number = 30;
|
||||
|
||||
private clickableRectHeight: number = 23;
|
||||
private clickableRectFactor: number = 2;
|
||||
private clickableRectWidth: number = 30;
|
||||
private hLineYOffset: number = 2;
|
||||
private hLineHeight: number = 1;
|
||||
private hLineWidth: number = 30;
|
||||
private hLineXOffset: number = 30;
|
||||
|
||||
private hLineYOffset: number = 2;
|
||||
private hLineHeight: number = 1;
|
||||
private hLineWidth: number = 30;
|
||||
private hLineXOffset: number = 30;
|
||||
private sliderXOffset: number = 6;
|
||||
private sliderYOffset: number = 16;
|
||||
private sliderRx: number = 4;
|
||||
private sliderWidth: number = 15;
|
||||
private sliderHeight: number = 23;
|
||||
|
||||
private sliderXOffset: number = 6;
|
||||
private sliderYOffset: number = 16;
|
||||
private sliderRx: number = 4;
|
||||
private sliderWidth: number = 15;
|
||||
private sliderHeight: number = 23;
|
||||
private vLineWidth: number = 2;
|
||||
private vLineHeight: number = 3;
|
||||
|
||||
private vLineWidth: number = 2;
|
||||
private vLineHeight: number = 3;
|
||||
private textLabelXOffset: number = 3;
|
||||
private textLabelYOffset: number = 3;
|
||||
private textLabelDx: string = "0.5em";
|
||||
|
||||
private textLabelXOffset: number = 3;
|
||||
private textLabelYOffset: number = 3;
|
||||
private textLabelDx: string = "0.5em";
|
||||
private datePeriods: ITimelineDatePeriod[] = [];
|
||||
private extendedLabel: IExtendedLabel;
|
||||
private shortMonthFormatter: valueFormatter.IValueFormatter;
|
||||
private granularityProps: IGranularityName = null;
|
||||
|
||||
protected calendar: Calendar;
|
||||
constructor(calendar: Calendar, private locale: string, granularityProps: IGranularityName) {
|
||||
this.calendar = calendar;
|
||||
this.shortMonthFormatter = valueFormatter.valueFormatter.create({ format: "MMM", cultureSelector: this.locale });
|
||||
this.granularityProps = granularityProps;
|
||||
}
|
||||
|
||||
private datePeriods: TimelineDatePeriod[] = [];
|
||||
private extendedLabel: ExtendedLabel;
|
||||
private shortMonthFormatter: powerbi.extensibility.utils.formatting.IValueFormatter;
|
||||
private granularityProps: GranularityName = null;
|
||||
public render(props: IGranularityRenderProps, isFirst: boolean): Selection<any, any, any, any> {
|
||||
const granularitySelection = props.selection
|
||||
.append("g")
|
||||
.attr("transform", svgManipulation.translate(0, 0));
|
||||
|
||||
constructor(calendar: Calendar, private locale: string, granularityProps: GranularityName) {
|
||||
this.calendar = calendar;
|
||||
this.shortMonthFormatter = valueFormatter.create({ format: "MMM", cultureSelector: this.locale });
|
||||
this.granularityProps = granularityProps;
|
||||
}
|
||||
// render vetical line
|
||||
granularitySelection.append("rect")
|
||||
.classed("timelineVertLine", true)
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", pixelConverter.toString(this.vLineWidth))
|
||||
.attr("height", pixelConverter.toString(this.vLineHeight));
|
||||
|
||||
public render(props: GranularityRenderProps, isFirst: boolean): Selection<any> {
|
||||
let granularitySelection = props.selection.append("g")
|
||||
.attr("transform", translate(0, 0));
|
||||
|
||||
// render vetical line
|
||||
// render horizontal line
|
||||
if (!isFirst) {
|
||||
granularitySelection.append("rect")
|
||||
.classed("timelineVertLine", true)
|
||||
.attr({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: convertToPx(this.vLineWidth),
|
||||
height: convertToPx(this.vLineHeight)
|
||||
});
|
||||
.classed("timelineHorzLine", true)
|
||||
.attr("x", pixelConverter.toString(0 - this.hLineXOffset))
|
||||
.attr("y", pixelConverter.toString(this.hLineYOffset))
|
||||
.attr("height", pixelConverter.toString(this.hLineHeight))
|
||||
.attr("width", pixelConverter.toString(this.hLineWidth));
|
||||
}
|
||||
|
||||
// render horizontal line
|
||||
if (!isFirst) {
|
||||
granularitySelection.append("rect")
|
||||
.classed("timelineHorzLine", true)
|
||||
.attr({
|
||||
x: convertToPx(0 - this.hLineXOffset),
|
||||
y: convertToPx(this.hLineYOffset),
|
||||
height: convertToPx(this.hLineHeight),
|
||||
width: convertToPx(this.hLineWidth)
|
||||
});
|
||||
// render marker
|
||||
granularitySelection.append("text")
|
||||
.classed("periodSlicerGranularities", true)
|
||||
.text(this.granularityProps.marker)
|
||||
.attr("x", pixelConverter.toString(0 - this.textLabelXOffset))
|
||||
.attr("y", pixelConverter.toString(0 - this.textLabelYOffset))
|
||||
.attr("dx", this.textLabelDx);
|
||||
|
||||
// render slider
|
||||
if (props.granularSettings.granularity === this.granularityProps.granularityType) {
|
||||
this.renderSlider(
|
||||
granularitySelection,
|
||||
props.granularSettings,
|
||||
);
|
||||
}
|
||||
|
||||
const granularityTypeClickHandler = () => {
|
||||
const event: MouseEvent = require("d3").event as MouseEvent;
|
||||
|
||||
event.stopPropagation();
|
||||
|
||||
props.selectPeriodCallback(this.granularityProps.granularityType);
|
||||
|
||||
const sliderSelection = selectAll("rect.periodSlicerRect");
|
||||
|
||||
if (sliderSelection) {
|
||||
sliderSelection.remove();
|
||||
}
|
||||
|
||||
// render marker
|
||||
granularitySelection.append("text")
|
||||
.classed("periodSlicerGranularities", true)
|
||||
.text(this.granularityProps.marker)
|
||||
.attr({
|
||||
x: convertToPx(0 - this.textLabelXOffset),
|
||||
y: convertToPx(0 - this.textLabelYOffset),
|
||||
dx: this.textLabelDx
|
||||
});
|
||||
this.renderSlider(
|
||||
granularitySelection,
|
||||
props.granularSettings,
|
||||
);
|
||||
};
|
||||
|
||||
// render slider
|
||||
if (props.granularSettings.granularity === this.granularityProps.granularityType) {
|
||||
this.renderSlider(
|
||||
granularitySelection,
|
||||
props.granularSettings
|
||||
);
|
||||
// render selection rects
|
||||
granularitySelection
|
||||
.append("rect")
|
||||
.classed("periodSlicerSelectionRect", true)
|
||||
.attr("x", pixelConverter.toString(0 - this.clickableRectWidth / this.clickableRectFactor))
|
||||
.attr("y", pixelConverter.toString(0 - this.clickableRectWidth / this.clickableRectFactor))
|
||||
.attr("width", pixelConverter.toString(this.clickableRectWidth))
|
||||
.attr("height", pixelConverter.toString(this.clickableRectHeight))
|
||||
.on("mousedown", granularityTypeClickHandler)
|
||||
.on("touchstart", granularityTypeClickHandler);
|
||||
|
||||
granularitySelection.attr("fill", props.granularSettings.scaleColor);
|
||||
|
||||
return granularitySelection;
|
||||
}
|
||||
|
||||
public splitDate(date: Date): Array<string | number> {
|
||||
return [];
|
||||
}
|
||||
|
||||
public splitDateForTitle(date: Date): Array<string | number> {
|
||||
return this.splitDate(date);
|
||||
}
|
||||
|
||||
public shortMonthName(date: Date): string {
|
||||
return this.shortMonthFormatter.format(date);
|
||||
}
|
||||
|
||||
public resetDatePeriods(): void {
|
||||
this.datePeriods = [];
|
||||
}
|
||||
|
||||
public getDatePeriods(): ITimelineDatePeriod[] {
|
||||
return this.datePeriods;
|
||||
}
|
||||
|
||||
public getExtendedLabel(): IExtendedLabel {
|
||||
return this.extendedLabel;
|
||||
}
|
||||
|
||||
public setExtendedLabel(extendedLabel: IExtendedLabel): void {
|
||||
this.extendedLabel = extendedLabel;
|
||||
}
|
||||
|
||||
public createLabels(granularity: IGranularity): ITimelineLabel[] {
|
||||
const labels: ITimelineLabel[] = [];
|
||||
let lastDatePeriod: ITimelineDatePeriod;
|
||||
|
||||
this.datePeriods.forEach((datePeriod: ITimelineDatePeriod) => {
|
||||
if (!labels.length || !granularity.sameLabel(datePeriod, lastDatePeriod)) {
|
||||
lastDatePeriod = datePeriod;
|
||||
labels.push(granularity.generateLabel(datePeriod));
|
||||
}
|
||||
});
|
||||
|
||||
const granularityTypeClickHandler = (d: any, index: number) => {
|
||||
props.selectPeriodCallback(this.granularityProps.granularityType);
|
||||
return labels;
|
||||
}
|
||||
|
||||
let sliderSelection = d3.selectAll("rect.periodSlicerRect");
|
||||
if (sliderSelection) {
|
||||
sliderSelection.remove();
|
||||
}
|
||||
/**
|
||||
* Adds the new date into the given datePeriods array
|
||||
* If the date corresponds to the last date period, given the current granularity,
|
||||
* it will be added to that date period. Otherwise, a new date period will be added to the array.
|
||||
* i.e. using Month granularity, Feb 2 2015 corresponds to Feb 3 2015.
|
||||
* It is assumed that the given date does not correspond to previous date periods, other than the last date period
|
||||
*/
|
||||
public addDate(date: Date): void {
|
||||
const datePeriods: ITimelineDatePeriod[] = this.getDatePeriods();
|
||||
const lastDatePeriod: ITimelineDatePeriod = datePeriods[datePeriods.length - 1];
|
||||
const identifierArray: Array<string | number> = this.splitDate(date);
|
||||
|
||||
this.renderSlider(
|
||||
granularitySelection,
|
||||
props.granularSettings
|
||||
);
|
||||
};
|
||||
if (datePeriods.length === 0
|
||||
|| !Utils.arraysEqual(lastDatePeriod.identifierArray, identifierArray)) {
|
||||
|
||||
// render selection rects
|
||||
granularitySelection
|
||||
.append("rect")
|
||||
.classed("periodSlicerSelectionRect", true)
|
||||
.attr({
|
||||
x: convertToPx(0 - this.clickableRectWidth / this.clickableRectFactor),
|
||||
y: convertToPx(0 - this.clickableRectWidth / this.clickableRectFactor),
|
||||
width: convertToPx(this.clickableRectWidth),
|
||||
height: convertToPx(this.clickableRectHeight)
|
||||
})
|
||||
.on("mousedown", granularityTypeClickHandler)
|
||||
.on("touchstart", granularityTypeClickHandler);
|
||||
|
||||
granularitySelection.attr("fill", props.granularSettings.scaleColor);
|
||||
|
||||
return granularitySelection;
|
||||
}
|
||||
|
||||
private renderSlider(
|
||||
selection: Selection<any>,
|
||||
granularSettings: GranularitySettings
|
||||
): void {
|
||||
selection
|
||||
.append("rect")
|
||||
.classed("periodSlicerRect", true)
|
||||
.style("stroke", granularSettings.sliderColor)
|
||||
.attr({
|
||||
x: convertToPx(0 - this.sliderXOffset),
|
||||
y: convertToPx(0 - this.sliderYOffset),
|
||||
rx: convertToPx(this.sliderRx),
|
||||
width: convertToPx(this.sliderWidth),
|
||||
height: convertToPx(this.sliderHeight)
|
||||
}).data([granularSettings.granularity]);
|
||||
}
|
||||
|
||||
public splitDate(date: Date): (string | number)[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
public splitDateForTitle(date: Date): (string | number)[] {
|
||||
return this.splitDate(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the short month name of the given date (e.g. Jan, Feb, Mar)
|
||||
*/
|
||||
public shortMonthName(date: Date): string {
|
||||
return this.shortMonthFormatter.format(date);
|
||||
}
|
||||
|
||||
public resetDatePeriods(): void {
|
||||
this.datePeriods = [];
|
||||
}
|
||||
|
||||
public getDatePeriods(): TimelineDatePeriod[] {
|
||||
return this.datePeriods;
|
||||
}
|
||||
|
||||
public getExtendedLabel(): ExtendedLabel {
|
||||
return this.extendedLabel;
|
||||
}
|
||||
|
||||
public setExtendedLabel(extendedLabel: ExtendedLabel): void {
|
||||
this.extendedLabel = extendedLabel;
|
||||
}
|
||||
|
||||
public createLabels(granularity: Granularity): TimelineLabel[] {
|
||||
let labels: TimelineLabel[] = [],
|
||||
lastDatePeriod: TimelineDatePeriod;
|
||||
|
||||
this.datePeriods.forEach((datePeriod: TimelineDatePeriod) => {
|
||||
if (!labels.length || !granularity.sameLabel(datePeriod, lastDatePeriod)) {
|
||||
lastDatePeriod = datePeriod;
|
||||
labels.push(granularity.generateLabel(datePeriod));
|
||||
}
|
||||
});
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the new date into the given datePeriods array
|
||||
* If the date corresponds to the last date period, given the current granularity,
|
||||
* it will be added to that date period. Otherwise, a new date period will be added to the array.
|
||||
* i.e. using Month granularity, Feb 2 2015 corresponds to Feb 3 2015.
|
||||
* It is assumed that the given date does not correspond to previous date periods, other than the last date period
|
||||
*/
|
||||
public addDate(date: Date): void {
|
||||
let datePeriods: TimelineDatePeriod[] = this.getDatePeriods(),
|
||||
lastDatePeriod: TimelineDatePeriod = datePeriods[datePeriods.length - 1],
|
||||
identifierArray: (string | number)[] = this.splitDate(date);
|
||||
|
||||
if (datePeriods.length === 0
|
||||
|| !Utils.arraysEqual(lastDatePeriod.identifierArray, identifierArray)) {
|
||||
|
||||
if (datePeriods.length > 0) {
|
||||
lastDatePeriod.endDate = date;
|
||||
}
|
||||
|
||||
datePeriods.push({
|
||||
identifierArray: identifierArray,
|
||||
startDate: date,
|
||||
endDate: date,
|
||||
week: this.determineWeek(date),
|
||||
year: this.determineYear(date),
|
||||
fraction: TimelineGranularityBase.DefaultFraction,
|
||||
index: datePeriods.length
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (datePeriods.length > 0) {
|
||||
lastDatePeriod.endDate = date;
|
||||
}
|
||||
|
||||
datePeriods.push({
|
||||
endDate: date,
|
||||
fraction: TimelineGranularityBase.DefaultFraction,
|
||||
identifierArray,
|
||||
index: datePeriods.length,
|
||||
startDate: date,
|
||||
week: this.determineWeek(date),
|
||||
year: this.determineYear(date),
|
||||
});
|
||||
}
|
||||
|
||||
public setNewEndDate(date: Date): void {
|
||||
this.datePeriods[this.datePeriods.length - 1].endDate = date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a given period into two periods.
|
||||
* The new period is added after the index of the old one, while the old one is simply updated.
|
||||
* @param index The index of the date priod to be split
|
||||
* @param newFraction The fraction value of the new date period
|
||||
* @param newDate The date in which the date period is split
|
||||
*/
|
||||
public splitPeriod(index: number, newFraction: number, newDate: Date): void {
|
||||
let oldDatePeriod: TimelineDatePeriod = this.datePeriods[index];
|
||||
|
||||
oldDatePeriod.fraction -= newFraction;
|
||||
|
||||
let newDateObject: TimelineDatePeriod = {
|
||||
identifierArray: oldDatePeriod.identifierArray,
|
||||
startDate: newDate,
|
||||
endDate: oldDatePeriod.endDate,
|
||||
week: this.determineWeek(newDate),
|
||||
year: this.determineYear(newDate),
|
||||
fraction: newFraction,
|
||||
index: oldDatePeriod.index + oldDatePeriod.fraction
|
||||
};
|
||||
|
||||
oldDatePeriod.endDate = newDate;
|
||||
|
||||
this.datePeriods.splice(index + 1, 0, newDateObject);
|
||||
}
|
||||
|
||||
public determineWeek(date: Date): number[] {
|
||||
let year: number = this.determineYear(date);
|
||||
|
||||
const dateOfFirstWeek: Date = this.calendar.getDateOfFirstWeek(year);
|
||||
const dateOfFirstFullWeek: Date = this.calendar.getDateOfFirstFullWeek(year);
|
||||
const weeks: number = Utils.getAmountOfWeeksBetweenDates(dateOfFirstFullWeek, date);
|
||||
|
||||
if (date >= dateOfFirstFullWeek && dateOfFirstWeek < dateOfFirstFullWeek) {
|
||||
return [weeks + 1, year];
|
||||
}
|
||||
|
||||
return [weeks, year];
|
||||
}
|
||||
|
||||
public determineYear(date: Date): number {
|
||||
const firstDay: Date = new Date(
|
||||
date.getFullYear(),
|
||||
this.calendar.getFirstMonthOfYear(),
|
||||
this.calendar.getFirstDayOfYear());
|
||||
|
||||
return date.getFullYear() - ((firstDay <= date)
|
||||
? TimelineGranularityBase.EmptyYearOffset
|
||||
: TimelineGranularityBase.YearOffset);
|
||||
else {
|
||||
lastDatePeriod.endDate = date;
|
||||
}
|
||||
}
|
||||
|
||||
public setNewEndDate(date: Date): void {
|
||||
this.datePeriods[this.datePeriods.length - 1].endDate = date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a given period into two periods.
|
||||
* The new period is added after the index of the old one, while the old one is simply updated.
|
||||
* @param index The index of the date priod to be split
|
||||
* @param newFraction The fraction value of the new date period
|
||||
* @param newDate The date in which the date period is split
|
||||
*/
|
||||
public splitPeriod(index: number, newFraction: number, newDate: Date): void {
|
||||
const oldDatePeriod: ITimelineDatePeriod = this.datePeriods[index];
|
||||
|
||||
oldDatePeriod.fraction -= newFraction;
|
||||
|
||||
const newDateObject: ITimelineDatePeriod = {
|
||||
endDate: oldDatePeriod.endDate,
|
||||
fraction: newFraction,
|
||||
identifierArray: oldDatePeriod.identifierArray,
|
||||
index: oldDatePeriod.index + oldDatePeriod.fraction,
|
||||
startDate: newDate,
|
||||
week: this.determineWeek(newDate),
|
||||
year: this.determineYear(newDate),
|
||||
};
|
||||
|
||||
oldDatePeriod.endDate = newDate;
|
||||
|
||||
this.datePeriods.splice(index + 1, 0, newDateObject);
|
||||
}
|
||||
|
||||
public determineWeek(date: Date): number[] {
|
||||
const year: number = this.determineYear(date);
|
||||
|
||||
const dateOfFirstWeek: Date = this.calendar.getDateOfFirstWeek(year);
|
||||
const dateOfFirstFullWeek: Date = this.calendar.getDateOfFirstFullWeek(year);
|
||||
const weeks: number = Utils.getAmountOfWeeksBetweenDates(dateOfFirstFullWeek, date);
|
||||
|
||||
if (date >= dateOfFirstFullWeek && dateOfFirstWeek < dateOfFirstFullWeek) {
|
||||
return [weeks + 1, year];
|
||||
}
|
||||
|
||||
return [weeks, year];
|
||||
}
|
||||
|
||||
public determineYear(date: Date): number {
|
||||
const firstDay: Date = new Date(
|
||||
date.getFullYear(),
|
||||
this.calendar.getFirstMonthOfYear(),
|
||||
this.calendar.getFirstDayOfYear(),
|
||||
);
|
||||
|
||||
return date.getFullYear() - ((firstDay <= date)
|
||||
? TimelineGranularityBase.EmptyYearOffset
|
||||
: TimelineGranularityBase.YearOffset);
|
||||
}
|
||||
|
||||
private renderSlider(
|
||||
selection: Selection<any, any, any, any>,
|
||||
granularSettings: GranularitySettings,
|
||||
): void {
|
||||
selection
|
||||
.append("rect")
|
||||
.classed("periodSlicerRect", true)
|
||||
.style("stroke", granularSettings.sliderColor)
|
||||
.attr("x", pixelConverter.toString(0 - this.sliderXOffset))
|
||||
.attr("y", pixelConverter.toString(0 - this.sliderYOffset))
|
||||
.attr("rx", pixelConverter.toString(this.sliderRx))
|
||||
.attr("width", pixelConverter.toString(this.sliderWidth))
|
||||
.attr("height", pixelConverter.toString(this.sliderHeight))
|
||||
.data([granularSettings.granularity]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,143 +24,154 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual.granularity {
|
||||
// utils
|
||||
import Utils = utils.Utils;
|
||||
import powerbi from "powerbi-visuals-api";
|
||||
import { manipulation as svgManipulation } from "powerbi-visuals-utils-svgutils";
|
||||
|
||||
import Selection = d3.Selection;
|
||||
import { DayGranularity } from "./dayGranularity";
|
||||
import { IGranularity } from "./granularity";
|
||||
import { IGranularityRenderProps } from "./granularityRenderProps";
|
||||
import { GranularityType } from "./granularityType";
|
||||
import { MonthGranularity } from "./monthGranularity";
|
||||
import { QuarterGranularity } from "./quarterGranularity";
|
||||
import { WeekGranularity } from "./weekGranularity";
|
||||
import { YearGranularity } from "./yearGranularity";
|
||||
|
||||
import GranularitySettings = settings.GranularitySettings;
|
||||
import { Calendar } from "../calendar";
|
||||
import { Utils } from "../utils";
|
||||
|
||||
// powerbi.extensibility.utils.svg
|
||||
import translate = powerbi.extensibility.utils.svg.translate;
|
||||
export class TimelineGranularityData {
|
||||
/**
|
||||
* Returns the date of the previos day
|
||||
* @param date The following date
|
||||
*/
|
||||
public static previousDay(date: Date): Date {
|
||||
const prevDay: Date = Utils.resetTime(date);
|
||||
|
||||
export class TimelineGranularityData {
|
||||
private static DayOffset: number = 1;
|
||||
prevDay.setDate(prevDay.getDate() - TimelineGranularityData.DayOffset);
|
||||
|
||||
private dates: Date[];
|
||||
private granularities: Granularity[];
|
||||
private endingDate: Date;
|
||||
private groupXOffset: number = 10;
|
||||
private groupWidth: number = 30;
|
||||
return prevDay;
|
||||
}
|
||||
|
||||
constructor(startDate: Date, endDate: Date) {
|
||||
this.granularities = [];
|
||||
this.setDatesRange(startDate, endDate);
|
||||
/**
|
||||
* Returns the date of the next day
|
||||
* @param date The previous date
|
||||
*/
|
||||
public static nextDay(date: Date): Date {
|
||||
const nextDay: Date = Utils.resetTime(date);
|
||||
|
||||
const lastDate: Date = this.dates[this.dates.length - 1];
|
||||
nextDay.setDate(nextDay.getDate() + TimelineGranularityData.DayOffset);
|
||||
|
||||
this.endingDate = TimelineGranularityData.nextDay(lastDate);
|
||||
return nextDay;
|
||||
}
|
||||
|
||||
private static DayOffset: number = 1;
|
||||
|
||||
private dates: Date[];
|
||||
private granularities: IGranularity[];
|
||||
private endingDate: Date;
|
||||
private groupXOffset: number = 10;
|
||||
private groupWidth: number = 30;
|
||||
|
||||
constructor(startDate: Date, endDate: Date) {
|
||||
this.granularities = [];
|
||||
this.setDatesRange(startDate, endDate);
|
||||
|
||||
const lastDate: Date = this.dates[this.dates.length - 1];
|
||||
|
||||
this.endingDate = TimelineGranularityData.nextDay(lastDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new granularity to the array of granularities.
|
||||
* Resets the new granularity, adds all dates to it, and then edits the last date period with the ending date.
|
||||
* @param granularity The new granularity to be added
|
||||
*/
|
||||
public addGranularity(granularity: IGranularity): void {
|
||||
granularity.resetDatePeriods();
|
||||
|
||||
for (const date of this.dates) {
|
||||
granularity.addDate(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date of the previos day
|
||||
* @param date The following date
|
||||
*/
|
||||
public static previousDay(date: Date): Date {
|
||||
const prevDay: Date = Utils.resetTime(date);
|
||||
granularity.setNewEndDate(this.endingDate);
|
||||
|
||||
prevDay.setDate(prevDay.getDate() - TimelineGranularityData.DayOffset);
|
||||
this.granularities.push(granularity);
|
||||
}
|
||||
|
||||
return prevDay;
|
||||
}
|
||||
/**
|
||||
* Renders all available granularities
|
||||
*/
|
||||
public renderGranularities(props: IGranularityRenderProps): void {
|
||||
let renderIndex = 0;
|
||||
this.granularities.forEach((granularity: IGranularity, index: number) => {
|
||||
const granularitySelection = granularity.render(props, renderIndex === 0);
|
||||
|
||||
/**
|
||||
* Returns the date of the next day
|
||||
* @param date The previous date
|
||||
*/
|
||||
public static nextDay(date: Date): Date {
|
||||
const nextDay: Date = Utils.resetTime(date);
|
||||
if (granularitySelection !== null) {
|
||||
granularitySelection.attr(
|
||||
"transform",
|
||||
svgManipulation.translate(this.groupXOffset + renderIndex * this.groupWidth, 0),
|
||||
);
|
||||
|
||||
nextDay.setDate(nextDay.getDate() + TimelineGranularityData.DayOffset);
|
||||
|
||||
return nextDay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of dates with all the days between the start date and the end date
|
||||
*/
|
||||
private setDatesRange(startDate: Date, endDate: Date): void {
|
||||
let date: Date = startDate;
|
||||
|
||||
this.dates = [];
|
||||
|
||||
while (date <= endDate) {
|
||||
this.dates.push(date);
|
||||
date = TimelineGranularityData.nextDay(date);
|
||||
renderIndex++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new granularity to the array of granularities.
|
||||
* Resets the new granularity, adds all dates to it, and then edits the last date period with the ending date.
|
||||
* @param granularity The new granularity to be added
|
||||
*/
|
||||
public addGranularity(granularity: Granularity): void {
|
||||
granularity.resetDatePeriods();
|
||||
/**
|
||||
* Returns a specific granularity from the array of granularities
|
||||
* @param index The index of the requested granularity
|
||||
*/
|
||||
public getGranularity(index: number): IGranularity {
|
||||
return this.granularities[index];
|
||||
}
|
||||
|
||||
for (let date of this.dates) {
|
||||
granularity.addDate(date);
|
||||
}
|
||||
public createGranularities(
|
||||
calendar: Calendar,
|
||||
locale: string,
|
||||
localizationManager: powerbi.extensibility.ILocalizationManager,
|
||||
): void {
|
||||
this.granularities = [];
|
||||
|
||||
granularity.setNewEndDate(this.endingDate);
|
||||
this.addGranularity(new YearGranularity(calendar, locale, localizationManager));
|
||||
this.addGranularity(new QuarterGranularity(calendar, locale));
|
||||
this.addGranularity(new MonthGranularity(calendar, locale));
|
||||
this.addGranularity(new WeekGranularity(calendar, locale, localizationManager));
|
||||
this.addGranularity(new DayGranularity(calendar, locale));
|
||||
}
|
||||
|
||||
this.granularities.push(granularity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders all available granularities
|
||||
*/
|
||||
public renderGranularities(props: GranularityRenderProps): void {
|
||||
let renderIndex = 0;
|
||||
this.granularities.forEach((granularity: Granularity, index: number) => {
|
||||
let granularitySelection = granularity.render(props, renderIndex === 0);
|
||||
|
||||
if (granularitySelection !== null) {
|
||||
granularitySelection.attr("transform", translate(this.groupXOffset + renderIndex * this.groupWidth, 0));
|
||||
renderIndex++;
|
||||
}
|
||||
public createLabels(): void {
|
||||
this.granularities.forEach((granularity: IGranularity) => {
|
||||
granularity.setExtendedLabel({
|
||||
dayLabels: granularity.getType() >= GranularityType.day
|
||||
? granularity.createLabels(this.granularities[GranularityType.day])
|
||||
: [],
|
||||
monthLabels: granularity.getType() >= GranularityType.month
|
||||
? granularity.createLabels(this.granularities[GranularityType.month])
|
||||
: [],
|
||||
quarterLabels: granularity.getType() >= GranularityType.quarter
|
||||
? granularity.createLabels(this.granularities[GranularityType.quarter])
|
||||
: [],
|
||||
weekLabels: granularity.getType() >= GranularityType.week
|
||||
? granularity.createLabels(this.granularities[GranularityType.week])
|
||||
: [],
|
||||
yearLabels: granularity.getType() >= GranularityType.year
|
||||
? granularity.createLabels(this.granularities[GranularityType.year])
|
||||
: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific granularity from the array of granularities
|
||||
* @param index The index of the requested granularity
|
||||
*/
|
||||
public getGranularity(index: number): Granularity {
|
||||
return this.granularities[index];
|
||||
}
|
||||
/**
|
||||
* Returns an array of dates with all the days between the start date and the end date
|
||||
*/
|
||||
private setDatesRange(startDate: Date, endDate: Date): void {
|
||||
let date: Date = startDate;
|
||||
|
||||
public createGranularities(calendar: Calendar, locale: string, localizationManager: ILocalizationManager): void {
|
||||
this.granularities = [];
|
||||
this.dates = [];
|
||||
|
||||
this.addGranularity(new YearGranularity(calendar, locale, localizationManager));
|
||||
this.addGranularity(new QuarterGranularity(calendar, locale));
|
||||
this.addGranularity(new MonthGranularity(calendar, locale));
|
||||
this.addGranularity(new WeekGranularity(calendar, locale, localizationManager));
|
||||
this.addGranularity(new DayGranularity(calendar, locale));
|
||||
}
|
||||
|
||||
public createLabels(): void {
|
||||
this.granularities.forEach((granularity: Granularity) => {
|
||||
granularity.setExtendedLabel({
|
||||
dayLabels: granularity.getType() >= GranularityType.day
|
||||
? granularity.createLabels(this.granularities[GranularityType.day])
|
||||
: [],
|
||||
weekLabels: granularity.getType() >= GranularityType.week
|
||||
? granularity.createLabels(this.granularities[GranularityType.week])
|
||||
: [],
|
||||
monthLabels: granularity.getType() >= GranularityType.month
|
||||
? granularity.createLabels(this.granularities[GranularityType.month])
|
||||
: [],
|
||||
quarterLabels: granularity.getType() >= GranularityType.quarter
|
||||
? granularity.createLabels(this.granularities[GranularityType.quarter])
|
||||
: [],
|
||||
yearLabels: granularity.getType() >= GranularityType.year
|
||||
? granularity.createLabels(this.granularities[GranularityType.year])
|
||||
: [],
|
||||
});
|
||||
});
|
||||
while (date <= endDate) {
|
||||
this.dates.push(date);
|
||||
date = TimelineGranularityData.nextDay(date);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,11 +24,11 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual.granularity {
|
||||
export interface GranularityName {
|
||||
granularityType: GranularityType;
|
||||
name: string;
|
||||
nameKey: string;
|
||||
marker: string;
|
||||
}
|
||||
import { GranularityType } from "./granularityType";
|
||||
|
||||
export interface IGranularityName {
|
||||
granularityType: GranularityType;
|
||||
name: string;
|
||||
nameKey: string;
|
||||
marker: string;
|
||||
}
|
||||
|
|
|
@ -24,33 +24,34 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual.granularity {
|
||||
export const GranularityNames: GranularityName[] = [
|
||||
{
|
||||
granularityType: GranularityType.year,
|
||||
name: "year",
|
||||
nameKey: "Visual_Granularity_Year",
|
||||
marker: "Y"
|
||||
}, {
|
||||
granularityType: GranularityType.quarter,
|
||||
name: "quarter",
|
||||
nameKey: "Visual_Granularity_Quarter",
|
||||
marker: "Q"
|
||||
}, {
|
||||
granularityType: GranularityType.month,
|
||||
name: "month",
|
||||
nameKey: "Visual_Granularity_Month",
|
||||
marker: "M"
|
||||
}, {
|
||||
granularityType: GranularityType.week,
|
||||
name: "week",
|
||||
nameKey: "Visual_Granularity_Week",
|
||||
marker: "W"
|
||||
}, {
|
||||
granularityType: GranularityType.day,
|
||||
name: "day",
|
||||
nameKey: "Visual_Granularity_Day",
|
||||
marker: "D"
|
||||
}
|
||||
];
|
||||
}
|
||||
import { IGranularityName } from "./granularityName";
|
||||
import { GranularityType } from "./granularityType";
|
||||
|
||||
export const GranularityNames: IGranularityName[] = [
|
||||
{
|
||||
granularityType: GranularityType.year,
|
||||
marker: "Y",
|
||||
name: "year",
|
||||
nameKey: "Visual_Granularity_Year",
|
||||
}, {
|
||||
granularityType: GranularityType.quarter,
|
||||
marker: "Q",
|
||||
name: "quarter",
|
||||
nameKey: "Visual_Granularity_Quarter",
|
||||
}, {
|
||||
granularityType: GranularityType.month,
|
||||
marker: "M",
|
||||
name: "month",
|
||||
nameKey: "Visual_Granularity_Month",
|
||||
}, {
|
||||
granularityType: GranularityType.week,
|
||||
marker: "W",
|
||||
name: "week",
|
||||
nameKey: "Visual_Granularity_Week",
|
||||
}, {
|
||||
granularityType: GranularityType.day,
|
||||
marker: "D",
|
||||
name: "day",
|
||||
nameKey: "Visual_Granularity_Day",
|
||||
},
|
||||
];
|
||||
|
|
|
@ -24,13 +24,13 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual.granularity {
|
||||
import Selection = d3.Selection;
|
||||
import GranularitySettings = settings.GranularitySettings;
|
||||
import { Selection } from "d3-selection";
|
||||
|
||||
export interface GranularityRenderProps {
|
||||
selection: Selection<any>;
|
||||
granularSettings: GranularitySettings;
|
||||
selectPeriodCallback: (granularityType: GranularityType) => void;
|
||||
}
|
||||
import { GranularitySettings } from "../settings/granularitySettings";
|
||||
import { GranularityType } from "./granularityType";
|
||||
|
||||
export interface IGranularityRenderProps {
|
||||
selection: Selection<any, any, any, any>;
|
||||
granularSettings: GranularitySettings;
|
||||
selectPeriodCallback: (granularityType: GranularityType) => void;
|
||||
}
|
||||
|
|
|
@ -24,12 +24,10 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual.granularity {
|
||||
export enum GranularityType {
|
||||
year,
|
||||
quarter,
|
||||
month,
|
||||
week,
|
||||
day
|
||||
}
|
||||
export enum GranularityType {
|
||||
year,
|
||||
quarter,
|
||||
month,
|
||||
week,
|
||||
day,
|
||||
}
|
||||
|
|
|
@ -24,50 +24,52 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual.granularity {
|
||||
// datePeriod
|
||||
import TimelineDatePeriod = datePeriod.TimelineDatePeriod;
|
||||
// utils
|
||||
import Utils = utils.Utils;
|
||||
import Selection = d3.Selection;
|
||||
import { Selection } from "d3-selection";
|
||||
|
||||
export class MonthGranularity extends TimelineGranularityBase {
|
||||
constructor(calendar: Calendar, locale: string) {
|
||||
super(calendar, locale, Utils.getGranularityPropsByMarker("M"));
|
||||
import { Calendar } from "../calendar";
|
||||
import { ITimelineLabel } from "../dataInterfaces";
|
||||
import { ITimelineDatePeriod } from "../datePeriod/datePeriod";
|
||||
import { Utils } from "../utils";
|
||||
import { TimelineGranularityBase } from "./granularityBase";
|
||||
import { IGranularityRenderProps } from "./granularityRenderProps";
|
||||
import { GranularityType } from "./granularityType";
|
||||
|
||||
export class MonthGranularity extends TimelineGranularityBase {
|
||||
constructor(calendar: Calendar, locale: string) {
|
||||
super(calendar, locale, Utils.getGranularityPropsByMarker("M"));
|
||||
}
|
||||
|
||||
public render(props: IGranularityRenderProps, isFirst: boolean): Selection<any, any, any, any> {
|
||||
if (!props.granularSettings.granularityMonthVisibility) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public render(props: GranularityRenderProps, isFirst: boolean): Selection<any> {
|
||||
if (!props.granularSettings.granularityMonthVisibility) {
|
||||
return null;
|
||||
}
|
||||
return super.render(props, isFirst);
|
||||
}
|
||||
|
||||
return super.render(props, isFirst);
|
||||
}
|
||||
public getType(): GranularityType {
|
||||
return GranularityType.month;
|
||||
}
|
||||
|
||||
public getType(): GranularityType {
|
||||
return GranularityType.month;
|
||||
}
|
||||
public splitDate(date: Date): Array<string | number> {
|
||||
return [
|
||||
this.shortMonthName(date),
|
||||
this.determineYear(date),
|
||||
];
|
||||
}
|
||||
|
||||
public splitDate(date: Date): (string | number)[] {
|
||||
return [
|
||||
this.shortMonthName(date),
|
||||
this.determineYear(date)
|
||||
];
|
||||
}
|
||||
public sameLabel(firstDatePeriod: ITimelineDatePeriod, secondDatePeriod: ITimelineDatePeriod): boolean {
|
||||
return this.shortMonthName(firstDatePeriod.startDate) === this.shortMonthName(secondDatePeriod.startDate)
|
||||
&& this.determineYear(firstDatePeriod.startDate) === this.determineYear(secondDatePeriod.startDate);
|
||||
}
|
||||
|
||||
public sameLabel(firstDatePeriod: TimelineDatePeriod, secondDatePeriod: TimelineDatePeriod): boolean {
|
||||
return this.shortMonthName(firstDatePeriod.startDate) === this.shortMonthName(secondDatePeriod.startDate)
|
||||
&& this.determineYear(firstDatePeriod.startDate) === this.determineYear(secondDatePeriod.startDate);
|
||||
}
|
||||
public generateLabel(datePeriod: ITimelineDatePeriod): ITimelineLabel {
|
||||
const shortMonthName: string = this.shortMonthName(datePeriod.startDate);
|
||||
|
||||
public generateLabel(datePeriod: TimelineDatePeriod): TimelineLabel {
|
||||
const shortMonthName: string = this.shortMonthName(datePeriod.startDate);
|
||||
|
||||
return {
|
||||
title: shortMonthName,
|
||||
text: shortMonthName,
|
||||
id: datePeriod.index
|
||||
};
|
||||
}
|
||||
return {
|
||||
id: datePeriod.index,
|
||||
text: shortMonthName,
|
||||
title: shortMonthName,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,74 +24,77 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual.granularity {
|
||||
// datePeriod
|
||||
import TimelineDatePeriod = datePeriod.TimelineDatePeriod;
|
||||
// utils
|
||||
import Utils = utils.Utils;
|
||||
import Selection = d3.Selection;
|
||||
import { Selection } from "d3-selection";
|
||||
|
||||
export class QuarterGranularity extends TimelineGranularityBase {
|
||||
private static DefaultQuarter: number = 3;
|
||||
import { Calendar } from "../calendar";
|
||||
import { ITimelineLabel } from "../dataInterfaces";
|
||||
import { ITimelineDatePeriod } from "../datePeriod/datePeriod";
|
||||
import { Utils } from "../utils";
|
||||
import { TimelineGranularityBase } from "./granularityBase";
|
||||
import { IGranularityRenderProps } from "./granularityRenderProps";
|
||||
import { GranularityType } from "./granularityType";
|
||||
|
||||
constructor(calendar: Calendar, locale: string) {
|
||||
super(calendar, locale, Utils.getGranularityPropsByMarker("Q"));
|
||||
export class QuarterGranularity extends TimelineGranularityBase {
|
||||
private static DefaultQuarter: number = 3;
|
||||
|
||||
constructor(calendar: Calendar, locale: string) {
|
||||
super(calendar, locale, Utils.getGranularityPropsByMarker("Q"));
|
||||
}
|
||||
|
||||
public render(props: IGranularityRenderProps, isFirst: boolean): Selection<any, any, any, any> {
|
||||
if (!props.granularSettings.granularityQuarterVisibility) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public render(props: GranularityRenderProps, isFirst: boolean): Selection<any> {
|
||||
if (!props.granularSettings.granularityQuarterVisibility) {
|
||||
return null;
|
||||
return super.render(props, isFirst);
|
||||
}
|
||||
|
||||
public getType(): GranularityType {
|
||||
return GranularityType.quarter;
|
||||
}
|
||||
|
||||
public splitDate(date: Date): Array<string | number> {
|
||||
return [
|
||||
this.quarterText(date),
|
||||
this.determineYear(date),
|
||||
];
|
||||
}
|
||||
|
||||
public sameLabel(firstDatePeriod: ITimelineDatePeriod, secondDatePeriod: ITimelineDatePeriod): boolean {
|
||||
return this.quarterText(firstDatePeriod.startDate) === this.quarterText(secondDatePeriod.startDate)
|
||||
&& firstDatePeriod.year === secondDatePeriod.year;
|
||||
}
|
||||
|
||||
public generateLabel(datePeriod: ITimelineDatePeriod): ITimelineLabel {
|
||||
const quarter: string = this.quarterText(datePeriod.startDate);
|
||||
|
||||
return {
|
||||
id: datePeriod.index,
|
||||
text: quarter,
|
||||
title: `${quarter} ${datePeriod.year}`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date's quarter name (e.g. Q1, Q2, Q3, Q4)
|
||||
* @param date A date
|
||||
*/
|
||||
private quarterText(date: Date): string {
|
||||
let quarter: number = QuarterGranularity.DefaultQuarter;
|
||||
let year: number = this.determineYear(date);
|
||||
|
||||
while (date < this.calendar.getQuarterStartDate(year, quarter)) {
|
||||
if (quarter > 0) {
|
||||
quarter--;
|
||||
}
|
||||
else {
|
||||
quarter = QuarterGranularity.DefaultQuarter;
|
||||
year--;
|
||||
}
|
||||
|
||||
return super.render(props, isFirst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date's quarter name (e.g. Q1, Q2, Q3, Q4)
|
||||
* @param date A date
|
||||
*/
|
||||
private quarterText(date: Date): string {
|
||||
let quarter: number = QuarterGranularity.DefaultQuarter,
|
||||
year: number = this.determineYear(date);
|
||||
quarter++;
|
||||
|
||||
while (date < this.calendar.getQuarterStartDate(year, quarter))
|
||||
if (quarter > 0) {
|
||||
quarter--;
|
||||
}
|
||||
else {
|
||||
quarter = QuarterGranularity.DefaultQuarter;
|
||||
year--;
|
||||
}
|
||||
|
||||
quarter++;
|
||||
|
||||
return `Q${quarter}`;
|
||||
}
|
||||
|
||||
public getType(): GranularityType {
|
||||
return GranularityType.quarter;
|
||||
}
|
||||
|
||||
public splitDate(date: Date): (string | number)[] {
|
||||
return [
|
||||
this.quarterText(date),
|
||||
this.determineYear(date)
|
||||
];
|
||||
}
|
||||
|
||||
public sameLabel(firstDatePeriod: TimelineDatePeriod, secondDatePeriod: TimelineDatePeriod): boolean {
|
||||
return this.quarterText(firstDatePeriod.startDate) === this.quarterText(secondDatePeriod.startDate)
|
||||
&& firstDatePeriod.year === secondDatePeriod.year;
|
||||
}
|
||||
|
||||
public generateLabel(datePeriod: TimelineDatePeriod): TimelineLabel {
|
||||
const quarter: string = this.quarterText(datePeriod.startDate);
|
||||
|
||||
return {
|
||||
title: `${quarter} ${datePeriod.year}`,
|
||||
text: quarter,
|
||||
id: datePeriod.index
|
||||
};
|
||||
}
|
||||
return `Q${quarter}`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,54 +24,66 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual.granularity {
|
||||
// datePeriod
|
||||
import TimelineDatePeriod = datePeriod.TimelineDatePeriod;
|
||||
// utils
|
||||
import Utils = utils.Utils;
|
||||
import Selection = d3.Selection;
|
||||
import { Selection } from "d3-selection";
|
||||
import powerbi from "powerbi-visuals-api";
|
||||
|
||||
export class WeekGranularity extends TimelineGranularityBase {
|
||||
constructor(calendar: Calendar, locale: string, protected localizationManager: ILocalizationManager) {
|
||||
super(calendar, locale, Utils.getGranularityPropsByMarker("W"));
|
||||
import { Calendar } from "../calendar";
|
||||
import { ITimelineLabel } from "../dataInterfaces";
|
||||
import { ITimelineDatePeriod } from "../datePeriod/datePeriod";
|
||||
import { Utils } from "../utils";
|
||||
import { TimelineGranularityBase } from "./granularityBase";
|
||||
import { IGranularityRenderProps } from "./granularityRenderProps";
|
||||
import { GranularityType } from "./granularityType";
|
||||
|
||||
export class WeekGranularity extends TimelineGranularityBase {
|
||||
private localizationKey: string = "Visual_Granularity_Year";
|
||||
|
||||
constructor(
|
||||
calendar: Calendar,
|
||||
locale: string,
|
||||
protected localizationManager: powerbi.extensibility.ILocalizationManager,
|
||||
) {
|
||||
super(calendar, locale, Utils.getGranularityPropsByMarker("W"));
|
||||
}
|
||||
|
||||
public render(props: IGranularityRenderProps, isFirst: boolean): Selection<any, any, any, any> {
|
||||
if (!props.granularSettings.granularityWeekVisibility) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public render(props: GranularityRenderProps, isFirst: boolean): Selection<any> {
|
||||
if (!props.granularSettings.granularityWeekVisibility) {
|
||||
return null;
|
||||
}
|
||||
return super.render(props, isFirst);
|
||||
}
|
||||
|
||||
return super.render(props, isFirst);
|
||||
}
|
||||
public getType(): GranularityType {
|
||||
return GranularityType.week;
|
||||
}
|
||||
|
||||
public getType(): GranularityType {
|
||||
return GranularityType.week;
|
||||
}
|
||||
public splitDate(date: Date): Array<string | number> {
|
||||
return this.determineWeek(date);
|
||||
}
|
||||
|
||||
public splitDate(date: Date): (string | number)[] {
|
||||
return this.determineWeek(date);
|
||||
}
|
||||
public splitDateForTitle(date: Date): Array<string | number> {
|
||||
const weekData = this.determineWeek(date);
|
||||
|
||||
public splitDateForTitle(date: Date): (string | number)[] {
|
||||
const weekData = this.determineWeek(date);
|
||||
return [
|
||||
`W${weekData[0]}`,
|
||||
weekData[1],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
`W${weekData[0]}`,
|
||||
weekData[1]
|
||||
];
|
||||
}
|
||||
public sameLabel(firstDatePeriod: ITimelineDatePeriod, secondDatePeriod: ITimelineDatePeriod): boolean {
|
||||
return Utils.arraysEqual(firstDatePeriod.week, secondDatePeriod.week);
|
||||
}
|
||||
|
||||
public sameLabel(firstDatePeriod: TimelineDatePeriod, secondDatePeriod: TimelineDatePeriod): boolean {
|
||||
return Utils.arraysEqual(firstDatePeriod.week, secondDatePeriod.week);
|
||||
}
|
||||
public generateLabel(datePeriod: ITimelineDatePeriod): ITimelineLabel {
|
||||
const localizedWeek = this.localizationManager
|
||||
? this.localizationManager.getDisplayName(this.localizationKey)
|
||||
: this.localizationKey;
|
||||
|
||||
public generateLabel(datePeriod: TimelineDatePeriod): TimelineLabel {
|
||||
const localWeek = this.localizationManager.getDisplayName("Visual_Granularity_Week");
|
||||
return {
|
||||
title: `${localWeek} ${datePeriod.week[0]} - ${datePeriod.week[1]}`,
|
||||
text: `W${datePeriod.week[0]}`,
|
||||
id: datePeriod.index
|
||||
};
|
||||
}
|
||||
return {
|
||||
id: datePeriod.index,
|
||||
text: `W${datePeriod.week[0]}`,
|
||||
title: `${localizedWeek} ${datePeriod.week[0]} - ${datePeriod.week[1]}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,45 +24,57 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual.granularity {
|
||||
// datePeriod
|
||||
import TimelineDatePeriod = datePeriod.TimelineDatePeriod;
|
||||
// utils
|
||||
import Utils = utils.Utils;
|
||||
import Selection = d3.Selection;
|
||||
import { Selection } from "d3-selection";
|
||||
import powerbi from "powerbi-visuals-api";
|
||||
|
||||
export class YearGranularity extends TimelineGranularityBase {
|
||||
constructor(calendar: Calendar, locale: string, protected localizationManager: ILocalizationManager) {
|
||||
super(calendar, locale, Utils.getGranularityPropsByMarker("Y"));
|
||||
import { Calendar } from "../calendar";
|
||||
import { ITimelineLabel } from "../dataInterfaces";
|
||||
import { ITimelineDatePeriod } from "../datePeriod/datePeriod";
|
||||
import { Utils } from "../utils";
|
||||
import { TimelineGranularityBase } from "./granularityBase";
|
||||
import { IGranularityRenderProps } from "./granularityRenderProps";
|
||||
import { GranularityType } from "./granularityType";
|
||||
|
||||
export class YearGranularity extends TimelineGranularityBase {
|
||||
private localizationKey: string = "Visual_Granularity_Year";
|
||||
|
||||
constructor(
|
||||
calendar: Calendar,
|
||||
locale: string,
|
||||
protected localizationManager: powerbi.extensibility.ILocalizationManager,
|
||||
) {
|
||||
super(calendar, locale, Utils.getGranularityPropsByMarker("Y"));
|
||||
}
|
||||
|
||||
public getType(): GranularityType {
|
||||
return GranularityType.year;
|
||||
}
|
||||
|
||||
public render(props: IGranularityRenderProps, isFirst: boolean): Selection<any, any, any, any> {
|
||||
if (!props.granularSettings.granularityYearVisibility) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getType(): GranularityType {
|
||||
return GranularityType.year;
|
||||
}
|
||||
return super.render(props, isFirst);
|
||||
}
|
||||
|
||||
public render(props: GranularityRenderProps, isFirst: boolean): Selection<any> {
|
||||
if (!props.granularSettings.granularityYearVisibility) {
|
||||
return null;
|
||||
}
|
||||
public splitDate(date: Date): Array<string | number> {
|
||||
return [this.determineYear(date)];
|
||||
}
|
||||
|
||||
return super.render(props, isFirst);
|
||||
}
|
||||
public sameLabel(firstDatePeriod: ITimelineDatePeriod, secondDatePeriod: ITimelineDatePeriod): boolean {
|
||||
return firstDatePeriod.year === secondDatePeriod.year;
|
||||
}
|
||||
|
||||
public splitDate(date: Date): (string | number)[] {
|
||||
return [this.determineYear(date)];
|
||||
}
|
||||
public generateLabel(datePeriod: ITimelineDatePeriod): ITimelineLabel {
|
||||
const localizedYear = this.localizationManager
|
||||
? this.localizationManager.getDisplayName(this.localizationKey)
|
||||
: this.localizationKey;
|
||||
|
||||
public sameLabel(firstDatePeriod: TimelineDatePeriod, secondDatePeriod: TimelineDatePeriod): boolean {
|
||||
return firstDatePeriod.year === secondDatePeriod.year;
|
||||
}
|
||||
|
||||
public generateLabel(datePeriod: TimelineDatePeriod): TimelineLabel {
|
||||
const localYear = this.localizationManager.getDisplayName("Visual_Granularity_Year");
|
||||
return {
|
||||
title: `${localYear} ${datePeriod.year}`,
|
||||
text: `${datePeriod.year}`,
|
||||
id: datePeriod.index
|
||||
};
|
||||
}
|
||||
return {
|
||||
id: datePeriod.index,
|
||||
text: `${datePeriod.year}`,
|
||||
title: `${localizedYear} ${datePeriod.year}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,18 +24,16 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual.scaleUtils {
|
||||
export interface ElementScale {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export function getScale(element: HTMLElement): ElementScale {
|
||||
const clientRect: ClientRect = element.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
x: clientRect.width / element.offsetWidth,
|
||||
y: clientRect.height / element.offsetHeight
|
||||
};
|
||||
}
|
||||
export interface IElementScale {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export function getScale(element: HTMLElement): IElementScale {
|
||||
const clientRect: ClientRect = element.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
x: clientRect.width / element.offsetWidth,
|
||||
y: clientRect.height / element.offsetHeight,
|
||||
};
|
||||
}
|
||||
|
|
111
src/settings.ts
111
src/settings.ts
|
@ -1,111 +0,0 @@
|
|||
/*
|
||||
* 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.settings {
|
||||
// powerbi.data
|
||||
import ISemanticFilter = powerbi.data.ISemanticFilter;
|
||||
|
||||
// datePeriod
|
||||
import TimelineDatePeriodBase = datePeriod.TimelineDatePeriodBase;
|
||||
|
||||
// granularity
|
||||
import GranularityType = granularity.GranularityType;
|
||||
|
||||
// powerbi.extensibility.utils.dataview
|
||||
import DataViewObjectsParser = powerbi.extensibility.utils.dataview.DataViewObjectsParser;
|
||||
|
||||
export class GeneralSettings {
|
||||
public datePeriod: TimelineDatePeriodBase | string = TimelineDatePeriodBase.createEmpty();
|
||||
public isUserSelection: boolean = false;
|
||||
public filter: ISemanticFilter = null;
|
||||
}
|
||||
|
||||
export class ForceSelectionSettings {
|
||||
public currentPeriod: boolean = false;
|
||||
public latestAvailableDate: boolean = false;
|
||||
}
|
||||
|
||||
export class CalendarSettings {
|
||||
public month: number = 0;
|
||||
public day: number = 1;
|
||||
}
|
||||
|
||||
export class WeekDaySettings {
|
||||
public daySelection: boolean = true;
|
||||
public day: number = 0;
|
||||
}
|
||||
|
||||
export class LabelsSettings {
|
||||
show: boolean = true;
|
||||
displayAll: boolean = true;
|
||||
fontColor: string = "#777777";
|
||||
textSize: number = 9;
|
||||
}
|
||||
|
||||
export class CellsSettings {
|
||||
public fillSelected: string = "#ADD8E6";
|
||||
public fillUnselected: string = "";
|
||||
public strokeColor: string = "#333444";
|
||||
public selectedStrokeColor: string = "#333444";
|
||||
}
|
||||
|
||||
export class GranularitySettings {
|
||||
public scaleColor: string = "#000000";
|
||||
public sliderColor: string = "#AAAAAA";
|
||||
public granularity: GranularityType = GranularityType.month;
|
||||
public granularityYearVisibility: boolean = true;
|
||||
public granularityQuarterVisibility: boolean = true;
|
||||
public granularityMonthVisibility: boolean = true;
|
||||
public granularityWeekVisibility: boolean = true;
|
||||
public granularityDayVisibility: boolean = true;
|
||||
}
|
||||
|
||||
export class ScaleSizeAdjustment {
|
||||
show: boolean = false;
|
||||
}
|
||||
|
||||
export class ScrollAutoAdjustment {
|
||||
show: boolean = false;
|
||||
}
|
||||
|
||||
export class CursorSettings {
|
||||
public color: string = "#808080";
|
||||
}
|
||||
|
||||
export class VisualSettings extends DataViewObjectsParser {
|
||||
public general: GeneralSettings = new GeneralSettings();
|
||||
public calendar: CalendarSettings = new CalendarSettings();
|
||||
public forceSelection: ForceSelectionSettings = new ForceSelectionSettings();
|
||||
public weekDay: WeekDaySettings = new WeekDaySettings();
|
||||
public rangeHeader: LabelsSettings = new LabelsSettings();
|
||||
public cells: CellsSettings = new CellsSettings();
|
||||
public granularity: GranularitySettings = new GranularitySettings();
|
||||
public labels: LabelsSettings = new LabelsSettings();
|
||||
public scaleSizeAdjustment: ScaleSizeAdjustment = new ScaleSizeAdjustment();
|
||||
public scrollAutoAdjustment: ScrollAutoAdjustment = new ScrollAutoAdjustment();
|
||||
public cursor: CursorSettings = new CursorSettings();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export class CalendarSettings {
|
||||
public month: number = 0;
|
||||
public day: number = 1;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export class CellsSettings {
|
||||
public fillSelected: string = "#ADD8E6";
|
||||
public fillUnselected: string = "";
|
||||
public strokeColor: string = "#333444";
|
||||
public selectedStrokeColor: string = "#333444";
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export class CursorSettings {
|
||||
public color: string = "#808080";
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export class ForceSelectionSettings {
|
||||
public currentPeriod: boolean = false;
|
||||
public latestAvailableDate: boolean = false;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { TimelineDatePeriodBase } from "../datePeriod/datePeriodBase";
|
||||
|
||||
export class GeneralSettings {
|
||||
public datePeriod: TimelineDatePeriodBase | string = TimelineDatePeriodBase.createEmpty();
|
||||
public isUserSelection: boolean = false;
|
||||
}
|
|
@ -24,31 +24,15 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/// <reference path="../_references.ts"/>
|
||||
import { GranularityType } from "../granularity/granularityType";
|
||||
|
||||
module powerbi.extensibility.visual.test.helpers {
|
||||
// powerbi.extensibility.utils.test
|
||||
import RgbColor = powerbi.extensibility.utils.test.helpers.color.RgbColor;
|
||||
import parseColorString = powerbi.extensibility.utils.test.helpers.color.parseColorString;
|
||||
|
||||
export function areColorsEqual(firstColor: string, secondColor: string): boolean {
|
||||
const firstConvertedColor: RgbColor = parseColorString(firstColor),
|
||||
secondConvertedColor: RgbColor = parseColorString(secondColor);
|
||||
|
||||
return firstConvertedColor.R === secondConvertedColor.R
|
||||
&& firstConvertedColor.G === secondConvertedColor.G
|
||||
&& firstConvertedColor.B === secondConvertedColor.B;
|
||||
}
|
||||
|
||||
export function getDateRange(start: Date, stop: Date, step: number): Date[] {
|
||||
return _.range(
|
||||
start.getTime(),
|
||||
stop.getTime(),
|
||||
step)
|
||||
.map((milliseconds: number) => new Date(milliseconds));
|
||||
}
|
||||
|
||||
export function getSolidColorStructuralObject(color: string): any {
|
||||
return { solid: { color: color } };
|
||||
}
|
||||
export class GranularitySettings {
|
||||
public scaleColor: string = "#000000";
|
||||
public sliderColor: string = "#AAAAAA";
|
||||
public granularity: GranularityType = GranularityType.month;
|
||||
public granularityYearVisibility: boolean = true;
|
||||
public granularityQuarterVisibility: boolean = true;
|
||||
public granularityMonthVisibility: boolean = true;
|
||||
public granularityWeekVisibility: boolean = true;
|
||||
public granularityDayVisibility: boolean = true;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export class LabelsSettings {
|
||||
public show: boolean = true;
|
||||
public displayAll: boolean = true;
|
||||
public fontColor: string = "#777777";
|
||||
public textSize: number = 9;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export class ScaleSizeAdjustment {
|
||||
public show: boolean = false;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export class ScrollAutoAdjustment {
|
||||
public show: boolean = false;
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { dataViewObjectsParser } from "powerbi-visuals-utils-dataviewutils";
|
||||
|
||||
import { CalendarSettings } from "./calendarSettings";
|
||||
import { CellsSettings } from "./cellsSettings";
|
||||
import { CursorSettings } from "./cursorSettings";
|
||||
import { ForceSelectionSettings } from "./forceSelectionSettings";
|
||||
import { GeneralSettings } from "./generalSettings";
|
||||
import { GranularitySettings } from "./granularitySettings";
|
||||
import { LabelsSettings } from "./labelsSettings";
|
||||
import { ScaleSizeAdjustment } from "./scaleSizeAdjustment";
|
||||
import { ScrollAutoAdjustment } from "./scrollAutoAdjustment";
|
||||
import { WeekDaySettings } from "./weekDaySettings";
|
||||
|
||||
export class VisualSettings extends dataViewObjectsParser.DataViewObjectsParser {
|
||||
public general: GeneralSettings = new GeneralSettings();
|
||||
public calendar: CalendarSettings = new CalendarSettings();
|
||||
public forceSelection: ForceSelectionSettings = new ForceSelectionSettings();
|
||||
public weekDay: WeekDaySettings = new WeekDaySettings();
|
||||
public rangeHeader: LabelsSettings = new LabelsSettings();
|
||||
public cells: CellsSettings = new CellsSettings();
|
||||
public granularity: GranularitySettings = new GranularitySettings();
|
||||
public labels: LabelsSettings = new LabelsSettings();
|
||||
public scaleSizeAdjustment: ScaleSizeAdjustment = new ScaleSizeAdjustment();
|
||||
public scrollAutoAdjustment: ScrollAutoAdjustment = new ScrollAutoAdjustment();
|
||||
public cursor: CursorSettings = new CursorSettings();
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export class WeekDaySettings {
|
||||
public daySelection: boolean = true;
|
||||
public day: number = 0;
|
||||
}
|
836
src/utils.ts
836
src/utils.ts
|
@ -23,432 +23,454 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
module powerbi.extensibility.visual.utils {
|
||||
// datePeriod
|
||||
import TimelineDatePeriod = datePeriod.TimelineDatePeriod;
|
||||
import ITimelineDatePeriod = datePeriod.ITimelineDatePeriod;
|
||||
|
||||
// settings
|
||||
import CellsSettings = settings.CellsSettings;
|
||||
import {
|
||||
ITimelineDatePeriod,
|
||||
ITimelineDatePeriodBase,
|
||||
} from "./datePeriod/datePeriod";
|
||||
|
||||
// granularity
|
||||
import GranularityType = granularity.GranularityType;
|
||||
import GranularityName = granularity.GranularityName;
|
||||
import GranularityNames = granularity.GranularityNames;
|
||||
import {
|
||||
ITimelineData,
|
||||
ITimelineDataPoint,
|
||||
} from "./dataInterfaces";
|
||||
|
||||
export class Utils {
|
||||
private static DateSplitter: string = " - ";
|
||||
private static MinFraction: number = 1;
|
||||
private static TotalDaysInWeek: number = 7;
|
||||
private static WeekDayOffset: number = 1;
|
||||
private static DateArrayJoiner: string = " ";
|
||||
import { IGranularityName } from "./granularity/granularityName";
|
||||
import { GranularityNames } from "./granularity/granularityNames";
|
||||
import { GranularityType } from "./granularity/granularityType";
|
||||
import { CellsSettings } from "./settings/cellsSettings";
|
||||
|
||||
public static DefaultCellColor: string = "transparent";
|
||||
public static TotalMilliseconds: number = 1000;
|
||||
public static TotalSeconds: number = 60;
|
||||
public static TotalMinutes: number = 60;
|
||||
public static TotalHours: number = 24;
|
||||
export class Utils {
|
||||
public static DefaultCellColor: string = "transparent";
|
||||
public static TotalMilliseconds: number = 1000;
|
||||
public static TotalSeconds: number = 60;
|
||||
public static TotalMinutes: number = 60;
|
||||
public static TotalHours: number = 24;
|
||||
|
||||
/**
|
||||
* We should reduce the latest date of selection using this value,
|
||||
* because, as far as I understand, PBI Framework rounds off milliseconds.
|
||||
*/
|
||||
private static OffsetMilliseconds: number = 999;
|
||||
public static convertToDaysFromMilliseconds(milliseconds: number): number {
|
||||
return milliseconds / (Utils.TotalMillisecondsInADay);
|
||||
}
|
||||
|
||||
private static TotalMillisecondsInADay: number =
|
||||
Utils.TotalMilliseconds
|
||||
* Utils.TotalSeconds
|
||||
* Utils.TotalMinutes
|
||||
* Utils.TotalHours;
|
||||
public static getAmountOfDaysBetweenDates(startDate: Date, endDate: Date): number {
|
||||
const offset: number = Utils.getDaylightSavingTimeOffset(startDate, endDate);
|
||||
const totalMilliseconds: number = endDate.getTime() - startDate.getTime() - offset;
|
||||
|
||||
public static convertToDaysFromMilliseconds(milliseconds: number): number {
|
||||
return milliseconds / (Utils.TotalMillisecondsInADay);
|
||||
}
|
||||
return Utils.convertToDaysFromMilliseconds(Math.abs(totalMilliseconds));
|
||||
}
|
||||
|
||||
public static getAmountOfDaysBetweenDates(startDate: Date, endDate: Date): number {
|
||||
const offset: number = Utils.getDaylightSavingTimeOffset(startDate, endDate);
|
||||
const totalMilliseconds: number = endDate.getTime() - startDate.getTime() - offset;
|
||||
public static getAmountOfWeeksBetweenDates(startDate: Date, endDate: Date): number {
|
||||
const totalDays: number = Utils.getAmountOfDaysBetweenDates(startDate, endDate);
|
||||
|
||||
return Utils.convertToDaysFromMilliseconds(Math.abs(totalMilliseconds));
|
||||
}
|
||||
|
||||
public static getAmountOfWeeksBetweenDates(startDate: Date, endDate: Date): number {
|
||||
const totalDays: number = Utils.getAmountOfDaysBetweenDates(startDate, endDate);
|
||||
|
||||
return Utils.WeekDayOffset + Math.floor(totalDays / Utils.TotalDaysInWeek);
|
||||
}
|
||||
|
||||
public static getMillisecondsWithoutTimezone(date: Date): number {
|
||||
if (!date) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return date.getTime()
|
||||
- date.getTimezoneOffset()
|
||||
* Utils.TotalMilliseconds
|
||||
* Utils.TotalSeconds;
|
||||
}
|
||||
|
||||
public static getDateWithoutTimezone(date: Date): Date {
|
||||
return new Date(Utils.getMillisecondsWithoutTimezone(date));
|
||||
}
|
||||
|
||||
public static getDaylightSavingTimeOffset(startDate: Date, endDate: Date): number {
|
||||
const startDateTzOffset: number = startDate.getTimezoneOffset();
|
||||
const endDateTzOffset: number = endDate.getTimezoneOffset();
|
||||
|
||||
return (endDateTzOffset - startDateTzOffset) * 60 * 1000;
|
||||
}
|
||||
|
||||
public static toStringDateWithoutTimezone(date: Date): string {
|
||||
if (!date) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Utils.getDateWithoutTimezone(date).toISOString();
|
||||
}
|
||||
|
||||
public static getEndOfThePreviousDate(date: Date): Date {
|
||||
const currentDate: Date = Utils.resetTime(date);
|
||||
|
||||
currentDate.setMilliseconds(-Utils.OffsetMilliseconds);
|
||||
|
||||
return currentDate;
|
||||
}
|
||||
|
||||
public static parseDateWithoutTimezone(dateString: string): Date {
|
||||
if (dateString === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let timeInMilliseconds: number,
|
||||
date: Date = new Date(dateString);
|
||||
|
||||
if (date.toString() === "Invalid Date") {
|
||||
return null;
|
||||
}
|
||||
|
||||
timeInMilliseconds = date.getTime()
|
||||
+ date.getTimezoneOffset() * Utils.TotalMilliseconds * Utils.TotalSeconds;
|
||||
|
||||
return new Date(timeInMilliseconds);
|
||||
}
|
||||
|
||||
public static resetTime(date: Date): Date {
|
||||
return new Date(
|
||||
date.getFullYear(),
|
||||
date.getMonth(),
|
||||
date.getDate());
|
||||
}
|
||||
|
||||
public static getDatePeriod(values: any[]): ITimelineDatePeriod {
|
||||
let startDate: Date,
|
||||
endDate: Date;
|
||||
|
||||
values = [].concat(values);
|
||||
|
||||
values.forEach((value: any) => {
|
||||
let date: Date = Utils.parseDate(value);
|
||||
|
||||
if (date < startDate || startDate === undefined) {
|
||||
startDate = date;
|
||||
}
|
||||
|
||||
if (date > endDate || endDate === undefined) {
|
||||
endDate = date;
|
||||
}
|
||||
});
|
||||
|
||||
return { startDate, endDate };
|
||||
}
|
||||
|
||||
public static parseDate(value: any): Date {
|
||||
let typeOfValue: string = typeof value,
|
||||
date: Date = value;
|
||||
|
||||
if (typeOfValue === "number") {
|
||||
date = new Date(value, 0);
|
||||
}
|
||||
|
||||
if (typeOfValue === "string") {
|
||||
date = new Date(value);
|
||||
}
|
||||
|
||||
if (date && date instanceof Date && date.toString() !== "Invalid Date") {
|
||||
return Utils.resetTime(date);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public static areBoundsOfSelectionAndAvailableDatesTheSame(timelineData: TimelineData): boolean {
|
||||
const datePeriod: TimelineDatePeriod[] = timelineData.currentGranularity.getDatePeriods(),
|
||||
startDate: Date = Utils.getStartSelectionDate(timelineData),
|
||||
endDate: Date = Utils.getEndSelectionDate(timelineData);
|
||||
|
||||
return datePeriod
|
||||
&& datePeriod.length >= 1
|
||||
&& startDate
|
||||
&& endDate
|
||||
&& datePeriod[0].startDate.getTime() === startDate.getTime()
|
||||
&& datePeriod[datePeriod.length - 1].endDate.getTime() === endDate.getTime();
|
||||
}
|
||||
|
||||
public static getTheLatestDayOfMonth(monthId: number): number {
|
||||
const date: Date = new Date(2008, monthId + 1, 0); // leap year, so the latest day of February is 29.
|
||||
|
||||
return date.getDate();
|
||||
}
|
||||
|
||||
public static isValueEmpty(value: any): boolean {
|
||||
return value === undefined || value === null || isNaN(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date of the start of the selection
|
||||
* @param timelineData The TimelineData which contains all the date periods
|
||||
*/
|
||||
public static getStartSelectionDate(timelineData: TimelineData): Date {
|
||||
return timelineData.currentGranularity.getDatePeriods()[timelineData.selectionStartIndex].startDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date of the end of the selection
|
||||
* @param timelineData The TimelineData which contains all the date periods
|
||||
*/
|
||||
public static getEndSelectionDate(timelineData: TimelineData): Date {
|
||||
return timelineData.currentGranularity.getDatePeriods()[timelineData.selectionEndIndex].endDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date period of the end of the selection
|
||||
* @param timelineData The TimelineData which contains all the date periods
|
||||
*/
|
||||
public static getEndSelectionPeriod(timelineData: TimelineData): TimelineDatePeriod {
|
||||
return timelineData.currentGranularity.getDatePeriods()[timelineData.selectionEndIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color of a cell, depending on whether its date period is between the selected date periods.
|
||||
* CellRects should be transparent filled by default if there isn't any color sets.
|
||||
* @param d The TimelineDataPoint of the cell
|
||||
* @param timelineData The TimelineData with the selected date periods
|
||||
* @param timelineFormat The TimelineFormat with the chosen colors
|
||||
*/
|
||||
public static getCellColor(
|
||||
dataPoint: TimelineDatapoint,
|
||||
timelineData: TimelineData,
|
||||
cellSettings: CellsSettings): string {
|
||||
|
||||
const inSelectedPeriods: boolean = dataPoint.datePeriod.startDate >= Utils.getStartSelectionDate(timelineData)
|
||||
&& dataPoint.datePeriod.endDate <= Utils.getEndSelectionDate(timelineData);
|
||||
|
||||
return inSelectedPeriods
|
||||
? cellSettings.fillSelected
|
||||
: (cellSettings.fillUnselected || Utils.DefaultCellColor);
|
||||
}
|
||||
|
||||
public static isGranuleSelected(dataPoint: TimelineDatapoint, timelineData: TimelineData, cellSettings: CellsSettings): boolean {
|
||||
return dataPoint.datePeriod.startDate >= Utils.getStartSelectionDate(timelineData)
|
||||
&& dataPoint.datePeriod.endDate <= Utils.getEndSelectionDate(timelineData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the granularity type of the given granularity name
|
||||
* @param granularityName The name of the granularity
|
||||
*/
|
||||
public static getGranularityType(granularityName: string): GranularityType {
|
||||
const index: number = Utils.findIndex(GranularityNames, (granularity: GranularityName) => {
|
||||
return granularity.name === granularityName;
|
||||
});
|
||||
|
||||
return GranularityNames[index].granularityType;
|
||||
}
|
||||
|
||||
public static getGranularityPropsByMarker(marker: string): GranularityName {
|
||||
const index: number = Utils.findIndex(GranularityNames, (granularity: GranularityName) => {
|
||||
return granularity.marker === marker;
|
||||
});
|
||||
|
||||
return GranularityNames[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the granularity type
|
||||
* @param granularity The type of granularity
|
||||
*/
|
||||
public static getGranularityNameKey(granularityType: GranularityType): string {
|
||||
const index: number = Utils.findIndex(GranularityNames, (granularity: GranularityName) => {
|
||||
return granularity.granularityType === granularityType;
|
||||
});
|
||||
|
||||
return GranularityNames[index].nameKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the date periods of the current granularity, in case the start and end of the selection is in between a date period.
|
||||
* i.e. for a quarter granularity and a selection between Feb 6 and Dec 23, the date periods for Q1 and Q4 will be split accordingly
|
||||
* @param timelineData The TimelineData that contains the date periods
|
||||
* @param startDate The starting date of the selection
|
||||
* @param endDate The ending date of the selection
|
||||
*/
|
||||
public static separateSelection(timelineData: TimelineData, startDate: Date, endDate: Date): void {
|
||||
let datePeriods: TimelineDatePeriod[] = timelineData.currentGranularity.getDatePeriods(),
|
||||
startDateIndex: number = Utils.findIndex(datePeriods, x => startDate < x.endDate),
|
||||
endDateIndex: number = Utils.findIndex(datePeriods, x => endDate <= x.endDate);
|
||||
|
||||
startDateIndex = startDateIndex >= 0
|
||||
? startDateIndex
|
||||
: 0;
|
||||
|
||||
endDateIndex = endDateIndex >= 0
|
||||
? endDateIndex
|
||||
: datePeriods.length - 1;
|
||||
|
||||
timelineData.selectionStartIndex = startDateIndex;
|
||||
timelineData.selectionEndIndex = endDateIndex;
|
||||
|
||||
const startRatio: number = Utils.getDateRatio(datePeriods[startDateIndex], startDate, true),
|
||||
endRatio: number = Utils.getDateRatio(datePeriods[endDateIndex], endDate, false);
|
||||
|
||||
if (endRatio > 0) {
|
||||
timelineData.currentGranularity.splitPeriod(endDateIndex, endRatio, endDate);
|
||||
}
|
||||
|
||||
if (startRatio > 0) {
|
||||
const startFration: number = datePeriods[startDateIndex].fraction - startRatio;
|
||||
|
||||
timelineData.currentGranularity.splitPeriod(startDateIndex, startFration, startDate);
|
||||
|
||||
timelineData.selectionStartIndex++;
|
||||
timelineData.selectionEndIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ratio of the given date compared to the whole date period.
|
||||
* The ratio is calculated either from the start or the end of the date period.
|
||||
* i.e. the ratio of Feb 7 2016 compared to the month of Feb 2016,
|
||||
* is 0.2142 from the start of the month, or 0.7857 from the end of the month.
|
||||
* @param datePeriod The date period that contain the specified date
|
||||
* @param date The date
|
||||
* @param fromStart Whether to calculater the ratio from the start of the date period.
|
||||
*/
|
||||
public static getDateRatio(datePeriod: TimelineDatePeriod, date: Date, fromStart: boolean): number {
|
||||
const dateDifference: number = fromStart
|
||||
? date.getTime() - datePeriod.startDate.getTime()
|
||||
: datePeriod.endDate.getTime() - date.getTime();
|
||||
|
||||
const periodDifference: number = datePeriod.endDate.getTime() - datePeriod.startDate.getTime();
|
||||
|
||||
return periodDifference === 0
|
||||
? 0
|
||||
: dateDifference / periodDifference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time range text, depending on the given granularity (e.g. "Feb 3 2014 - Apr 5 2015", "Q1 2014 - Q2 2015")
|
||||
*/
|
||||
public static timeRangeText(timelineData: TimelineData): string {
|
||||
let startSelectionDateArray: (string | number)[] = timelineData.currentGranularity
|
||||
.splitDateForTitle(Utils.getStartSelectionDate(timelineData));
|
||||
|
||||
let endSelectionDateArray: (string | number)[] = timelineData.currentGranularity
|
||||
.splitDateForTitle(Utils.getEndSelectionPeriod(timelineData).startDate);
|
||||
|
||||
return `${startSelectionDateArray.join(Utils.DateArrayJoiner)}${Utils.DateSplitter}${endSelectionDateArray.join(Utils.DateArrayJoiner)}`;
|
||||
}
|
||||
|
||||
public static dateRangeText(datePeriod: TimelineDatePeriod): string {
|
||||
return `${datePeriod.startDate.toDateString()}${Utils.DateSplitter}${this.previousDay(datePeriod.endDate).toDateString()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines the first two partial date periods, into a single date period.
|
||||
* i.e. combines "Feb 1 2016 - Feb 5 2016" with "Feb 5 2016 - Feb 29 2016" into "Feb 1 2016 - Feb 29 2016"
|
||||
* @param datePeriods The list of date periods
|
||||
*/
|
||||
public static unseparateSelection(datePeriods: TimelineDatePeriod[]): void {
|
||||
const separationIndex: number = Utils.findIndex(
|
||||
datePeriods,
|
||||
(datePeriod: TimelineDatePeriod) => {
|
||||
return datePeriod.fraction < Utils.MinFraction;
|
||||
});
|
||||
|
||||
if (separationIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
datePeriods[separationIndex].endDate = datePeriods[separationIndex + 1].endDate;
|
||||
datePeriods[separationIndex].fraction += datePeriods[separationIndex + 1].fraction;
|
||||
|
||||
datePeriods.splice(separationIndex + 1, 1);
|
||||
}
|
||||
|
||||
public static getIndexByPosition(
|
||||
elements: number[],
|
||||
widthOfElement: number,
|
||||
position: number,
|
||||
offset: number = 0): number {
|
||||
|
||||
elements = elements || [];
|
||||
|
||||
const length: number = elements.length;
|
||||
|
||||
if (!Utils.isValueEmpty(elements[0])
|
||||
&& !Utils.isValueEmpty(elements[1])
|
||||
&& position <= elements[1] * widthOfElement + offset) {
|
||||
|
||||
return 0;
|
||||
} else if (
|
||||
!Utils.isValueEmpty(elements[length - 1])
|
||||
&& position >= elements[length - 1] * widthOfElement + offset) {
|
||||
|
||||
return length - 1;
|
||||
}
|
||||
|
||||
for (let i: number = 1; i < length; i++) {
|
||||
const left: number = elements[i] * widthOfElement + offset,
|
||||
right: number = elements[i + 1] * widthOfElement + offset;
|
||||
|
||||
if (position >= left && position <= right) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return Utils.WeekDayOffset + Math.floor(totalDays / Utils.TotalDaysInWeek);
|
||||
}
|
||||
|
||||
public static getMillisecondsWithoutTimezone(date: Date): number {
|
||||
if (!date) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static arraysEqual(a: any[], b: any[]): boolean {
|
||||
if (a === b) return true;
|
||||
if (a === null || b === null) return false;
|
||||
if (a.length !== b.length) return false;
|
||||
return date.getTime()
|
||||
- date.getTimezoneOffset()
|
||||
* Utils.TotalMilliseconds
|
||||
* Utils.TotalSeconds;
|
||||
}
|
||||
|
||||
// If you don't care about the order of the elements inside
|
||||
// the array, you should sort both arrays here.
|
||||
public static getDateWithoutTimezone(date: Date): Date {
|
||||
return new Date(Utils.getMillisecondsWithoutTimezone(date));
|
||||
}
|
||||
|
||||
for (let i = 0; i < a.length; ++i) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public static findIndex(array: any[], predicate: Function): number {
|
||||
let value: any;
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
value = array[i];
|
||||
if (predicate(value, i, array)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
public static getDaylightSavingTimeOffset(startDate: Date, endDate: Date): number {
|
||||
const startDateTzOffset: number = startDate.getTimezoneOffset();
|
||||
const endDateTzOffset: number = endDate.getTimezoneOffset();
|
||||
|
||||
return (endDateTzOffset - startDateTzOffset) * 60 * 1000;
|
||||
}
|
||||
|
||||
public static toStringDateWithoutTimezone(date: Date): string {
|
||||
if (!date) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static previousDay(date: Date): Date {
|
||||
const prevDay: Date = Utils.resetTime(date);
|
||||
return Utils.getDateWithoutTimezone(date).toISOString();
|
||||
}
|
||||
|
||||
prevDay.setDate(prevDay.getDate() - 1);
|
||||
public static getEndOfThePreviousDate(date: Date): Date {
|
||||
const currentDate: Date = Utils.resetTime(date);
|
||||
|
||||
return prevDay;
|
||||
currentDate.setMilliseconds(-Utils.OffsetMilliseconds);
|
||||
|
||||
return currentDate;
|
||||
}
|
||||
|
||||
public static parseDateWithoutTimezone(dateString: string): Date {
|
||||
if (dateString === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const date: Date = new Date(dateString);
|
||||
|
||||
if (date.toString() === "Invalid Date") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const timeInMilliseconds: number = date.getTime()
|
||||
+ date.getTimezoneOffset() * Utils.TotalMilliseconds * Utils.TotalSeconds;
|
||||
|
||||
return new Date(timeInMilliseconds);
|
||||
}
|
||||
|
||||
public static resetTime(date: Date): Date {
|
||||
return new Date(
|
||||
date.getFullYear(),
|
||||
date.getMonth(),
|
||||
date.getDate());
|
||||
}
|
||||
|
||||
public static getDatePeriod(values: any[]): ITimelineDatePeriodBase {
|
||||
let startDate: Date;
|
||||
let endDate: Date;
|
||||
|
||||
values = [].concat(values);
|
||||
|
||||
values.forEach((value: any) => {
|
||||
const date: Date = Utils.parseDate(value);
|
||||
|
||||
if (date < startDate || startDate === undefined) {
|
||||
startDate = date;
|
||||
}
|
||||
|
||||
if (date > endDate || endDate === undefined) {
|
||||
endDate = date;
|
||||
}
|
||||
});
|
||||
|
||||
return { startDate, endDate };
|
||||
}
|
||||
|
||||
public static parseDate(value: any): Date {
|
||||
const typeOfValue: string = typeof value;
|
||||
let date: Date = value;
|
||||
|
||||
if (typeOfValue === "number") {
|
||||
date = new Date(value, 0);
|
||||
}
|
||||
|
||||
if (typeOfValue === "string") {
|
||||
date = new Date(value);
|
||||
}
|
||||
|
||||
if (date && date instanceof Date && date.toString() !== "Invalid Date") {
|
||||
return Utils.resetTime(date);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public static areBoundsOfSelectionAndAvailableDatesTheSame(timelineData: ITimelineData): boolean {
|
||||
const datePeriod: ITimelineDatePeriod[] = timelineData.currentGranularity.getDatePeriods();
|
||||
const startDate: Date = Utils.getStartSelectionDate(timelineData);
|
||||
const endDate: Date = Utils.getEndSelectionDate(timelineData);
|
||||
|
||||
return datePeriod
|
||||
&& datePeriod.length >= 1
|
||||
&& startDate
|
||||
&& endDate
|
||||
&& datePeriod[0].startDate.getTime() === startDate.getTime()
|
||||
&& datePeriod[datePeriod.length - 1].endDate.getTime() === endDate.getTime();
|
||||
}
|
||||
|
||||
public static getTheLatestDayOfMonth(monthId: number): number {
|
||||
const date: Date = new Date(2008, monthId + 1, 0); // leap year, so the latest day of February is 29.
|
||||
|
||||
return date.getDate();
|
||||
}
|
||||
|
||||
public static isValueEmpty(value: any): boolean {
|
||||
return value === undefined || value === null || isNaN(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date of the start of the selection
|
||||
* @param timelineData The TimelineData which contains all the date periods
|
||||
*/
|
||||
public static getStartSelectionDate(timelineData: ITimelineData): Date {
|
||||
return timelineData.currentGranularity.getDatePeriods()[timelineData.selectionStartIndex].startDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date of the end of the selection
|
||||
* @param timelineData The TimelineData which contains all the date periods
|
||||
*/
|
||||
public static getEndSelectionDate(timelineData: ITimelineData): Date {
|
||||
return timelineData.currentGranularity.getDatePeriods()[timelineData.selectionEndIndex].endDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date period of the end of the selection
|
||||
* @param timelineData The TimelineData which contains all the date periods
|
||||
*/
|
||||
public static getEndSelectionPeriod(timelineData: ITimelineData): ITimelineDatePeriod {
|
||||
return timelineData.currentGranularity.getDatePeriods()[timelineData.selectionEndIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color of a cell, depending on whether its date period is between the selected date periods.
|
||||
* CellRects should be transparent filled by default if there isn't any color sets.
|
||||
* @param d The TimelineDataPoint of the cell
|
||||
* @param timelineData The TimelineData with the selected date periods
|
||||
* @param timelineFormat The TimelineFormat with the chosen colors
|
||||
*/
|
||||
public static getCellColor(
|
||||
dataPoint: ITimelineDataPoint,
|
||||
timelineData: ITimelineData,
|
||||
cellSettings: CellsSettings): string {
|
||||
|
||||
const inSelectedPeriods: boolean = dataPoint.datePeriod.startDate >= Utils.getStartSelectionDate(timelineData)
|
||||
&& dataPoint.datePeriod.endDate <= Utils.getEndSelectionDate(timelineData);
|
||||
|
||||
return inSelectedPeriods
|
||||
? cellSettings.fillSelected
|
||||
: (cellSettings.fillUnselected || Utils.DefaultCellColor);
|
||||
}
|
||||
|
||||
public static isGranuleSelected(dataPoint: ITimelineDataPoint, timelineData: ITimelineData, cellSettings: CellsSettings): boolean {
|
||||
return dataPoint.datePeriod.startDate >= Utils.getStartSelectionDate(timelineData)
|
||||
&& dataPoint.datePeriod.endDate <= Utils.getEndSelectionDate(timelineData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the granularity type of the given granularity name
|
||||
* @param granularityName The name of the granularity
|
||||
*/
|
||||
public static getGranularityType(granularityName: string): GranularityType {
|
||||
const index: number = Utils.findIndex(GranularityNames, (granularity: IGranularityName) => {
|
||||
return granularity.name === granularityName;
|
||||
});
|
||||
|
||||
return GranularityNames[index].granularityType;
|
||||
}
|
||||
|
||||
public static getGranularityPropsByMarker(marker: string): IGranularityName {
|
||||
const index: number = Utils.findIndex(GranularityNames, (granularity: IGranularityName) => {
|
||||
return granularity.marker === marker;
|
||||
});
|
||||
|
||||
return GranularityNames[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the granularity type
|
||||
* @param granularity The type of granularity
|
||||
*/
|
||||
public static getGranularityNameKey(granularityType: GranularityType): string {
|
||||
const index: number = Utils.findIndex(GranularityNames, (granularity: IGranularityName) => {
|
||||
return granularity.granularityType === granularityType;
|
||||
});
|
||||
|
||||
return GranularityNames[index].nameKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the date periods of the current granularity, in case the start and end of the selection is in between a date period.
|
||||
* i.e. for a quarter granularity and a selection between Feb 6 and Dec 23, the date periods for Q1 and Q4 will be split accordingly
|
||||
* @param timelineData The TimelineData that contains the date periods
|
||||
* @param startDate The starting date of the selection
|
||||
* @param endDate The ending date of the selection
|
||||
*/
|
||||
public static separateSelection(timelineData: ITimelineData, startDate: Date, endDate: Date): void {
|
||||
const datePeriods: ITimelineDatePeriod[] = timelineData.currentGranularity.getDatePeriods();
|
||||
|
||||
let startDateIndex: number = Utils.findIndex(datePeriods, (x) => startDate < x.endDate);
|
||||
let endDateIndex: number = Utils.findIndex(datePeriods, (x) => endDate <= x.endDate);
|
||||
|
||||
startDateIndex = startDateIndex >= 0
|
||||
? startDateIndex
|
||||
: 0;
|
||||
|
||||
endDateIndex = endDateIndex >= 0
|
||||
? endDateIndex
|
||||
: datePeriods.length - 1;
|
||||
|
||||
timelineData.selectionStartIndex = startDateIndex;
|
||||
timelineData.selectionEndIndex = endDateIndex;
|
||||
|
||||
const startRatio: number = Utils.getDateRatio(datePeriods[startDateIndex], startDate, true);
|
||||
const endRatio: number = Utils.getDateRatio(datePeriods[endDateIndex], endDate, false);
|
||||
|
||||
if (endRatio > 0) {
|
||||
timelineData.currentGranularity.splitPeriod(endDateIndex, endRatio, endDate);
|
||||
}
|
||||
|
||||
if (startRatio > 0) {
|
||||
const startFration: number = datePeriods[startDateIndex].fraction - startRatio;
|
||||
|
||||
timelineData.currentGranularity.splitPeriod(startDateIndex, startFration, startDate);
|
||||
|
||||
timelineData.selectionStartIndex++;
|
||||
timelineData.selectionEndIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ratio of the given date compared to the whole date period.
|
||||
* The ratio is calculated either from the start or the end of the date period.
|
||||
* i.e. the ratio of Feb 7 2016 compared to the month of Feb 2016,
|
||||
* is 0.2142 from the start of the month, or 0.7857 from the end of the month.
|
||||
* @param datePeriod The date period that contain the specified date
|
||||
* @param date The date
|
||||
* @param fromStart Whether to calculater the ratio from the start of the date period.
|
||||
*/
|
||||
public static getDateRatio(datePeriod: ITimelineDatePeriod, date: Date, fromStart: boolean): number {
|
||||
const dateDifference: number = fromStart
|
||||
? date.getTime() - datePeriod.startDate.getTime()
|
||||
: datePeriod.endDate.getTime() - date.getTime();
|
||||
|
||||
const periodDifference: number = datePeriod.endDate.getTime() - datePeriod.startDate.getTime();
|
||||
|
||||
return periodDifference === 0
|
||||
? 0
|
||||
: dateDifference / periodDifference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time range text, depending on the given granularity (e.g. "Feb 3 2014 - Apr 5 2015", "Q1 2014 - Q2 2015")
|
||||
*/
|
||||
public static timeRangeText(timelineData: ITimelineData): string {
|
||||
const startSelectionDateArray: Array<string | number> = timelineData.currentGranularity
|
||||
.splitDateForTitle(Utils.getStartSelectionDate(timelineData));
|
||||
|
||||
const endSelectionDateArray: Array<string | number> = timelineData.currentGranularity
|
||||
.splitDateForTitle(Utils.getEndSelectionPeriod(timelineData).startDate);
|
||||
|
||||
const startSelectionString: string = startSelectionDateArray.join(Utils.DateArrayJoiner);
|
||||
const endSelectionString: string = endSelectionDateArray.join(Utils.DateArrayJoiner);
|
||||
|
||||
return `${startSelectionString}${Utils.DateSplitter}${endSelectionString}`;
|
||||
}
|
||||
|
||||
public static dateRangeText(datePeriod: ITimelineDatePeriod): string {
|
||||
return `${datePeriod.startDate.toDateString()}${Utils.DateSplitter}${this.previousDay(datePeriod.endDate).toDateString()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines the first two partial date periods, into a single date period.
|
||||
* i.e. combines "Feb 1 2016 - Feb 5 2016" with "Feb 5 2016 - Feb 29 2016" into "Feb 1 2016 - Feb 29 2016"
|
||||
* @param datePeriods The list of date periods
|
||||
*/
|
||||
public static unseparateSelection(datePeriods: ITimelineDatePeriod[]): void {
|
||||
const separationIndex: number = Utils.findIndex(
|
||||
datePeriods,
|
||||
(datePeriod: ITimelineDatePeriod) => {
|
||||
return datePeriod.fraction < Utils.MinFraction;
|
||||
});
|
||||
|
||||
if (separationIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
datePeriods[separationIndex].endDate = datePeriods[separationIndex + 1].endDate;
|
||||
datePeriods[separationIndex].fraction += datePeriods[separationIndex + 1].fraction;
|
||||
|
||||
datePeriods.splice(separationIndex + 1, 1);
|
||||
}
|
||||
|
||||
public static getIndexByPosition(
|
||||
elements: number[],
|
||||
widthOfElement: number,
|
||||
position: number,
|
||||
offset: number = 0): number {
|
||||
|
||||
elements = elements || [];
|
||||
|
||||
const length: number = elements.length;
|
||||
|
||||
if (!Utils.isValueEmpty(elements[0])
|
||||
&& !Utils.isValueEmpty(elements[1])
|
||||
&& position <= elements[1] * widthOfElement + offset) {
|
||||
|
||||
return 0;
|
||||
} else if (
|
||||
!Utils.isValueEmpty(elements[length - 1])
|
||||
&& position >= elements[length - 1] * widthOfElement + offset) {
|
||||
|
||||
return length - 1;
|
||||
}
|
||||
|
||||
for (let i: number = 1; i < length; i++) {
|
||||
const left: number = elements[i] * widthOfElement + offset;
|
||||
const right: number = elements[i + 1] * widthOfElement + offset;
|
||||
|
||||
if (position >= left && position <= right) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static arraysEqual(a: any[], b: any[]): boolean {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a === null || b === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If you don't care about the order of the elements inside
|
||||
// the array, you should sort both arrays here.
|
||||
|
||||
for (let i = 0; i < a.length; ++i) {
|
||||
if (a[i] !== b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static findIndex(
|
||||
array: any[],
|
||||
predicate: (value: any, index: number, array: any[]) => boolean,
|
||||
): number {
|
||||
let value: any;
|
||||
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
value = array[i];
|
||||
if (predicate(value, i, array)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static DateSplitter: string = " - ";
|
||||
private static MinFraction: number = 1;
|
||||
private static TotalDaysInWeek: number = 7;
|
||||
private static WeekDayOffset: number = 1;
|
||||
private static DateArrayJoiner: string = " ";
|
||||
|
||||
/**
|
||||
* We should reduce the latest date of selection using this value,
|
||||
* because, as far as I understand, PBI Framework rounds off milliseconds.
|
||||
*/
|
||||
private static OffsetMilliseconds: number = 999;
|
||||
|
||||
private static TotalMillisecondsInADay: number =
|
||||
Utils.TotalMilliseconds
|
||||
* Utils.TotalSeconds
|
||||
* Utils.TotalMinutes
|
||||
* Utils.TotalHours;
|
||||
|
||||
private static previousDay(date: Date): Date {
|
||||
const prevDay: Date = Utils.resetTime(date);
|
||||
|
||||
prevDay.setDate(prevDay.getDate() - 1);
|
||||
|
||||
return prevDay;
|
||||
}
|
||||
}
|
||||
|
|
2743
src/visual.ts
2743
src/visual.ts
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -32,71 +32,78 @@
|
|||
@import (less) "node_modules/powerbi-visuals-utils-formattingutils/lib/index.css";
|
||||
@import (less) "node_modules/powerbi-visuals-utils-chartutils/lib/index.css";
|
||||
|
||||
.timeline {
|
||||
font-family: helvetica, arial, sans-serif;
|
||||
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
.timeline-component {
|
||||
cursor: default;
|
||||
|
||||
.label,
|
||||
.mainArea .label,
|
||||
.upperTextCell,
|
||||
.lowerTextCell {
|
||||
text-anchor: middle;
|
||||
}
|
||||
.timeline {
|
||||
font-family: helvetica, arial, sans-serif;
|
||||
|
||||
.cellRect {
|
||||
stroke: #333444;
|
||||
}
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
.selection {
|
||||
&Cursor {
|
||||
fill: gray;
|
||||
}
|
||||
cursor: default;
|
||||
|
||||
&RangeContainer {
|
||||
text-anchor: start;
|
||||
}
|
||||
}
|
||||
|
||||
.periodSlicerSelectionRect,
|
||||
.timelineVertLine,
|
||||
.cellRect {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selectionCursor {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
}
|
||||
|
||||
.timelineSlicer {
|
||||
fill: #000000;
|
||||
.periodSlicer {
|
||||
&Rect {
|
||||
stroke: #aaa;
|
||||
stroke-width: 2px;
|
||||
fill: transparent;
|
||||
}
|
||||
|
||||
&Granularities,
|
||||
&Selection {
|
||||
font-size: 10px;
|
||||
.label,
|
||||
.mainArea .label,
|
||||
.upperTextCell,
|
||||
.lowerTextCell {
|
||||
text-anchor: middle;
|
||||
}
|
||||
|
||||
&SelectionRect {
|
||||
fill: transparent;
|
||||
.cellRect {
|
||||
stroke: #333444;
|
||||
}
|
||||
|
||||
.selection {
|
||||
&Cursor {
|
||||
fill: gray;
|
||||
}
|
||||
|
||||
&RangeContainer {
|
||||
text-anchor: start;
|
||||
}
|
||||
}
|
||||
|
||||
.periodSlicerSelectionRect,
|
||||
.timelineVertLine,
|
||||
.cellRect {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selectionCursor {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
}
|
||||
|
||||
.periodSlicerRect {
|
||||
pointer-events: none;
|
||||
.timelineSlicer {
|
||||
fill: #000000;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
.periodSlicer {
|
||||
&Rect {
|
||||
stroke: #aaa;
|
||||
stroke-width: 2px;
|
||||
fill: transparent;
|
||||
}
|
||||
|
||||
&Granularities,
|
||||
&Selection {
|
||||
font-size: 10px;
|
||||
text-anchor: middle;
|
||||
}
|
||||
|
||||
&SelectionRect {
|
||||
fill: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.periodSlicerRect {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.timelineWrapper {
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.timelineWrapper {
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
const path = require('path');
|
||||
const webpack = require("webpack");
|
||||
|
||||
module.exports = {
|
||||
devtool: 'inline-source-map',
|
||||
mode: 'development',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
enforce: 'pre',
|
||||
exclude: /node_modules/,
|
||||
use: [{
|
||||
loader: 'tslint-loader',
|
||||
options: {
|
||||
emitErrors: true,
|
||||
failOnHint: true,
|
||||
fix: false,
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /(node_modules|dist|coverage|karma.conf.ts)/
|
||||
},
|
||||
{
|
||||
test: /\.ts$/i,
|
||||
enforce: 'post',
|
||||
include: /(src)/,
|
||||
exclude: /(specs|node_modules|resources\/js\/vendor)/,
|
||||
loader: 'istanbul-instrumenter-loader',
|
||||
options: {
|
||||
esModules: true
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: [{
|
||||
loader: 'style-loader'
|
||||
},
|
||||
{
|
||||
loader: 'css-loader'
|
||||
},
|
||||
{
|
||||
loader: 'less-loader',
|
||||
options: {
|
||||
paths: [path.resolve(__dirname, 'node_modules')]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
externals: {
|
||||
"powerbi-visuals-api": '{}'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js', '.css']
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, ".tmp"),
|
||||
filename: "specs.js"
|
||||
},
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({
|
||||
'powerbi-visuals-api': null
|
||||
}),
|
||||
],
|
||||
};
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { Selection } from "d3-selection";
|
||||
|
||||
import { ITimelineDatePeriod } from "../src/datePeriod/datePeriod";
|
||||
import { IGranularity } from "../src/granularity/granularity";
|
||||
import { IGranularityRenderProps } from "../src/granularity/granularityRenderProps";
|
||||
import { GranularityType } from "../src/granularity/granularityType";
|
||||
|
||||
import {
|
||||
IExtendedLabel,
|
||||
ITimelineLabel,
|
||||
} from "../src/dataInterfaces";
|
||||
|
||||
export class TimelineGranularityMock implements IGranularity {
|
||||
private datePeriod: ITimelineDatePeriod[];
|
||||
|
||||
constructor(datePeriod: ITimelineDatePeriod[] = []) {
|
||||
this.datePeriod = datePeriod;
|
||||
}
|
||||
|
||||
public setDatePeriod(datePeriod: ITimelineDatePeriod[]): void {
|
||||
this.datePeriod = datePeriod;
|
||||
}
|
||||
|
||||
public getType(): GranularityType {
|
||||
return GranularityType.day;
|
||||
}
|
||||
|
||||
public splitDate(date: Date): Array<string | number> {
|
||||
return [0];
|
||||
}
|
||||
|
||||
public getDatePeriods(): ITimelineDatePeriod[] {
|
||||
return this.datePeriod;
|
||||
}
|
||||
|
||||
public resetDatePeriods(): void {
|
||||
// No need to implement it for UTs
|
||||
}
|
||||
|
||||
public getExtendedLabel(): IExtendedLabel {
|
||||
return null;
|
||||
}
|
||||
|
||||
public setExtendedLabel(extendedLabel: IExtendedLabel): void {
|
||||
// No need to implement it for UTs
|
||||
}
|
||||
|
||||
public createLabels(granularity: IGranularity): ITimelineLabel[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
public sameLabel(
|
||||
firstDatePeriod: ITimelineDatePeriod,
|
||||
secondDatePeriod: ITimelineDatePeriod,
|
||||
): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public generateLabel(datePeriod: ITimelineDatePeriod): ITimelineLabel {
|
||||
return null;
|
||||
}
|
||||
|
||||
public addDate(date: Date) {
|
||||
// No need to implement it for UTs
|
||||
}
|
||||
|
||||
public setNewEndDate(date: Date): void {
|
||||
// No need to implement it for UTs
|
||||
}
|
||||
|
||||
public splitPeriod(index: number, newFraction: number, newDate: Date): void {
|
||||
// No need to implement it for UTs
|
||||
}
|
||||
|
||||
public splitDateForTitle(date: Date): Array<string | number> {
|
||||
return [];
|
||||
}
|
||||
|
||||
public render(
|
||||
props: IGranularityRenderProps,
|
||||
isFirst: boolean,
|
||||
): Selection<any, any, any, any> {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -24,23 +24,27 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
// Power BI API
|
||||
/// <reference path="../.api/v1.13.0/PowerBI-visuals.d.ts" />
|
||||
import {
|
||||
parseColorString,
|
||||
RgbColor,
|
||||
} from "powerbi-visuals-utils-testutils";
|
||||
|
||||
// Power BI Extensibility
|
||||
/// <reference path="../node_modules/powerbi-visuals-utils-dataviewutils/lib/index.d.ts" />
|
||||
/// <reference path="../node_modules/powerbi-visuals-utils-typeutils/lib/index.d.ts" />
|
||||
/// <reference path="../node_modules/powerbi-visuals-utils-svgutils/lib/index.d.ts" />
|
||||
/// <reference path="../node_modules/powerbi-visuals-utils-interactivityutils/lib/index.d.ts" />
|
||||
/// <reference path="../node_modules/powerbi-visuals-utils-formattingutils/lib/index.d.ts" />
|
||||
/// <reference path="../node_modules/powerbi-visuals-utils-chartutils/lib/index.d.ts" />
|
||||
/// <reference path="../node_modules/powerbi-visuals-utils-testutils/lib/index.d.ts"/>
|
||||
import { range } from "d3-array";
|
||||
|
||||
// The visual
|
||||
/// <reference path="../.tmp/drop/visual.d.ts" />
|
||||
export function areColorsEqual(firstColor: string, secondColor: string): boolean {
|
||||
const firstConvertedColor: RgbColor = parseColorString(firstColor);
|
||||
const secondConvertedColor: RgbColor = parseColorString(secondColor);
|
||||
|
||||
// Test
|
||||
/// <reference path="helpers/helpers.ts" />
|
||||
/// <reference path="mocks/granularityMock.ts" />
|
||||
/// <reference path="visualData.ts" />
|
||||
/// <reference path="visualBuilder.ts" />
|
||||
return firstConvertedColor.R === secondConvertedColor.R
|
||||
&& firstConvertedColor.G === secondConvertedColor.G
|
||||
&& firstConvertedColor.B === secondConvertedColor.B;
|
||||
}
|
||||
|
||||
export function getDateRange(start: Date, stop: Date, step: number): Date[] {
|
||||
return range(start.getTime(), stop.getTime(), step)
|
||||
.map((milliseconds: number) => new Date(milliseconds));
|
||||
}
|
||||
|
||||
export function getSolidColorStructuralObject(color: string): any {
|
||||
return { solid: { color } };
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/// <reference path="../_references.ts"/>
|
||||
|
||||
module powerbi.extensibility.visual.test.mocks {
|
||||
// Timeline1447991079100
|
||||
import ExtendedLabel = powerbi.extensibility.visual.Timeline1447991079100.ExtendedLabel;
|
||||
import TimelineLabel = powerbi.extensibility.visual.Timeline1447991079100.TimelineLabel;
|
||||
import Granularity = powerbi.extensibility.visual.Timeline1447991079100.granularity.Granularity;
|
||||
import GranularityType = powerbi.extensibility.visual.Timeline1447991079100.granularity.GranularityType;
|
||||
import TimelineDatePeriod = powerbi.extensibility.visual.Timeline1447991079100.datePeriod.TimelineDatePeriod;
|
||||
|
||||
export class TimelineGranularityMock implements Granularity {
|
||||
private datePeriod: TimelineDatePeriod[];
|
||||
|
||||
constructor(datePeriod: TimelineDatePeriod[] = []) {
|
||||
this.datePeriod = datePeriod;
|
||||
}
|
||||
|
||||
public setDatePeriod(datePeriod: TimelineDatePeriod[]): void {
|
||||
this.datePeriod = datePeriod;
|
||||
}
|
||||
|
||||
public getType(): GranularityType {
|
||||
return GranularityType.day;
|
||||
}
|
||||
|
||||
public splitDate(date: Date): (string | number)[] {
|
||||
return [0];
|
||||
}
|
||||
|
||||
public getDatePeriods(): TimelineDatePeriod[] {
|
||||
return this.datePeriod;
|
||||
}
|
||||
|
||||
public resetDatePeriods(): void { }
|
||||
|
||||
public getExtendedLabel(): ExtendedLabel {
|
||||
return null;
|
||||
}
|
||||
|
||||
public setExtendedLabel(extendedLabel: ExtendedLabel): void { }
|
||||
|
||||
public createLabels(granularity: Granularity): TimelineLabel[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
public sameLabel(
|
||||
firstDatePeriod: TimelineDatePeriod,
|
||||
secondDatePeriod: TimelineDatePeriod): boolean {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public generateLabel(datePeriod: TimelineDatePeriod): TimelineLabel {
|
||||
return null;
|
||||
}
|
||||
|
||||
public addDate(date: Date) { }
|
||||
|
||||
public setNewEndDate(date: Date): void { }
|
||||
|
||||
public splitPeriod(index: number, newFraction: number, newDate: Date): void { }
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -24,87 +24,122 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/// <reference path="_references.ts"/>
|
||||
import powerbi from "powerbi-visuals-api";
|
||||
|
||||
module powerbi.extensibility.visual.test {
|
||||
// powerbi.extensibility.utils.test
|
||||
import VisualBuilderBase = powerbi.extensibility.utils.test.VisualBuilderBase;
|
||||
import {
|
||||
AdvancedFilter,
|
||||
} from "powerbi-models";
|
||||
|
||||
// Timeline1447991079100
|
||||
import SandboxedVisualNameSpace = powerbi.extensibility.visual.Timeline1447991079100;
|
||||
import VisualClass = SandboxedVisualNameSpace.Timeline;
|
||||
import VisualSettings = SandboxedVisualNameSpace.settings.VisualSettings;
|
||||
import TimelineDatePeriodBase = SandboxedVisualNameSpace.datePeriod.TimelineDatePeriodBase;
|
||||
import {
|
||||
d3Click,
|
||||
VisualBuilderBase,
|
||||
} from "powerbi-visuals-utils-testutils";
|
||||
|
||||
export class TimelineBuilder extends VisualBuilderBase<VisualClass> {
|
||||
constructor(width: number, height: number) {
|
||||
super(width, height, "Timeline1447991079100");
|
||||
import { TimelineDatePeriodBase } from "../src/datePeriod/datePeriodBase";
|
||||
import { Timeline } from "../src/visual";
|
||||
|
||||
this.visualHost.applyJsonFilter = () => {};
|
||||
}
|
||||
export class TimelineBuilder extends VisualBuilderBase<Timeline> {
|
||||
public static setDatePeriod(
|
||||
dataView: powerbi.DataView,
|
||||
datePeriod: TimelineDatePeriodBase): void {
|
||||
|
||||
protected build(options: VisualConstructorOptions): VisualClass {
|
||||
return new VisualClass(options);
|
||||
}
|
||||
(dataView.metadata.objects as any).general = {
|
||||
datePeriod: datePeriod.toString(),
|
||||
isUserSelection: true,
|
||||
};
|
||||
}
|
||||
|
||||
public get visualObject(): VisualClass {
|
||||
return this.visual;
|
||||
}
|
||||
private jsonFilters: powerbi.IFilter[] = [];
|
||||
|
||||
public get mainElement(): JQuery {
|
||||
return this.element
|
||||
.find("svg.timeline");
|
||||
}
|
||||
constructor(width: number, height: number) {
|
||||
super(width, height);
|
||||
|
||||
public get headerElement(): JQuery {
|
||||
return this.element
|
||||
.children("div")
|
||||
.children("svg");
|
||||
}
|
||||
this.visualHost.applyJsonFilter = () => {
|
||||
// No need to implement it
|
||||
};
|
||||
}
|
||||
|
||||
public get cellRects(): JQuery {
|
||||
return this.mainArea
|
||||
.children(".cellsArea")
|
||||
.children(".cellRect");
|
||||
}
|
||||
public get visualObject(): Timeline {
|
||||
return this.visual;
|
||||
}
|
||||
|
||||
public get mainArea() {
|
||||
return this.mainElement
|
||||
.children("g.mainArea");
|
||||
}
|
||||
public get rootElement(): JQuery {
|
||||
return this.element.find(".timeline-component");
|
||||
}
|
||||
|
||||
public get allLabels() {
|
||||
return this.mainArea
|
||||
.children("g")
|
||||
.children("text.label");
|
||||
}
|
||||
public get mainElement(): JQuery {
|
||||
return this.element
|
||||
.find("svg.timeline");
|
||||
}
|
||||
|
||||
public get rangeHeaderText() {
|
||||
return this.headerElement
|
||||
.children("g.rangeTextArea")
|
||||
.children("text.selectionRangeContainer");
|
||||
}
|
||||
public get headerElement(): JQuery {
|
||||
return this.element
|
||||
.children("div")
|
||||
.children("svg");
|
||||
}
|
||||
|
||||
public get timelineSlicer() {
|
||||
return this.headerElement
|
||||
.children("g.timelineSlicer");
|
||||
}
|
||||
public get cellRects(): JQuery {
|
||||
return this.mainArea
|
||||
.children(".cellsArea")
|
||||
.children(".cellRect");
|
||||
}
|
||||
|
||||
public selectTheLatestCell(dataView: DataView): void {
|
||||
this.mainElement
|
||||
.find(".cellRect")
|
||||
.last()
|
||||
.d3Click(0, 0);
|
||||
}
|
||||
public get mainArea() {
|
||||
return this.mainElement
|
||||
.children("g.mainArea");
|
||||
}
|
||||
|
||||
public static setDatePeriod(
|
||||
dataView: DataView,
|
||||
datePeriod: TimelineDatePeriodBase): void {
|
||||
public get allLabels() {
|
||||
return this.mainArea
|
||||
.children("g")
|
||||
.children("text.label");
|
||||
}
|
||||
|
||||
(dataView.metadata.objects as any).general = {
|
||||
datePeriod: datePeriod.toString(),
|
||||
isUserSelection: true
|
||||
};
|
||||
}
|
||||
public get rangeHeaderText() {
|
||||
return this.headerElement
|
||||
.children("g.rangeTextArea")
|
||||
.children("text.selectionRangeContainer");
|
||||
}
|
||||
|
||||
public get timelineSlicer() {
|
||||
return this.headerElement
|
||||
.children("g.timelineSlicer");
|
||||
}
|
||||
|
||||
public selectTheLatestCell(): void {
|
||||
d3Click(this.mainElement.find(".cellRect").last(), 0, 0);
|
||||
}
|
||||
|
||||
public setFilter(startDate: Date, endDate: Date): void {
|
||||
const filter = new AdvancedFilter(
|
||||
{
|
||||
column: "Test",
|
||||
table: "Demo",
|
||||
},
|
||||
"And",
|
||||
{
|
||||
operator: "GreaterThanOrEqual",
|
||||
value: startDate,
|
||||
},
|
||||
{
|
||||
operator: "LessThanOrEqual",
|
||||
value: endDate,
|
||||
},
|
||||
);
|
||||
|
||||
this.jsonFilters = [filter];
|
||||
}
|
||||
|
||||
public update(dataView) {
|
||||
this.visual.update({
|
||||
dataViews: [].concat(dataView),
|
||||
jsonFilters: this.jsonFilters,
|
||||
type: undefined,
|
||||
viewport: this.viewport,
|
||||
});
|
||||
}
|
||||
|
||||
protected build(options: powerbi.extensibility.visual.VisualConstructorOptions): Timeline {
|
||||
return new Timeline(options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,62 +24,57 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/// <reference path="_references.ts"/>
|
||||
import powerbi from "powerbi-visuals-api";
|
||||
|
||||
module powerbi.extensibility.visual.test {
|
||||
// powerbi.extensibility.visual.test
|
||||
import getDateRange = powerbi.extensibility.visual.test.helpers.getDateRange;
|
||||
import { testDataViewBuilder } from "powerbi-visuals-utils-testutils";
|
||||
import { valueType } from "powerbi-visuals-utils-typeutils";
|
||||
|
||||
// powerbi.extensibility.utils.type
|
||||
import ValueType = powerbi.extensibility.utils.type.ValueType;
|
||||
import { getDateRange } from "./helpers";
|
||||
|
||||
// powerbi.extensibility.utils.test
|
||||
import TestDataViewBuilder = powerbi.extensibility.utils.test.dataViewBuilder.TestDataViewBuilder;
|
||||
export class TimelineData extends testDataViewBuilder.TestDataViewBuilder {
|
||||
public static ColumnCategory: string = "Date";
|
||||
|
||||
export class TimelineData extends TestDataViewBuilder {
|
||||
public static ColumnCategory: string = "Date";
|
||||
public valuesCategory: Date[] = getDateRange(
|
||||
new Date(2016, 0, 1),
|
||||
new Date(2016, 0, 10),
|
||||
1000 * 24 * 3600,
|
||||
);
|
||||
|
||||
public valuesCategory: Date[] = getDateRange(
|
||||
new Date(2016, 0, 1),
|
||||
new Date(2016, 0, 10),
|
||||
1000 * 24 * 3600);
|
||||
public setDateRange(startDate: Date, endDate: Date) {
|
||||
this.valuesCategory = getDateRange(startDate, endDate, 1000 * 24 * 3600);
|
||||
}
|
||||
|
||||
public setDateRange(startDate: Date, endDate: Date) {
|
||||
this.valuesCategory = getDateRange(startDate, endDate, 1000 * 24 * 3600);
|
||||
}
|
||||
public getDataView(columnNames?: string[]): powerbi.DataView {
|
||||
return this.createCategoricalDataViewBuilder([
|
||||
{
|
||||
source: {
|
||||
displayName: TimelineData.ColumnCategory,
|
||||
roles: { Category: true },
|
||||
type: valueType.ValueType.fromDescriptor({ dateTime: true }),
|
||||
},
|
||||
values: this.valuesCategory,
|
||||
},
|
||||
|
||||
public getDataView(columnNames?: string[]): powerbi.DataView {
|
||||
return this.createCategoricalDataViewBuilder([
|
||||
{
|
||||
source: {
|
||||
displayName: TimelineData.ColumnCategory,
|
||||
roles: { Category: true },
|
||||
type: ValueType.fromDescriptor({ dateTime: true })
|
||||
},
|
||||
values: this.valuesCategory
|
||||
}
|
||||
], null, columnNames).build();
|
||||
}
|
||||
|
||||
], null, columnNames).build();
|
||||
}
|
||||
|
||||
public getUnWorkableDataView(columnNames?: string[]): powerbi.DataView {
|
||||
return this.createCategoricalDataViewBuilder([
|
||||
{
|
||||
source: {
|
||||
displayName: "Country",
|
||||
type: ValueType.fromDescriptor({ text: true }),
|
||||
roles: { Category: true }
|
||||
},
|
||||
values: [
|
||||
"Australia",
|
||||
"Canada",
|
||||
"France",
|
||||
"Germany",
|
||||
"United Kingdom",
|
||||
"United States"
|
||||
]
|
||||
}
|
||||
], null, null).build();
|
||||
}
|
||||
public getUnWorkableDataView(): powerbi.DataView {
|
||||
return this.createCategoricalDataViewBuilder([
|
||||
{
|
||||
source: {
|
||||
displayName: "Country",
|
||||
roles: { Category: true },
|
||||
type: valueType.ValueType.fromDescriptor({ text: true }),
|
||||
},
|
||||
values: [
|
||||
"Australia",
|
||||
"Canada",
|
||||
"France",
|
||||
"Germany",
|
||||
"United Kingdom",
|
||||
"United States",
|
||||
],
|
||||
},
|
||||
], null, null).build();
|
||||
}
|
||||
}
|
||||
|
|
1654
test/visualTest.ts
1654
test/visualTest.ts
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -3,40 +3,18 @@
|
|||
"allowJs": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "ES5",
|
||||
"module": "es2015",
|
||||
"target": "es2015",
|
||||
"sourceMap": true,
|
||||
"out": "./.tmp/build/visual.js",
|
||||
"declaration": true
|
||||
"outDir": "./.tmp/build/",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"lib": [
|
||||
"es2015",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
".api/v1.13.0/PowerBI-visuals.d.ts",
|
||||
"node_modules/powerbi-models/dist/models-noexports.d.ts",
|
||||
"node_modules/powerbi-visuals-utils-typeutils/lib/index.d.ts",
|
||||
"node_modules/powerbi-visuals-utils-svgutils/lib/index.d.ts",
|
||||
"node_modules/powerbi-visuals-utils-dataviewutils/lib/index.d.ts",
|
||||
"node_modules/powerbi-visuals-utils-formattingutils/lib/index.d.ts",
|
||||
"node_modules/powerbi-visuals-utils-interactivityutils/lib/index.d.ts",
|
||||
"node_modules/powerbi-visuals-utils-chartutils/lib/index.d.ts",
|
||||
"src/dataInterfaces.ts",
|
||||
"src/granularity/granularityType.ts",
|
||||
"src/granularity/granularityNames.ts",
|
||||
"src/utils.ts",
|
||||
"src/granularity/granularity.ts",
|
||||
"src/granularity/granularityBase.ts",
|
||||
"src/granularity/dayGranularity.ts",
|
||||
"src/granularity/granularityData.ts",
|
||||
"src/granularity/granularityName.ts",
|
||||
"src/granularity/granularityRenderProps.ts",
|
||||
"src/granularity/monthGranularity.ts",
|
||||
"src/granularity/quarterGranularity.ts",
|
||||
"src/granularity/weekGranularity.ts",
|
||||
"src/granularity/yearGranularity.ts",
|
||||
"src/calendar.ts",
|
||||
"src/datePeriod/datePeriod.ts",
|
||||
"src/granularity/granularity.ts",
|
||||
"src/scaleUtils.ts",
|
||||
"src/datePeriod/datePeriodBase.ts",
|
||||
"src/settings.ts",
|
||||
"src/visual.ts"
|
||||
"./src/visual.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
158
tslint.json
158
tslint.json
|
@ -1,72 +1,88 @@
|
|||
{
|
||||
"rules": {
|
||||
"no-banned-terms": true,
|
||||
"no-delete-expression": true,
|
||||
"no-document-domain": true,
|
||||
"no-disable-auto-sanitization": true,
|
||||
"no-duplicate-parameter-names": true,
|
||||
"no-exec-script": true,
|
||||
"no-function-constructor-with-string-args": true,
|
||||
"no-octal-literal": true,
|
||||
"no-reserved-keywords": true,
|
||||
"no-string-based-set-immediate": true,
|
||||
"no-string-based-set-interval": true,
|
||||
"no-string-based-set-timeout": true,
|
||||
"no-eval": true,
|
||||
"class-name": true,
|
||||
"comment-format": [
|
||||
true,
|
||||
"check-space"
|
||||
],
|
||||
"indent": [
|
||||
true,
|
||||
"spaces"
|
||||
],
|
||||
"no-duplicate-variable": true,
|
||||
"no-internal-module": false,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unsafe-finally": true,
|
||||
"no-var-keyword": true,
|
||||
"no-unused-variable": true,
|
||||
"no-unused-expression": true,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-open-brace",
|
||||
"check-whitespace"
|
||||
],
|
||||
"quotemark": [
|
||||
true,
|
||||
"double"
|
||||
],
|
||||
"semicolon": [
|
||||
true,
|
||||
"always"
|
||||
],
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
],
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"variable-name": [
|
||||
true,
|
||||
"ban-keywords"
|
||||
],
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type"
|
||||
]
|
||||
}
|
||||
}
|
||||
"extends": "tslint:recommended",
|
||||
"rulesDirectory": ["./node_modules/tslint-microsoft-contrib"],
|
||||
"rules": {
|
||||
"insecure-random": true,
|
||||
"no-banned-terms": true,
|
||||
"no-delete-expression": true,
|
||||
"no-disable-auto-sanitization": true,
|
||||
"no-document-domain": true,
|
||||
"no-document-write": true,
|
||||
"no-exec-script": true,
|
||||
"no-function-constructor-with-string-args": true,
|
||||
"no-http-string": [
|
||||
true
|
||||
],
|
||||
"no-inner-html": true,
|
||||
"no-octal-literal": true,
|
||||
"no-reserved-keywords": true,
|
||||
"no-string-based-set-immediate": true,
|
||||
"no-string-based-set-interval": true,
|
||||
"no-string-based-set-timeout": true,
|
||||
"non-literal-require": true,
|
||||
"possible-timing-attack": true,
|
||||
"react-anchor-blank-noopener": true,
|
||||
"react-iframe-missing-sandbox": true,
|
||||
"react-no-dangerous-html": true,
|
||||
"no-eval": true,
|
||||
"class-name": true,
|
||||
"max-line-length": {
|
||||
"options": [140]
|
||||
},
|
||||
"comment-format": [
|
||||
true,
|
||||
"check-space"
|
||||
],
|
||||
"indent": [
|
||||
true,
|
||||
"spaces"
|
||||
],
|
||||
"no-duplicate-variable": true,
|
||||
"no-internal-module": false,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unsafe-finally": true,
|
||||
"no-var-keyword": true,
|
||||
"no-unused-expression": true,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-open-brace",
|
||||
"check-whitespace"
|
||||
],
|
||||
"quotemark": [
|
||||
true,
|
||||
"double"
|
||||
],
|
||||
"semicolon": [
|
||||
true,
|
||||
"always"
|
||||
],
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
],
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"variable-name": [
|
||||
true,
|
||||
"ban-keywords"
|
||||
],
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type"
|
||||
],
|
||||
"forin": false,
|
||||
"no-reserved-keywords": false
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче