Merge pull request #2 from vtkalek/master
LineDotChart after convertion to new API 1.3.0
This commit is contained in:
Коммит
aeff8b0aab
|
@ -0,0 +1,7 @@
|
|||
node_modules
|
||||
.DS_Store
|
||||
.tmp
|
||||
dist
|
||||
typings
|
||||
.api
|
||||
*.log
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 457 B |
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Шар_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="20px" height="20px" viewBox="0 0 20 20" enable-background="new 0 0 20 20" xml:space="preserve">
|
||||
<polygon fill="#808080" points="2,17.984 2,16.18 2,14.767 2,11.584 2,10.199 2,1 1,1 1,18.969 1.047,18.969 1.047,18.984
|
||||
18.984,18.984 18.984,17.984 "/>
|
||||
<polygon fill="#4D82B8" points="5.482,8.126 10.537,13.182 18.775,4.9 18.068,4.193 10.516,11.746 5.463,6.693 2,10.199 2,11.584
|
||||
"/>
|
||||
<circle r="2" cx="5.482" cy="8.126" fill="#005c55"/>
|
||||
<circle r="2" cx="10.537" cy="13.182" fill="#005c55"/>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 865 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 33 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 6.6 KiB |
|
@ -0,0 +1,106 @@
|
|||
{
|
||||
"dataRoles": [
|
||||
{
|
||||
"name": "Date",
|
||||
"kind": "Grouping",
|
||||
"displayName": "Date"
|
||||
},
|
||||
{
|
||||
"name": "Values",
|
||||
"kind": "Measure",
|
||||
"displayName": "Values"
|
||||
}
|
||||
],
|
||||
"dataViewMappings": [
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"Date": {
|
||||
"min": 0,
|
||||
"max": 1
|
||||
},
|
||||
"Values": {
|
||||
"min": 0,
|
||||
"max": 1
|
||||
},
|
||||
"Labels": {
|
||||
"min": 0,
|
||||
"max": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"categorical": {
|
||||
"categories": {
|
||||
"select": [
|
||||
{ "for": { "in": "Date" } }
|
||||
],
|
||||
"dataReductionAlgorithm": { "top": { "count": 10000 } }
|
||||
},
|
||||
"values": {
|
||||
"select": [
|
||||
{ "bind": { "to": "Values" } }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"supportsHighlight": true,
|
||||
"objects": {
|
||||
"lineoptions": {
|
||||
"displayName": "Line",
|
||||
"properties": {
|
||||
"fill": {
|
||||
"displayName": "Fill",
|
||||
"type": { "fill": { "solid": { "color": true } } }
|
||||
},
|
||||
"lineThickness": {
|
||||
"displayName": "Thickness",
|
||||
"type": { "numeric": true }
|
||||
}
|
||||
}
|
||||
},
|
||||
"dotoptions": {
|
||||
"displayName": "Dot",
|
||||
"properties": {
|
||||
"color": {
|
||||
"displayName": "Fill",
|
||||
"type": { "fill": { "solid": { "color": true } } }
|
||||
},
|
||||
"dotSizeMin": {
|
||||
"displayName": "Min size",
|
||||
"type": { "numeric": true }
|
||||
},
|
||||
"dotSizeMax": {
|
||||
"displayName": "Max size",
|
||||
"type": { "numeric": true }
|
||||
}
|
||||
}
|
||||
},
|
||||
"counteroptions": {
|
||||
"displayName": "Counter",
|
||||
"properties": {
|
||||
"counterTitle": {
|
||||
"displayName": "Title",
|
||||
"type": { "text": true }
|
||||
}
|
||||
}
|
||||
},
|
||||
"misc": {
|
||||
"displayName": "Animation",
|
||||
"properties": {
|
||||
"isAnimated": {
|
||||
"displayName": "Animated",
|
||||
"type": { "bool": true }
|
||||
},
|
||||
"isStopped": {
|
||||
"displayName": "Stop on load",
|
||||
"type": { "bool": true }
|
||||
},
|
||||
"duration": {
|
||||
"displayName": "Time",
|
||||
"type": { "numeric": true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Power BI Visualizations
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
* All rights reserved.
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the ""Software""), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const recursivePathToTests = 'test/**/*.ts';
|
||||
|
||||
module.exports = (config) => {
|
||||
const browsers = [];
|
||||
|
||||
if (process.env.TRAVIS) {
|
||||
browsers.push('ChromeTravisCI');
|
||||
} else {
|
||||
browsers.push('Chrome');
|
||||
}
|
||||
|
||||
config.set({
|
||||
browsers,
|
||||
customLaunchers: {
|
||||
ChromeTravisCI: {
|
||||
base: 'Chrome',
|
||||
flags: ['--no-sandbox']
|
||||
}
|
||||
},
|
||||
colors: true,
|
||||
frameworks: ['jasmine'],
|
||||
reporters: ['progress'],
|
||||
singleRun: true,
|
||||
files: [
|
||||
'.tmp/drop/visual.css',
|
||||
'.tmp/drop/visual.js',
|
||||
'node_modules/powerbi-visuals-utils-testutils/lib/index.js',
|
||||
'node_modules/jasmine-jquery/lib/jasmine-jquery.js',
|
||||
recursivePathToTests
|
||||
],
|
||||
preprocessors: {
|
||||
[recursivePathToTests]: ['typescript']
|
||||
},
|
||||
typescriptPreprocessor: {
|
||||
options: {
|
||||
sourceMap: false,
|
||||
target: 'ES5',
|
||||
removeComments: false,
|
||||
concatenateOutput: false
|
||||
},
|
||||
transformPath: (path) => {
|
||||
return path.replace(/\.ts$/, '.js');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"name": "powerbi-visuals-linedotchart",
|
||||
"description": "LineDot Chart",
|
||||
"version": "0.3.3",
|
||||
"author": {
|
||||
"name": "Microsoft",
|
||||
"email": "pbicvsupport@microsoft.com"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "typings install && pbiviz update 1.3.0",
|
||||
"typings": "typings",
|
||||
"pbiviz": "pbiviz",
|
||||
"start": "pbiviz start",
|
||||
"package": "pbiviz package",
|
||||
"lint": "node node_modules/tslint/bin/tslint \"+(src|test)/**/*.ts\"",
|
||||
"pretest": "pbiviz package --resources --no-minify --no-pbiviz",
|
||||
"test": "karma start"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Microsoft/PowerBI-visuals-linedotchart.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jasmine": "^2.5.37",
|
||||
"@types/jasmine-jquery": "^1.5.28",
|
||||
"@types/lodash": "^4.14.43",
|
||||
"d3": "3.5.5",
|
||||
"globalize": "0.1.0-a2",
|
||||
"jasmine": "^2.5.2",
|
||||
"jasmine-jquery": "2.1.1",
|
||||
"jquery": "3.1.1",
|
||||
"karma": "1.3.0",
|
||||
"karma-chrome-launcher": "2.0.0",
|
||||
"karma-jasmine": "1.0.2",
|
||||
"karma-typescript-preprocessor": "0.3.0",
|
||||
"lodash": "4.16.2",
|
||||
"moment": "2.15.1",
|
||||
"powerbi-visuals-tools": "1.3.0",
|
||||
"powerbi-visuals-utils-chartutils": "^0.2.1",
|
||||
"powerbi-visuals-utils-dataviewutils": "^1.0.1",
|
||||
"powerbi-visuals-utils-testutils": "^0.2.2",
|
||||
"powerbi-visuals-utils-tooltiputils": "^0.3.0",
|
||||
"tslint": "3.15.1",
|
||||
"typings": "1.4.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"visual": {
|
||||
"name": "LineDotChart",
|
||||
"displayName": "LineDot Chart",
|
||||
"guid": "LineDotChart1460463831201",
|
||||
"visualClassName": "LineDotChart",
|
||||
"version": "0.3.3",
|
||||
"description": "The LineDot chart is an animated line chart with fun animated dots. Use the LineDot chart to engage your audience especially in a presentation context. The bubbles size can be dynamic based on data you provide. A counter is provided that you can use to show a running value as the chart animates. Format options are provided for Lines, Dots, and Animation.",
|
||||
"supportUrl": "http://community.powerbi.com",
|
||||
"gitHubUrl": "https://github.com/Microsoft/powerbi-visuals-linedotchart"
|
||||
},
|
||||
"apiVersion": "1.3.0",
|
||||
"author": {
|
||||
"name": "Microsoft",
|
||||
"email": "pbicvsupport@microsoft.com"
|
||||
},
|
||||
"assets": {
|
||||
"icon": "assets/icon.png"
|
||||
},
|
||||
"externalJS": [
|
||||
"node_modules/jquery/dist/jquery.min.js",
|
||||
"node_modules/lodash/lodash.min.js",
|
||||
"node_modules/d3/d3.js",
|
||||
"node_modules/powerbi-visuals-utils-typeutils/lib/index.js",
|
||||
"node_modules/globalize/lib/globalize.js",
|
||||
"node_modules/globalize/lib/cultures/globalize.culture.en-US.js",
|
||||
"node_modules/powerbi-visuals-utils-svgutils/lib/index.js",
|
||||
"node_modules/powerbi-visuals-utils-dataviewutils/lib/index.js",
|
||||
"node_modules/powerbi-visuals-utils-formattingutils/lib/index.js",
|
||||
"node_modules/powerbi-visuals-utils-interactivityutils/lib/index.js",
|
||||
"node_modules/powerbi-visuals-utils-chartutils/lib/index.js",
|
||||
"node_modules/powerbi-visuals-utils-dataviewutils/lib/index.js",
|
||||
"node_modules/powerbi-visuals-utils-tooltiputils/lib/index.js"
|
||||
],
|
||||
"style": "style/lineDotChart.less",
|
||||
"capabilities": "capabilities.json"
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Power BI Visualizations
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
* All rights reserved.
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the ""Software""), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual {
|
||||
import converterHelper = powerbi.extensibility.utils.dataview.converterHelper;
|
||||
import IInteractiveBehavior = powerbi.extensibility.utils.interactivity.IInteractiveBehavior;
|
||||
import IInteractivityService = powerbi.extensibility.utils.interactivity.IInteractivityService;
|
||||
import ISelectionHandler = powerbi.extensibility.utils.interactivity.ISelectionHandler;
|
||||
import SelectableDataPoint = powerbi.extensibility.utils.interactivity.SelectableDataPoint;
|
||||
|
||||
export class LineDotChartWebBehavior implements IInteractiveBehavior {
|
||||
private selection: d3.Selection<any>;
|
||||
private interactivityService: IInteractivityService;
|
||||
private hasHighlights: boolean;
|
||||
|
||||
public bindEvents(options: LineDotChartBehaviorOptions, selectionHandler: ISelectionHandler): void {
|
||||
let selection: d3.Selection<any> = this.selection = options.selection;
|
||||
let clearCatcher: d3.Selection<any> = options.clearCatcher;
|
||||
this.interactivityService = options.interactivityService;
|
||||
this.hasHighlights = options.hasHighlights;
|
||||
|
||||
selection.on('click', function (d: SelectableDataPoint) {
|
||||
selectionHandler.handleSelection(d, (d3.event as MouseEvent).ctrlKey);
|
||||
(d3.event as MouseEvent).stopPropagation();
|
||||
});
|
||||
|
||||
clearCatcher.on('click', function () {
|
||||
selectionHandler.handleClearSelection();
|
||||
});
|
||||
}
|
||||
|
||||
public renderSelection(hasSelection: boolean): void {
|
||||
let hasHighlights: boolean = this.hasHighlights;
|
||||
|
||||
this.selection.style("opacity", (d: LineDotPoint) => {
|
||||
return lineDotChartUtils.getFillOpacity(d.selected, d.highlight, !d.highlight && hasSelection, !d.selected && hasHighlights);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface LineDotChartBehaviorOptions {
|
||||
selection: d3.Selection<any>;
|
||||
clearCatcher: d3.Selection<any>;
|
||||
interactivityService: IInteractivityService;
|
||||
hasHighlights: boolean;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Power BI Visualizations
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
* All rights reserved.
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the ""Software""), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual {
|
||||
import DataViewMetadataColumn = powerbi.DataViewMetadataColumn;
|
||||
import DataViewValueColumns = powerbi.DataViewValueColumns;
|
||||
import DataViewCategoricalColumn = powerbi.DataViewCategoricalColumn;
|
||||
import DataViewValueColumn = powerbi.DataViewValueColumn;
|
||||
import converterHelper = powerbi.extensibility.utils.dataview.converterHelper;
|
||||
|
||||
export class LineDotChartColumns<T> {
|
||||
|
||||
public static getColumnSources(dataView: DataView) {
|
||||
return this.getColumnSourcesT<DataViewMetadataColumn>(dataView);
|
||||
}
|
||||
|
||||
public static getTableValues(dataView: DataView) {
|
||||
let table: DataViewTable = dataView && dataView.table;
|
||||
let columns: LineDotChartColumns<any[]> = this.getColumnSourcesT<any[]>(dataView);
|
||||
return columns && table && _.mapValues(
|
||||
columns, (n: DataViewMetadataColumn, i) => n && table.rows.map(row => row[n.index]));
|
||||
}
|
||||
|
||||
public static getTableRows(dataView: DataView) {
|
||||
let table: DataViewTable = dataView && dataView.table;
|
||||
let columns: LineDotChartColumns<any[]> = this.getColumnSourcesT<any[]>(dataView);
|
||||
return columns && table && table.rows.map(row =>
|
||||
_.mapValues(columns, (n: DataViewMetadataColumn, i) => n && row[n.index]));
|
||||
}
|
||||
|
||||
public static getCategoricalValues(dataView: DataView) {
|
||||
let categorical: DataViewCategorical = dataView && dataView.categorical;
|
||||
let categories: DataViewCategoryColumn[] = categorical && categorical.categories || [];
|
||||
let values: DataViewValueColumns = categorical && categorical.values || <DataViewValueColumns>[];
|
||||
let series: any = categorical && values.source && this.getSeriesValues(dataView);
|
||||
return categorical && _.mapValues(new this<any[]>(), (n, i) =>
|
||||
(<DataViewCategoricalColumn[]>_.toArray(categories)).concat(_.toArray(values))
|
||||
.filter(x => x.source.roles && x.source.roles[i]).map(x => x.values)[0]
|
||||
|| values.source && values.source.roles && values.source.roles[i] && series);
|
||||
}
|
||||
|
||||
public static getSeriesValues(dataView: DataView) {
|
||||
return dataView && dataView.categorical && dataView.categorical.values
|
||||
&& dataView.categorical.values.map(x => converterHelper.getSeriesName(x.source));
|
||||
}
|
||||
|
||||
public static getCategoricalColumns(dataView: DataView) {
|
||||
let categorical: DataViewCategorical = dataView && dataView.categorical;
|
||||
let categories: DataViewCategoryColumn[] = categorical && categorical.categories || [];
|
||||
let values: DataViewValueColumns = categorical && categorical.values || <DataViewValueColumns>[];
|
||||
return categorical && _.mapValues(
|
||||
new this<DataViewCategoryColumn & DataViewValueColumn[] & DataViewValueColumns>(),
|
||||
(n, i) => {
|
||||
let result: any = categories.filter(x => x.source.roles && x.source.roles[i])[0];
|
||||
if (!result) {
|
||||
result = values.source && values.source.roles && values.source.roles[i] && values;
|
||||
}
|
||||
if (!result) {
|
||||
result = values.filter(x => x.source.roles && x.source.roles[i]);
|
||||
if (_.isEmpty(result)) {
|
||||
result = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
public static getGroupedValueColumns(dataView: DataView) {
|
||||
let categorical: DataViewCategorical = dataView && dataView.categorical;
|
||||
let values: DataViewValueColumns = categorical && categorical.values;
|
||||
let grouped: DataViewValueColumnGroup[] = values && values.grouped();
|
||||
return grouped && grouped.map(g => _.mapValues(
|
||||
new this<DataViewValueColumn>(),
|
||||
(n, i) => g.values.filter(v => v.source.roles[i])[0]));
|
||||
}
|
||||
|
||||
private static getColumnSourcesT<T>(dataView: DataView) {
|
||||
let columns: DataViewMetadataColumn[] = dataView && dataView.metadata && dataView.metadata.columns;
|
||||
return columns && _.mapValues(
|
||||
new this<T>(), (n, i) => columns.filter(x => x.roles && x.roles[i])[0]);
|
||||
}
|
||||
|
||||
public Date: T = null;
|
||||
public Values: T = null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Power BI Visualizations
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
* All rights reserved.
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the ""Software""), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual {
|
||||
import SelectableDataPoint = powerbi.extensibility.utils.interactivity.SelectableDataPoint;
|
||||
import IInteractivityService = powerbi.extensibility.utils.interactivity.IInteractivityService;
|
||||
import IAxisProperties = powerbi.extensibility.utils.chart.axis.IAxisProperties;
|
||||
import IValueFormatter = powerbi.extensibility.utils.formatting.IValueFormatter;
|
||||
|
||||
export interface LineDotPoint extends SelectableDataPoint {
|
||||
time: number | Date;
|
||||
value: number;
|
||||
dot: number;
|
||||
sum: number;
|
||||
highlight?: boolean;
|
||||
}
|
||||
|
||||
export interface Legend {
|
||||
text: string;
|
||||
transform?: string;
|
||||
dx?: string;
|
||||
dy?: string;
|
||||
}
|
||||
|
||||
export interface LineDotChartViewModel {
|
||||
dotPoints: LineDotPoint[];
|
||||
settings: LineDotChartSettings;
|
||||
dateMetadataColumn: DataViewMetadataColumn;
|
||||
valuesMetadataColumn: DataViewMetadataColumn;
|
||||
dateColumnFormatter: IValueFormatter;
|
||||
isDateTime: boolean;
|
||||
minDate: number;
|
||||
maxDate: number;
|
||||
minValue: number;
|
||||
maxValue: number;
|
||||
sumOfValues: number;
|
||||
hasHighlights: boolean;
|
||||
}
|
||||
|
||||
export interface MinMaxValue {
|
||||
min: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
export interface LineDotChartDefaultSettingsRange {
|
||||
dotSize: MinMaxValue;
|
||||
lineThickness: MinMaxValue;
|
||||
animationDuration: MinMaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Power BI Visualizations
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
* All rights reserved.
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the ""Software""), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual {
|
||||
import DataViewObjectsParser = powerbi.extensibility.utils.dataview.DataViewObjectsParser;
|
||||
|
||||
export class LineDotChartSettings extends DataViewObjectsParser {
|
||||
public lineoptions: LineSettings = new LineSettings();
|
||||
public dotoptions: DotSettings = new DotSettings();
|
||||
public counteroptions: CounterSettings = new CounterSettings();
|
||||
public misc: MiscSettings = new MiscSettings();
|
||||
}
|
||||
|
||||
export class LineSettings {
|
||||
public fill: string = "rgb(102, 212, 204)";
|
||||
public lineThickness: number = 3;
|
||||
}
|
||||
|
||||
export class DotSettings {
|
||||
public color: string = "#005c55";
|
||||
public dotSizeMin: number = 4;
|
||||
public dotSizeMax: number = 38;
|
||||
}
|
||||
|
||||
export class CounterSettings {
|
||||
public counterTitle: string = "Total features";
|
||||
}
|
||||
|
||||
export class MiscSettings {
|
||||
public isAnimated: boolean = true;
|
||||
public isStopped: boolean = true;
|
||||
public duration: number = 20;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,738 @@
|
|||
/*
|
||||
* Power BI Visualizations
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
* All rights reserved.
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the ""Software""), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual {
|
||||
import ClassAndSelector = powerbi.extensibility.utils.svg.CssConstants.ClassAndSelector;
|
||||
import createClassAndSelector = powerbi.extensibility.utils.svg.CssConstants.createClassAndSelector;
|
||||
import DataViewObjectPropertyTypeDescriptor = powerbi.DataViewPropertyValue;
|
||||
import SelectableDataPoint = powerbi.extensibility.utils.interactivity.SelectableDataPoint;
|
||||
import IValueFormatter = powerbi.extensibility.utils.formatting.IValueFormatter;
|
||||
import IInteractivityService = powerbi.extensibility.utils.interactivity.IInteractivityService;
|
||||
import IMargin = powerbi.extensibility.utils.chart.axis.IMargin;
|
||||
import IInteractiveBehavior = powerbi.extensibility.utils.interactivity.IInteractiveBehavior;
|
||||
import ISelectionHandler = powerbi.extensibility.utils.interactivity.ISelectionHandler;
|
||||
import appendClearCatcher = powerbi.extensibility.utils.interactivity.appendClearCatcher;
|
||||
import createInteractivityService = powerbi.extensibility.utils.interactivity.createInteractivityService;
|
||||
import valueFormatter = powerbi.extensibility.utils.formatting.valueFormatter;
|
||||
import IAxisProperties = powerbi.extensibility.utils.chart.axis.IAxisProperties;
|
||||
import IVisualHost = powerbi.extensibility.visual.IVisualHost;
|
||||
import SVGUtil = powerbi.extensibility.utils.svg;
|
||||
import AxisHelper = powerbi.extensibility.utils.chart.axis;
|
||||
import TextMeasurementService = powerbi.extensibility.utils.formatting.textMeasurementService;
|
||||
import IColorPalette = powerbi.extensibility.IColorPalette;
|
||||
import tooltip = powerbi.extensibility.utils.tooltip;
|
||||
import TooltipEventArgs = powerbi.extensibility.utils.tooltip.TooltipEventArgs;
|
||||
import ITooltipServiceWrapper = powerbi.extensibility.utils.tooltip.ITooltipServiceWrapper;
|
||||
import valueType = utils.type.ValueType;
|
||||
import DataViewObjectsParser = utils.dataview.DataViewObjectsParser;
|
||||
|
||||
export interface LineDotChartDataRoles<T> {
|
||||
Date?: T;
|
||||
Values?: T;
|
||||
}
|
||||
|
||||
export class LineDotChart implements IVisual {
|
||||
|
||||
private static Identity: ClassAndSelector = createClassAndSelector("lineDotChart");
|
||||
private static Axes: ClassAndSelector = createClassAndSelector("axes");
|
||||
private static Axis: ClassAndSelector = createClassAndSelector("axis");
|
||||
private static Legends: ClassAndSelector = createClassAndSelector("legends");
|
||||
private static Legend: ClassAndSelector = createClassAndSelector("legend");
|
||||
private static Line: ClassAndSelector = createClassAndSelector("line");
|
||||
|
||||
private static LegendSize: number = 50;
|
||||
private static AxisSize: number = 30;
|
||||
|
||||
private static defaultSettingsRange: LineDotChartDefaultSettingsRange = {
|
||||
dotSize: {
|
||||
min: 0,
|
||||
max: 100
|
||||
},
|
||||
lineThickness: {
|
||||
min: 0,
|
||||
max: 50,
|
||||
},
|
||||
animationDuration: {
|
||||
min: 0,
|
||||
max: 1000,
|
||||
}
|
||||
};
|
||||
|
||||
private data: LineDotChartViewModel;
|
||||
private root: d3.Selection<any>;
|
||||
private main: d3.Selection<any>;
|
||||
private axes: d3.Selection<any>;
|
||||
private axisX: d3.Selection<any>;
|
||||
private axisY: d3.Selection<any>;
|
||||
private axisY2: d3.Selection<any>;
|
||||
private legends: d3.Selection<any>;
|
||||
private line: d3.Selection<any>;
|
||||
private colors: IColorPalette;
|
||||
private xAxisProperties: IAxisProperties;
|
||||
private yAxisProperties: IAxisProperties;
|
||||
private yAxis2Properties: IAxisProperties;
|
||||
private layout: VisualLayout;
|
||||
private interactivityService: IInteractivityService;
|
||||
private behavior: IInteractiveBehavior;
|
||||
private hostService: IVisualHost;
|
||||
|
||||
private get settings(): LineDotChartSettings {
|
||||
return this.data && this.data.settings;
|
||||
}
|
||||
|
||||
private static viewportMargins = {
|
||||
top: 10,
|
||||
right: 30,
|
||||
bottom: 10,
|
||||
left: 10
|
||||
};
|
||||
|
||||
private static viewportDimentions = {
|
||||
width: 150,
|
||||
height: 150
|
||||
};
|
||||
|
||||
private tooltipServiceWrapper: ITooltipServiceWrapper;
|
||||
constructor(options: VisualConstructorOptions) {
|
||||
this.tooltipServiceWrapper = tooltip.createTooltipServiceWrapper(
|
||||
options.host.tooltipService,
|
||||
options.element);
|
||||
this.hostService = options.host;
|
||||
this.layout = new VisualLayout(null, LineDotChart.viewportMargins);
|
||||
this.layout.minViewport = <IViewport>LineDotChart.viewportDimentions;
|
||||
this.interactivityService = createInteractivityService(options.host);
|
||||
this.behavior = new LineDotChartWebBehavior();
|
||||
this.root = d3.select(options.element)
|
||||
.append('svg')
|
||||
.classed(LineDotChart.Identity.class, true);
|
||||
|
||||
this.main = this.root.append('g');
|
||||
this.axes = this.main.append('g').classed(LineDotChart.Axes.class, true);
|
||||
this.axisX = this.axes.append('g').classed(LineDotChart.Axis.class, true);
|
||||
this.axisY = this.axes.append('g').classed(LineDotChart.Axis.class, true);
|
||||
this.axisY2 = this.axes.append('g').classed(LineDotChart.Axis.class, true);
|
||||
this.legends = this.main.append('g').classed(LineDotChart.Legends.class, true);
|
||||
this.line = this.main.append('g').classed(LineDotChart.Line.class, true);
|
||||
|
||||
this.colors = options.host.colorPalette;
|
||||
}
|
||||
|
||||
public update(options: VisualUpdateOptions) {
|
||||
|
||||
if (!options || !options.dataViews || !options.dataViews[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.layout.viewport = options.viewport;
|
||||
let data: LineDotChartViewModel = LineDotChart.converter(options.dataViews[0], this.hostService);
|
||||
if (!data || _.isEmpty(data.dotPoints)) {
|
||||
this.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
this.data = data;
|
||||
|
||||
if (this.interactivityService) {
|
||||
this.interactivityService.applySelectionStateToData(this.data.dotPoints);
|
||||
}
|
||||
|
||||
this.resize();
|
||||
this.calculateAxes();
|
||||
this.draw();
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.root = null;
|
||||
}
|
||||
|
||||
public onClearSelection(): void {
|
||||
if (this.interactivityService) {
|
||||
this.interactivityService.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.settings.misc.isAnimated = false;
|
||||
this.axes.selectAll(LineDotChart.Axis.selector).selectAll("*").remove();
|
||||
this.main.selectAll(LineDotChart.Legends.selector).selectAll("*").remove();
|
||||
this.main.selectAll(LineDotChart.Line.selector).selectAll("*").remove();
|
||||
this.main.selectAll(LineDotChart.Legend.selector).selectAll("*").remove();
|
||||
this.line.selectAll(LineDotChart.textSelector).remove();
|
||||
}
|
||||
|
||||
public setIsStopped(isStopped: Boolean): void {
|
||||
let objects: VisualObjectInstancesToPersist = {
|
||||
merge: [
|
||||
<VisualObjectInstance>{
|
||||
objectName: "misc",
|
||||
selector: undefined,
|
||||
properties: {
|
||||
"isStopped": isStopped,
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
this.hostService.persistProperties(objects);
|
||||
}
|
||||
|
||||
public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {
|
||||
return LineDotChartSettings.enumerateObjectInstances(
|
||||
this.settings || LineDotChartSettings.getDefault(),
|
||||
options);
|
||||
}
|
||||
|
||||
private static validateDataValue(value: number, defaultValues: MinMaxValue): number {
|
||||
if (value < defaultValues.min) {
|
||||
return defaultValues.min;
|
||||
} else if (value > defaultValues.max) {
|
||||
return defaultValues.max;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
private static dateMaxCutter: number = .05;
|
||||
private static makeSomeSpaceForCounter: number = .10;
|
||||
private static converter(dataView: DataView, visualHost: IVisualHost): LineDotChartViewModel {
|
||||
let categorical: LineDotChartColumns<DataViewCategoryColumn & DataViewValueColumn[]> = LineDotChartColumns.getCategoricalColumns(dataView);
|
||||
if (!categorical
|
||||
|| !categorical.Date
|
||||
|| _.isEmpty(categorical.Date.values)
|
||||
|| !categorical.Values
|
||||
|| !categorical.Values[0]
|
||||
|| _.isEmpty(categorical.Values[0].values)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let categoryType: valueType = AxisHelper.getCategoryValueType(categorical.Date.source, true);
|
||||
if (AxisHelper.isOrdinal(categoryType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let isDateTime: boolean = AxisHelper.isDateTime(categoryType);
|
||||
let categoricalValues: LineDotChartColumns<any[]> = LineDotChartColumns.getCategoricalValues(dataView);
|
||||
let settings: LineDotChartSettings = this.parseSettings(dataView);
|
||||
let dateValues: number[] = [],
|
||||
valueValues: number[] = [];
|
||||
for (let i = 0, length = categoricalValues.Date.length; i < length; i++) {
|
||||
if (_.isDate(categoricalValues.Date[i]) || _.isNumber(categoricalValues.Date[i])) {
|
||||
if (isDateTime) {
|
||||
dateValues.push((<Date>categoricalValues.Date[i]).getTime());
|
||||
} else {
|
||||
dateValues.push(categoricalValues.Date[i]);
|
||||
}
|
||||
|
||||
valueValues.push(categoricalValues.Values[i] || 0);
|
||||
}
|
||||
}
|
||||
|
||||
let hasHighlights: boolean = !!(categorical.Values.length > 0 && categorical.Values[0].highlights);
|
||||
|
||||
let extentDate: [number, number] = d3.extent(dateValues);
|
||||
let minDate: number = extentDate[0];
|
||||
let maxDate: number = extentDate[1] + (extentDate[1] - extentDate[0]) * LineDotChart.dateMaxCutter;
|
||||
let dateColumnFormatter = valueFormatter.create({
|
||||
format: valueFormatter.getFormatStringByColumn(categorical.Date.source, true) || categorical.Date.source.format
|
||||
});
|
||||
|
||||
let extentValues: [number, number] = d3.extent(valueValues);
|
||||
let minValue: number = extentValues[0];
|
||||
let maxValue: number = extentValues[1];
|
||||
let dotPoints: LineDotPoint[] = [];
|
||||
let sumOfValues: number = 0;
|
||||
for (let i: number = 0, length: number = dateValues.length; i < length; i++) {
|
||||
let value: number = valueValues[i];
|
||||
let time: number = dateValues[i];
|
||||
sumOfValues += value;
|
||||
|
||||
let selector: ISelectionId = visualHost.createSelectionIdBuilder().withCategory(categorical.Date, i).createSelectionId();
|
||||
dotPoints.push({
|
||||
dot: (maxValue - minValue) ? (value - minValue) / (maxValue - minValue) : 0,
|
||||
value: value,
|
||||
sum: sumOfValues,
|
||||
time: time,
|
||||
selected: false,
|
||||
identity: selector,
|
||||
highlight: hasHighlights && !!(categorical.Values[0].highlights[i])
|
||||
});
|
||||
}
|
||||
|
||||
// make some space for counter + 25%
|
||||
sumOfValues = sumOfValues + (sumOfValues - minValue) * LineDotChart.makeSomeSpaceForCounter;
|
||||
|
||||
return {
|
||||
dotPoints: dotPoints,
|
||||
settings: settings,
|
||||
dateMetadataColumn: categorical.Date.source,
|
||||
valuesMetadataColumn: categorical.Values[0].source,
|
||||
dateColumnFormatter: dateColumnFormatter,
|
||||
isDateTime: isDateTime,
|
||||
minDate: minDate,
|
||||
maxDate: maxDate,
|
||||
minValue: minValue,
|
||||
maxValue: maxValue,
|
||||
sumOfValues: sumOfValues,
|
||||
hasHighlights: hasHighlights,
|
||||
};
|
||||
}
|
||||
|
||||
private static parseSettings(dataView: DataView): LineDotChartSettings {
|
||||
let settings: LineDotChartSettings = LineDotChartSettings.parse<LineDotChartSettings>(dataView);
|
||||
let defaultRange: LineDotChartDefaultSettingsRange = this.defaultSettingsRange;
|
||||
settings.dotoptions.dotSizeMin = this.validateDataValue(settings.dotoptions.dotSizeMin, defaultRange.dotSize);
|
||||
settings.dotoptions.dotSizeMax = this.validateDataValue(settings.dotoptions.dotSizeMax, {
|
||||
min: settings.dotoptions.dotSizeMin,
|
||||
max: defaultRange.dotSize.max
|
||||
});
|
||||
settings.lineoptions.lineThickness = this.validateDataValue(settings.lineoptions.lineThickness, defaultRange.lineThickness);
|
||||
settings.misc.duration = this.validateDataValue(settings.misc.duration, defaultRange.animationDuration);
|
||||
|
||||
return settings;
|
||||
}
|
||||
private static outerPadding: number = 0;
|
||||
private static forcedTickSize: number = 150;
|
||||
private static xLabelMaxWidth: number = 160;
|
||||
private static xLabelTickSize: number = 3.2;
|
||||
private calculateAxes() {
|
||||
let effectiveWidth: number = Math.max(0, this.layout.viewportIn.width - LineDotChart.LegendSize - LineDotChart.AxisSize);
|
||||
let effectiveHeight: number = Math.max(0, this.layout.viewportIn.height - LineDotChart.LegendSize);
|
||||
|
||||
this.xAxisProperties = AxisHelper.createAxis({
|
||||
pixelSpan: effectiveWidth,
|
||||
dataDomain: [this.data.minDate, this.data.maxDate],
|
||||
metaDataColumn: this.data.dateMetadataColumn,
|
||||
formatString: null,
|
||||
outerPadding: LineDotChart.outerPadding,
|
||||
isCategoryAxis: true,
|
||||
isScalar: true,
|
||||
isVertical: false,
|
||||
forcedTickCount: Math.max(this.layout.viewport.width / LineDotChart.forcedTickSize, 0),
|
||||
useTickIntervalForDisplayUnits: true,
|
||||
getValueFn: (index: number, type: valueType) => {
|
||||
if (this.data.isDateTime) {
|
||||
return this.data.dateColumnFormatter.format(new Date(index));
|
||||
} else {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.xAxisProperties.xLabelMaxWidth = Math.min(LineDotChart.xLabelMaxWidth, this.layout.viewportIn.width / LineDotChart.xLabelTickSize);
|
||||
this.xAxisProperties.formatter = this.data.dateColumnFormatter;
|
||||
|
||||
this.yAxisProperties = AxisHelper.createAxis({
|
||||
pixelSpan: effectiveHeight,
|
||||
dataDomain: [this.data.minValue, this.data.sumOfValues],
|
||||
metaDataColumn: this.data.valuesMetadataColumn,
|
||||
formatString: null,
|
||||
outerPadding: LineDotChart.outerPadding,
|
||||
isCategoryAxis: false,
|
||||
isScalar: true,
|
||||
isVertical: true,
|
||||
useTickIntervalForDisplayUnits: true
|
||||
});
|
||||
|
||||
this.yAxis2Properties = AxisHelper.createAxis({
|
||||
pixelSpan: effectiveHeight,
|
||||
dataDomain: [this.data.minValue, this.data.sumOfValues],
|
||||
metaDataColumn: this.data.valuesMetadataColumn,
|
||||
formatString: null,
|
||||
outerPadding: LineDotChart.outerPadding,
|
||||
isCategoryAxis: false,
|
||||
isScalar: true,
|
||||
isVertical: true,
|
||||
useTickIntervalForDisplayUnits: true
|
||||
});
|
||||
this.yAxis2Properties.axis.orient('right');
|
||||
}
|
||||
|
||||
private static rotateAngle: number = 270;
|
||||
private generateAxisLabels(): Legend[] {
|
||||
return [
|
||||
{
|
||||
transform: SVGUtil.translate((this.layout.viewportIn.width) / 2, (this.layout.viewportIn.height)),
|
||||
text: "", // xAxisTitle
|
||||
dx: "1em",
|
||||
dy: "-1em"
|
||||
}, {
|
||||
transform: SVGUtil.translateAndRotate(0, this.layout.viewportIn.height / 2, 0, 0, LineDotChart.rotateAngle),
|
||||
text: "", // yAxisTitle
|
||||
dx: "3em"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private resize(): void {
|
||||
this.root.attr({
|
||||
width: this.layout.viewport.width,
|
||||
height: this.layout.viewport.height
|
||||
});
|
||||
this.main.attr('transform', SVGUtil.translate(this.layout.margin.left, this.layout.margin.top));
|
||||
this.legends.attr('transform', SVGUtil.translate(this.layout.margin.left, this.layout.margin.top));
|
||||
this.line.attr('transform', SVGUtil.translate(this.layout.margin.left + LineDotChart.LegendSize, 0));
|
||||
this.axes.attr('transform', SVGUtil.translate(this.layout.margin.left + LineDotChart.LegendSize, 0));
|
||||
this.axisX.attr('transform', SVGUtil.translate(0, this.layout.viewportIn.height - LineDotChart.LegendSize));
|
||||
this.axisY2.attr('transform', SVGUtil.translate(this.layout.viewportIn.width - LineDotChart.LegendSize - LineDotChart.AxisSize, 0));
|
||||
}
|
||||
private static tickText: string = '.tick text';
|
||||
private static dotPointsText: string = "g.path, g.dot-points";
|
||||
private static dotPathText: string = "g.path";
|
||||
private draw(): void {
|
||||
this.stopAnimation();
|
||||
this.renderLegends();
|
||||
this.drawPlaybackButtons();
|
||||
|
||||
this.axisX.call(this.xAxisProperties.axis);
|
||||
this.axisY.call(this.yAxisProperties.axis);
|
||||
this.axisY2.call(this.yAxis2Properties.axis);
|
||||
|
||||
this.axisX.selectAll(LineDotChart.tickText).call(
|
||||
AxisHelper.LabelLayoutStrategy.clip,
|
||||
this.xAxisProperties.xLabelMaxWidth,
|
||||
TextMeasurementService.svgEllipsis);
|
||||
|
||||
if (this.settings.misc.isAnimated && this.settings.misc.isStopped) {
|
||||
this.main.selectAll(LineDotChart.Line.selector).selectAll(LineDotChart.dotPointsText).remove();
|
||||
this.line.selectAll(LineDotChart.textSelector).remove();
|
||||
// this.updateLineText("");
|
||||
return;
|
||||
}
|
||||
|
||||
let linePathSelection: d3.selection.Update<LineDotPoint[]> = this.line
|
||||
.selectAll(LineDotChart.dotPathText)
|
||||
.data([this.data.dotPoints]);
|
||||
|
||||
this.drawLine(linePathSelection);
|
||||
this.drawClipPath(linePathSelection);
|
||||
|
||||
linePathSelection
|
||||
.exit().remove();
|
||||
|
||||
this.drawDots();
|
||||
}
|
||||
|
||||
private static lineDotChartPlayBtn: string = "lineDotChart__playBtn";
|
||||
private static lineDotChartPlayBtnTranslate: string = "lineDotChartPlayBtnTranslate";
|
||||
private static gLineDotChartPayBtn: string = "g.lineDotChart__playBtn";
|
||||
private static playBtnGroupDiameter: number = 34;
|
||||
private static playBtnGroupLineValues: string = "M0 2l10 6-10 6z";
|
||||
private static playBtnGroupPlayTranslate: string = "playBtnGroupPlayTranslate";
|
||||
private static playBtnGroupPathTranslate: string = "playBtnGroupPathTranslate";
|
||||
private static playBtnGroupRectTranslate: string = "playBtnGroupRectTranslate";
|
||||
private static playBtnGroupRectWidth: string = "2";
|
||||
private static playBtnGroupRectHeight: string = "12";
|
||||
private static StopButton: ClassAndSelector = createClassAndSelector("stop");
|
||||
private drawPlaybackButtons() {
|
||||
let playBtn: d3.selection.Update<string> = this.line.selectAll(LineDotChart.gLineDotChartPayBtn).data([""]);
|
||||
let playBtnGroup: d3.Selection<string> = playBtn.enter()
|
||||
.append("g")
|
||||
.classed(LineDotChart.lineDotChartPlayBtn, true);
|
||||
|
||||
playBtnGroup
|
||||
.classed(LineDotChart.lineDotChartPlayBtnTranslate, true)
|
||||
.append("circle")
|
||||
.attr("r", LineDotChart.playBtnGroupDiameter / 2)
|
||||
.on('click', () => this.setIsStopped(!this.settings.misc.isStopped));
|
||||
|
||||
playBtnGroup.append("path")
|
||||
.classed("play", true)
|
||||
.classed(LineDotChart.playBtnGroupPlayTranslate, true)
|
||||
.attr("d", LineDotChart.playBtnGroupLineValues)
|
||||
.attr('pointer-events', "none");
|
||||
|
||||
playBtnGroup
|
||||
.append("path")
|
||||
.classed(LineDotChart.StopButton.class, true)
|
||||
.classed(LineDotChart.playBtnGroupPathTranslate, true)
|
||||
.attr("d", LineDotChart.playBtnGroupLineValues)
|
||||
.attr("transform-origin", "center")
|
||||
.attr('pointer-events', "none");
|
||||
|
||||
playBtnGroup
|
||||
.append("rect")
|
||||
.classed(LineDotChart.StopButton.class, true)
|
||||
.classed(LineDotChart.playBtnGroupRectTranslate, true)
|
||||
.attr("width", LineDotChart.playBtnGroupRectWidth)
|
||||
.attr("height", LineDotChart.playBtnGroupRectHeight)
|
||||
.attr('pointer-events', "none");
|
||||
|
||||
playBtn.selectAll("circle").attr("opacity", () => this.settings.misc.isAnimated ? 1 : 0);
|
||||
playBtn.selectAll(".play").attr("opacity", () => this.settings.misc.isAnimated && this.settings.misc.isStopped ? 1 : 0);
|
||||
playBtn.selectAll(LineDotChart.StopButton.selector).attr("opacity", () => this.settings.misc.isAnimated && !this.settings.misc.isStopped ? 1 : 0);
|
||||
|
||||
playBtn.exit().remove();
|
||||
}
|
||||
|
||||
private static pathClassName: string = "path";
|
||||
private static pathPlotClassName: string = "path.plot";
|
||||
private static plotClassName: string = "plot";
|
||||
private static lineClip: string = "lineClip";
|
||||
private drawLine(linePathSelection: d3.selection.Update<LineDotPoint[]>) {
|
||||
linePathSelection.enter().append("g").classed(LineDotChart.pathClassName, true);
|
||||
|
||||
let pathPlot: d3.selection.Update<LineDotPoint[]> = linePathSelection.selectAll(LineDotChart.pathPlotClassName).data(d => [d]);
|
||||
pathPlot.enter()
|
||||
.append('path')
|
||||
.classed(LineDotChart.plotClassName, true);
|
||||
|
||||
// Draw the line
|
||||
let drawLine: d3.svg.Line<any> = d3.svg.line()
|
||||
.x((d: any) => this.xAxisProperties.scale(d.time))
|
||||
.y((d: any) => this.yAxisProperties.scale(d.sum));
|
||||
|
||||
pathPlot
|
||||
.attr('stroke', () => this.settings.lineoptions.fill)
|
||||
.attr('stroke-width', this.settings.lineoptions.lineThickness)
|
||||
.attr('d', drawLine)
|
||||
.attr("clip-path", "url(" + location.href + '#' + LineDotChart.lineClip + ")");
|
||||
}
|
||||
|
||||
private static zeroX: number = 0;
|
||||
private static zeroY: number = 0;
|
||||
private static millisecondsInOneSecond: number = 1000;
|
||||
private drawClipPath(linePathSelection: d3.selection.Update<any>) {
|
||||
let clipPath: d3.selection.Update<any> = linePathSelection.selectAll("clipPath").data(d => [d]);
|
||||
clipPath.enter().append("clipPath")
|
||||
.attr("id", LineDotChart.lineClip)
|
||||
.append("rect")
|
||||
.attr("x", LineDotChart.zeroX)
|
||||
.attr("y", LineDotChart.zeroY);
|
||||
|
||||
let line_left: any = this.xAxisProperties.scale(_.first(this.data.dotPoints).time);
|
||||
let line_right: any = this.xAxisProperties.scale(_.last(this.data.dotPoints).time);
|
||||
|
||||
if (this.settings.misc.isAnimated) {
|
||||
clipPath
|
||||
.selectAll("rect")
|
||||
.attr('x', line_left)
|
||||
.attr('width', 0)
|
||||
.interrupt()
|
||||
.transition()
|
||||
.ease("linear")
|
||||
.duration(this.animationDuration * LineDotChart.millisecondsInOneSecond)
|
||||
.attr('width', line_right - line_left)
|
||||
.attr("height", this.layout.viewportIn.height);
|
||||
} else {
|
||||
clipPath
|
||||
.selectAll("rect")
|
||||
.interrupt()
|
||||
.attr('x', line_left)
|
||||
.attr('width', line_right - line_left);
|
||||
}
|
||||
}
|
||||
|
||||
private static pointTime: number = 300;
|
||||
private static dotPointsClass: string = "dot-points";
|
||||
private static pointClassName: string = 'point';
|
||||
private static pointScaleValue: number = 0.005;
|
||||
private static pointTransformScaleValue: number = 3.4;
|
||||
private drawDots() {
|
||||
let point_time: number = this.settings.misc.isAnimated && !this.settings.misc.isStopped ? LineDotChart.pointTime : 0;
|
||||
|
||||
let hasHighlights: boolean = this.data.hasHighlights;
|
||||
let hasSelection: boolean = this.interactivityService && this.interactivityService.hasSelection();
|
||||
|
||||
// Draw the individual data points that will be shown on hover with a tooltip
|
||||
let lineTipSelection: d3.selection.Update<LineDotPoint[]> = this.line.selectAll('g.' + LineDotChart.dotPointsClass)
|
||||
.data([this.data.dotPoints]);
|
||||
|
||||
lineTipSelection.enter()
|
||||
.append("g")
|
||||
.classed(LineDotChart.dotPointsClass, true);
|
||||
|
||||
let dotsSelection: d3.selection.Update<LineDotPoint> = lineTipSelection
|
||||
.selectAll("circle." + LineDotChart.pointClassName)
|
||||
.data(d => d);
|
||||
|
||||
dotsSelection.enter()
|
||||
.append('circle')
|
||||
.classed(LineDotChart.pointClassName, true)
|
||||
.on('mouseover.point', this.showDataPoint)
|
||||
.on('mouseout.point', this.hideDataPoint);
|
||||
|
||||
dotsSelection
|
||||
.attr('fill', this.settings.dotoptions.color)
|
||||
.style("opacity", (d: LineDotPoint) => {
|
||||
return lineDotChartUtils.getFillOpacity(d.selected, d.highlight, !d.highlight && hasSelection, !d.selected && hasHighlights);
|
||||
})
|
||||
.attr('r', (d: LineDotPoint) =>
|
||||
this.settings.dotoptions.dotSizeMin + d.dot * (this.settings.dotoptions.dotSizeMax - this.settings.dotoptions.dotSizeMin));
|
||||
|
||||
if (this.settings.misc.isAnimated) {
|
||||
let maxTextLength: number = Math.min(350, this.xAxisProperties.scale.range()[1] - this.xAxisProperties.scale.range()[0] - 60);
|
||||
let lineTextSelection: d3.Selection<any> = this.line.selectAll(LineDotChart.textSelector);
|
||||
let lineText: d3.selection.Update<string> = lineTextSelection.data([""]);
|
||||
lineText
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr('text-anchor', "end")
|
||||
.classed("text", true);
|
||||
lineText
|
||||
.attr('x', this.layout.viewportIn.width - LineDotChart.widthMargin)
|
||||
.attr('y', LineDotChart.yPosition)
|
||||
.call(selection => TextMeasurementService.svgEllipsis(<any>selection.node(), maxTextLength));
|
||||
lineText.exit().remove();
|
||||
|
||||
dotsSelection
|
||||
.interrupt()
|
||||
.attr('transform', (d: LineDotPoint) =>
|
||||
SVGUtil.translateAndScale(this.xAxisProperties.scale(d.time), this.yAxisProperties.scale(d.sum), LineDotChart.pointScaleValue))
|
||||
.transition()
|
||||
.each("start", (d: LineDotPoint, i: number) => {
|
||||
let text = this.settings.counteroptions.counterTitle + ' ' + (i + 1);
|
||||
this.updateLineText(lineText, text);
|
||||
})
|
||||
.duration(point_time)
|
||||
.delay((d: LineDotPoint, i: number) => this.pointDelay(this.data.dotPoints, i, this.animationDuration))
|
||||
.ease("linear")
|
||||
.attr('transform', (d: LineDotPoint) =>
|
||||
SVGUtil.translateAndScale(this.xAxisProperties.scale(d.time), this.yAxisProperties.scale(d.sum), LineDotChart.pointTransformScaleValue))
|
||||
.transition()
|
||||
.duration(point_time)
|
||||
.delay((d: LineDotPoint, i: number) => this.pointDelay(this.data.dotPoints, i, this.animationDuration) + point_time)
|
||||
.ease("elastic")
|
||||
.attr('transform', (d: LineDotPoint) =>
|
||||
SVGUtil.translateAndScale(this.xAxisProperties.scale(d.time), this.yAxisProperties.scale(d.sum), 1));
|
||||
} else {
|
||||
dotsSelection
|
||||
.interrupt()
|
||||
.attr('transform', (d: LineDotPoint) =>
|
||||
SVGUtil.translateAndScale(this.xAxisProperties.scale(d.time), this.yAxisProperties.scale(d.sum), 1));
|
||||
this.line.selectAll(LineDotChart.textSelector).remove();
|
||||
}
|
||||
|
||||
for (let i: number = 0; i < dotsSelection[0].length; i++) {
|
||||
this.addTooltip(dotsSelection[0][i]);
|
||||
}
|
||||
|
||||
dotsSelection.exit().remove();
|
||||
lineTipSelection.exit().remove();
|
||||
|
||||
if (this.interactivityService) {
|
||||
// Register interactivity;
|
||||
let behaviorOptions: LineDotChartBehaviorOptions = {
|
||||
selection: dotsSelection,
|
||||
clearCatcher: this.root,
|
||||
interactivityService: this.interactivityService,
|
||||
hasHighlights: hasHighlights
|
||||
};
|
||||
this.interactivityService.bind(this.data.dotPoints, this.behavior, behaviorOptions);
|
||||
}
|
||||
}
|
||||
|
||||
private get animationDuration(): number {
|
||||
if (this.settings && this.settings.misc) {
|
||||
return this.settings.misc.duration;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private stopAnimation(): void {
|
||||
this.line.selectAll("*")
|
||||
.transition()
|
||||
.duration(0)
|
||||
.delay(0);
|
||||
|
||||
d3.timer.flush();
|
||||
}
|
||||
|
||||
private static textSelector: string = "text.text";
|
||||
private static widthMargin: number = 85;
|
||||
private static yPosition: number = 30;
|
||||
private updateLineText(textSelector: d3.Selection<any>, text?: string): void {
|
||||
textSelector.text(d => text);
|
||||
}
|
||||
|
||||
private pointDelay(points: LineDotPoint[], num: number, animation_duration: number): number {
|
||||
if (!points.length || !points[num] || num === 0 || !this.settings.misc.isAnimated || this.settings.misc.isStopped) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let time: number = <number>points[num].time;
|
||||
let min: number = <number>points[0].time;
|
||||
let max: number = <number>points[points.length - 1].time;
|
||||
return animation_duration * 1000 * (time - min) / (max - min);
|
||||
}
|
||||
private static showClassName: string = 'show';
|
||||
private showDataPoint(data: LineDotPoint, index: number): void {
|
||||
d3.select(<any>this).classed(LineDotChart.showClassName, true);
|
||||
}
|
||||
|
||||
private hideDataPoint(data: LineDotPoint, index: number): void {
|
||||
d3.select(<any>this).classed(LineDotChart.showClassName, false);
|
||||
}
|
||||
|
||||
private addTooltip(element: any): void {
|
||||
let selection: d3.Selection<any> = d3.select(element);
|
||||
let data: LineDotPoint = selection.datum();
|
||||
this.tooltipServiceWrapper.addTooltip(selection, (event) => {
|
||||
return [
|
||||
{
|
||||
displayName: "",
|
||||
value: this.data.dateColumnFormatter.format(data.time)
|
||||
},
|
||||
{
|
||||
displayName: "",
|
||||
value: data.value.toString()
|
||||
}
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
private renderLegends(): void {
|
||||
let legends: Legend[] = this.generateAxisLabels();
|
||||
let legendSelection: d3.selection.Update<any> = this.legends
|
||||
.selectAll(LineDotChart.Legend.selector)
|
||||
.data(legends);
|
||||
|
||||
legendSelection
|
||||
.enter()
|
||||
.append("svg:text");
|
||||
|
||||
legendSelection
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("dx", (item: Legend) => item.dx)
|
||||
.attr("dy", (item: Legend) => item.dy)
|
||||
.attr("transform", (item: Legend) => item.transform)
|
||||
.text((item: Legend) => item.text)
|
||||
.classed(LineDotChart.Legend.class, true);
|
||||
|
||||
legendSelection
|
||||
.exit()
|
||||
.remove();
|
||||
}
|
||||
}
|
||||
|
||||
export module lineDotChartUtils {
|
||||
export let DimmedOpacity: number = 0.4;
|
||||
export let DefaultOpacity: number = 1.0;
|
||||
|
||||
export function getFillOpacity(selected: boolean, highlight: boolean, hasSelection: boolean, hasPartialHighlights: boolean): number {
|
||||
if ((hasPartialHighlights && !highlight) || (hasSelection && !selected)) {
|
||||
return DimmedOpacity;
|
||||
}
|
||||
return DefaultOpacity;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Power BI Visualizations
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
* All rights reserved.
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the ""Software""), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
module powerbi.extensibility.visual {
|
||||
// powerbi
|
||||
import IViewport = powerbi.IViewport;
|
||||
|
||||
// powerbi.visuals
|
||||
import IMargin = powerbi.extensibility.utils.chart.axis.IMargin;
|
||||
|
||||
export class VisualLayout {
|
||||
private marginValue: IMargin;
|
||||
private viewportValue: IViewport;
|
||||
private viewportInValue: IViewport;
|
||||
private minViewportValue: IViewport;
|
||||
private originalViewportValue: IViewport;
|
||||
private previousOriginalViewportValue: IViewport;
|
||||
|
||||
public defaultMargin: IMargin;
|
||||
public defaultViewport: IViewport;
|
||||
|
||||
constructor(defaultViewport?: IViewport, defaultMargin?: IMargin) {
|
||||
this.defaultViewport = defaultViewport || { width: 0, height: 0 };
|
||||
this.defaultMargin = defaultMargin || { top: 0, bottom: 0, right: 0, left: 0 };
|
||||
}
|
||||
|
||||
public get viewport(): IViewport {
|
||||
return this.viewportValue || (this.viewportValue = this.defaultViewport);
|
||||
}
|
||||
|
||||
public get viewportCopy(): IViewport {
|
||||
return _.clone(this.viewport);
|
||||
}
|
||||
|
||||
// Returns viewport minus margin
|
||||
public get viewportIn(): IViewport {
|
||||
return this.viewportInValue || this.viewport;
|
||||
}
|
||||
|
||||
public get minViewport(): IViewport {
|
||||
return this.minViewportValue || { width: 0, height: 0 };
|
||||
}
|
||||
|
||||
public get margin(): IMargin {
|
||||
return this.marginValue || (this.marginValue = this.defaultMargin);
|
||||
}
|
||||
|
||||
public set minViewport(value: IViewport) {
|
||||
this.setUpdateObject(value, v => this.minViewportValue = v, VisualLayout.restrictToMinMax);
|
||||
}
|
||||
|
||||
public set viewport(value: IViewport) {
|
||||
this.previousOriginalViewportValue = _.clone(this.originalViewportValue);
|
||||
this.originalViewportValue = _.clone(value);
|
||||
this.setUpdateObject(value,
|
||||
v => this.viewportValue = v,
|
||||
o => VisualLayout.restrictToMinMax(o, this.minViewport));
|
||||
}
|
||||
|
||||
public set margin(value: IMargin) {
|
||||
this.setUpdateObject(value, v => this.marginValue = v, VisualLayout.restrictToMinMax);
|
||||
}
|
||||
|
||||
// Returns true if viewport has updated after last change.
|
||||
public get viewportChanged(): boolean {
|
||||
return !!this.originalViewportValue && (!this.previousOriginalViewportValue
|
||||
|| this.previousOriginalViewportValue.height !== this.originalViewportValue.height
|
||||
|| this.previousOriginalViewportValue.width !== this.originalViewportValue.width);
|
||||
}
|
||||
|
||||
public get viewportInIsZero(): boolean {
|
||||
return this.viewportIn.width === 0 || this.viewportIn.height === 0;
|
||||
}
|
||||
|
||||
public resetMargin(): void {
|
||||
this.margin = this.defaultMargin;
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
this.viewportInValue = VisualLayout.restrictToMinMax({
|
||||
width: this.viewport.width - (this.margin.left + this.margin.right),
|
||||
height: this.viewport.height - (this.margin.top + this.margin.bottom)
|
||||
}, this.minViewportValue);
|
||||
}
|
||||
|
||||
private setUpdateObject<T>(object: T, setObjectFn: (T) => void, beforeUpdateFn?: (T) => void): void {
|
||||
object = _.clone(object);
|
||||
setObjectFn(VisualLayout.createNotifyChangedObject(object, o => {
|
||||
if (beforeUpdateFn) beforeUpdateFn(object);
|
||||
this.update();
|
||||
}));
|
||||
|
||||
if (beforeUpdateFn) { beforeUpdateFn(object); }
|
||||
this.update();
|
||||
}
|
||||
|
||||
private static createNotifyChangedObject<T>(object: T, objectChanged: (o?: T, key?: string) => void): T {
|
||||
let result: T = <any>{};
|
||||
_.keys(object).forEach(key => Object.defineProperty(result, key, {
|
||||
get: () => object[key],
|
||||
set: (value) => { object[key] = value; objectChanged(object, key); },
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
}));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static restrictToMinMax<T>(value: T, minValue?: T): T {
|
||||
_.keys(value).forEach(x => value[x] = Math.max(minValue && minValue[x] || 0, value[x]));
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Power BI Visualizations
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
* All rights reserved.
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the ""Software""), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Imports external styles.
|
||||
* We compile it as a less file in order to wrap the external CSS rules.
|
||||
*/
|
||||
@import (less) "node_modules/powerbi-visuals-utils-interactivityutils/lib/index.css";
|
||||
@import (less) "node_modules/powerbi-visuals-utils-formattingutils/lib/index.css";
|
||||
|
||||
.lineDotChart {
|
||||
font-family: helvetica, arial, sans-serif;
|
||||
}
|
||||
.lineDotChart .axis path,
|
||||
.lineDotChart .legends path,
|
||||
.lineDotChart .axis line,
|
||||
.lineDotChart .legends line {
|
||||
fill: none;
|
||||
stroke: black;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
.lineDotChart .axis text,
|
||||
.lineDotChart .legends text {
|
||||
fill: black;
|
||||
font-size: 14px;
|
||||
}
|
||||
.lineDotChart .legends text {
|
||||
font-size: 16px;
|
||||
fill: #000;
|
||||
text-anchor: middle;
|
||||
}
|
||||
.lineDotChart .line {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
.lineDotChart .point {
|
||||
stroke: white;
|
||||
stroke-opacity: .7;
|
||||
stroke-width: .5;
|
||||
pointer-events: all;
|
||||
transition: opacity 0.2s ease-out;
|
||||
}
|
||||
.lineDotChart .text {
|
||||
fill: black;
|
||||
font-size: 32px;
|
||||
}
|
||||
.lineDotChart__playBtn {
|
||||
opacity: .23;
|
||||
cursor: pointer;
|
||||
transition: opacity .3s;
|
||||
}
|
||||
.lineDotChart__playBtn:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.lineDotChart__playBtn circle {
|
||||
stroke-width: .5;
|
||||
stroke: gray;
|
||||
fill: white;
|
||||
}
|
||||
.lineDotChart__playBtn rect {
|
||||
fill: black;
|
||||
}
|
||||
|
||||
.lineDotChart__playBtn path {
|
||||
fill: black;
|
||||
}
|
||||
|
||||
.playBtnGroupPathTranslate {
|
||||
transform:translate(-6px, -8px) rotate(180deg);
|
||||
}
|
||||
|
||||
.lineDotChartPlayBtnTranslate{
|
||||
transform:translate(40px, 20px);
|
||||
}
|
||||
|
||||
.playBtnGroupPlayTranslate{
|
||||
transform:translate(-4px, -8px);
|
||||
}
|
||||
|
||||
.playBtnGroupRectTranslate{
|
||||
transform:translate(-7px, -6px);
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
|
||||
/*
|
||||
* Power BI Visualizations
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
* All rights reserved.
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the ""Software""), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
// External
|
||||
/// <reference path="../typings/index.d.ts" />
|
||||
/// <reference path="../node_modules/@types/jasmine/index.d.ts" />
|
||||
/// <reference path="../node_modules/@types/jasmine-jquery/index.d.ts" />
|
||||
|
||||
// Power BI API
|
||||
/// <reference path="../.api/v1.3.0/PowerBI-visuals.d.ts" />
|
||||
|
||||
// Power BI Extensibility
|
||||
/// <reference path="../node_modules/powerbi-visuals-utils-dataviewutils/lib/index.d.ts" />
|
||||
/// <reference path="../node_modules/powerbi-visuals-utils-typeutils/lib/index.d.ts" />
|
||||
/// <reference path="../node_modules/powerbi-visuals-utils-svgutils/lib/index.d.ts" />
|
||||
/// <reference path="../node_modules/powerbi-visuals-utils-interactivityutils/lib/index.d.ts" />
|
||||
/// <reference path="../node_modules/powerbi-visuals-utils-tooltiputils/lib/index.d.ts" />
|
||||
/// <reference path="../node_modules/powerbi-visuals-utils-dataviewutils/lib/index.d.ts" />
|
||||
/// <reference path="../node_modules/powerbi-visuals-utils-formattingutils/lib/index.d.ts" />
|
||||
/// <reference path="../node_modules/powerbi-visuals-utils-chartutils/lib/index.d.ts"/>
|
||||
/// <reference path="../node_modules/powerbi-visuals-utils-testutils/lib/index.d.ts"/>
|
||||
|
||||
// The visual
|
||||
/// <reference path="../.tmp/drop/visual.d.ts" />
|
||||
|
||||
// Test
|
||||
/// <reference path="helpers.ts" />
|
||||
/// <reference path="visualData.ts" />
|
||||
/// <reference path="visualBuilder.ts" />
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Power BI Visualizations
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
* All rights reserved.
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the ""Software""), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/// <reference path="_references.ts"/>
|
||||
|
||||
namespace powerbitests.customVisuals {
|
||||
import helpers = powerbi.extensibility.utils.test.helpers;
|
||||
|
||||
export function getHexColorFromNumber(value: number) {
|
||||
let hex: string = value.toString(16).toUpperCase();
|
||||
return "#" + (hex.length === 6 ? hex : _.range(0, 6 - hex.length, 0).join("") + hex);
|
||||
}
|
||||
export function getRandomInteger(min: number, max: number, exceptionList?: number[]): number {
|
||||
return helpers.getRandomNumber(max, min, exceptionList, Math.floor);
|
||||
}
|
||||
export function getRandomHexColor(): string {
|
||||
return getHexColorFromNumber(getRandomInteger(0, 16777215 + 1));
|
||||
}
|
||||
|
||||
export function getRandomHexColors(count: number): string[] {
|
||||
return _.range(count).map(x => getRandomHexColor());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Power BI Visualizations
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
* All rights reserved.
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the ""Software""), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/// <reference path="_references.ts"/>
|
||||
|
||||
module powerbi.extensibility.visual.test {
|
||||
import VisualBuilderBase = powerbi.extensibility.utils.test.VisualBuilderBase;
|
||||
import getRandomNumber = powerbi.extensibility.utils.test.helpers.getRandomNumber;
|
||||
// LineDotChart1460463831201
|
||||
import VisualPlugin = powerbi.visuals.plugins.LineDotChart1460463831201;
|
||||
import VisualClass = powerbi.extensibility.visual.LineDotChart1460463831201.LineDotChart;
|
||||
import VisualSettings = powerbi.extensibility.visual.LineDotChart1460463831201.LineDotChartSettings;
|
||||
|
||||
export class LineDotChartBuilder extends VisualBuilderBase<VisualClass> {
|
||||
constructor(width: number, height: number) {
|
||||
super(width, height);
|
||||
}
|
||||
|
||||
protected build(options: VisualConstructorOptions) {
|
||||
return new VisualClass(options);
|
||||
}
|
||||
|
||||
public get mainElement(): JQuery {
|
||||
return this.element.children(".lineDotChart");
|
||||
}
|
||||
|
||||
public get line() {
|
||||
return this.mainElement
|
||||
.children("g")
|
||||
.children("g.line");
|
||||
}
|
||||
|
||||
public get linePath() {
|
||||
return this.line
|
||||
.children("g.path")
|
||||
.children("path.plot");
|
||||
}
|
||||
|
||||
public get dots() {
|
||||
return this.line
|
||||
.children("g.dot-points")
|
||||
.children("circle.point");
|
||||
}
|
||||
|
||||
public get animationPlay(): JQuery {
|
||||
return this.mainElement
|
||||
.find("g.lineDotChart__playBtn");
|
||||
}
|
||||
|
||||
public get counterTitle(): JQuery {
|
||||
return this.line
|
||||
.children("text.text");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Power BI Visualizations
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
* All rights reserved.
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the ""Software""), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/// <reference path="_references.ts" />
|
||||
|
||||
module powerbi.extensibility.visual.test {
|
||||
// powerbi.extensibility.utils.type
|
||||
import ValueType = powerbi.extensibility.utils.type.ValueType;
|
||||
|
||||
// powerbi.extensibility.utils.test
|
||||
import getRandomNumber = powerbi.extensibility.utils.test.helpers.getRandomNumber;
|
||||
import CustomizeColumnFn = powerbi.extensibility.utils.test.dataViewBuilder.CustomizeColumnFn;
|
||||
import TestDataViewBuilder = powerbi.extensibility.utils.test.dataViewBuilder.TestDataViewBuilder;
|
||||
import helpers = powerbi.extensibility.utils.test.helpers;
|
||||
|
||||
export function getRandomUniqueNumbers(count: number, min: number = 0, max: number = 1): number[] {
|
||||
let result: number[] = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
result.push(getRandomNumber(min, max, result));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getRandomUniqueDates(count: number, start: Date, end: Date): Date[] {
|
||||
return getRandomUniqueNumbers(count, start.getTime(), end.getTime()).map(x => new Date(x));
|
||||
}
|
||||
|
||||
export function getRandomUniqueSortedDates(count: number, start: Date, end: Date): Date[] {
|
||||
return getRandomUniqueDates(count, start, end).sort((a, b) => a.getTime() - b.getTime());
|
||||
}
|
||||
|
||||
export class LineDotChartData extends TestDataViewBuilder {
|
||||
public static ColumnDate: string = "Date";
|
||||
public static ColumnValue: string = "Value";
|
||||
|
||||
public valuesDate: Date[] = getRandomUniqueSortedDates(
|
||||
50,
|
||||
new Date(2014, 9, 12, 3, 9, 50),
|
||||
new Date(2016, 3, 1, 2, 43, 3));
|
||||
public valuesValue = helpers.getRandomNumbers(this.valuesDate.length, 0, 5361);
|
||||
|
||||
public getDataView(columnNames?: string[]): powerbi.DataView {
|
||||
return this.createCategoricalDataViewBuilder([
|
||||
{
|
||||
source: {
|
||||
displayName: LineDotChartData.ColumnDate,
|
||||
type: ValueType.fromDescriptor({ dateTime: true }),
|
||||
roles: { Date: true }
|
||||
},
|
||||
values: this.valuesDate
|
||||
}
|
||||
], [
|
||||
{
|
||||
source: {
|
||||
displayName: "Values",
|
||||
type: ValueType.fromDescriptor({ integer: true }),
|
||||
roles: { Values: true }
|
||||
},
|
||||
values: this.valuesValue
|
||||
}
|
||||
], columnNames).build();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Power BI Visualizations
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
* All rights reserved.
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the ""Software""), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/// <reference path="_references.ts"/>
|
||||
|
||||
namespace powerbitests.customVisuals {
|
||||
import VisualClass = powerbi.extensibility.visual.test.LineDotChartBuilder;
|
||||
import LineDotChartData = powerbi.extensibility.visual.test.LineDotChartData;
|
||||
import LineDotChartBuilder = powerbi.extensibility.visual.test.LineDotChartBuilder;
|
||||
import helpers = powerbi.extensibility.utils.test.helpers;
|
||||
import colorHelper = powerbi.extensibility.utils.test.helpers.color;
|
||||
import RgbColor = powerbi.extensibility.utils.test.helpers.color.RgbColor;
|
||||
import MockISelectionId = powerbi.extensibility.utils.test.mocks.MockISelectionId;
|
||||
import createSelectionId = powerbi.extensibility.utils.test.mocks.createSelectionId;
|
||||
import fromPointToPixel = powerbi.extensibility.utils.type.PixelConverter.fromPointToPixel;
|
||||
import getRandomHexColor = powerbitests.customVisuals.getRandomHexColor;
|
||||
|
||||
describe("LineDotChartTests", () => {
|
||||
let visualBuilder: powerbi.extensibility.visual.test.LineDotChartBuilder;
|
||||
let defaultDataViewBuilder: LineDotChartData;
|
||||
let dataView: powerbi.DataView;
|
||||
beforeEach(() => {
|
||||
visualBuilder = new LineDotChartBuilder(1000, 500);
|
||||
defaultDataViewBuilder = new LineDotChartData();
|
||||
dataView = defaultDataViewBuilder.getDataView();
|
||||
});
|
||||
|
||||
describe("DOM tests", () => {
|
||||
it("main element was created", () => {
|
||||
expect(visualBuilder.mainElement.get(0)).toBeDefined();
|
||||
});
|
||||
|
||||
it("update", (done) => {
|
||||
visualBuilder.updateRenderTimeout(dataView, () => {
|
||||
expect(visualBuilder.mainElement.find(".axis").length).not.toBe(0);
|
||||
expect(visualBuilder.mainElement.find(".tick").length).not.toBe(0);
|
||||
expect(visualBuilder.mainElement.find(".lineDotChart__playBtn").get(0)).toBeDefined();
|
||||
expect(visualBuilder.mainElement.find(".legends").get(0)).toBeDefined();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Resize test", () => {
|
||||
it("Counter", (done) => {
|
||||
visualBuilder.viewport.width = 300;
|
||||
dataView.metadata.objects = {
|
||||
misc: {
|
||||
isAnimated: true,
|
||||
duration: 20
|
||||
},
|
||||
counteroptions: {
|
||||
counterTitle: "Counter: "
|
||||
}
|
||||
};
|
||||
visualBuilder.updateFlushAllD3Transitions(dataView);
|
||||
|
||||
helpers.clickElement(visualBuilder.animationPlay);
|
||||
helpers.renderTimeout(() => {
|
||||
expect(visualBuilder.counterTitle).toBeInDOM();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Format settings test", () => {
|
||||
beforeEach(() => {
|
||||
dataView.metadata.objects = {
|
||||
misc: {
|
||||
isAnimated: false
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
describe("Line", () => {
|
||||
it("color", () => {
|
||||
let color: string = getRandomHexColor();
|
||||
(dataView.metadata.objects as any).lineoptions = { fill: colorHelper.getSolidColorStructuralObject(color) };
|
||||
visualBuilder.updateFlushAllD3Transitions(dataView);
|
||||
colorHelper.assertColorsMatch(visualBuilder.linePath.css('stroke'), color);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Dot", () => {
|
||||
it("color", () => {
|
||||
let color: string = getRandomHexColor();
|
||||
|
||||
dataView.metadata.objects = {
|
||||
dotoptions: {
|
||||
color: colorHelper.getSolidColorStructuralObject(color)
|
||||
}
|
||||
};
|
||||
visualBuilder.updateFlushAllD3Transitions(dataView);
|
||||
visualBuilder.dots.toArray().map($).forEach(e =>
|
||||
colorHelper.assertColorsMatch(e.attr('fill'), color));
|
||||
});
|
||||
});
|
||||
|
||||
describe("Validate params", () => {
|
||||
it("Dots", () => {
|
||||
|
||||
dataView.metadata.objects = {
|
||||
dotoptions: {
|
||||
dotSizeMin: -6,
|
||||
dotSizeMax: 678
|
||||
}
|
||||
};
|
||||
visualBuilder.updateFlushAllD3Transitions(dataView);
|
||||
visualBuilder.dots.toArray().map($).forEach(e => {
|
||||
expect(e.attr("r")).toBeGreaterThan(-1);
|
||||
expect(e.attr("r")).toBeLessThan(101);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "ES5",
|
||||
"sourceMap": true,
|
||||
"out": "./.tmp/build/visual.js",
|
||||
"declaration": true
|
||||
},
|
||||
"files": [
|
||||
"typings/index.d.ts",
|
||||
".api/v1.3.0/PowerBI-visuals.d.ts",
|
||||
"node_modules/powerbi-visuals-utils-formattingutils/lib/index.d.ts",
|
||||
"node_modules/powerbi-visuals-utils-interactivityutils/lib/index.d.ts",
|
||||
"node_modules/powerbi-visuals-utils-typeutils/lib/index.d.ts",
|
||||
"node_modules/powerbi-visuals-utils-svgutils/lib/index.d.ts",
|
||||
"node_modules/powerbi-visuals-utils-chartutils/lib/index.d.ts",
|
||||
"node_modules/powerbi-visuals-utils-dataviewutils/lib/index.d.ts",
|
||||
"node_modules/powerbi-visuals-utils-tooltiputils/lib/index.d.ts",
|
||||
"src/dataInterfaces.ts",
|
||||
"src/settings.ts",
|
||||
"src/columns.ts",
|
||||
"src/visualLayout.ts",
|
||||
"src/behavior.ts",
|
||||
"src/visual.ts"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"rules": {
|
||||
"class-name": true,
|
||||
"comment-format": [
|
||||
true,
|
||||
"check-space"
|
||||
],
|
||||
"indent": [
|
||||
true,
|
||||
"spaces"
|
||||
],
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": true,
|
||||
"no-internal-module": false,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unsafe-finally": true,
|
||||
"no-var-keyword": true,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-open-brace",
|
||||
"check-whitespace"
|
||||
],
|
||||
"quotemark": [
|
||||
false,
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"globalDependencies": {
|
||||
"d3": "registry:dt/d3#0.0.0+20160907005744",
|
||||
"jasmine": "registry:dt/jasmine#2.5.0+20161119044246",
|
||||
"jasmine-jquery": "registry:dt/jasmine-jquery#1.5.8+20161128184045",
|
||||
"jquery": "registry:dt/jquery#1.10.0+20161119044246",
|
||||
"lodash": "registry:dt/lodash#4.14.0+20161110215204"
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче