Auto Precision + Stale Data options per metric + Subtitle form a Field + Infrastructure changes (#15)

* Auto Precision + Stale Data options per metric + Subtitle form a Field

* Infrastructure changes
This commit is contained in:
Evgenii Elkin 2021-02-05 12:29:47 +03:00 коммит произвёл GitHub
Родитель 01b059af73
Коммит b73ba91e5e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
49 изменённых файлов: 5250 добавлений и 6509 удалений

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

@ -1,3 +1,12 @@
## 2.3.0
* API 3.5.1
* Packages update
* Subtitle can be loaded form data also (will be merged with subtitle from options)
* Some of options from Stale Data option group can be set up for a metric
* New option "Auto Precision" that build values showing minimum 3 digits as 3.56 or 25.7 or 754 or 2345
* Context menu support
* FIXED: it is expected to see the line when values are the same but it will be shown a dot in sparklines and nothing in the main chart
## 2.2.1
* Fix for last date visual issue

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

@ -24,6 +24,10 @@
"displayName": "Change start date",
"name": "changeStartDateColumn",
"kind": "Grouping"
}, {
"displayName": "Subtitle",
"name": "subtitleColumn",
"kind": "Measure"
}],
"objects": {
"date": {
@ -56,6 +60,12 @@
}
}
},
"autoPrecision": {
"displayName": "Auto precision",
"type": {
"bool": true
}
},
"precision": {
"displayName": "Precision",
"type": {
@ -101,6 +111,12 @@
}
}
},
"autoPrecision": {
"displayName": "Auto precision",
"type": {
"bool": true
}
},
"precision": {
"displayName": "Precision",
"type": {
@ -171,6 +187,12 @@
}
}
},
"autoPrecision": {
"displayName": "Auto precision",
"type": {
"bool": true
}
},
"precision": {
"displayName": "Precision",
"type": {
@ -733,6 +755,12 @@
}
}
},
"autoPrecision": {
"displayName": "Auto precision",
"type": {
"bool": true
}
},
"precision": {
"displayName": "Precision",
"type": {
@ -789,6 +817,12 @@
}
}
},
"autoPrecision": {
"displayName": "Auto precision",
"type": {
"bool": true
}
},
"precision": {
"displayName": "Precision",
"type": {
@ -893,7 +927,7 @@
"displayName": "Stale Data",
"description": "To turn on Stale Data make sure to turn on Subtitle first",
"properties": {
"show": {
"isShown": {
"displayName": "Show",
"type": {
"bool": true
@ -920,7 +954,7 @@
}
},
"color": {
"displayName": "Color",
"displayName": "Color (for all only)",
"type": {
"fill": {
"solid": {
@ -930,7 +964,7 @@
}
},
"background": {
"displayName": "Background Color",
"displayName": "Background Color (for all only)",
"type": {
"fill": {
"solid": {
@ -972,6 +1006,9 @@
},
"changeStartDateColumn": {
"max": 0
},
"subtitleColumn": {
"max": 0
}
}, {
"dateColumn": {
@ -990,6 +1027,9 @@
},
"changeStartDateColumn": {
"max": 1
},
"subtitleColumn": {
"max": 1
}
}],
"categorical": {
@ -1006,6 +1046,10 @@
"for": {
"in": "changeStartDateColumn"
}
}, {
"for": {
"in": "subtitleColumn"
}
}],
"dataReductionAlgorithm": {
"top": {
@ -1033,5 +1077,6 @@
"direction": 1
}]
}
}
},
"supportsMultiVisualSelection": true
}

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

@ -32,13 +32,10 @@ const path = require("path");
const webpackConfig = require("./test.webpack.config.js");
const tsconfig = require("./tsconfig.json");
import { Config, ConfigOptions } from "karma";
const testRecursivePath = "specs/*.spec.ts";
const coverageFolder = "coverage";
module.exports = (config: Config) => {
module.exports = (config) => {
config.set({
browsers: ["ChromeHeadless"],
colors: true,
@ -92,5 +89,5 @@ module.exports = (config: Config) => {
webpackMiddleware: {
noInfo: true,
},
} as ConfigOptions);
});
};

10062
package-lock.json сгенерированный

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

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

@ -1,12 +1,13 @@
{
"name": "powerbi-visuals-multikpi",
"version": "2.2.1",
"version": "2.3.0",
"description": "Shows a KPI metric along with other metrics as sparklines",
"scripts": {
"start": "pbiviz start",
"package": "pbiviz package",
"test": "karma start",
"watch": "karma start --single-run=false"
"watch": "karma start --single-run=false",
"cert": "pbiviz --install-cert"
},
"repository": {
"type": "git",
@ -27,50 +28,51 @@
},
"homepage": "https://github.com/Microsoft/PowerBI-visuals-MultiKPI#readme",
"devDependencies": {
"@types/d3": "5.7.2",
"@types/jasmine": "3.4.4",
"@types/d3": "6.2.0",
"@types/jasmine": "3.6.2",
"@types/jasmine-jquery": "1.5.33",
"@types/jquery": "3.3.31",
"@types/karma": "^4.4.1",
"@types/puppeteer": "1.20.2",
"core-js": "3.3.3",
"css-loader": "3.2.0",
"@types/jquery": "3.5.5",
"@types/karma": "5.0.1",
"@types/puppeteer": "5.4.2",
"core-js": "3.8.1",
"css-loader": "5.0.1",
"istanbul-instrumenter-loader": "3.0.1",
"jasmine": "3.5.0",
"jasmine": "3.6.3",
"jasmine-jquery": "2.1.1",
"jquery": "3.4.1",
"karma": "4.4.1",
"jquery": "3.5.1",
"karma": "5.2.3",
"karma-chrome-launcher": "3.1.0",
"karma-coverage-istanbul-reporter": "2.1.0",
"karma-jasmine": "2.0.1",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-jasmine": "4.0.1",
"karma-junit-reporter": "2.0.1",
"karma-sourcemap-loader": "0.3.7",
"karma-sourcemap-loader": "0.3.8",
"karma-webpack": "4.0.2",
"less": "3.10.3",
"less-loader": "5.0.0",
"mini-css-extract-plugin": "0.8.0",
"powerbi-visuals-api": "2.6.1",
"powerbi-visuals-tools": "^3.1.12",
"powerbi-visuals-utils-testutils": "2.2.1",
"puppeteer": "1.13.0",
"style-loader": "1.0.0",
"ts-loader": "6.2.1",
"ts-node": "8.4.1",
"tslint": "5.20.0",
"less": "4.0.0",
"less-loader": "^5.0.0",
"mini-css-extract-plugin": "1.3.3",
"powerbi-visuals-api": "3.5.1",
"powerbi-visuals-tools": "3.1.15",
"powerbi-visuals-utils-testutils": "2.3.4",
"puppeteer": "5.5.0",
"style-loader": "2.0.0",
"ts-loader": "8.0.12",
"ts-node": "9.1.1",
"tslint": "6.1.1",
"tslint-loader": "3.5.4",
"tslint-microsoft-contrib": "6.2.0",
"typescript": "3.6.4",
"webpack": "4.41.2"
"typescript": "4.1.3",
"webpack": "^4.44.2"
},
"dependencies": {
"d3": "5.12.0",
"powerbi-visuals-utils-chartutils": "2.4.1",
"powerbi-visuals-utils-colorutils": "2.2.1",
"powerbi-visuals-utils-dataviewutils": "2.2.1",
"powerbi-visuals-utils-formattingutils": "4.4.2",
"powerbi-visuals-utils-interactivityutils": "5.6.0",
"powerbi-visuals-utils-svgutils": "2.2.1",
"powerbi-visuals-utils-tooltiputils": "2.3.1",
"powerbi-visuals-utils-typeutils": "2.2.1"
"d3": "6.3.1",
"powerbi-visuals-utils-chartutils": "2.5.0",
"powerbi-visuals-utils-colorutils": "2.3.0",
"powerbi-visuals-utils-dataviewutils": "2.4.0",
"powerbi-visuals-utils-formattingutils": "4.7.0",
"powerbi-visuals-utils-interactivityutils": "5.7.0",
"powerbi-visuals-utils-svgutils": "2.3.0",
"powerbi-visuals-utils-tooltiputils": "^2.5.1",
"powerbi-visuals-utils-typeutils": "2.3.0",
"regenerator-runtime": "^0.13.7"
}
}

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

@ -1,10 +1,10 @@
{
"visual": {
"name": "MultiKpi",
"displayName": "Multi KPI 2.2.1",
"displayName": "Multi KPI 2.3.0",
"guid": "multiKpiEA8DA325489E436991F0E411F2D85FF3",
"visualClassName": "MultiKpi",
"version": "2.2.1",
"version": "2.3.0",
"description": "Shows a KPI metric along with other metrics as sparklines",
"supportUrl": "https://aka.ms/customvisualscommunity",
"gitHubUrl": "https://github.com/Microsoft/PowerBI-visuals-MultiKPI"

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

@ -28,7 +28,7 @@ import "jasmine-jquery";
import * as $ from "jquery";
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { dispatch } from "d3-dispatch";
import { select as d3Select, Selection } from "d3-selection";
@ -55,7 +55,7 @@ import {
IDataRepresentationPoint,
} from "../src/converter/data/dataRepresentation";
import { isValueValid } from "../src/utils/valueUtils";
import { isValueValid } from "../src/utils/isValueValid";
import { DataConverter } from "../src/converter/data/dataConverter";
import { getFormattedValueWithFallback } from "../src/converter/data/dataFormatter";
@ -64,11 +64,457 @@ import { KpiBaseDescriptor } from "../src/settings/descriptors/kpi/kpiBaseDescri
import { NumericDescriptor } from "../src/settings/descriptors/numericDescriptor";
import { TestWrapper } from "./testWrapper";
import { SubtitleWarningComponent } from "../src/visualComponent/subtitleWarningComponent";
describe("Multi KPI", () => {
describe("Version 2.3.0 Changes", () => {
describe("DataFormatter", () => {
it("should return N/A if a variance is not valid", () => {
const numericDescriptor: NumericDescriptor = new NumericDescriptor();
numericDescriptor.autoPrecision = true;
expect(getFormattedValueWithFallback(NaN, numericDescriptor)).toBe("N/A");
});
it("should return 12.3K for 12340 if precision is auto and display units are auto", () => {
const value: number = 12340;
const expectedValue: string = "12.3K";
const numericDescriptor: NumericDescriptor = new NumericDescriptor();
numericDescriptor.autoPrecision = true;
numericDescriptor.precision = 5;
const actualValue: string = getFormattedValueWithFallback(
value,
numericDescriptor,
);
expect(actualValue).toBe(expectedValue);
});
it("should return 2.34K for 2340 if precision is auto and display units are auto", () => {
const value: number = 2340;
const expectedValue: string = "2.34K";
const numericDescriptor: NumericDescriptor = new NumericDescriptor();
numericDescriptor.autoPrecision = true;
numericDescriptor.precision = 5;
const actualValue: string = getFormattedValueWithFallback(
value,
numericDescriptor,
);
expect(actualValue).toBe(expectedValue);
});
it("should return 123K for 123403 if precision is auto and display units are auto", () => {
const value: number = 123403;
const expectedValue: string = "123K";
const numericDescriptor: NumericDescriptor = new NumericDescriptor();
numericDescriptor.autoPrecision = true;
numericDescriptor.precision = 5;
const actualValue: string = getFormattedValueWithFallback(
value,
numericDescriptor,
);
expect(actualValue).toBe(expectedValue);
});
it("should return 1.23M for 1234035 if precision is auto and display units are auto", () => {
const value: number = 1234035;
const expectedValue: string = "1.23M";
const numericDescriptor: NumericDescriptor = new NumericDescriptor();
numericDescriptor.autoPrecision = true;
numericDescriptor.precision = 5;
const actualValue: string = getFormattedValueWithFallback(
value,
numericDescriptor,
);
expect(actualValue).toBe(expectedValue);
});
it("should return 1234K for 1234035 if precision is auto and display units are thousands", () => {
const value: number = 1234035;
const expectedValue: string = "1234K";
const numericDescriptor: NumericDescriptor = new NumericDescriptor();
numericDescriptor.autoPrecision = true;
numericDescriptor.displayUnits = 1000;
numericDescriptor.precision = 5;
const actualValue: string = getFormattedValueWithFallback(
value,
numericDescriptor,
);
expect(actualValue).toBe(expectedValue);
});
it("should return 1.23K% for 12.34312 if precision is auto and display units are thousand and default format in percents", () => {
const value: number = 12.34312;
const expectedValue: string = "+1.23K%";
const numericDescriptor: NumericDescriptor = new NumericDescriptor();
numericDescriptor.autoPrecision = true;
numericDescriptor.displayUnits = 1000;
numericDescriptor.defaultFormat = "+0.00%;-0.00%;0.00%";
numericDescriptor.precision = 5;
const actualValue: string = getFormattedValueWithFallback(
value,
numericDescriptor,
);
expect(actualValue).toBe(expectedValue);
});
it("should return 100% for 1.012 if precision is auto and display units are auto and format in percents", () => {
const value: number = 1.0012;
const expectedValue: string = "+100%";
const numericDescriptor: NumericDescriptor = new NumericDescriptor();
numericDescriptor.autoPrecision = true;
numericDescriptor.format = "+0.00%;-0.00%;0.00%";
numericDescriptor.precision = 5;
const actualValue: string = getFormattedValueWithFallback(
value,
numericDescriptor,
);
expect(actualValue).toBe(expectedValue);
});
it("should return 87.7% for 0.87712 if precision is auto and display units are auto and default format in percents", () => {
const value: number = 0.87712;
const expectedValue: string = "+87.7%";
const numericDescriptor: NumericDescriptor = new NumericDescriptor();
numericDescriptor.autoPrecision = true;
numericDescriptor.defaultFormat = "+0.00%;-0.00%;0.00%";
numericDescriptor.precision = 5;
const actualValue: string = getFormattedValueWithFallback(
value,
numericDescriptor,
);
expect(actualValue).toBe(expectedValue);
});
it("should return 7.43% for 0.0742712 if precision is auto and display units are auto and column format in percents", () => {
const value: number = 0.0742712;
const expectedValue: string = "+7.43%";
const numericDescriptor: NumericDescriptor = new NumericDescriptor();
numericDescriptor.autoPrecision = true;
numericDescriptor.columnFormat = "+0.00%;-0.00%;0.00%";
numericDescriptor.precision = 5;
const actualValue: string = getFormattedValueWithFallback(
value,
numericDescriptor,
);
expect(actualValue).toBe(expectedValue);
});
it("should return -0.02% for -0.00293312 if precision is auto and display units are auto and column format in percents", () => {
const value: number = -0.000243312;
const expectedValue: string = "-0.02%";
const numericDescriptor: NumericDescriptor = new NumericDescriptor();
numericDescriptor.autoPrecision = true;
numericDescriptor.columnFormat = "+0.00%;-0.00%;0.00%";
numericDescriptor.precision = 5;
const actualValue: string = getFormattedValueWithFallback(
value,
numericDescriptor,
);
expect(actualValue).toBe(expectedValue);
});
it("should return 1354% for 1.3537905 if precision is auto and display units are auto and format in percents", () => {
const value: number = 13.537905;
const expectedValue: string = "+1354%";
const numericDescriptor: NumericDescriptor = new NumericDescriptor();
numericDescriptor.autoPrecision = true;
numericDescriptor.format = "+0.00%;-0.00%;0.00%";
numericDescriptor.precision = 5;
const actualValue: string = getFormattedValueWithFallback(
value,
numericDescriptor,
);
expect(actualValue).toBe(expectedValue);
});
});
it("stale data is set up for a metric", (done) => {
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 0, 1);
castZeroToNullOrReturnBack(testWrapper.dataView);
testWrapper.dataView.metadata.objects = {
staleData: {
show: true,
staleDataText: "label {$1}",
staleDataThreshold: 1,
},
subtitle: {
show: true,
},
values: {
showLatterAvailableValue: true,
treatEmptyValuesAsZero: false,
},
};
// This call will be skipped because of isShown = false
testWrapper.dataView.metadata.columns[2].objects = {
staleData: {
isShown: false,
},
};
// This call will be skipped because of threshold days
testWrapper.dataView.metadata.columns[3].objects = {
staleData: {
staleDataText: "unique {$1}",
staleDataThreshold: 5,
},
};
testWrapper.dataView.metadata.columns[4].objects = {
staleData: {
staleDataText: "custom label {$1}",
staleDataThreshold: 0,
},
};
const components = testWrapper.visualBuilder.instance.rootComponent.getComponents();
const warningComponent = <SubtitleWarningComponent>(components.filter(c => c instanceof SubtitleWarningComponent)[0]);
spyOn(warningComponent, "getTitle");
testWrapper.visualBuilder.updateRenderTimeout(testWrapper.dataView, () => {
expect(warningComponent.getTitle).toHaveBeenCalledWith("label {$1}", 4, 0);
expect(warningComponent.getTitle).toHaveBeenCalledWith("custom label {$1}", 1, 0);
expect(warningComponent.getTitle).toHaveBeenCalledTimes(2);
done();
});
});
it("stale data is set up for a metric with staleDataText from susbtitles (compatibility support)", (done) => {
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 0, 2);
castZeroToNullOrReturnBack(testWrapper.dataView);
testWrapper.dataView.metadata.objects = {
staleData: {
show: true,
},
subtitle: {
show: true,
staleDataText: "Compatibility title"
},
values: {
showLatterAvailableValue: true,
treatEmptyValuesAsZero: false,
},
};
testWrapper.dataView.metadata.columns[2].objects = {
staleData: {
isShown: false,
},
};
testWrapper.dataView.metadata.columns[3].objects = {
staleData: {
staleDataText: "unique {$1}",
staleDataThreshold: 1,
},
};
testWrapper.dataView.metadata.columns[4].objects = {
staleData: {
staleDataText: "custom label {$1}",
staleDataThreshold: 1,
},
};
const components = testWrapper.visualBuilder.instance.rootComponent.getComponents();
const warningComponent = <SubtitleWarningComponent>(components.filter(c => c instanceof SubtitleWarningComponent)[0]);
spyOn(warningComponent, "getTitle");
testWrapper.visualBuilder.updateRenderTimeout(testWrapper.dataView, () => {
expect(warningComponent.getTitle).toHaveBeenCalledWith("Data is ${1} days late.Compatibility title", 5, 0);
expect(warningComponent.getTitle).toHaveBeenCalledWith("unique {$1}", 2, 0);
expect(warningComponent.getTitle).toHaveBeenCalledWith("custom label {$1}", 2, 0);
expect(warningComponent.getTitle).toHaveBeenCalledTimes(3);
done();
});
});
it("stale data with goup threshold days deduction option", (done) => {
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 0, 1);
castZeroToNullOrReturnBack(testWrapper.dataView);
testWrapper.dataView.metadata.objects = {
staleData: {
show: true,
staleDataThreshold: 0,
deductThresholdDays: true,
},
subtitle: {
show: true,
},
values: {
showLatterAvailableValue: true,
treatEmptyValuesAsZero: false,
},
};
testWrapper.dataView.metadata.columns[1].objects = {
staleData: {
staleDataText: "first label {$1}",
staleDataThreshold: 2,
},
};
testWrapper.dataView.metadata.columns[2].objects = {
staleData: {
staleDataText: "second label {$1}",
},
};
// because of high threshold days limit, this item is actual and will not be shown in stale data block
testWrapper.dataView.metadata.columns[3].objects = {
staleData: {
staleDataText: "third label {$1}",
staleDataThreshold: 33,
},
};
const components = testWrapper.visualBuilder.instance.rootComponent.getComponents();
const warningComponent = <SubtitleWarningComponent>(components.filter(c => c instanceof SubtitleWarningComponent)[0]);
spyOn(warningComponent, "getTitle");
testWrapper.visualBuilder.updateRenderTimeout(testWrapper.dataView, () => {
expect(warningComponent.getTitle).toHaveBeenCalledWith("first label {$1}", 4, 2);
expect(warningComponent.getTitle).toHaveBeenCalledWith("second label {$1}", 1, 0);
expect(warningComponent.getTitle).toHaveBeenCalledWith("Data is ${1} days late.", 1, 0);
expect(warningComponent.getTitle).toHaveBeenCalledTimes(3);
done();
});
});
it("stale data with threshold deduction per metric", (done) => {
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 0, 5);
castZeroToNullOrReturnBack(testWrapper.dataView);
testWrapper.dataView.metadata.objects = {
staleData: {
show: true,
staleDataThreshold: 1,
deductThresholdDays: false,
},
subtitle: {
show: true,
},
values: {
showLatterAvailableValue: true,
treatEmptyValuesAsZero: false,
},
};
testWrapper.dataView.metadata.columns[1].objects = {
staleData: {
staleDataText: "first label {$1}",
staleDataThreshold: 2,
},
};
testWrapper.dataView.metadata.columns[2].objects = {
staleData: {
staleDataText: "second label {$1}",
staleDataThreshold: 3,
deductThresholdDays: true,
},
};
// because of high threshold days limit, this item is actual and will not be shown in stale data block
testWrapper.dataView.metadata.columns[3].objects = {
staleData: {
staleDataText: "third label {$1}",
staleDataThreshold: 33,
},
};
const components = testWrapper.visualBuilder.instance.rootComponent.getComponents();
const warningComponent = <SubtitleWarningComponent>(components.filter(c => c instanceof SubtitleWarningComponent)[0]);
spyOn(warningComponent, "getTitle");
testWrapper.visualBuilder.updateRenderTimeout(testWrapper.dataView, () => {
expect(warningComponent.getTitle).toHaveBeenCalledWith("first label {$1}", 8, 0);
expect(warningComponent.getTitle).toHaveBeenCalledWith("second label {$1}", 5, 3);
expect(warningComponent.getTitle).toHaveBeenCalledWith("Data is ${1} days late.", 5, 0);
expect(warningComponent.getTitle).toHaveBeenCalledTimes(3);
done();
});
});
it("subtitle shouldn't be rendered if it's turned off in Format Panel explicitly", (done) => {
const testWrapper: TestWrapper = TestWrapper.CREATE();
testWrapper.dataView.metadata.objects = {
subtitle: {
show: false,
titleText: "Power BI rocks",
},
};
testWrapper.visualBuilder.updateRenderTimeout(testWrapper.dataView, () => {
const displayStatus = testWrapper.visualBuilder.$subtitle.css("display");
expect(displayStatus).toEqual("none");
done();
});
});
it("merged subtitle should be rendered if it's turned on in Format Panel and provided as data field", (done) => {
const testWrapper: TestWrapper = TestWrapper
.CREATE(undefined, undefined, undefined, true);
testWrapper.dataView.metadata.objects = {
subtitle: {
show: true,
titleText: "Power BI rocks",
},
};
testWrapper.visualBuilder.updateRenderTimeout(testWrapper.dataView, () => {
const displayStatus = testWrapper.visualBuilder.$subtitle.css("display");
expect(displayStatus).toEqual("flex");
const subtitleElm = testWrapper.visualBuilder.$subtitle.find(".multiKpi_subtitle");
expect(subtitleElm.text()).toEqual("Power BI rocksSubtitle form data");
done();
});
});
it("subtitle from data should be rendered if it's turned on in Format Panel and provided only as data field", (done) => {
const testWrapper: TestWrapper = TestWrapper
.CREATE(undefined, undefined, undefined, true);
testWrapper.dataView.metadata.objects = {
subtitle: {
show: true,
},
};
testWrapper.visualBuilder.updateRenderTimeout(testWrapper.dataView, () => {
const displayStatus = testWrapper.visualBuilder.$subtitle.css("display");
expect(displayStatus).toEqual("flex");
const subtitleElm = testWrapper.visualBuilder.$subtitle.find(".multiKpi_subtitle");
expect(subtitleElm.text()).toEqual("Subtitle form data");
done();
});
});
});
describe("Version 2.2.0 Changes", () => {
it("Treat Empty/Missing Values As Zero is enabled", (done) => {
const testWrapper: TestWrapper = TestWrapper.create(true, 2);
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 2);
castZeroToNullOrReturnBack(testWrapper.dataView);
@ -86,7 +532,7 @@ describe("Multi KPI", () => {
});
it("Treat Empty/Missing Values As Zero is disabled", (done) => {
const testWrapper: TestWrapper = TestWrapper.create(true, 2);
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 2);
castZeroToNullOrReturnBack(testWrapper.dataView);
@ -104,7 +550,7 @@ describe("Multi KPI", () => {
});
it("Treat Empty/Missing Values As Zero is disabled but Show Latest Available As Current Value is enabled", (done) => {
const testWrapper: TestWrapper = TestWrapper.create(true, 2);
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 2);
castZeroToNullOrReturnBack(testWrapper.dataView);
@ -123,7 +569,7 @@ describe("Multi KPI", () => {
});
it("Treat Empty/Missing Values As Zero is enabled and Show Latest Available As Current Value is enabled", (done) => {
const testWrapper: TestWrapper = TestWrapper.create(true, 2);
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 2);
castZeroToNullOrReturnBack(testWrapper.dataView);
@ -142,7 +588,7 @@ describe("Multi KPI", () => {
});
it("Missing Value label is customized", (done) => {
const testWrapper: TestWrapper = TestWrapper.create(true, 2);
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 2);
castZeroToNullOrReturnBack(testWrapper.dataView);
@ -163,7 +609,7 @@ describe("Multi KPI", () => {
});
it("Missing Value label is customized generally and for certain series", (done) => {
const testWrapper: TestWrapper = TestWrapper.create(true, 2);
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 2);
castZeroToNullOrReturnBack(testWrapper.dataView);
@ -190,7 +636,7 @@ describe("Multi KPI", () => {
});
it("Missing Variance label is customized", (done) => {
const testWrapper: TestWrapper = TestWrapper.create(true, 0);
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 0);
castZeroToNullOrReturnBack(testWrapper.dataView);
@ -211,7 +657,7 @@ describe("Multi KPI", () => {
});
it("Missing Variance label is customized generally and for certain series", (done) => {
const testWrapper: TestWrapper = TestWrapper.create(true, 0);
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 0);
castZeroToNullOrReturnBack(testWrapper.dataView);
@ -238,7 +684,7 @@ describe("Multi KPI", () => {
});
it("Stale Data is enabled but Dates are actual", (done) => {
const testWrapper: TestWrapper = TestWrapper.create(true, 0);
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 0);
castZeroToNullOrReturnBack(testWrapper.dataView);
@ -264,7 +710,7 @@ describe("Multi KPI", () => {
});
it("Stale Data is enabled and be shown", (done) => {
const testWrapper: TestWrapper = TestWrapper.create(true, 0, 1);
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 0, 1);
castZeroToNullOrReturnBack(testWrapper.dataView);
@ -287,7 +733,7 @@ describe("Multi KPI", () => {
});
it("Stale Data is enabled but Threshold Days are actual", (done) => {
const testWrapper: TestWrapper = TestWrapper.create(true, 0, 1);
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 0, 1);
castZeroToNullOrReturnBack(testWrapper.dataView);
@ -310,7 +756,7 @@ describe("Multi KPI", () => {
});
it("Stale Data is enabled but One of the metrics have more obsolete data than others", (done) => {
const testWrapper: TestWrapper = TestWrapper.create(true, 0, 1);
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 0, 1);
castZeroToNullOrReturnBack(testWrapper.dataView);
@ -338,7 +784,7 @@ describe("Multi KPI", () => {
});
it("Stale Data is enabled and has sufficient threshold days to handle any metrics, even if one of them more obsolete", (done) => {
const testWrapper: TestWrapper = TestWrapper.create(true, 0, 1);
const testWrapper: TestWrapper = TestWrapper.CREATE(true, 0, 1);
castZeroToNullOrReturnBack(testWrapper.dataView);
@ -368,7 +814,7 @@ describe("Multi KPI", () => {
describe("DOM", () => {
it("root element should be defined in DOM", (done) => {
const testWrapper: TestWrapper = TestWrapper.create();
const testWrapper: TestWrapper = TestWrapper.CREATE();
testWrapper.visualBuilder.updateRenderTimeout(testWrapper.dataView, () => {
expect(testWrapper.visualBuilder.$root).toBeInDOM();
@ -379,7 +825,7 @@ describe("Multi KPI", () => {
describe("Main Chart", () => {
it("the main chart should be rendered", (done) => {
const testWrapper: TestWrapper = TestWrapper.create();
const testWrapper: TestWrapper = TestWrapper.CREATE();
testWrapper.visualBuilder.updateRenderTimeout(testWrapper.dataView, () => {
expect(testWrapper.visualBuilder.$mainChart).toBeInDOM();
@ -410,6 +856,7 @@ describe("Multi KPI", () => {
alternativeColor: "blue",
color: "green",
current: undefined,
isLine: false,
points: [
{
index: 0,
@ -449,7 +896,7 @@ describe("Multi KPI", () => {
max: maxDate,
min: minDate,
scale: DataRepresentationScale
.create()
.CREATE()
.domain(
[minDate, maxDate],
DataRepresentationTypeEnum.DateType,
@ -461,7 +908,7 @@ describe("Multi KPI", () => {
max: maxValue,
min: minValue,
scale: DataRepresentationScale
.create()
.CREATE()
.domain(
[minValue, maxValue],
DataRepresentationTypeEnum.NumberType,
@ -483,7 +930,7 @@ describe("Multi KPI", () => {
describe("Sparkline", () => {
it("sparkline component should be rendered", (done) => {
const testWrapper: TestWrapper = TestWrapper.create();
const testWrapper: TestWrapper = TestWrapper.CREATE();
testWrapper.visualBuilder.updateRenderTimeout(testWrapper.dataView, () => {
testWrapper.visualBuilder.$sparkline.each(function checkEachElement() {
@ -498,7 +945,7 @@ describe("Multi KPI", () => {
describe("Subtitle", () => {
it("subtitle of each sparkline should be rendered", (done) => {
const testWrapper: TestWrapper = TestWrapper.create();
const testWrapper: TestWrapper = TestWrapper.CREATE();
testWrapper.visualBuilder.updateRenderTimeout(testWrapper.dataView, () => {
testWrapper.visualBuilder.$sparklineSubtitle.each(function checkEachElement() {
@ -514,7 +961,7 @@ describe("Multi KPI", () => {
describe("Line", () => {
it("line of each sparkline should be rendered", (done) => {
const testWrapper: TestWrapper = TestWrapper.create();
const testWrapper: TestWrapper = TestWrapper.CREATE();
testWrapper.visualBuilder.updateRenderTimeout(testWrapper.dataView, () => {
testWrapper.visualBuilder.$sparklineLine.each(function checkEachElement() {
@ -531,7 +978,7 @@ describe("Multi KPI", () => {
describe("Subtitle", () => {
it("subtitle should be rendered if it's turned on via Format Panel explicitly", (done) => {
const testWrapper: TestWrapper = TestWrapper.create();
const testWrapper: TestWrapper = TestWrapper.CREATE();
testWrapper.dataView.metadata.objects = {
subtitle: {
@ -541,8 +988,10 @@ describe("Multi KPI", () => {
};
testWrapper.visualBuilder.updateRenderTimeout(testWrapper.dataView, () => {
expect(testWrapper.visualBuilder.$subtitle).toBeInDOM();
const displayStatus = testWrapper.visualBuilder.$subtitle.css("display");
expect(displayStatus).toEqual("flex");
const subtitleElm = testWrapper.visualBuilder.$subtitle.find(".multiKpi_subtitle");
expect(subtitleElm.text()).toEqual("Power BI rocks");
done();
});
});
@ -696,14 +1145,14 @@ describe("Multi KPI", () => {
});
});
function createElement(viewport: powerbi.IViewport = { height: 600, width: 800 }): Selection<any, any, any, any> {
return d3Select(testDom(
function createElement(viewport: powerbiVisualsApi.IViewport = { height: 600, width: 800 }): Selection<any, any, any, any> {
return d3Select($(testDom(
viewport.height.toString(),
viewport.width.toString(),
).get(0));
)).get(0));
}
function castZeroToNullOrReturnBack(dataView: powerbi.DataView): void {
function castZeroToNullOrReturnBack(dataView: powerbiVisualsApi.DataView): void {
dataView.categorical.values.forEach((x) => x.values.forEach((value, index, theArray) => {
if (value === 0) {
theArray[index] = null;

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

@ -26,12 +26,14 @@
import { VisualBuilderBase } from "powerbi-visuals-utils-testutils";
import { MultiKpi } from "../src/visual";
import { MultiKpi } from "../src/multiKpi";
import * as $ from "jquery";
export class MultiKpiBuilder extends VisualBuilderBase<MultiKpi> {
protected build(): MultiKpi {
return new MultiKpi({
element: this.element.get(0),
element: $(this.element).get(0),
host: this.visualHost,
});
}
@ -41,7 +43,7 @@ export class MultiKpiBuilder extends VisualBuilderBase<MultiKpi> {
}
public get $root(): JQuery {
return this.element.find(".multiKpi_multiKpi");
return $(this.element).find(".multiKpi_multiKpi");
}
public get $sparkline(): JQuery {

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

@ -24,7 +24,7 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { getRandomNumbers, testDataViewBuilder } from "powerbi-visuals-utils-testutils";
import { valueType } from "powerbi-visuals-utils-typeutils";
@ -32,6 +32,7 @@ import { valueType } from "powerbi-visuals-utils-typeutils";
import {
dateColumn,
valueColumn,
subtitleColumn,
} from "../src/columns/columns";
export class MultiKpiData extends testDataViewBuilder.TestDataViewBuilder {
@ -40,7 +41,10 @@ export class MultiKpiData extends testDataViewBuilder.TestDataViewBuilder {
public dates: Date[] = [];
public seriesValues: number[][] = [];
constructor(withMisisngValues?: boolean, brokenMetricIndex: number = 0, howOlderDatesAreInDays: number = 0) {
constructor(
withMisisngValues: boolean = false,
brokenMetricIndex: number = 0,
howOlderDatesAreInDays: number = 0) {
super();
const today = new Date();
@ -96,18 +100,57 @@ export class MultiKpiData extends testDataViewBuilder.TestDataViewBuilder {
}
}
public getDataView(columnNames?: string[]): powerbi.DataView {
const datesCategory = {
public getDataView(columnNames?: string[]): powerbiVisualsApi.DataView {
const datesCategory = this.buildDatesCategory(this.dates);
const valuesCategory = this.buildValuesCategory(this.seriesValues);
return this.createCategoricalDataViewBuilder(
[datesCategory],
valuesCategory,
columnNames,
).build();
}
public getDataViewWithSubtitle(columnNames?: string[]): powerbiVisualsApi.DataView {
const datesCategory = this.buildDatesCategory(this.dates);
const valuesCategory = this.buildValuesCategory(this.seriesValues);
const repeatsNum: number = this.dates.length;
let subtitleArr: string[] = [];
for (let i = 0; i < repeatsNum; i++) {
subtitleArr.push("Subtitle form data");
}
valuesCategory.push({
source: {
displayName: subtitleColumn.name,
roles: { subtitleColumn: true },
type: valueType.ValueType.fromDescriptor({ text: true }),
},
values: subtitleArr,
});
return this.createCategoricalDataViewBuilder(
[datesCategory],
valuesCategory,
columnNames,
).build();
}
private buildDatesCategory(dates: Date[]): any {
return {
source: {
displayName: dateColumn.name,
format: "%M/%d/yyyy",
roles: { dateColumn: true },
type: valueType.ValueType.fromDescriptor({ dateTime: true }),
},
values: this.dates,
};
values: dates,
}
}
const valuesCategory = this.seriesValues
private buildValuesCategory(seriesValues: number[][]): any {
return seriesValues
.map((values: number[]) => {
return {
source: {
@ -118,11 +161,5 @@ export class MultiKpiData extends testDataViewBuilder.TestDataViewBuilder {
values,
};
});
return this.createCategoricalDataViewBuilder(
[datesCategory],
valuesCategory,
columnNames,
).build();
}
}

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

@ -24,20 +24,25 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { MultiKpiData } from "./dataBuilder";
import { MultiKpiBuilder } from "./visualBuilder";
import { MultiKpiData } from "./multiKpiData";
import { MultiKpiBuilder } from "./multiKpiBuilder";
export class TestWrapper {
public static create(
public static CREATE(
withMisisngValues: boolean = false,
brokenMetricIndex: number = 0,
howOlderDatesAreInDays: number = 0): TestWrapper {
return new TestWrapper(withMisisngValues, brokenMetricIndex, howOlderDatesAreInDays);
howOlderDatesAreInDays: number = 0,
attachSubtitleData: boolean = false): TestWrapper {
return new TestWrapper(
withMisisngValues,
brokenMetricIndex,
howOlderDatesAreInDays,
attachSubtitleData);
}
public dataView: powerbi.DataView;
public dataView: powerbiVisualsApi.DataView;
public dataViewBuilder: MultiKpiData;
public visualBuilder: MultiKpiBuilder;
@ -45,12 +50,20 @@ export class TestWrapper {
withMisisngValues: boolean = false,
brokenMetricIndex: number = 0,
howOlderDatesAreInDays: number = 0,
attachSubtitleData: boolean = false,
width: number = 1024,
height: number = 768) {
this.visualBuilder = new MultiKpiBuilder(width, height);
this.dataViewBuilder = new MultiKpiData(withMisisngValues, brokenMetricIndex, howOlderDatesAreInDays);
this.dataViewBuilder = new MultiKpiData(
withMisisngValues,
brokenMetricIndex,
howOlderDatesAreInDays);
this.dataView = this.dataViewBuilder.getDataView();
if (attachSubtitleData) {
this.dataView = this.dataViewBuilder.getDataViewWithSubtitle();
} else {
this.dataView = this.dataViewBuilder.getDataView();
}
}
}

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

@ -53,3 +53,8 @@ export const changeStartDateColumn: IVisualDataRole = {
displayName: "Change start date",
name: "changeStartDateColumn",
};
export const subtitleColumn: IVisualDataRole = {
displayName: "Subtitle",
name: "subtitleColumn",
};

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

@ -24,7 +24,7 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import {
changeStartDateColumn,
@ -32,6 +32,7 @@ import {
tooltipColumn,
valueColumn,
warningStateColumn,
subtitleColumn,
} from "../../columns/columns";
import { AxisDescriptor } from "../../settings/descriptors/axisDescriptor";
@ -55,7 +56,7 @@ import {
DataRepresentationTypeEnum,
} from "../data/dataRepresentationScale";
import { createVarianceConverterByType } from "../variance";
import { createVarianceConverterByType } from "../variance/createVarianceConverterByType";
import {
getFormattedDate,
@ -65,7 +66,7 @@ import {
export interface IColumnGroup {
name: string;
values: any[];
columns: Array<powerbi.DataViewValueColumn | powerbi.DataViewCategoryColumn>;
columns: (powerbiVisualsApi.DataViewValueColumn | powerbiVisualsApi.DataViewCategoryColumn)[];
}
export interface IColumnGroupByRole {
@ -73,13 +74,13 @@ export interface IColumnGroupByRole {
}
export interface IDataConverterConstructorOptions {
createSelectionIdBuilder: () => powerbi.visuals.ISelectionIdBuilder;
createSelectionIdBuilder: () => powerbiVisualsApi.visuals.ISelectionIdBuilder;
}
export interface IDataConverterOptions {
dataView: powerbi.DataView;
dataView: powerbiVisualsApi.DataView;
settings: Settings;
viewport: powerbi.IViewport;
viewport: powerbiVisualsApi.IViewport;
}
export class DataConverter implements IConverter<IDataConverterOptions, IDataRepresentation> {
@ -99,7 +100,7 @@ export class DataConverter implements IConverter<IDataConverterOptions, IDataRep
const dataRepresentation: IDataRepresentation = this.getDefaultData(settings.kpi.percentCalcDate);
if (this.isDataViewValid(dataView)) {
const columns: Array<powerbi.DataViewValueColumn | powerbi.DataViewCategoryColumn> = [
const columns: (powerbiVisualsApi.DataViewValueColumn | powerbiVisualsApi.DataViewCategoryColumn)[] = [
...this.getColumns(dataView.categorical.categories),
...this.getColumns(dataView.categorical.values),
];
@ -154,7 +155,7 @@ export class DataConverter implements IConverter<IDataConverterOptions, IDataRep
return closestDataPoint || defaultDataPoint;
}
public isDataViewValid(dataView: powerbi.DataView): boolean {
public isDataViewValid(dataView: powerbiVisualsApi.DataView): boolean {
return !!(dataView
&& dataView.categorical
&& dataView.categorical.categories
@ -167,12 +168,12 @@ export class DataConverter implements IConverter<IDataConverterOptions, IDataRep
}
protected getColumnGroupByRole(
columns: Array<powerbi.DataViewValueColumn | powerbi.DataViewCategoryColumn>,
columns: (powerbiVisualsApi.DataViewValueColumn | powerbiVisualsApi.DataViewCategoryColumn)[],
index: number,
): IColumnGroupByRole {
const columnGroups: IColumnGroupByRole = {};
columns.forEach((column: powerbi.DataViewValueColumn | powerbi.DataViewCategoryColumn, valueIndex: number) => {
columns.forEach((column: powerbiVisualsApi.DataViewValueColumn | powerbiVisualsApi.DataViewCategoryColumn, valueIndex: number) => {
Object.keys(column.source.roles)
.forEach((roleName: string) => {
if (!columnGroups[roleName]) {
@ -192,10 +193,10 @@ export class DataConverter implements IConverter<IDataConverterOptions, IDataRep
}
private getColumns<Type>(columns: Type[]): Type[] {
return columns.map((column) => column); // TODO: Why is it using map to create a copy of array?
return columns.map((column) => column);
}
private getViewportSize(viewport: powerbi.IViewport): ViewportSize {
private getViewportSize(viewport: powerbiVisualsApi.IViewport): ViewportSize {
if (viewport.height < 120 || viewport.width < 120) {
return ViewportSize.tiny;
} else if (viewport.height < 180 || viewport.width < 300) {
@ -229,7 +230,7 @@ export class DataConverter implements IConverter<IDataConverterOptions, IDataRep
dataRepresentation: IDataRepresentation,
columnGroupByRole: IColumnGroupByRole,
settings: Settings,
valuesColumn: powerbi.DataViewValueColumns,
valuesColumn: powerbiVisualsApi.DataViewValueColumns,
): void {
const dateColumnGroup: IColumnGroup = columnGroupByRole[dateColumn.name];
const valueColumnGroup: IColumnGroup = columnGroupByRole[valueColumn.name];
@ -252,159 +253,175 @@ export class DataConverter implements IConverter<IDataConverterOptions, IDataRep
createSelectionIdBuilder,
} = this.constructorOptions;
valueColumnGroup.columns.forEach((column: powerbi.DataViewValueColumn, columnIndex: number) => {
const x: Date = dateColumnGroup.values[0] as Date;
valueColumnGroup.columns.forEach((column: powerbiVisualsApi.DataViewValueColumn, columnIndex: number) => {
const x: Date = <Date>(dateColumnGroup.values[0]);
if (x instanceof Date && x !== undefined && x !== null) {
if (!dataRepresentation.series[columnIndex]) {
const selectionId: powerbi.visuals.ISelectionId = createSelectionIdBuilder()
const selectionId: powerbiVisualsApi.visuals.ISelectionId = createSelectionIdBuilder()
.withSeries(valuesColumn, column)
.withMeasure(column.source.queryName)
.createSelectionId();
const seriesSettings: SeriesSettings = SeriesSettings.getDefault() as SeriesSettings;
for (const propertyName in seriesSettings) {
const descriptor: BaseDescriptor = seriesSettings[propertyName];
const defaultDescriptor: BaseDescriptor = settings[propertyName];
if (descriptor && descriptor.applyDefault && defaultDescriptor) {
descriptor.applyDefault(defaultDescriptor);
}
}
seriesSettings.parseObjects(column.source.objects);
seriesSettings.values.setColumnFormat(column.source.format);
seriesSettings.yAxis.setColumnFormat(column.source.format);
seriesSettings.sparklineValue.setColumnFormat(column.source.format);
seriesSettings.sparklineYAxis.setColumnFormat(column.source.format);
const series: IDataRepresentationSeries = {
current: undefined,
dateDifference: undefined,
formattedDate: "",
formattedTooltip: undefined,
formattedVariance: "",
index: dataRepresentation.series.length,
name: column.source.displayName,
points: [],
const seriesSettings: SeriesSettings = this.prepareSeriesSettings(settings, column);
const series = this.initDataRepresentationSeries(
selectionId,
settings: seriesSettings,
smoothedPoints: [],
x: {
initialMax: undefined,
initialMin: undefined,
max: undefined,
min: undefined,
scale: DataRepresentationScale.create(),
},
y: {
initialMax: undefined,
initialMin: undefined,
max: undefined,
min: undefined,
scale: DataRepresentationScale.create(),
},
ySparkline: {
initialMax: undefined,
initialMin: undefined,
max: undefined,
min: undefined,
scale: DataRepresentationScale.create(),
},
tooltip: undefined,
variance: undefined,
};
dataRepresentation.series.length,
column.source.displayName,
seriesSettings,
);
dataRepresentation.series.push(series);
dataRepresentation.sortedSeries.push(series);
}
const seriesItem: IDataRepresentationSeries = dataRepresentation.series[columnIndex];
const y: number = this.parseValue(
valueColumnGroup.values[columnIndex],
dataRepresentation.series[columnIndex].settings.values.treatEmptyValuesAsZero,
seriesItem.settings.values.treatEmptyValuesAsZero,
);
const dataPoint: IDataRepresentationPoint = {
index: dataRepresentation.series[columnIndex].points.length,
index: seriesItem.points.length,
x,
y,
};
dataRepresentation.latestDate = x;
dataRepresentation.series[columnIndex].points.push(dataPoint);
if (seriesItem.points.length > 1 && (seriesItem.points[seriesItem.points.length - 1].y !== dataPoint.y)) {
seriesItem.isLine = false;
}
if (dataRepresentation.series[columnIndex].settings.values.showLatterAvailableValue) {
seriesItem.points.push(dataPoint);
if (seriesItem.settings.values.showLatterAvailableValue) {
if (!isNaN(dataPoint.y)) {
dataRepresentation.series[columnIndex].current = dataPoint;
seriesItem.current = dataPoint;
}
} else {
dataRepresentation.series[columnIndex].current = dataPoint;
seriesItem.current = dataPoint;
}
dataRepresentation.series[columnIndex].x.min = this.getMin(
dataRepresentation.series[columnIndex].x.min,
x,
);
dataRepresentation.series[columnIndex].x.max = this.getMax(
dataRepresentation.series[columnIndex].x.max,
x,
);
if (!isNaN(y)) {
dataRepresentation.series[columnIndex].y.min = this.getMin(
dataRepresentation.series[columnIndex].y.min,
y,
);
dataRepresentation.series[columnIndex].y.max = this.getMax(
dataRepresentation.series[columnIndex].y.max,
y,
);
dataRepresentation.series[columnIndex].ySparkline.min = this.getMin(
dataRepresentation.series[columnIndex].ySparkline.min,
y,
);
dataRepresentation.series[columnIndex].ySparkline.max = this.getMax(
dataRepresentation.series[columnIndex].ySparkline.max,
y,
);
}
const tooltip: string = tooltipColumnGroup
&& tooltipColumnGroup.values
&& tooltipColumnGroup.values[columnIndex]
|| undefined;
seriesItem.x.min = this.getMin(seriesItem.x.min, x);
seriesItem.x.max = this.getMax(seriesItem.x.max, x);
this.setupYMinMax(y, seriesItem);
const tooltip: string = tooltipColumnGroup && tooltipColumnGroup.values && tooltipColumnGroup.values[columnIndex] || undefined;
dataRepresentation.series[columnIndex].tooltip = tooltip;
}
});
const warningColumnGroup: IColumnGroup = columnGroupByRole[warningStateColumn.name];
const subtitleColumnGroup: IColumnGroup = columnGroupByRole[subtitleColumn.name];
if (subtitleColumnGroup) {
dataRepresentation.subtitle = subtitleColumnGroup.values[0];
}
const warningColumnGroup: IColumnGroup = columnGroupByRole[warningStateColumn.name];
if (warningColumnGroup) {
dataRepresentation.warningState = warningColumnGroup.values[0];
}
const changeStartDateColumnGroup: IColumnGroup = columnGroupByRole[changeStartDateColumn.name];
if (changeStartDateColumnGroup) {
const date: Date = changeStartDateColumnGroup
&& changeStartDateColumnGroup.values
&& changeStartDateColumnGroup.values[0];
dataRepresentation.percentCalcDate = date instanceof Date
? date
: dataRepresentation.percentCalcDate;
dataRepresentation.percentCalcDate = date instanceof Date ? date : dataRepresentation.percentCalcDate;
}
}
private setupYMinMax(y: number, seriesItem: IDataRepresentationSeries): void {
if (!isNaN(y)) {
seriesItem.y.min = this.getMin(
seriesItem.y.min,
y,
);
seriesItem.y.max = this.getMax(
seriesItem.y.max,
y,
);
seriesItem.ySparkline.min = this.getMin(
seriesItem.ySparkline.min,
y,
);
seriesItem.ySparkline.max = this.getMax(
seriesItem.ySparkline.max,
y,
);
}
}
private prepareSeriesSettings(settings: Settings, column: powerbiVisualsApi.DataViewValueColumn): SeriesSettings {
const seriesSettings: SeriesSettings = <SeriesSettings>(SeriesSettings.getDefault());
for (const propertyName of Object.keys(seriesSettings)) {
const descriptor: BaseDescriptor = seriesSettings[propertyName];
const defaultDescriptor: BaseDescriptor = settings[propertyName];
if (descriptor && descriptor.applyDefault && defaultDescriptor) {
descriptor.applyDefault(defaultDescriptor);
}
}
seriesSettings.parseObjects(column.source.objects);
seriesSettings.values.setColumnFormat(column.source.format);
seriesSettings.yAxis.setColumnFormat(column.source.format);
seriesSettings.sparklineValue.setColumnFormat(column.source.format);
seriesSettings.sparklineYAxis.setColumnFormat(column.source.format);
return seriesSettings;
}
private initDataRepresentationSeries(
selectionId: powerbiVisualsApi.visuals.ISelectionId,
seriesLength: number,
sourceDispalyName: string,
seriesSettings: SeriesSettings
): IDataRepresentationSeries {
return {
current: undefined,
dateDifference: undefined,
formattedDate: "",
formattedTooltip: undefined,
formattedVariance: "",
index: seriesLength,
isLine: true,
name: sourceDispalyName,
points: [],
selectionId,
settings: seriesSettings,
smoothedPoints: [],
x: {
initialMax: undefined,
initialMin: undefined,
max: undefined,
min: undefined,
scale: DataRepresentationScale.CREATE(),
},
y: {
initialMax: undefined,
initialMin: undefined,
max: undefined,
min: undefined,
scale: DataRepresentationScale.CREATE(),
},
ySparkline: {
initialMax: undefined,
initialMin: undefined,
max: undefined,
min: undefined,
scale: DataRepresentationScale.CREATE(),
},
tooltip: undefined,
variance: undefined,
};
}
private postProcessData(dataRepresentation: IDataRepresentation, settings: Settings): void {
dataRepresentation.staleDateDifference = 0;
@ -494,19 +511,19 @@ export class DataConverter implements IConverter<IDataConverterOptions, IDataRep
axis: IDataRepresentationAxis,
axisDescriptor: AxisDescriptor,
) {
if ((!isNaN(axisDescriptor.min as number) && axisDescriptor.min !== null)
|| (!isNaN(axisDescriptor.defaultMin as number) && axisDescriptor.defaultMin !== null)) {
if ((!isNaN(<number>(axisDescriptor.min)) && axisDescriptor.min !== null)
|| (!isNaN(<number>(axisDescriptor.defaultMin)) && axisDescriptor.defaultMin !== null)) {
axis.min = axisDescriptor.getMin();
}
else if (!isNaN(axis.min as number) && axis.min !== null) {
else if (!isNaN(<number>(axis.min)) && axis.min !== null) {
axisDescriptor.defaultMin = axis.min;
}
if ((!isNaN(axisDescriptor.max as number) && axisDescriptor.max !== null)
|| (!isNaN(axisDescriptor.defaultMax as number) && axisDescriptor.defaultMax !== null)) {
if ((!isNaN(<number>(axisDescriptor.max)) && axisDescriptor.max !== null)
|| (!isNaN(<number>(axisDescriptor.defaultMax)) && axisDescriptor.defaultMax !== null)) {
axis.max = axisDescriptor.getMax();
} else if (!isNaN(axis.max as number) && axis.max !== null) {
axisDescriptor.defaultMax = (axis.max as number) + (axis.max as number) * this.increasedDomainValueInPercentage;
} else if (!isNaN(<number>(axis.max)) && axis.max !== null) {
axisDescriptor.defaultMax = (<number>(axis.max)) + (<number>(axis.max)) * this.increasedDomainValueInPercentage;
axis.max = axisDescriptor.defaultMax;
}

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

@ -26,7 +26,7 @@
import { displayUnitSystemType, valueFormatter } from "powerbi-visuals-utils-formattingutils";
import { NumericDescriptor } from "../../settings/descriptors/numericDescriptor";
import { isValueValid } from "../../utils/valueUtils";
import { isValueValid } from "../../utils/isValueValid";
const wholeUnits: displayUnitSystemType.DisplayUnitSystemType = displayUnitSystemType.DisplayUnitSystemType.WholeUnits;
@ -55,7 +55,16 @@ export function getValueFormatter(value: number, settings: NumericDescriptor): v
return valueFormatter.create({
displayUnitSystemType: wholeUnits,
format: settings.getFormat(),
precision: settings.precision,
precision: detectPrecision(value, settings),
value: settings.displayUnits || value,
});
}
export function detectPrecision(inputValue: number, settings: NumericDescriptor): number {
if (settings.autoPrecision) {
const format = settings.format || settings.defaultFormat || settings.columnFormat;
return valueFormatter.calculateExactDigitsPrecision(inputValue, format, settings.displayUnits, 3);
}
return settings.precision;
}

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

@ -24,7 +24,7 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { SeriesSettings } from "../../settings/seriesSettings";
import { DataRepresentationScale } from "./dataRepresentationScale";
@ -66,8 +66,9 @@ export interface IDataRepresentationSeries {
staleDateDifference?: number;
tooltip: string;
formattedTooltip: string;
selectionId: powerbi.visuals.ISelectionId;
selectionId: powerbiVisualsApi.visuals.ISelectionId;
settings: SeriesSettings;
isLine: boolean; // There is a bag in SVG that hide line that has gradient attachment. The mark indicate to apply workaround
}
export enum ViewportSize {
@ -87,7 +88,7 @@ export interface IDataRepresentation {
latestDate: Date;
staleDateDifference?: number;
percentCalcDate: Date;
viewport: powerbi.IViewport;
subtitle?: string;
viewport: powerbiVisualsApi.IViewport;
viewportSize: ViewportSize;
}

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

@ -44,7 +44,7 @@ export enum DataRepresentationTypeEnum {
}
export class DataRepresentationScale {
public static create(): DataRepresentationScale {
public static CREATE(): DataRepresentationScale {
return new DataRepresentationScale();
}
@ -75,7 +75,7 @@ export class DataRepresentationScale {
}
if (scale) {
(scale as any).domain(values); // "as any" is some kind of hack for TS to build it well
(<any>scale).domain(values); // "as any" is some kind of hack for TS to build it well
}
this.baseScale = scale;
@ -97,7 +97,7 @@ export class DataRepresentationScale {
return 0;
}
return (this.baseScale as any)(value); // "as any" is some kind of hack for TS to build it well
return (<any>(this.baseScale))(value); // "as any" is some kind of hack for TS to build it well
}
public copy(): DataRepresentationScale {

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

@ -33,6 +33,12 @@ export interface ISmoothConstructorOptions {
accuracy?: number;
}
interface IAlphaBeta {
alpha: number;
beta: number;
w: number;
}
export class SmoothDataConverter implements IConverter<IDataRepresentationPoint[], IDataRepresentationPoint[]> {
private bandwidth: number = 0.1;
private robustnessIters: number = 2;
@ -48,9 +54,7 @@ export class SmoothDataConverter implements IConverter<IDataRepresentationPoint[
public convert(points: IDataRepresentationPoint[]): IDataRepresentationPoint[] {
const length = points.length;
const bandwidthInPoints = Math.floor(this.bandwidth * length);
const resultPoints: IDataRepresentationPoint[] = [];
const residuals: number[] = [];
const robustnessWeights: number[] = [];
@ -60,7 +64,7 @@ export class SmoothDataConverter implements IConverter<IDataRepresentationPoint[
robustnessWeights[i] = 1;
}
let w: number;
let alphaBeta: IAlphaBeta;
for (let iter: number = 0; iter < this.robustnessIters; iter++) {
const bandwidthInterval: number[] = [0, bandwidthInPoints - 1];
@ -81,49 +85,12 @@ export class SmoothDataConverter implements IConverter<IDataRepresentationPoint[
&& ileft >= 0
&& iright >= 0
) {
const edge: number = (points[i].x.getTime() - points[ileft].x.getTime())
> (points[iright].x.getTime() - points[i].x.getTime())
? ileft
: iright;
alphaBeta = this.calcAlphaBeta(robustnessWeights, i, x, ileft, iright, points);
let sumWeights: number = 0;
let sumX: number = 0;
let sumXSquared: number = 0;
let sumY: number = 0;
let sumXY: number = 0;
const denom: number = Math.abs(1 / (points[edge].x.getTime() - x));
for (let k: number = ileft; k <= iright; ++k) {
const xk: number = points[k].x.getTime();
const yk: number = points[k].y;
const dist: number = k < i ? x - xk : xk - x;
w = this.science_stats_loessTricube(dist * denom) * robustnessWeights[k];
const xkw: number = xk * w;
sumWeights += w;
sumX += xkw;
sumXSquared += xk * xkw;
sumY += yk * w;
sumXY += yk * xkw;
}
const meanX: number = sumX / sumWeights;
const meanY: number = sumY / sumWeights;
const meanXY: number = sumXY / sumWeights;
const meanXSquared: number = sumXSquared / sumWeights;
const beta: number = (Math.sqrt(Math.abs(meanXSquared - meanX * meanX)) < this.accuracy)
? 0 : ((meanXY - meanX * meanY) / (meanXSquared - meanX * meanX));
const alpha: number = meanY - beta * meanX;
if (!isNaN(beta) && !isNaN(alpha)) {
if (!isNaN(alphaBeta.beta) && !isNaN(alphaBeta.alpha)) {
resultPoints[i] = {
...points[i],
y: beta * x + alpha,
y: alphaBeta.beta * x + alphaBeta.alpha,
};
} else {
resultPoints[i] = { ...points[i] };
@ -151,13 +118,67 @@ export class SmoothDataConverter implements IConverter<IDataRepresentationPoint[
for (let i: number = 0; i < length; i++) {
const arg: number = residuals[i] / (6 * medianResidual);
robustnessWeights[i] = (arg >= 1) ? 0 : ((w = 1 - arg * arg) * w);
robustnessWeights[i] = (arg >= 1) ? 0 : ((alphaBeta.w = 1 - arg * arg) * alphaBeta.w);
}
}
return resultPoints;
}
private calcAlphaBeta(
robustnessWeights: number[],
itrn: number,
timePoint: number,
ileft: number,
iright: number,
points: IDataRepresentationPoint[],
): IAlphaBeta {
const edge: number = (points[itrn].x.getTime() - points[ileft].x.getTime())
> (points[iright].x.getTime() - points[itrn].x.getTime())
? ileft
: iright;
let w: number;
let sumWeights: number = 0;
let sumX: number = 0;
let sumXSquared: number = 0;
let sumY: number = 0;
let sumXY: number = 0;
const denom: number = Math.abs(1 / (points[edge].x.getTime() - timePoint));
for (let k: number = ileft; k <= iright; ++k) {
const xk: number = points[k].x.getTime();
const yk: number = points[k].y;
const dist: number = k < itrn ? timePoint - xk : xk - timePoint;
w = this.science_stats_loessTricube(dist * denom) * robustnessWeights[k];
const xkw: number = xk * w;
sumWeights += w;
sumX += xkw;
sumXSquared += xk * xkw;
sumY += yk * w;
sumXY += yk * xkw;
}
const meanX: number = sumX / sumWeights;
const meanY: number = sumY / sumWeights;
const meanXY: number = sumXY / sumWeights;
const meanXSquared: number = sumXSquared / sumWeights;
const beta: number = (Math.sqrt(Math.abs(meanXSquared - meanX * meanX)) < this.accuracy)
? 0 : ((meanXY - meanX * meanY) / (meanXSquared - meanX * meanX));
const alpha: number = meanY - beta * meanX;
return {
alpha,
beta,
w
}
}
/**
* Computes the tricube weight function.
* https://en.wikipedia.org/wiki/Local_regression#Weight_function

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

@ -26,7 +26,10 @@
import "../styles/styles.less";
import powerbi from "powerbi-visuals-api";
import "regenerator-runtime/runtime.js";
import powerbiVisualsApi from "powerbi-visuals-api";
import ISelectionManager = powerbiVisualsApi.extensibility.ISelectionManager;
import { dispatch, Dispatch } from "d3-dispatch";
import { select as d3Select } from "d3-selection";
@ -52,25 +55,25 @@ import { ScaleService } from "./services/scaleService";
// powerbi.extensibility.utils.tooltip
import { ITooltipServiceWrapper, TooltipServiceWrapper } from "powerbi-visuals-utils-tooltiputils";
export class MultiKpi implements powerbi.extensibility.visual.IVisual {
export class MultiKpi implements powerbiVisualsApi.extensibility.visual.IVisual {
private dataConverter: DataConverter;
private minViewport: powerbi.IViewport = {
private minViewport: powerbiVisualsApi.IViewport = {
height: 95,
width: 200,
};
private dataRepresentation: IDataRepresentation;
private settings: Settings;
private viewport: powerbi.IViewport;
private rootComponent: IVisualComponent<IVisualComponentRenderOptions>;
private viewport: powerbiVisualsApi.IViewport;
private eventDispatcher: Dispatch<any> = dispatch(...Object.keys(EventName));
private tooltipServiceWrapper: ITooltipServiceWrapper;
private host: powerbiVisualsApi.extensibility.visual.IVisualHost;
private selectionManager: ISelectionManager;
constructor(options: powerbi.extensibility.visual.VisualConstructorOptions) {
public rootComponent: IVisualComponent<IVisualComponentRenderOptions>;
constructor(options: powerbiVisualsApi.extensibility.visual.VisualConstructorOptions) {
if (window.location !== window.parent.location) {
require("core-js/stable");
}
@ -80,6 +83,8 @@ export class MultiKpi implements powerbi.extensibility.visual.IVisual {
host,
} = options;
this.host = host;
this.tooltipServiceWrapper = new TooltipServiceWrapper(
{
handleTouchDelay: 0,
@ -113,35 +118,54 @@ export class MultiKpi implements powerbi.extensibility.visual.IVisual {
style: host.colorPalette,
tooltipServiceWrapper: this.tooltipServiceWrapper,
});
this.selectionManager = this.host.createSelectionManager();
const visualSelection = d3Select(element);
visualSelection.on("contextmenu", (event) => {
let dataPoint: any = d3Select(event.target).datum();
this.selectionManager.showContextMenu(dataPoint ? dataPoint.selectionId : {}, {
x: event.clientX,
y: event.clientY
});
event.preventDefault();
});
}
public update(options: powerbi.extensibility.visual.VisualUpdateOptions) {
public update(options: powerbiVisualsApi.extensibility.visual.VisualUpdateOptions) {
if (!this.dataConverter || !this.rootComponent) {
return;
}
const dataView: powerbi.DataView = options
&& options.dataViews
&& options.dataViews[0];
try {
this.host.eventService.renderingStarted(options);
this.viewport = this.getViewport(options && options.viewport);
const dataView: powerbiVisualsApi.DataView = options
&& options.dataViews
&& options.dataViews[0];
this.settings = Settings.parseSettings(dataView) as Settings;
this.viewport = this.getViewport(options && options.viewport);
this.dataRepresentation = this.dataConverter.convert({
dataView,
settings: this.settings,
viewport: this.viewport,
});
this.settings = <Settings>(Settings.PARSE_SETTINGS(dataView));
this.render(
this.dataRepresentation,
this.settings,
this.viewport,
);
this.dataRepresentation = this.dataConverter.convert({
dataView,
settings: this.settings,
viewport: this.viewport,
});
this.render(
this.dataRepresentation,
this.settings,
this.viewport,
);
} catch (ex) {
this.host.eventService.renderingFailed(options, JSON.stringify(ex));
}
this.host.eventService.renderingFinished(options);
}
public enumerateObjectInstances(options: powerbi.EnumerateVisualObjectInstancesOptions): powerbi.VisualObjectInstanceEnumeration {
public enumerateObjectInstances(options: powerbiVisualsApi.EnumerateVisualObjectInstancesOptions): powerbiVisualsApi.VisualObjectInstanceEnumeration {
if (!this.settings) {
return [];
}
@ -154,7 +178,7 @@ export class MultiKpi implements powerbi.extensibility.visual.IVisual {
return this.settings.enumerateObjectInstances(options);
}
const enumerationObject: powerbi.VisualObjectInstanceEnumerationObject
const enumerationObject: powerbiVisualsApi.VisualObjectInstanceEnumerationObject
= this.settings.enumerateObjectInstancesWithSelectionId(
options,
"[All]",
@ -178,7 +202,7 @@ export class MultiKpi implements powerbi.extensibility.visual.IVisual {
private render(
data: IDataRepresentation,
settings: Settings,
viewport: powerbi.IViewport,
viewport: powerbiVisualsApi.IViewport,
): void {
this.rootComponent.render({
data,
@ -187,7 +211,7 @@ export class MultiKpi implements powerbi.extensibility.visual.IVisual {
});
}
private getViewport(currentViewport: powerbi.IViewport): powerbi.IViewport {
private getViewport(currentViewport: powerbiVisualsApi.IViewport): powerbiVisualsApi.IViewport {
if (!currentViewport) {
return { ...this.minViewport };
}

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

@ -24,12 +24,12 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
export class ScaleService {
constructor(private rootElement: HTMLElement) { }
public getScale(): powerbi.IViewport {
public getScale(): powerbiVisualsApi.IViewport {
if (!this.rootElement) {
return {
height: 1,

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

@ -34,6 +34,7 @@ export class NumericDescriptor
public noValueLabel: string = "N/A";
public displayUnits: number = 0;
public precision: number = 0;
public autoPrecision: boolean = false;
protected minPrecision: number = 0;
protected maxPrecision: number = 17;

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

@ -27,7 +27,7 @@
import { BaseDescriptor } from "./baseDescriptor";
export class StaleDataDescriptor extends BaseDescriptor {
public staleDataText: string = undefined;
public staleDataText: string = "";
public staleDataThreshold: number = 0;
public deductThresholdDays: boolean = false;
public color: string = "#3599b8";

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

@ -34,6 +34,7 @@ import { SubtitleDescriptor } from "./descriptors/subtitleDescriptor";
import { TooltipDescriptor } from "./descriptors/tooltipDescriptor";
import { ValuesDescriptor } from "./descriptors/valuesDescriptor";
import { VarianceDescriptor } from "./descriptors/varianceDescriptor";
import { StaleDataDescriptor } from "./descriptors/staleDataDescriptor";
export class SeriesSettings extends SettingsBase {
public values: ValuesDescriptor = new ValuesDescriptor();
@ -45,4 +46,5 @@ export class SeriesSettings extends SettingsBase {
public sparklineChart: SparklineChartDescriptor = new SparklineChartDescriptor();
public sparklineYAxis: SparklineAxisDescriptor = new SparklineAxisDescriptor();
public sparklineValue: SubtitleDescriptor = new SubtitleDescriptor();
public staleData: StaleDataDescriptor = new StaleDataDescriptor();
}

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

@ -97,7 +97,7 @@ export class Settings extends SettingsBase {
}
public parse(): void {
if (this.staleData.staleDataText === undefined) {
if (this.staleData.staleDataText === "") {
this.staleData.staleDataText = "Data is ${1} days late." + (this.subtitle.staleDataText || "");
}

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

@ -24,7 +24,7 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import {
dataViewObjects,
@ -38,12 +38,12 @@ import { IDescriptor } from "./descriptors/descriptor";
import { BaseDescriptor } from "./descriptors/baseDescriptor";
export class SettingsBase extends dataViewObjectsParser.DataViewObjectsParser {
public static parseSettings(options: powerbi.DataView): SettingsBase {
public static PARSE_SETTINGS(options: powerbiVisualsApi.DataView): SettingsBase {
const settings: SettingsBase = this.parse<SettingsBase>(options);
Object.keys(settings).forEach((descriptorName: string) => {
if ((settings[descriptorName] as IDescriptor).parse) {
(settings[descriptorName] as IDescriptor).parse();
if ((<IDescriptor>settings[descriptorName]).parse) {
(<IDescriptor>settings[descriptorName]).parse();
}
});
@ -56,12 +56,12 @@ export class SettingsBase extends dataViewObjectsParser.DataViewObjectsParser {
// This function is required to update settings once they are parsed from a data view object
}
public parseObjects(objects: powerbi.DataViewObjects): SettingsBase {
public parseObjects(objects: powerbiVisualsApi.DataViewObjects): SettingsBase {
if (objects) {
const properties: dataViewObjectsParser.DataViewProperties = this.getProperties();
for (const objectName in properties) {
for (const propertyName in properties[objectName]) {
for (const objectName of Object.keys(properties)) {
for (const propertyName of Object.keys(properties[objectName])) {
const defaultValue: any = this[objectName][propertyName];
this[objectName][propertyName] = dataViewObjects.getCommonValue(
@ -70,8 +70,8 @@ export class SettingsBase extends dataViewObjectsParser.DataViewObjectsParser {
defaultValue);
}
if ((this[objectName] as IDescriptor).parse) {
(this[objectName] as IDescriptor).parse();
if ((<IDescriptor>this[objectName]).parse) {
(<IDescriptor>this[objectName]).parse();
}
}
}
@ -81,8 +81,8 @@ export class SettingsBase extends dataViewObjectsParser.DataViewObjectsParser {
return this;
}
public enumerateObjectInstances(options: powerbi.EnumerateVisualObjectInstancesOptions): powerbi.VisualObjectInstanceEnumerationObject {
const enumerationObject: powerbi.VisualObjectInstanceEnumerationObject = {
public enumerateObjectInstances(options: powerbiVisualsApi.EnumerateVisualObjectInstancesOptions): powerbiVisualsApi.VisualObjectInstanceEnumerationObject {
const enumerationObject: powerbiVisualsApi.VisualObjectInstanceEnumerationObject = {
containers: [],
instances: [],
};
@ -101,18 +101,18 @@ export class SettingsBase extends dataViewObjectsParser.DataViewObjectsParser {
}
public enumerateObjectInstancesWithSelectionId(
options: powerbi.EnumerateVisualObjectInstancesOptions,
options: powerbiVisualsApi.EnumerateVisualObjectInstancesOptions,
displayName: string,
selectionId: powerbi.visuals.ISelectionId,
enumerationObject: powerbi.VisualObjectInstanceEnumerationObject = {
selectionId: powerbiVisualsApi.visuals.ISelectionId,
enumerationObject: powerbiVisualsApi.VisualObjectInstanceEnumerationObject = {
containers: [],
instances: [],
},
): powerbi.VisualObjectInstanceEnumerationObject {
): powerbiVisualsApi.VisualObjectInstanceEnumerationObject {
if (this.areOptionsValid(options)) {
const { objectName } = options;
const selector: powerbi.data.Selector = selectionId && selectionId.getSelector();
const selector: powerbiVisualsApi.data.Selector = selectionId && selectionId.getSelector();
this.addSettingsToContainer(
objectName,
@ -126,17 +126,17 @@ export class SettingsBase extends dataViewObjectsParser.DataViewObjectsParser {
return enumerationObject;
}
private areOptionsValid(options: powerbi.EnumerateVisualObjectInstancesOptions): boolean {
private areOptionsValid(options: powerbiVisualsApi.EnumerateVisualObjectInstancesOptions): boolean {
return !!(options
&& options.objectName
&& this[options.objectName]
);
}
private getSettings(settings: BaseDescriptor): { [propertyName: string]: powerbi.DataViewPropertyValue } {
const properties: { [propertyName: string]: powerbi.DataViewPropertyValue; } = {};
private getSettings(settings: BaseDescriptor): { [propertyName: string]: powerbiVisualsApi.DataViewPropertyValue } {
const properties: { [propertyName: string]: powerbiVisualsApi.DataViewPropertyValue; } = {};
for (const descriptor in settings) {
for (const descriptor of Object.keys(settings)) {
const value: any = settings.getValueByPropertyName
? settings.getValueByPropertyName(descriptor)
: settings[descriptor];
@ -159,9 +159,9 @@ export class SettingsBase extends dataViewObjectsParser.DataViewObjectsParser {
private addSettingsToContainer(
objectName: string,
displayName: string,
selector: powerbi.data.Selector,
enumerationObject: powerbi.VisualObjectInstanceEnumerationObject,
properties: { [propertyName: string]: powerbi.DataViewPropertyValue },
selector: powerbiVisualsApi.data.Selector,
enumerationObject: powerbiVisualsApi.VisualObjectInstanceEnumerationObject,
properties: { [propertyName: string]: powerbiVisualsApi.DataViewPropertyValue },
): void {
const containerIdx: number = enumerationObject.containers.push({ displayName }) - 1;
@ -176,12 +176,12 @@ export class SettingsBase extends dataViewObjectsParser.DataViewObjectsParser {
private addSettingsToInstances(
objectName: string,
enumerationObject: powerbi.VisualObjectInstanceEnumerationObject,
properties: { [propertyName: string]: powerbi.DataViewPropertyValue },
selector: powerbi.data.Selector = null,
enumerationObject: powerbiVisualsApi.VisualObjectInstanceEnumerationObject,
properties: { [propertyName: string]: powerbiVisualsApi.DataViewPropertyValue },
selector: powerbiVisualsApi.data.Selector = null,
containerIdx?: number,
): void {
const instance: powerbi.VisualObjectInstance = {
const instance: powerbiVisualsApi.VisualObjectInstance = {
objectName,
properties,
selector,

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

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

@ -24,7 +24,7 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import {
CssConstants,
@ -52,10 +52,10 @@ export abstract class BaseComponent<ConstructorOptionsType, RenderOptionsType> i
protected renderOptions: RenderOptionsType;
protected minWidth: number = 20;
protected width: number = undefined;
protected width: number;
protected minHeight: number = 20;
protected height: number = undefined;
protected height: number;
private isComponentShown: boolean = true;
private classNamePrefix: string = "multiKpi_";
@ -136,7 +136,7 @@ export abstract class BaseComponent<ConstructorOptionsType, RenderOptionsType> i
this.updateSizeOfElement(this.width, this.height);
}
public getViewport(): powerbi.IViewport {
public getViewport(): powerbiVisualsApi.IViewport {
return {
height: this.height,
width: this.width,
@ -186,7 +186,7 @@ export abstract class BaseComponent<ConstructorOptionsType, RenderOptionsType> i
.remove();
}
protected updateViewport(viewport: powerbi.IViewport): void {
protected updateViewport(viewport: powerbiVisualsApi.IViewport): void {
this.element
.style("width", pixelConverter.toString(viewport.width))
.style("height", pixelConverter.toString(viewport.height));

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

@ -30,9 +30,13 @@ import { IVisualComponent } from "./visualComponent";
export abstract class BaseContainerComponent<ConstructorOptionsType, RenderOptionsType, ComponentsRenderOptions>
extends BaseComponent<ConstructorOptionsType, RenderOptionsType> {
protected components: Array<IVisualComponent<ComponentsRenderOptions>> = [];
protected components: IVisualComponent<ComponentsRenderOptions>[] = [];
public clear(components: Array<IVisualComponent<ComponentsRenderOptions>> = this.components): void {
public getComponents(): IVisualComponent<any>[] {
return this.components;
}
public clear(components: IVisualComponent<ComponentsRenderOptions>[] = this.components): void {
this.forEach(
components,
(component: IVisualComponent<ComponentsRenderOptions>) => {
@ -43,7 +47,7 @@ export abstract class BaseContainerComponent<ConstructorOptionsType, RenderOptio
super.clear();
}
public destroy(components: Array<IVisualComponent<ComponentsRenderOptions>> = this.components): void {
public destroy(components: IVisualComponent<ComponentsRenderOptions>[] = this.components): void {
this.forEach(
components.splice(0, components.length),
(component: IVisualComponent<any>) => {
@ -55,7 +59,7 @@ export abstract class BaseContainerComponent<ConstructorOptionsType, RenderOptio
}
protected forEach<ForEachComponentsRenderOptions>(
components: Array<IVisualComponent<ForEachComponentsRenderOptions>>,
components: IVisualComponent<ForEachComponentsRenderOptions>[],
iterator: (
component: IVisualComponent<ForEachComponentsRenderOptions>,
index: number,
@ -69,7 +73,7 @@ export abstract class BaseContainerComponent<ConstructorOptionsType, RenderOptio
}
protected initComponents<InitComponentsRenderOptions>(
components: Array<IVisualComponent<InitComponentsRenderOptions>>,
components: IVisualComponent<InitComponentsRenderOptions>[],
expectedAmountOfComponents: number,
initComponent: (index: number) => IVisualComponent<InitComponentsRenderOptions>,
): void {

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

@ -28,12 +28,11 @@ import { axisRight } from "d3-axis";
import { ScaleLinear } from "d3-scale";
import { select as d3Select } from "d3-selection";
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { valueFormatter } from "powerbi-visuals-utils-formattingutils";
import {
DataRepresentationAxisValueType,
IDataRepresentationAxis,
IDataRepresentationSeries,
} from "../../converter/data/dataRepresentation";
@ -44,9 +43,10 @@ import { AxisDescriptor } from "../../settings/descriptors/axisDescriptor";
import { BaseComponent } from "../baseComponent";
import { IVisualComponentConstructorOptions } from "../visualComponentConstructorOptions";
import { detectPrecision } from "../../converter/data/dataFormatter";
export interface IAxisComponentRenderOptions {
viewport: powerbi.IViewport;
viewport: powerbiVisualsApi.IViewport;
settings: AxisDescriptor;
series: IDataRepresentationSeries;
y: IDataRepresentationAxis;
@ -90,16 +90,16 @@ export class AxisComponent extends BaseComponent<IVisualComponentConstructorOpti
.copy()
.range([viewport.height, 0]);
const domain: number[] = yScale.getDomain() as number[];
const domain: number[] = <number[]>(yScale.getDomain());
const axisValueFormatter: valueFormatter.IValueFormatter = valueFormatter.create({
displayUnitSystemType: 2,
format: settings.getFormat(),
precision: settings.precision,
precision: detectPrecision(domain[1] || domain[0], settings),
value: settings.displayUnits || domain[1] || domain[0],
});
const yAxis = axisRight(yScale.getScale() as ScaleLinear<number, number>) // TODO: This is a place for potential issue
const yAxis = axisRight(<ScaleLinear<number, number>>(yScale.getScale()))
.tickValues(domain)
.tickFormat((value: number) => {
return axisValueFormatter.format(value);

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

@ -28,7 +28,7 @@ import { bisector } from "d3-array";
import { Selection } from "d3-selection";
import { line } from "d3-shape";
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { IMargin } from "powerbi-visuals-utils-svgutils";
@ -36,6 +36,7 @@ import {
IDataRepresentationPoint,
IDataRepresentationSeries,
ViewportSize,
DataRepresentationPointGradientType,
} from "../../converter/data/dataRepresentation";
import { DataRepresentationScale } from "../../converter/data/dataRepresentationScale";
@ -71,12 +72,12 @@ import { HoverLabelComponent } from "./hoverLabelComponent";
export interface IZeroLineRenderOptions {
series: IDataRepresentationSeries;
chart: ChartDescriptor;
viewport: powerbi.IViewport;
viewport: powerbiVisualsApi.IViewport;
}
export interface IChartComponentRenderOptions {
series: IDataRepresentationSeries;
viewport: powerbi.IViewport;
viewport: powerbiVisualsApi.IViewport;
settings: Settings;
viewportSize: ViewportSize;
}
@ -99,7 +100,7 @@ export class ChartComponent extends BaseContainerComponent<
private axisComponent: IVisualComponent<IAxisComponentRenderOptions>;
private lineComponent: IVisualComponent<ILineComponentRenderOptions>;
private dynamicComponents: Array<IVisualComponent<IHoverLabelComponentRenderOptions>> = [];
private dynamicComponents: IVisualComponent<IHoverLabelComponentRenderOptions>[] = [];
constructor(options: IVisualComponentConstructorOptions) {
super();
@ -204,9 +205,10 @@ export class ChartComponent extends BaseContainerComponent<
alternativeColor: settings.chart.alternativeColor,
color: settings.chart.color,
current: series.current,
isLine: series.isLine,
points: series.points,
thickness: settings.chart.thickness,
type: settings.chart.chartType,
type: series.isLine ? DataRepresentationPointGradientType.line : settings.chart.chartType,
viewport,
x: series.x,
y: series.y,
@ -251,7 +253,7 @@ export class ChartComponent extends BaseContainerComponent<
return NaN;
}
const areaScale: powerbi.IViewport = this.constructorOptions.scaleService.getScale();
const areaScale: powerbiVisualsApi.IViewport = this.constructorOptions.scaleService.getScale();
const width: number = this.width;
@ -263,7 +265,7 @@ export class ChartComponent extends BaseContainerComponent<
const leftPosition: number = (position - thickness) / areaScale.width;
const x: Date = scale.invert(leftPosition) as Date;
const x: Date = <Date>(scale.invert(leftPosition));
return this.dataBisector(this.renderOptions.series.points, x, 1);
}

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

@ -24,7 +24,7 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { Selection } from "d3-selection";
@ -34,7 +34,7 @@ import { pixelConverter } from "powerbi-visuals-utils-typeutils";
import { BaseComponent } from "../baseComponent";
import { IVisualComponentConstructorOptions } from "../visualComponentConstructorOptions";
import VisualTooltipDataItem = powerbi.extensibility.VisualTooltipDataItem;
import VisualTooltipDataItem = powerbiVisualsApi.extensibility.VisualTooltipDataItem;
export interface IRenderGroup {
data: string;
@ -126,10 +126,7 @@ export abstract class ChartLabelBaseComponent<RenderOptions> extends BaseCompone
this.constructorOptions.tooltipServiceWrapper.addTooltip<IRenderGroup>(
itemSelection,
(args) => {
if (args.data.tooltipDataItems) {
return args.data.tooltipDataItems;
}
});
(data) => data.tooltipDataItems ? data.tooltipDataItems : null
);
}
}

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

@ -24,7 +24,7 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { FormatDescriptor } from "../../settings/descriptors/formatDescriptor";
import { KpiDescriptor } from "../../settings/descriptors/kpi/kpiDescriptor";
@ -37,13 +37,13 @@ import { ChartLabelBaseComponent } from "./chartLabelBaseComponent";
import { getFormattedValueWithFallback } from "../../converter/data/dataFormatter";
import { EventName } from "../../event/eventName";
import { isValueValid } from "../../utils/valueUtils";
import { isValueValid } from "../../utils/isValueValid";
export interface IChartLabelComponentRenderOptions {
dateSettings: FormatDescriptor;
kpiSettings: KpiDescriptor;
series: IDataRepresentationSeries;
viewport: powerbi.IViewport;
viewport: powerbiVisualsApi.IViewport;
}
export class ChartLabelComponent extends ChartLabelBaseComponent<IChartLabelComponentRenderOptions> {

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

@ -24,11 +24,11 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { ChartLabelBaseComponent, IRenderGroup } from "./chartLabelBaseComponent";
import { createVarianceConverterByType } from "../../converter/variance";
import { createVarianceConverterByType } from "../../converter/variance/createVarianceConverterByType";
import { IDataRepresentationPoint } from "../../converter/data/dataRepresentation";
@ -37,7 +37,7 @@ import {
getFormattedValueWithFallback,
} from "../../converter/data/dataFormatter";
import { isValueValid } from "../../utils/valueUtils";
import { isValueValid } from "../../utils/isValueValid";
import { IVerticalReferenceLineComponentRenderOptions } from "../verticalReferenceLineComponent";
import { IVisualComponentConstructorOptions } from "../visualComponentConstructorOptions";
@ -45,7 +45,7 @@ import { IVisualComponentConstructorOptions } from "../visualComponentConstructo
import { KpiOnHoverDescriptor } from "../../settings/descriptors/kpi/kpiOnHoverDescriptor";
import { VarianceDescriptor } from "../../settings/descriptors/varianceDescriptor";
import VisualTooltipDataItem = powerbi.extensibility.VisualTooltipDataItem;
import VisualTooltipDataItem = powerbiVisualsApi.extensibility.VisualTooltipDataItem;
export interface IHoverLabelComponentRenderOptions extends IVerticalReferenceLineComponentRenderOptions {
kpiOnHoverSettings: KpiOnHoverDescriptor;
@ -75,14 +75,12 @@ export class HoverLabelComponent extends ChartLabelBaseComponent<IHoverLabelComp
if (!series || !series.points || !dataPoint || !series.points.length) {
this.hide();
return;
} else {
this.show();
}
const latestDataPoint: IDataRepresentationPoint = series.current;
const variance: number = createVarianceConverterByType(varianceSettings.shouldCalculateDifference)
.convert({
firstDataPoint: dataPoint,
@ -103,7 +101,6 @@ export class HoverLabelComponent extends ChartLabelBaseComponent<IHoverLabelComp
);
const isVarianceValid: boolean = isValueValid(variance);
const tooltipText: string = series && series.formattedTooltip || null;
let tooltipDataItems: VisualTooltipDataItem[];

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

@ -25,7 +25,7 @@
*/
import { Dispatch } from "d3-dispatch";
import { mouse as d3Mouse } from "d3-selection";
import { pointer as d3Mouse } from "d3-selection";
import { BaseContainerComponent } from "../baseContainerComponent";
import { IVisualComponentConstructorOptions } from "../visualComponentConstructorOptions";
@ -98,23 +98,18 @@ export class MainChartComponent extends BaseContainerComponent<IVisualComponentC
private initMouseEvents(): void {
const eventDispatcher: Dispatch<any> = this.constructorOptions.eventDispatcher;
function onMouseMove(e: any) {
const event: MouseEvent = require("d3").event;
function onMouseMove(event) {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
eventDispatcher.call(EventName.onMouseMove, undefined, d3Mouse(this));
eventDispatcher.call(EventName.onMouseMove, undefined, d3Mouse(event, this));
}
this.element.on("mousemove", onMouseMove);
this.element.on("touchmove", onMouseMove);
this.element.on("touchstart", onMouseMove);
function onMouseOut(e: any) {
const event: MouseEvent = require("d3").event;
function onMouseOut(event) {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();

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

@ -24,9 +24,9 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { mouse as d3Mouse } from "d3-selection";
import { pointer as d3Mouse } from "d3-selection";
import { BaseContainerComponent } from "./baseContainerComponent";
@ -55,12 +55,12 @@ export class RootComponent extends BaseContainerComponent<IVisualComponentConstr
private isPrintModeActivated: boolean = false;
private mainChartComponentViewport: powerbi.IViewport;
private mainChartComponentViewport: powerbiVisualsApi.IViewport;
private onChartChangeDelay: number = 300;
private onChartChangeTimer: number = null;
private currentChartName: string = undefined;
private currentlyHoveringChartName: string = undefined;
private currentChartName: string;
private currentlyHoveringChartName: string;
private startCoordinates: [number, number];
constructor(options: IVisualComponentConstructorOptions) {
@ -153,9 +153,7 @@ export class RootComponent extends BaseContainerComponent<IVisualComponentConstr
this.subtitleComponent = null;
}
private mousemoveHandler(): void {
const event: MouseEvent = require("d3").event;
private mousemoveHandler(event): void {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
@ -164,22 +162,24 @@ export class RootComponent extends BaseContainerComponent<IVisualComponentConstr
return;
}
const coordinates: [number, number] = d3Mouse(this.element.node()) as [number, number];
const coordinates: [number, number] = <[number, number]>d3Mouse(event, this.element.node());
const delay: number = this.getOnChartChangeDelay(coordinates);
if (delay) {
this.clearOnChartChangeTimer();
this.onChartChangeTimer = setTimeout(
this.onChartChangeTimer = <number>(<unknown>setTimeout(
this.applyCurrentlyHoveringChartName.bind(
this,
event,
this.currentlyHoveringChartName,
coordinates,
),
delay,
) as unknown as number;
));
} else {
this.applyCurrentlyHoveringChartName(
event,
this.currentlyHoveringChartName,
coordinates,
);
@ -187,7 +187,7 @@ export class RootComponent extends BaseContainerComponent<IVisualComponentConstr
}
private getOnChartChangeDelay([x, y]): number {
const scale: powerbi.IViewport = this.constructorOptions.scaleService.getScale();
const scale: powerbiVisualsApi.IViewport = this.constructorOptions.scaleService.getScale();
const scaledX: number = x / scale.width;
const scaledY: number = y / scale.height;
@ -215,13 +215,14 @@ export class RootComponent extends BaseContainerComponent<IVisualComponentConstr
staleDataSettings: settings.staleData,
subtitleSettings: settings.subtitle,
warningState: data.warningState,
subtitle: data.subtitle,
});
const subtitleComponentHeight: number = this.subtitleComponent.getViewport().height;
const viewportFactor: number = this.getViewportFactorByViewportSize(data.viewportSize);
const viewport: powerbi.IViewport = {
const viewport: powerbiVisualsApi.IViewport = {
height: options.viewport.height / viewportFactor,
width: options.viewport.width,
};
@ -298,8 +299,8 @@ export class RootComponent extends BaseContainerComponent<IVisualComponentConstr
try {
if (!window
|| !window.addEventListener
|| !("onbeforeprint" in window as any)
|| !("onafterprint" in window as any)
|| !("onbeforeprint" in <any>window)
|| !("onafterprint" in <any>window)
) {
return;
}
@ -337,6 +338,7 @@ export class RootComponent extends BaseContainerComponent<IVisualComponentConstr
}
private onChartChangeHoverHandler(
event,
seriesName: string,
coordinates: number[],
): void {
@ -353,9 +355,9 @@ export class RootComponent extends BaseContainerComponent<IVisualComponentConstr
this.currentChartName = seriesName;
this.currentlyHoveringChartName = undefined;
this.startCoordinates = (coordinates
this.startCoordinates = <[number, number]>(coordinates
? coordinates
: d3Mouse(this.element.node())) as [number, number];
: d3Mouse(event, this.element.node()));
if (!this.constructorOptions
|| !this.constructorOptions.eventDispatcher
@ -379,22 +381,21 @@ export class RootComponent extends BaseContainerComponent<IVisualComponentConstr
this.onChartChangeTimer = null;
}
private applyCurrentlyHoveringChartName(currentlyHoveringChartName: string, positions: number[]): void {
private applyCurrentlyHoveringChartName(event, currentlyHoveringChartName: string, positions: number[]): void {
this.clearOnChartChangeTimer();
if (currentlyHoveringChartName) {
this.currentChartName = undefined;
this.onChartChangeHoverHandler(
event,
currentlyHoveringChartName,
positions as [number, number],
<[number, number]>positions,
);
}
}
private onChartViewReset(): void {
const event: MouseEvent = require("d3").event;
private onChartViewReset(event): void {
if (event.target !== this.element.node()) {
return;
}

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

@ -26,7 +26,7 @@
import { Selection } from "d3-selection";
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { CssConstants } from "powerbi-visuals-utils-svgutils";
import {
@ -41,10 +41,10 @@ import { IVisualComponentConstructorOptions } from "../visualComponentConstructo
import { DataRepresentationScale } from "../../converter/data/dataRepresentationScale";
import { isValueValid } from "../../utils/valueUtils";
import { isValueValid } from "../../utils/isValueValid";
export interface IDotsComponentRenderOptions {
viewport: powerbi.IViewport;
viewport: powerbiVisualsApi.IViewport;
x: IDataRepresentationAxis;
y: IDataRepresentationAxis;
points: IDataRepresentationPoint[];

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

@ -26,7 +26,7 @@
import { Selection } from "d3-selection";
import { area, Area, line, Line } from "d3-shape";
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { CssConstants } from "powerbi-visuals-utils-svgutils";
import { pixelConverter } from "powerbi-visuals-utils-typeutils";
import {
@ -37,17 +37,18 @@ import {
} from "../../converter/data/dataRepresentation";
import { DataRepresentationScale } from "../../converter/data/dataRepresentationScale";
import { EventName } from "../../event/eventName";
import { isValueValid } from "../../utils/valueUtils";
import { isValueValid } from "../../utils/isValueValid";
import { BaseComponent } from "../baseComponent";
import { IVisualComponentConstructorOptions } from "../visualComponentConstructorOptions";
export interface ILineComponentRenderOptions {
alternativeColor: string;
color: string;
isLine: boolean;
points: IDataRepresentationPoint[];
thickness: number;
type: DataRepresentationPointGradientType;
viewport: powerbi.IViewport;
viewport: powerbiVisualsApi.IViewport;
x: IDataRepresentationAxis;
y: IDataRepresentationAxis;
current: IDataRepresentationPoint;
@ -189,6 +190,7 @@ export class LineComponent extends BaseComponent<IVisualComponentConstructorOpti
y,
viewport,
color,
isLine,
} = options;
this.updateGradient([{
@ -219,6 +221,10 @@ export class LineComponent extends BaseComponent<IVisualComponentConstructorOpti
.attr("d", (lineRenderOptions: ILineComponentRenderOptions) => {
const points: IDataRepresentationPoint[] = this.getValidPoints(lineRenderOptions.points);
if (isLine && points.length > 0) {
points[0].y += 0.0000001;
}
switch (lineRenderOptions.type) {
case DataRepresentationPointGradientType.area: {
return this.getArea(xScale, yScale, viewport, y.min)(points);
@ -258,7 +264,8 @@ export class LineComponent extends BaseComponent<IVisualComponentConstructorOpti
}
case DataRepresentationPointGradientType.line:
default: {
return pixelConverter.toString(lineRenderOptions.thickness);
//return lineRenderOptions.thickness;
pixelConverter.toString(lineRenderOptions.thickness);
}
}
});
@ -280,7 +287,7 @@ export class LineComponent extends BaseComponent<IVisualComponentConstructorOpti
private getArea(
xScale: DataRepresentationScale,
yScale: DataRepresentationScale,
viewport: powerbi.IViewport,
viewport: powerbiVisualsApi.IViewport,
yMin: DataRepresentationAxisValueType,
): Area<IDataRepresentationPoint> {
return area<IDataRepresentationPoint>()
@ -302,7 +309,7 @@ export class LineComponent extends BaseComponent<IVisualComponentConstructorOpti
private getGradientUrl(): string {
const href: string = window.location.href;
return `url(${href}#${this.gradientId})`;
return `url(#${this.gradientId})`;
}
private updateGradient(gradients: ILineComponentGradient[]): void {
@ -328,7 +335,7 @@ export class LineComponent extends BaseComponent<IVisualComponentConstructorOpti
}
private getId(): string {
const crypto: Crypto = window.crypto || (window as any).msCrypto;
const crypto: Crypto = window.crypto || (<any>window).msCrypto;
const generatedIds: Uint32Array = new Uint32Array(2);
crypto.getRandomValues(generatedIds);

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

@ -82,6 +82,7 @@ export class MultiLineComponent
alternativeColor: currentSeries.settings.sparklineChart.alternativeColor,
color: currentSeries.settings.sparklineChart.color,
current: currentSeries.current,
isLine: currentSeries.isLine,
points: currentSeries.smoothedPoints,
thickness: currentSeries.settings.sparklineChart.thickness,
type: DataRepresentationPointGradientType.line,

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

@ -24,7 +24,7 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { BaseContainerComponent } from "../baseContainerComponent";
import { ISparklineComponentRenderOptions } from "./sparklineComponent";
@ -50,7 +50,6 @@ export class PlotComponent
};
this.components = [
// TODO: Axis Component must be included here
new SvgComponent(this.constructorOptions),
];
}
@ -60,7 +59,7 @@ export class PlotComponent
this.updateSize(viewport.width, viewport.height);
const componentViewport: powerbi.IViewport = { ...viewport };
const componentViewport: powerbiVisualsApi.IViewport = { ...viewport };
this.forEach(
this.components,
@ -70,7 +69,7 @@ export class PlotComponent
viewport: { ...componentViewport },
});
const margins: powerbi.IViewport = component.getViewport();
const margins: powerbiVisualsApi.IViewport = component.getViewport();
componentViewport.height -= margins.height;
},

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

@ -24,7 +24,7 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { getFormattedValueWithFallback } from "../../converter/data/dataFormatter";
import { IDataRepresentation, IDataRepresentationPoint, IDataRepresentationSeries, ViewportSize } from "../../converter/data/dataRepresentation";
import { EventName } from "../../event/eventName";
@ -40,7 +40,7 @@ export interface ISparklineComponentRenderOptions {
series: IDataRepresentationSeries[];
current: IDataRepresentationSeries;
dataRepresentation: IDataRepresentation;
viewport: powerbi.IViewport;
viewport: powerbiVisualsApi.IViewport;
position: number;
}
@ -64,9 +64,7 @@ export class SparklineComponent extends BaseContainerComponent<IVisualComponentC
element: this.element,
};
this.element.on("click", () => {
const event: MouseEvent = require("d3").event;
this.element.on("click", (event) => {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
@ -125,14 +123,8 @@ export class SparklineComponent extends BaseContainerComponent<IVisualComponentC
this.constructorOptions.tooltipServiceWrapper.addTooltip(
this.element,
() => {
if (tooltipText) {
return [{
displayName: null,
value: tooltipText,
}];
}
});
(data) => tooltipText ? [{ displayName: null, value: tooltipText, }] : null
);
} else {
this.destroyComponents();
@ -222,7 +214,7 @@ export class SparklineComponent extends BaseContainerComponent<IVisualComponentC
}
private renderPlot(options: ISparklineComponentRenderOptions): void {
const plotComponentViewport: powerbi.IViewport = this.getReducedViewport(
const plotComponentViewport: powerbiVisualsApi.IViewport = this.getReducedViewport(
{ ...options.viewport },
[this.topLabelComponent, this.bottomLabelComponent],
);
@ -269,12 +261,12 @@ export class SparklineComponent extends BaseContainerComponent<IVisualComponentC
}
}
private getReducedViewport(viewport: powerbi.IViewport, components: Array<IVisualComponent<any>>): powerbi.IViewport {
return components.reduce<powerbi.IViewport>((
previousViewport: powerbi.IViewport,
private getReducedViewport(viewport: powerbiVisualsApi.IViewport, components: IVisualComponent<any>[]): powerbiVisualsApi.IViewport {
return components.reduce<powerbiVisualsApi.IViewport>((
previousViewport: powerbiVisualsApi.IViewport,
component: IVisualComponent<any>,
): powerbi.IViewport => {
const componentViewport: powerbi.IViewport = component.getViewport();
): powerbiVisualsApi.IViewport => {
const componentViewport: powerbiVisualsApi.IViewport = component.getViewport();
return {
height: previousViewport.height - componentViewport.height,

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

@ -139,9 +139,9 @@ export class SparklineGroupComponent
clearTimeout(this.renderingTimers[index]);
}
this.renderingTimers[index] = setTimeout(
this.renderingTimers[index] = <number>(<unknown>setTimeout(
component.render.bind(component, options),
this.renderingDelay,
) as unknown as number;
));
}
}

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

@ -24,7 +24,7 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { IMargin } from "powerbi-visuals-utils-svgutils";
import {
@ -51,7 +51,7 @@ import {
import { EventName } from "../../event/eventName";
import { MultiLineComponent } from "./multiLineComponent";
import { isValueValid } from "../../utils/valueUtils";
import { isValueValid } from "../../utils/isValueValid";
export class SvgComponent extends BaseContainerComponent<
IVisualComponentConstructorOptions,
@ -63,7 +63,7 @@ export class SvgComponent extends BaseContainerComponent<
private multiLineComponent: IVisualComponent<ISparklineComponentRenderOptions>;
private axisComponent: IVisualComponent<IAxisComponentRenderOptions>;
private dynamicComponents: Array<IVisualComponent<IDotsComponentRenderOptions>> = [];
private dynamicComponents: IVisualComponent<IDotsComponentRenderOptions>[] = [];
constructor(options: IVisualComponentConstructorOptions) {
super();
@ -119,7 +119,7 @@ export class SvgComponent extends BaseContainerComponent<
const margin: IMargin = this.getMarginByThickness(maxThickness);
const viewport: powerbi.IViewport = {
const viewport: powerbiVisualsApi.IViewport = {
height: Math.max(0, options.viewport.height - margin.top - margin.bottom),
width: Math.max(0, options.viewport.width - margin.left - margin.right),
};

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

@ -26,7 +26,7 @@
import { Selection } from "d3-selection";
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { CssConstants } from "powerbi-visuals-utils-svgutils";
import { pixelConverter } from "powerbi-visuals-utils-typeutils";
@ -38,6 +38,7 @@ import { IVisualComponentConstructorOptions } from "./visualComponentConstructor
export interface ISubtitleComponentRenderOptions {
subtitleSettings: SubtitleDescriptor;
subtitle?: string;
}
export class SubtitleComponent extends BaseComponent<IVisualComponentConstructorOptions, ISubtitleComponentRenderOptions> {
@ -60,19 +61,19 @@ export class SubtitleComponent extends BaseComponent<IVisualComponentConstructor
}
public render(options: ISubtitleComponentRenderOptions): void {
const { subtitleSettings: settings } = options;
const { subtitleSettings: settings, subtitle } = options;
if (settings.shouldBeShown) {
this.show();
this.renderComponent(settings);
this.renderComponent(settings, subtitle);
} else {
this.hide();
}
}
public getViewport(): powerbi.IViewport {
public getViewport(): powerbiVisualsApi.IViewport {
const height: number = this.element && this.isShown
? (this.element.node() as HTMLElement).clientHeight
? (<HTMLElement>(this.element.node())).clientHeight
: 0;
return {
@ -81,7 +82,7 @@ export class SubtitleComponent extends BaseComponent<IVisualComponentConstructor
};
}
private renderComponent(settings: SubtitleDescriptor): void {
private renderComponent(settings: SubtitleDescriptor, subtitle?: string): void {
const subtitleSelection: Selection<any, any, any, any> = this.element
.selectAll(this.subtitleSelector.selectorName)
.data(settings.shouldBeShown ? [[]] : []);
@ -90,12 +91,14 @@ export class SubtitleComponent extends BaseComponent<IVisualComponentConstructor
.exit()
.remove();
const subtitleText: string = `${settings.titleText}${(subtitle ?? "")}`;
subtitleSelection
.enter()
.append("div")
.classed(this.subtitleSelector.className, true)
.merge(subtitleSelection)
.text(settings.titleText)
.text(subtitleText)
.style("text-align", settings.alignment);
this.updateFormatting(this.element, settings);

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

@ -24,7 +24,7 @@
* THE SOFTWARE.
*/
import { Selection } from "d3-selection";
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { CssConstants } from "powerbi-visuals-utils-svgutils";
import { IDataRepresentationSeries } from "../converter/data/dataRepresentation";
import { StaleDataDescriptor } from "../settings/descriptors/staleDataDescriptor";
@ -32,7 +32,7 @@ import { SubtitleWarningDescriptor } from "../settings/descriptors/subtitleWarni
import { ISubtitleComponentRenderOptions, SubtitleComponent } from "./subtitleComponent";
import { IVisualComponentConstructorOptions } from "./visualComponentConstructorOptions";
import VisualTooltipDataItem = powerbi.extensibility.VisualTooltipDataItem;
import VisualTooltipDataItem = powerbiVisualsApi.extensibility.VisualTooltipDataItem;
export interface ISubtitleWarningComponentRenderOptions extends ISubtitleComponentRenderOptions {
warningState: number;
@ -103,17 +103,17 @@ export class SubtitleWarningComponent extends SubtitleComponent {
const isDataStale: boolean = this.isDataStale(
staleDataDifference,
staleDataThreshold,
series,
);
let tooltipItems: VisualTooltipDataItem[] = [];
const filterItemsFunc = (x: IDataRepresentationSeries) => {
if (!x.settings.staleData.isShown) {
return false;
}
if (x.staleDateDifference) {
if (staleDataSettings.deductThresholdDays) {
return (x.staleDateDifference - staleDataThreshold > 0);
}
return (x.staleDateDifference > 0);
return (x.staleDateDifference - x.settings.staleData.staleDataThreshold > 0);
}
return false;
};
@ -132,17 +132,15 @@ export class SubtitleWarningComponent extends SubtitleComponent {
if (!isTheSameStaledays) {
tooltipItems = series.filter(filterItemsFunc).map((s) => {
const title: string = this.getTitle(
staleDataText,
s.settings.staleData.staleDataText,
s.staleDateDifference,
staleDataSettings.deductThresholdDays ? staleDataThreshold : 0,
s.settings.staleData.deductThresholdDays ? s.settings.staleData.staleDataThreshold : 0,
);
const tolltipItem: VisualTooltipDataItem = {
return {
displayName: s.name,
value: title,
};
return tolltipItem;
});
} else {
tooltipItems = [
@ -166,11 +164,17 @@ export class SubtitleWarningComponent extends SubtitleComponent {
});
}
private isDataStale(dateDifference: number, staleDataThreshold: number): boolean {
return dateDifference > staleDataThreshold;
private isDataStale(dateDifference: number, series: IDataRepresentationSeries[]): boolean {
let isStale: boolean = false;
series.forEach((s) => {
if (dateDifference > s.settings.staleData.staleDataThreshold) {
isStale = true;
}
})
return isStale;
}
private getTitle(stringTemplate: string, dateDifference: number, staleDataThreshold: number): string {
public getTitle(stringTemplate: string, dateDifference: number, staleDataThreshold: number): string {
const days: number = dateDifference - staleDataThreshold;
return stringTemplate && stringTemplate.replace
? stringTemplate.replace("${1}", `${days}`)
@ -202,10 +206,7 @@ export class SubtitleWarningComponent extends SubtitleComponent {
this.constructorOptions.tooltipServiceWrapper.addTooltip(
iconSelection,
() => {
if (tooltipItems && tooltipItems.length > 0) {
return tooltipItems;
}
});
(data) => tooltipItems ? tooltipItems : null
);
}
}

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

@ -24,7 +24,7 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { Selection } from "d3-selection";
@ -46,7 +46,7 @@ import { DataRepresentationScale } from "../converter/data/dataRepresentationSca
export interface IVerticalReferenceLineComponentRenderOptions {
offset: number;
viewport: powerbi.IViewport;
viewport: powerbiVisualsApi.IViewport;
series: IDataRepresentationSeries;
dataPoint: IDataRepresentationPoint;
kpiSettings: KpiDescriptor;

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

@ -24,7 +24,7 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
export interface IVisualComponent<RenderOptionsType> {
isShown?: boolean;
@ -35,5 +35,6 @@ export interface IVisualComponent<RenderOptionsType> {
hide?(): void;
show?(): void;
toggle?(): void;
getViewport?(): powerbi.IViewport;
getViewport?(): powerbiVisualsApi.IViewport;
getComponents?(): IVisualComponent<any>[]
}

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

@ -25,7 +25,7 @@
*/
import { Dispatch } from "d3-dispatch";
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { ScaleService } from "../services/scaleService";
import { Settings } from "../settings/settings";
@ -36,7 +36,7 @@ export interface IVisualComponentConstructorOptions extends IVisualComponentBase
eventDispatcher?: Dispatch<any>; // TODO
id?: number | string;
scaleService?: ScaleService;
style?: powerbi.extensibility.IColorPalette; // TODO: must be renamed
style?: powerbiVisualsApi.extensibility.IColorPalette; // TODO: must be renamed
getSettings?: () => Settings;
tooltipServiceWrapper?: ITooltipServiceWrapper;
}

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

@ -24,13 +24,13 @@
* THE SOFTWARE.
*/
import powerbi from "powerbi-visuals-api";
import powerbiVisualsApi from "powerbi-visuals-api";
import { IDataRepresentation } from "../converter/data/dataRepresentation";
import { Settings } from "../settings/settings";
export interface IVisualComponentRenderOptions {
settings: Settings;
viewport: powerbi.IViewport;
viewport: powerbiVisualsApi.IViewport;
data: IDataRepresentation;
}

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

@ -15,6 +15,6 @@
]
},
"files": [
"./src/visual.ts"
"src/multiKpi.ts"
]
}

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

@ -1,88 +1,9 @@
{
"extends": "tslint:recommended",
"rulesDirectory": ["./node_modules/tslint-microsoft-contrib"],
"rules": {
"insecure-random": false,
"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
"extends": "tslint-microsoft-contrib/recommended",
"rulesDirectory": [
"node_modules/tslint-microsoft-contrib"
],
"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
}
"rules": {
"no-relative-imports": false
}
}