* 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:
Ignat Vilesov 2018-12-27 18:50:05 +03:00 коммит произвёл GitHub
Родитель 98379e1f29
Коммит 2a02083b5b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
52 изменённых файлов: 12089 добавлений и 11270 удалений

17
.editorconfig Normal file
Просмотреть файл

@ -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
.gitignore поставляемый
Просмотреть файл

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

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

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

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

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

96
karma.conf.ts Normal file
Просмотреть файл

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

12791
package-lock.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"
}
}

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

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

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

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

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

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

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

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

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

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

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

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

70
test.webpack.config.js Normal file
Просмотреть файл

@ -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
}),
],
};

111
test/granularityMock.ts Normal file
Просмотреть файл

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

1610
test/visual.test.ts Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -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();
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

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

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

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