204 lines
10 KiB
TypeScript
204 lines
10 KiB
TypeScript
/*
|
|
* Power BI Visual CLI
|
|
*
|
|
* Copyright (c) Microsoft Corporation
|
|
* All rights reserved.
|
|
* MIT License
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the ""Software""), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
"use strict";
|
|
//powerbi and d3
|
|
import "core-js/stable";
|
|
import "./../style/visual.less";
|
|
import powerbi from "powerbi-visuals-api";
|
|
import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions;
|
|
import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions;
|
|
import IVisual = powerbi.extensibility.visual.IVisual;
|
|
import EnumerateVisualObjectInstancesOptions = powerbi.EnumerateVisualObjectInstancesOptions;
|
|
import VisualObjectInstance = powerbi.VisualObjectInstance;
|
|
import DataView = powerbi.DataView;
|
|
import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject;
|
|
import ISelectionManager = powerbi.extensibility.ISelectionManager;
|
|
import IVisualHost = powerbi.extensibility.visual.IVisualHost;
|
|
import ISelectionId = powerbi.extensibility.ISelectionId;
|
|
import * as d3 from "d3";
|
|
type Selection<T extends d3.BaseType> = d3.Selection<T, any, any, any>;
|
|
import { VisualSettings } from "./settings/visualSettings";
|
|
import { stratify, rgb } from "d3";
|
|
import * as models from 'powerbi-models';
|
|
import { IAdvancedFilter } from "powerbi-models";
|
|
import IVisualEventService=powerbi.extensibility.IVisualEventService;
|
|
import {pixelConverter} from "powerbi-visuals-utils-typeutils";
|
|
//custom
|
|
import { IPeriod, Period, DefaultPeriodType,OrientationType } from "./settings/datePeriod/datePeriod";
|
|
import { RelativePeriodHelper } from "./settings/datePeriod/relativePeriodHelper";
|
|
import { IPeriodSelectorManager, PeriodSelectorManager } from "./settings/datePeriod/periodSelectorManager";
|
|
//third party
|
|
import * as moment from "moment";
|
|
|
|
export class Visual implements IVisual {
|
|
//pbi
|
|
private host: IVisualHost;
|
|
private settings: VisualSettings;
|
|
private category: powerbi.DataViewCategoryColumn;
|
|
private selectionManager: powerbi.extensibility.ISelectionManager;
|
|
private events: IVisualEventService;
|
|
|
|
//others
|
|
private container: Selection<HTMLElement>;
|
|
private periodSelectorManager: IPeriodSelectorManager;
|
|
constructor(options: VisualConstructorOptions) {
|
|
//pbi
|
|
//console.log('Visual constructor', options);
|
|
this.host = options.host;
|
|
this.selectionManager = this.host.createSelectionManager();
|
|
this.events = options.host.eventService;
|
|
//others
|
|
this.container = d3.select(options.element).classed("container", true).append("div");
|
|
if (document) {
|
|
const dateSelector_Start = this.container.append("input")
|
|
.attr("type", "date")
|
|
.style("margin", "4px");
|
|
const dateSelector_End = this.container.append("input")
|
|
.attr("type", "date")
|
|
.style("margin", "4px");
|
|
dateSelector_Start.on("input change", () => { this.filterPeriod(); });
|
|
dateSelector_End.on("input change", () => { this.filterPeriod(); });
|
|
this.periodSelectorManager = new PeriodSelectorManager(dateSelector_Start, dateSelector_End);
|
|
//Select datePeriod when click this button
|
|
this.container.on('contextmenu', () => {
|
|
const mouseEvent: MouseEvent = <MouseEvent>d3.event;
|
|
//const eventTarget: EventTarget = mouseEvent.target;
|
|
//let dataPoint = d3.select(eventTarget).datum();
|
|
this.selectionManager.showContextMenu({}, {
|
|
x: mouseEvent.clientX,
|
|
y: mouseEvent.clientY
|
|
});
|
|
mouseEvent.preventDefault();
|
|
});
|
|
}
|
|
}
|
|
public update(options: VisualUpdateOptions) {
|
|
//pbi
|
|
this.events.renderingStarted(options);
|
|
this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]);
|
|
let dataView = options.dataViews[0];
|
|
this.category = dataView.categorical.categories[0];
|
|
//style
|
|
let width: number = options.viewport.width;
|
|
if(this.settings.datePickers.backgroundTransparency<0||this.settings.datePickers.backgroundTransparency>1){
|
|
this.settings.datePickers.backgroundTransparency=0;
|
|
}
|
|
let backgroundColor=d3.rgb(this.settings.datePickers.backgroundColor).rgb();
|
|
let backgroundColorWithTransparency=rgb(backgroundColor.r,backgroundColor.g,backgroundColor.b,1-this.settings.datePickers.backgroundTransparency).toString();
|
|
let fontSize:number=pixelConverter.fromPointToPixel(this.settings.datePickers.textSize);
|
|
let datePickerWidth:number=(this.settings.datePickers.orientationType==OrientationType.Horizontal)?width/2-10-4*this.settings.datePickers.borderWidth:width-10-2*this.settings.datePickers.borderWidth;
|
|
//Set style for these date-pickers one-by-one, but not group by class
|
|
this.periodSelectorManager.dateSelector_Start
|
|
.style("width", datePickerWidth + "px")
|
|
.style("font-size",fontSize+"px")
|
|
.style("color",this.settings.datePickers.fontColor)
|
|
.style("background-color",backgroundColorWithTransparency)
|
|
.style("border-width",this.settings.datePickers.borderWidth+"px")
|
|
.style("border-color",this.settings.datePickers.borderColor)
|
|
.style("outline-color",this.settings.datePickers.outlineColor)
|
|
.style("display", (this.settings.datePickers.orientationType==OrientationType.Horizontal)?"inline":"block");
|
|
this.periodSelectorManager.dateSelector_End
|
|
.style("width", datePickerWidth+"px")
|
|
.style("font-size",fontSize+"px")
|
|
.style("color",this.settings.datePickers.fontColor)
|
|
.style("background-color",backgroundColorWithTransparency)
|
|
.style("border-width",this.settings.datePickers.borderWidth+"px")
|
|
.style("border-color",this.settings.datePickers.borderColor)
|
|
.style("display", (this.settings.datePickers.orientationType==OrientationType.Horizontal)?"inline":"block");
|
|
//set new default period
|
|
let newDefaultPeriod: IPeriod = new Period();
|
|
if (this.settings.period.defaultPeriodType == DefaultPeriodType.Custom) {
|
|
|
|
dataView.categorical.values.map((measure: powerbi.DataViewValueColumn, index: number) => {
|
|
let measureDate: Date = moment(measure.values[0].toString()).startOf("day").toDate();
|
|
if (measure.source.roles["StartDate"]) {
|
|
newDefaultPeriod.dateStart = measureDate;
|
|
}
|
|
if (measure.source.roles["EndDate"]) {
|
|
newDefaultPeriod.dateEnd = measureDate;
|
|
}
|
|
});
|
|
} else {
|
|
let relativePeriodHelper: RelativePeriodHelper = new RelativePeriodHelper(this.settings.period.relativeToday, this.settings.period.firstDayOfWeek);
|
|
newDefaultPeriod = relativePeriodHelper.getPeriod(this.settings.period.defaultPeriodType);
|
|
}
|
|
this.periodSelectorManager.defaultPeriod = newDefaultPeriod;
|
|
this.events.renderingFinished(options);
|
|
}
|
|
private filterPeriod() {
|
|
//determine period from dateSelectors
|
|
let period: IPeriod = this.periodSelectorManager.period;
|
|
let target: models.IFilterColumnTarget;
|
|
let splitPosition:number=this.category.source.queryName.indexOf('.');
|
|
let tableName:string=this.category.source.queryName.substr(0, splitPosition);
|
|
let fieldName:string=this.category.source.queryName.substr(splitPosition+1,this.category.source.queryName.length-splitPosition-1);
|
|
target = {
|
|
table: tableName, // table
|
|
column: fieldName // col1
|
|
};
|
|
|
|
//filter
|
|
let conditions: models.IAdvancedFilterCondition[] = [];
|
|
if (period.dateStart) {
|
|
conditions.push(
|
|
{
|
|
operator: "GreaterThanOrEqual",
|
|
value: period.dateStart.toJSON()
|
|
}
|
|
);
|
|
}
|
|
if (period.dateEnd) {
|
|
conditions.push(
|
|
{
|
|
operator: "LessThanOrEqual",
|
|
value: period.dateEnd.toJSON()
|
|
}
|
|
);
|
|
}
|
|
let filter: IAdvancedFilter = {
|
|
// tslint:disable-next-line: no-http-string
|
|
"$schema": "http://powerbi.com/product/schema#advanced",
|
|
"target": target,
|
|
"filterType": models.FilterType.Advanced,
|
|
"logicalOperator": "And",
|
|
"conditions": conditions
|
|
};
|
|
this.host.applyJsonFilter((period.dateStart || period.dateEnd) ? filter : null, "general", "filter", powerbi.FilterAction.merge);
|
|
|
|
}
|
|
private static parseSettings(dataView: DataView): VisualSettings {
|
|
return <VisualSettings>VisualSettings.parse(dataView);
|
|
}
|
|
|
|
/**
|
|
* This function gets called for each of the objects defined in the capabilities files and allows you to select which of the
|
|
* objects and properties you want to expose to the users in the property pane.
|
|
*
|
|
*/
|
|
public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] | VisualObjectInstanceEnumerationObject {
|
|
return VisualSettings.enumerateObjectInstances(this.settings || VisualSettings.getDefault(), options);
|
|
}
|
|
} |