Compare commits

...

8 Commits

29 changed files with 767 additions and 356 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
/dist /dist
/.tmp /.tmp
/.vscode /.vscode
webpack.statistics.*

26
ReadMe.md Normal file
View File

@ -0,0 +1,26 @@
# A utility slicer for Power Bi
Assign default selection to this slicer by messures, just use DAX!
## Tutorial
1. Click the top right corner of this slicer
1. Click "Edit"
1. Save&publish the report
## Functionalities
* Implemented
1. Dropdown-slicer for text field
1. Use DAX to assign **single** default selected items to the dropdown-slicer
1. Support async slicers
* Planned
1. List-slicer for text field
1. Use DAX to assign **one** default selected items to the list-slicer
1. Use DAX to assign **multiple** default selected items to the dropdown-slicer and list-slicer
1. Range-slicer for number field
1. Use DAX to assign default selected range to the range-slicer
1. Single-date-slicer for datetime field
1. Use DAX to assign default selected date to the single-date-slicer
1. Period-slicer for datetime field
1. Use DAX to assign default selected period to the period-slicer
## Known issures
1. Slicer components for number field and datetime field are in developing, just slicer for text field is usable now
1. The outline setting has no effectivity on dropdown items
1. I can't change background-color of dropdown items
1. This slicer wouldn't support bookmarks

View File

@ -1,4 +1,6 @@
{ {
"supportsSynchronizingFilterState": true,
"advancedEditModeSupport": 1,
"dataRoles": [ "dataRoles": [
{ {
"displayName": "Field", "displayName": "Field",
@ -46,7 +48,7 @@
"objects": { "objects": {
"general": { "general": {
"displayName": "General", "displayName": "General",
"displayNameKey": "formattingGeneral", "displayNameKey": "Formatting-General",
"properties": { "properties": {
"filter": { "filter": {
"type": { "type": {
@ -62,11 +64,12 @@
} }
} }
}, },
"dataPoint": { "slicerHeader": {
"displayName": "Data colors", "displayName": "Slicer header",
"displayNameKey":"Formatting-SicerHeader",
"properties": { "properties": {
"defaultColor": { "fontColor": {
"displayName": "Default color", "displayName": "Font color",
"type": { "type": {
"fill": { "fill": {
"solid": { "solid": {
@ -75,6 +78,79 @@
} }
} }
}, },
"backgroundColor": {
"displayName": "Background color",
"type": {
"fill": {
"solid": {
"color": true
}
}
}
},
"outline":{
"displayName": "Outline",
"type": {
"enumeration": [
{
"value": "0",
"displayName": "None",
"displayNameKey": "Formatting_Outline_None"
},
{
"value": "1",
"displayName": "Bottom only",
"displayNameKey": "Formatting_Outline_BottomOnly"
},
{
"value": "2",
"displayName": "Top only",
"displayNameKey": "Formatting_Outline_TopOnly"
},
{
"value": "3",
"displayName": "Left only",
"displayNameKey": "Formatting_Outline_LeftOnly"
},
{
"value": "4",
"displayName": "Right only",
"displayNameKey": "Formatting_Outline_RightOnly"
},
{
"value": "5",
"displayName": "Top + bottom",
"displayNameKey": "Formatting_Outline_TopAndBottom"
},
{
"value": "6",
"displayName": "Left + right",
"displayNameKey": "Formatting_Outline_LeftAndRight"
},
{
"value": "7",
"displayName": "Frame",
"displayNameKey": "Formatting_Outline_Frame"
}
]
}
},
"outlineColor":{
"displayName": "Outline color",
"type": {
"fill": {
"solid": {
"color":true
}
}
}
},
"outlineWeight":{
"displayName": "Outline weight",
"type":{
"integer": true
}
},
"showAllDataPoints": { "showAllDataPoints": {
"displayName": "Show all", "displayName": "Show all",
"type": { "type": {
@ -106,6 +182,90 @@
} }
} }
} }
},
"items":{
"displayName": "Items",
"displayNameKey": "Formatting_Items",
"properties": {
"fontColor": {
"displayName": "Font color",
"displayNameKey": "Formatting_Items_FontColor",
"type": {
"fill": {
"solid": {
"color": true
}
}
}
},
"backgroundColor": {
"displayName": "Background color",
"displayNameKey": "Formatting_Items_BackgroundColor",
"type": {
"fill": {
"solid": {
"color": true
}
}
}
},
"outline":{
"displayName": "Outline",
"displayNameKey": "Formatting_Items_Outline",
"type": {
"enumeration": [
{
"value": "0",
"displayName": "None",
"displayNameKey": "Formatting_Outline_None"
},
{
"value": "1",
"displayName": "Bottom only",
"displayNameKey": "Formatting_Outline_BottomOnly"
},
{
"value": "2",
"displayName": "Top only",
"displayNameKey": "Formatting_Outline_TopOnly"
},
{
"value": "3",
"displayName": "Left only",
"displayNameKey": "Formatting_Outline_LeftOnly"
},
{
"value": "4",
"displayName": "Right only",
"displayNameKey": "Formatting_Outline_RightOnly"
},
{
"value": "5",
"displayName": "Top + bottom",
"displayNameKey": "Formatting_Outline_TopAndBottom"
},
{
"value": "6",
"displayName": "Left + right",
"displayNameKey": "Formatting_Outline_LeftAndRight"
},
{
"value": "7",
"displayName": "Frame",
"displayNameKey": "Formatting_Outline_Frame"
}
]
}
},
"textSize":{
"displayName": "Text size",
"displayNameKey": "Formatting_Items_TextSize",
"type":{
"integer": true
}
}
}
} }
}, },
"dataViewMappings": [ "dataViewMappings": [

56
package-lock.json generated
View File

@ -53,23 +53,6 @@
} }
} }
}, },
"@types/bootstrap": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-3.4.0.tgz",
"integrity": "sha512-LS05hVAAsX86qbHg7W+ydwBlNHrVCoFw6wEP3/uW4eYmRXl08bWmPeN/+onM+8qZTFfDgUlG/OItJI8SW972oQ==",
"requires": {
"@types/jquery": "*"
}
},
"@types/bootstrap-multiselect": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@types/bootstrap-multiselect/-/bootstrap-multiselect-0.9.0.tgz",
"integrity": "sha512-tJU7V4ORRMdyqA9GoN6KepewlQwKjcCPQC4GrP2cow/ixr1FO6hi4FKfVrgxLg/KnRKCZvJnNthvSXz1y7qqRQ==",
"dev": true,
"requires": {
"@types/jquery": "*"
}
},
"@types/d3": { "@types/d3": {
"version": "5.7.2", "version": "5.7.2",
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-5.7.2.tgz", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-5.7.2.tgz",
@ -303,10 +286,11 @@
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz",
"integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ==" "integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ=="
}, },
"@types/jquery": { "@types/node": {
"version": "2.0.54", "version": "13.13.4",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-2.0.54.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.4.tgz",
"integrity": "sha512-D/PomKwNkDfSKD13DEVQT/pq2TUjN54c6uB341fEZanIzkjfGe7UaFuuaLZbpEiS5j7Wk2MUHAZqZIoECw29lg==" "integrity": "sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA==",
"dev": true
}, },
"ansi-styles": { "ansi-styles": {
"version": "3.2.1", "version": "3.2.1",
@ -338,26 +322,6 @@
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
"dev": true "dev": true
}, },
"bootstrap": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz",
"integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA=="
},
"bootstrap-multiselect": {
"version": "0.9.15",
"resolved": "https://registry.npmjs.org/bootstrap-multiselect/-/bootstrap-multiselect-0.9.15.tgz",
"integrity": "sha512-UwF32a0QR82xkEEGpuNrn57Bu0b/7DfCuoiOaziSHfQKFj5arR6c7+MYLs5RiIf3zl4XZ+YnY7ZBi6/EN3vEZA==",
"requires": {
"jquery": "~2.1.3"
},
"dependencies": {
"jquery": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-2.1.4.tgz",
"integrity": "sha1-IoveaYoMYUMdwmMKahVPFYkNIxc="
}
}
},
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -816,11 +780,6 @@
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true "dev": true
}, },
"jquery": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz",
"integrity": "sha1-LInWiJterFIqfuoywUUhVZxsvwI="
},
"js-tokens": { "js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -946,6 +905,11 @@
"resolved": "https://registry.npmjs.org/powerbi-visuals-utils-dataviewutils/-/powerbi-visuals-utils-dataviewutils-2.2.1.tgz", "resolved": "https://registry.npmjs.org/powerbi-visuals-utils-dataviewutils/-/powerbi-visuals-utils-dataviewutils-2.2.1.tgz",
"integrity": "sha512-Ai+TM1gj6DpAsNbn0IhOwUCAPfcaH4Z7y6Ow2OwAfbxNpELwQSF0S8D+vlJN2AoqV/ruQhnEngUC88mMFNyvJQ==" "integrity": "sha512-Ai+TM1gj6DpAsNbn0IhOwUCAPfcaH4Z7y6Ow2OwAfbxNpELwQSF0S8D+vlJN2AoqV/ruQhnEngUC88mMFNyvJQ=="
}, },
"powerbi-visuals-utils-typeutils": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/powerbi-visuals-utils-typeutils/-/powerbi-visuals-utils-typeutils-2.2.1.tgz",
"integrity": "sha512-xm5xNBVudCiU9ZZggsLlpHr+a4bnHtgw6Cy1UtNM/zILtOE2HUamjw+yZovLe6YNov4N2EaCmPO8XPhcXkuz+A=="
},
"process-nextick-args": { "process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",

View File

@ -9,20 +9,16 @@
"dependencies": { "dependencies": {
"@babel/runtime": "7.6.0", "@babel/runtime": "7.6.0",
"@babel/runtime-corejs2": "7.6.0", "@babel/runtime-corejs2": "7.6.0",
"@types/bootstrap": "^3.4.0",
"@types/d3": "5.7.2", "@types/d3": "5.7.2",
"@types/jquery": "^2.0.54",
"bootstrap": "^3.4.1",
"bootstrap-multiselect": "^0.9.15",
"core-js": "3.2.1", "core-js": "3.2.1",
"d3": "5.12.0", "d3": "5.12.0",
"jquery": "^2.2.4",
"powerbi-models": "^1.3.3", "powerbi-models": "^1.3.3",
"powerbi-visuals-api": "~2.6.1", "powerbi-visuals-api": "~2.6.1",
"powerbi-visuals-utils-dataviewutils": "2.2.1" "powerbi-visuals-utils-dataviewutils": "2.2.1",
"powerbi-visuals-utils-typeutils": "^2.2.1"
}, },
"devDependencies": { "devDependencies": {
"@types/bootstrap-multiselect": "^0.9.0", "@types/node": "^13.13.4",
"ts-loader": "6.1.0", "ts-loader": "6.1.0",
"tslint": "^5.18.0", "tslint": "^5.18.0",
"tslint-microsoft-contrib": "^6.2.0", "tslint-microsoft-contrib": "^6.2.0",

View File

@ -18,10 +18,7 @@
"icon": "assets/icon.png" "icon": "assets/icon.png"
}, },
"externalJS": [ "externalJS": [
"node_modules/jquery/dist/jquery.min.js", "./src/cover.js"
"src/coverJquery.js",
"node_modules/bootstrap/dist/js/bootstrap.min.js",
"node_modules/bootstrap-multiselect/dist/js/bootstrap-multiselect.min.js"
], ],
"style": "style/visual.less", "style": "style/visual.less",
"capabilities": "capabilities.json", "capabilities": "capabilities.json",

7
src/cover.js Normal file
View File

@ -0,0 +1,7 @@
var Debug=true;
function blankConsoleDebug(){
return;
}
if(!Debug){
console.debug=blankConsoleDebug;
}

View File

@ -1,3 +0,0 @@
var jQuery = typeof jQuery !== 'undefined'
? jQuery
: window['$'];

View File

@ -11,9 +11,53 @@ import { select, json } from "d3";
export class FilterManager implements IFilterManager{ export class FilterManager implements IFilterManager{
private target: models.IFilterColumnTarget={table:'',column:''}; private target: models.IFilterColumnTarget={table:'',column:''};
private host:IVisualHost; private host:IVisualHost;
private _jsonFilter:models.IAdvancedFilter;
public get jsonFilter() : models.IAdvancedFilter {
return this._jsonFilter;
}
public get advancedEditModeMark():string{
return 'SyinpoSlicer_AdvancedEditMode_'+this.inAdvancedEditMode;
}
constructor(host:IVisualHost){ constructor(host:IVisualHost){
this.host=host; this.host=host;
} }
_inAdvancedEditMode: boolean=false;
public set inAdvancedEditMode(newValue:boolean){
this._inAdvancedEditMode=this._inAdvancedEditMode?true:newValue;
}
public get inAdvancedEditMode():boolean{
return this._inAdvancedEditMode;
}
setFilter(jsonFilter:models.IAdvancedFilter,applyNow:boolean): void {
jsonFilter.conditions[0].value=this.advancedEditModeMark
this._jsonFilter=jsonFilter;
if(applyNow){
this.applyJsonFilter();
}
}
public applyJsonFilter():void{
console.debug('filter manager applyJsonFilter start');
this.host.applyJsonFilter(this._jsonFilter,'general','filter',powerbi.FilterAction.merge);
console.debug(this.jsonFilter);
console.debug('filter manager applyJsonFilter end');
}
public dispose():void{
console.debug('filterManager disposing');
if(this.target.table&&this.target.column){
this.clear();
}
console.debug('filterManager disposed');
}
public clear():void{
console.debug("clear start:");
this.host.applyJsonFilter(null,"general","filter",powerbi.FilterAction.remove);
console.debug("clear end");
}
public update(field:powerbi.DataViewCategoryColumn):void{ public update(field:powerbi.DataViewCategoryColumn):void{
let queryName:string=field.source.queryName; let queryName:string=field.source.queryName;
let splitPosition:number=queryName.indexOf('.'); let splitPosition:number=queryName.indexOf('.');
@ -23,17 +67,33 @@ export class FilterManager implements IFilterManager{
console.debug("queryName.length:",queryName.length); console.debug("queryName.length:",queryName.length);
console.debug("filterManager update end, now filter:",this.target); console.debug("filterManager update end, now filter:",this.target);
} }
public filterStringField(selection:string[]):void{ public setFilter_String(selection:string[],applyNow:boolean):void{
console.debug("filterStringField start:",selection); console.debug("setFilter_String start:",selection);
let jsonFilter:models.IBasicFilter={ let conditions:models.IAdvancedFilterCondition[]=[];
"$schema": "http://powerbi.com/product/schema#basic",
conditions.push({
"operator":"StartsWith",
"value":this.advancedEditModeMark
});
selection.forEach(function(item){
conditions.push({
"operator":"Is",
"value":item
});
});
let jsonFilter:models.IAdvancedFilter={
"$schema": "http://powerbi.com/product/schema#advanced",
"target": this.target, "target": this.target,
"filterType": models.FilterType.Basic, "filterType": models.FilterType.Advanced,
"operator":"In", "conditions":conditions,
"values": selection "logicalOperator":"Or"
} }
console.debug("jsonFilter:",jsonFilter); console.debug("jsonFilter:",jsonFilter);
this.host.applyJsonFilter(jsonFilter?jsonFilter:null,"general","filter",powerbi.FilterAction.merge); this._jsonFilter=jsonFilter;
console.debug("filterStringField end"); if(applyNow){
this.applyJsonFilter();
}
console.debug("setFilter_String end");
} }
} }

View File

@ -3,14 +3,32 @@ import powerbi from "powerbi-visuals-api";
import DataView = powerbi.DataView; import DataView = powerbi.DataView;
import * as visualInterfaces from "../visual/visualInterfaces"; import * as visualInterfaces from "../visual/visualInterfaces";
import * as d3 from "d3"; import * as d3 from "d3";
import { ILayoutManager } from "../visual/visualInterfaces"; import { ILayoutManager,IFilterManager } from "../visual/visualInterfaces";
import { IFilter } from "powerbi-models";
type Selection<T extends d3.BaseType> = d3.Selection<T, any, any, any>; type Selection<T extends d3.BaseType> = d3.Selection<T, any, any, any>;
export class LayoutManager implements ILayoutManager{ export class LayoutManager implements ILayoutManager{
layout:Selection<HTMLElement>; layout:Selection<HTMLElement>;
// constructor(layout:Selection<HTMLElement>){ // constructor(layout:Selection<HTMLElement>){
// this.layout=layout; // this.layout=layout;
// } // }
headerContainer:Selection<HTMLDivElement>
constructor(layout:Selection<HTMLElement>,filterManager:IFilterManager){
this.layout=layout;
this.headerContainer=layout.append("div").classed("header-container",true);
this.headerContainer.append('button').attr('clear',true).text('clear').on('click',function(){
//filterManager.clear();
});
this.headerContainer.style('display','none');
}
public dispose():void{
console.debug('layoutManager disposing');
this.headerContainer.remove();
this.layout.remove();
console.debug('layoutManager disposed');
}
update(dataView:DataView,width:number,height:number):void{ update(dataView:DataView,width:number,height:number):void{
//this.layout.style("width",width+"px").style("height",height+"px"); this.layout.style("width",width+"px").style("height",height+"px");
} }
} }

View File

@ -2,13 +2,15 @@
import * as visualInterfaces from "../visual/visualInterfaces"; import * as visualInterfaces from "../visual/visualInterfaces";
import * as d3 from "d3"; import * as d3 from "d3";
import powerbi from "powerbi-visuals-api"; import powerbi from "powerbi-visuals-api";
import { EventEmitter } from "events";
type Selection<T extends d3.BaseType> = d3.Selection<T, any, any, any>; type Selection<T extends d3.BaseType> = d3.Selection<T, any, any, any>;
export class ManagerFactory{ export class ManagerFactory{
public static CreateLayoutManager(classLayoutManager:visualInterfaces.ILayoutManagerConstructor,container:Selection<HTMLElement>):visualInterfaces.ILayoutManager{ public static CreateLayoutManager(classLayoutManager:visualInterfaces.ILayoutManagerConstructor,container:Selection<HTMLElement>,filterManager:visualInterfaces.IFilterManager):visualInterfaces.ILayoutManager{
return new classLayoutManager(container); return new classLayoutManager(container,filterManager);
} }
public static CreateSelectorManager(classSelectorManager:visualInterfaces.ISelectorManagerConstructor,container:Selection<HTMLElement>,filterManager:visualInterfaces.IFilterManager):visualInterfaces.ISelectorManager{ public static CreateSelectorManager(classSelectorManager:visualInterfaces.ISelectorManagerConstructor,container:Selection<HTMLElement>):visualInterfaces.ISelectorManager&EventEmitter{
return new classSelectorManager(container,filterManager);
return new classSelectorManager(container);
} }
public static CreateFilterManager(classSelectorManager:visualInterfaces.IFilterManagerConstructor,host:powerbi.extensibility.visual.IVisualHost):visualInterfaces.IFilterManager{ public static CreateFilterManager(classSelectorManager:visualInterfaces.IFilterManagerConstructor,host:powerbi.extensibility.visual.IVisualHost):visualInterfaces.IFilterManager{
return new classSelectorManager(host); return new classSelectorManager(host);

View File

@ -1,145 +1,75 @@
"Use strict"; "Use strict";
import "../../node_modules/bootstrap/dist/css/bootstrap.css";
import "../../node_modules/bootstrap-multiselect/dist/css/bootstrap-multiselect.css";
import powerbi from "powerbi-visuals-api"; import powerbi from "powerbi-visuals-api";
import {ISelectorManager,ISelectorManagerConstructor, IFilterManager} from "../visual/visualInterfaces"; import {ISelectorManager,ISelectorManagerConstructor} from "../visual/visualInterfaces";
import * as d3 from "d3"; import * as d3 from "d3";
import * as settings from "../settings";
import * as selectors from "./selectors";
type Selection<T extends d3.BaseType> = d3.Selection<T, any, any, any>; type Selection<T extends d3.BaseType> = d3.Selection<T, any, any, any>;
import {EventEmitter} from "events";
/* /*
*/ */
export class SelectorManager implements ISelectorManager{ export class SelectorManager extends EventEmitter implements ISelectorManager {
public selectorContainer : Selection<HTMLElement>; public selectorContainer : Selection<HTMLElement>;
private selector:ISelector; private selector:selectors.ISelector;
public filterManager:IFilterManager;
private categories: powerbi.DataViewCategoryColumn; private field: powerbi.DataViewCategoryColumn;
private defaultSelect: powerbi.DataViewValueColumn; private defaultSelect: powerbi.DataViewValueColumn;
private defaultStart: powerbi.DataViewValueColumn; private defaultStart: powerbi.DataViewValueColumn;
private defaultEnd: powerbi.DataViewValueColumn; private defaultEnd: powerbi.DataViewValueColumn;
constructor(selectorContainer:Selection<HTMLElement>,filterManager:IFilterManager){
//events
public stringFieldFilter_event: string="stringFieldFilter_event";
constructor(selectorContainer:Selection<HTMLElement>){
super();
console.debug("selectorManager constructor start"); console.debug("selectorManager constructor start");
this.selectorContainer=selectorContainer; this.selectorContainer=selectorContainer;
this.filterManager=filterManager;
} }
public switchSelector<T extends ISelector>(classSelector:new ()=>T){ select(items: string[]): void {
this.selector.select(items);
}
updateFormat(visualSettings:settings.VisualSettings,width:number,height:number): void {
console.debug('selectorManager updateFormat start');
console.debug('visualSettings:',visualSettings);
if(visualSettings&&visualSettings.items){
this.selector.updateFormat(visualSettings.items,width,height);
}
console.debug('selectorManager updateFormat end');
}
public dispose():void{
this.selector.dispose();
this.selectorContainer.remove();
}
public switchSelector<T extends selectors.ISelector>(classSelector:new ()=>T){
let newSelector=new classSelector(); let newSelector=new classSelector();
this.selector?.dispose(); this.selector?.dispose();
this.selector=newSelector; this.selector=newSelector;
} }
updateData(categories: powerbi.DataViewCategoryColumn, defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn){ updateData(field: powerbi.DataViewCategoryColumn, defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn){
if(!this.selector){ if(!this.selector){
let fieldType=categories.source.type; let fieldType=field.source.type;
if(fieldType.text){ if(fieldType.text){
this.selector=new DropDownSelector(this); let dropDownSelectorContainer=this.selectorContainer.append("div").classed("dropDown-selector-container",true);
this.selector=new selectors.DropDownSelector(dropDownSelectorContainer);
}else if(fieldType.numeric){ }else if(fieldType.numeric){
this.selector=new DropDownSelector(this); let dropDownSelectorContainer=this.selectorContainer.append("div").classed("dropDown-selector-container",true);
this.selector=new selectors.DropDownSelector(dropDownSelectorContainer);
}else if(fieldType.dateTime){ }else if(fieldType.dateTime){
this.selector=new CalendarSelector(this); let dropDownSelectorContainer=this.selectorContainer.append("div").classed("dropDown-selector-container",true);
this.selector=new selectors.DropDownSelector(dropDownSelectorContainer);
}else{ }else{
throw "only receive text/num/datetime field"; throw "only receive text/num/datetime field";
} }
this.selector.on(this.selector.stringFieldFilter_event,(selectionValues:string[],applyNow:boolean)=>{
this.emit(this.stringFieldFilter_event,selectionValues,applyNow);
});
console.debug('selector stringFieldFilter registered');
} }
this.selector.update(field, defaultSelect, defaultStart, defaultEnd);
this.selector.update(categories, defaultSelect, defaultStart, defaultEnd);
};
public filterStringField(selection:string[]){
}; };
} }
interface ISelector{
update(categories: powerbi.DataViewCategoryColumn, defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn);
dispose():void;
}
abstract class Selector implements ISelector{
constructor(manager: ISelectorManager){
console.debug("Abstract selector:","constructor start");
this.manager=manager;
this.createView();
}
protected abstract createView():void;
public abstract dispose():void;
protected readonly manager:ISelectorManager;
public abstract update(categories: powerbi.DataViewCategoryColumn, defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn):void;
}
class DropDownSelector extends Selector{
private dropDown:Selection<HTMLSelectElement>;
protected createView() {
console.debug('dropDownViewManager','createView start');
this.dropDown=this.manager.selectorContainer.append("select").classed("dropDown-selector",true).classed("selector",true).attr("multiple",true);
console.debug('dropDownViewManager','createView end');
}
public dispose(){
this.dropDown.remove();
}
public update(categories: powerbi.DataViewCategoryColumn, defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn) {
console.debug('dropDownViewManager','update start');
let options=this.dropDown.selectAll("option").data(categories.values,function(d){return d.toString();});//map data
options.enter().append("option").text(function(d){return d.toString();}).attr('dropDown-option');//add
options.exit().remove();//delete
console.debug("dropDown:",this.dropDown);
let filterManager:IFilterManager=this.manager.filterManager;
// this.dropDown.on("input change",function(){
// console.debug('DropDownSelector:',"input change");
// console.debug('this',this);
// console.debug('filterManager:',filterManager);
// let selectedValues:string[]=[];
// for(let i=0;i<this.selectedOptions.length;i++){
// let option=this.selectedOptions.item(i);
// if(option.selected){
// console.debug("option selected",option);
// console.debug('option.text:',option.text);
// selectedValues.push(option.text);
// }
// }
// if(selectedValues.length==0){
// }else{
// console.debug("selection.length",selectedValues.length);
// filterManager.filterStringField(selectedValues);
// }
// console.debug("this",this.selectedOptions);
// });
$('.dropDown-selector').multiselect({
onChange:function(){
let selectedValues:string[]=[];
$('option:selected').map(function(a,item){
selectedValues.push(item.textContent);
console.debug('item:',item.textContent);
filterManager.filterStringField(selectedValues);
});
},
maxHeight:100,
includeSelectAllOption:true
});
console.debug('dropDownViewManager','update end');
}
}
class ListSelector extends Selector{
public dispose() {
throw new Error("Method not implemented.");
}
protected createView() {
throw new Error("Method not implemented.");
}
public update(categories: powerbi.DataViewCategoryColumn, defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn) {
console.debug('ListSelector','updateView');
let dropDownSelector=this.manager.selectorContainer.append("select").classed("listSelector");
}
}
class CalendarSelector extends Selector{
public dispose() {
throw new Error("Method not implemented.");
}
protected createView() {
throw new Error("Method not implemented.");
}
public update(categories: powerbi.DataViewCategoryColumn, defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn) {
console.debug('calendarViewManager','updateView');
let dropDownSelector=this.manager.selectorContainer.append("select").classed("listSelector");
}
}

View File

@ -1,3 +0,0 @@
/*
In the future, move all selectors here.
*/

View File

@ -0,0 +1,168 @@
import { Selector } from "./selector";
import * as settings from "../../settings";
import powerbi from "powerbi-visuals-api";
import * as d3 from "d3";
import { selection } from "d3";
type Selection<T extends d3.BaseType> = d3.Selection<T, any, any, any>;
import {pixelConverter} from "powerbi-visuals-utils-typeutils";
export class DropDownSelector extends Selector {
public select(items: string[]): void {
console.debug('dropDownSelector select items:',items);
this.dropDown.selectAll('option').each(function(d,i){
console.debug('this.text:',d3.select(this).text());
if(items.includes(d3.select(this).text())){
console.debug('dropDownSelector select, included this:',this);
d3.select(this).attr('selected','selected');
}else{
console.debug('dropDownSelector select, not included this:',this);
d3.select(this).attr('selected',null);
}
});
}
public updateFormat(itemsSetting: settings.ItemsSetting, width: number, height: number) {
console.debug('dropDownSelector updateFormat start');
if (itemsSetting) {
this.dropDown.style('background-color', itemsSetting.backgroundColor)
.attr('postion','absolute')
.style('color', itemsSetting.fontColor)
.style('font-size',pixelConverter.fromPointToPixel(itemsSetting.textSize) + 'px')
.style('width', (width - 10) + 'px')
.style('border-style', settings.Enums.getOutlineStyle(itemsSetting.outline))
.style('border-width', '2px')
.style('border-color', 'black');
this.dropDown.selectAll('option').style('background-color', itemsSetting.backgroundColor)
.style('color', itemsSetting.fontColor)
.style('font-size', pixelConverter.fromPointToPixel(itemsSetting.textSize) + 'px')
.style('border-style', settings.Enums.getOutlineStyle(itemsSetting.outline))
.style('border-width', '2px')
.style('border-color', 'black');
}
console.debug('dropDownSelector updateFormat end');
}
protected checkDefaultSelectionChange(defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn): boolean {
//The defaultSelect is nullable, so first check the new defaultSelect
console.debug('checkDefaultSelectionChange start');
let flag: boolean;
if (!defaultSelect || !defaultSelect.values || !defaultSelect.values[0]) {
console.debug('new defaultSelect is null, set flag=false')
flag = flag || false;
} else {
console.debug('new defaultSelect is not null, check the oldDefaultSelect');
console.debug('new defaultSelect:', defaultSelect);
if (!this.defaultSelect || !this.defaultSelect.values || !defaultSelect.values[0]) {
console.debug('previous defaultSelect is null, set flag=true');
flag = flag || true;
} else {
if (defaultSelect.values[0].toString() != this.defaultSelect.values[0].toString()) {
console.debug('new defaultSelect not equal to the previous, set flag=true');
flag = flag || true;
} else {
console.debug('new defaultSelect equal to the previous, set flag=false');
flag = flag || false;
}
}
}
console.debug('checkDefaultSelectionChange end, result:', flag);
return flag;
}
private container: Selection<HTMLDivElement>;
private dropDown: Selection<HTMLSelectElement>;
protected createView(container: Selection<HTMLDivElement>) {
console.debug('dropDownViewManager', 'createView start');
this.container = container;
this.dropDown = this.container.append("select").classed("dropDown-selector", true).classed("selector", true);//.attr('multiple',true);
let dropDownSelector=this;
//on change, filter
this.dropDown.on("input change", function () {
console.debug('DropDownSelector:', "input change");
console.debug('this', this);
let selectedValues: string[] = [];
for (let i = 0; i < this.selectedOptions.length; i++) {
let option = this.selectedOptions.item(i);
if (option.selected) {
console.debug("option selected", option);
console.debug('option.text:', option.text);
selectedValues.push(option.text);
}
}
if (selectedValues.length == 0) {
} else {
console.debug("selection", selectedValues);
dropDownSelector.emit(dropDownSelector.stringFieldFilter_event,selectedValues,true);
}
console.debug("this", this.selectedOptions);
});
console.debug('dropDownViewManager', 'createView end');
}
public dispose() {
console.debug('dropDown-selector disposing');
this.dropDown.remove();
console.debug('dropDown-selector disposed');
}
public update(field: powerbi.DataViewCategoryColumn, defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn) {
console.debug('dropDownViewManager', 'update start');
//Check field, defaultSelect are changed or not
let needUpdateDefaultSelection: boolean = this.checkFieldAndDefaultSelectionChange(field, defaultSelect, defaultStart, defaultEnd);
let newDefaultSelect: string;
if (needUpdateDefaultSelection && defaultSelect && defaultSelect.values && defaultSelect.values[0]) {
newDefaultSelect = defaultSelect.values[0].toString();
console.debug('newDefaultSelect:', newDefaultSelect);
}
//let values=field.values
let options: d3.Selection<HTMLOptionElement, powerbi.PrimitiveValue, HTMLSelectElement, any> = <d3.Selection<HTMLOptionElement, powerbi.PrimitiveValue, HTMLSelectElement, any>>this.dropDown.selectAll("option").data(field.values, function (d) { return d.toString(); });//map data
options.exit().remove();//delete
if (needUpdateDefaultSelection && newDefaultSelect) {
console.debug('reset defaultSelection start');
options = options.enter().append("option")
.text(function (d) { return d.toString(); })
.attr('label', function (d) { return d.toString(); })
.classed('dropDown-option', true)
.attr('title',function (d) { return d.toString(); })
.merge(options)
.attr("selected", function (d) {
console.debug('d.toString():', d.toString());
console.debug('newDefaultSelect:', newDefaultSelect);
return (d.toString() == newDefaultSelect) ? 'selected' : null;
});
console.debug('Set defaultSelection end');
} else {
console.debug('defaultSelection not reset, start update options');
options=options.enter().append("option")
.text(function (d) { return d.toString(); })
.attr('label', function (d) { return d.toString(); })
.attr('title',function (d) { return d.toString(); })
.attr('label', function (d) { return d.toString(); }).classed('dropDown-option', true).merge(options);//add
console.debug('defaultSelection not reset, end update options');
}
let selectedValues: string[] = [];
this.dropDown.selectAll('option').each(function(d,i){
let option=d3.select(this);
console.debug('option',option.text());
console.debug('selected?',option.property('selected'));
if(option.property('selected')){
console.debug('find a selected option:',option.text());
selectedValues.push(option.text());
}
});
if (selectedValues.length == 0) {
} else {
console.debug("selection", selectedValues);
}
this.emit(this.stringFieldFilter_event,selectedValues,false);
console.debug("dropDown:", this.dropDown);
this.field = field;
this.defaultSelect = defaultSelect;
console.debug('new defaultSelect:', defaultSelect);
console.debug('this.defaultSelect=defaultSelect, this.defaultSelect:', this.defaultSelect);
console.debug('dropDownViewManager', 'update end');
}
}

View File

@ -0,0 +1,5 @@
/*
In the future, move all selectors here.
*/
export {ISelector,Selector} from "./selector";
export {DropDownSelector} from "./dropDownSelector";

View File

View File

@ -0,0 +1,52 @@
import powerbi from "powerbi-visuals-api";
import * as settings from "../../settings";
import * as d3 from "d3";
import { EventEmitter } from "events";
type Selection<T extends d3.BaseType> = d3.Selection<T, any, any, any>;
export interface ISelector extends EventEmitter{
update(field: powerbi.DataViewCategoryColumn, defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn):void;
dispose():void;
select(items:string[]):void;
updateFormat(itemsSetting:settings.ItemsSetting,width:number,height:number):void;
stringFieldFilter_event:string;//filter a string field, give one param: string[]
}
export abstract class Selector extends EventEmitter implements ISelector{
protected field: powerbi.DataViewCategoryColumn;
protected defaultSelect: powerbi.DataViewValueColumn;
protected defaultStart: powerbi.DataViewValueColumn;
protected defaultEnd: powerbi.DataViewValueColumn;
//events
public stringFieldFilter_event:string="stringFieldFilter_event";
protected checkFieldChange(field: powerbi.DataViewCategoryColumn):boolean{
//Field is not null, so first check the old field
if(!this.field){
return true;
}
if(field.source.queryName==this.field.source.queryName){
console.debug('checkFieldChange: false');
return false;
}else{
console.debug('checkFieldChange: true');
return true;
}
}
protected abstract checkDefaultSelectionChange(defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn):boolean;
protected checkFieldAndDefaultSelectionChange(field: powerbi.DataViewCategoryColumn, defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn){
console.debug('checkFieldAndDefaultSelectionChange start');
let flag:boolean=this.checkFieldChange(field)||this.checkDefaultSelectionChange(defaultSelect,defaultStart,defaultEnd)
console.debug('checkFieldAndDefaultSelectionChange end, result:',flag);
return flag;
}
constructor(container:Selection<HTMLDivElement>){
super();
console.debug("Abstract selector:","constructor start");
this.createView(container);
}
public abstract select(items: string[]):void;
public abstract updateFormat(itemsSetting: settings.ItemsSetting,width:number,height:number):void;
protected abstract createView(container:Selection<HTMLDivElement>):void;
public abstract dispose():void;
public abstract update(field: powerbi.DataViewCategoryColumn, defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn):void;
}

View File

@ -0,0 +1 @@
export {Outline,getOutlineStyle} from "./outline";

View File

@ -0,0 +1,38 @@
"Use strict";
export enum Outline {
None, BottomOnly, TopOnly, LeftOnly, RightOnly, TopAndBottom, LeftAndRight, Frame
}
export function getOutlineStyle(outline: Outline): string {
let outlineStyle: string;
switch (outline) {
case Outline.None:
outlineStyle = 'none';
break;
case Outline.BottomOnly:
outlineStyle = 'none none solid none';
break;
case Outline.TopOnly:
outlineStyle = 'solid none none none';
break;
case Outline.LeftOnly:
outlineStyle = 'none none none solid';
break;
case Outline.RightOnly:
outlineStyle = 'none solid none none';
break;
case Outline.TopAndBottom:
outlineStyle = 'solid none';
break;
case Outline.LeftAndRight:
outlineStyle = 'none solid';
break;
case Outline.Frame:
outlineStyle = 'solid';
break;
default:
break;
}
return outlineStyle;
}

4
src/settings/index.ts Normal file
View File

@ -0,0 +1,4 @@
export {ItemsSetting} from "./itemsSetting";
export {SlicerHeaderSetting} from "./slicerHeaderSetting";
export {VisualSettings} from "./visualSettings";
export * as Enums from "./enums";

View File

@ -0,0 +1,8 @@
"Use strict";
import * as enums from "./enums"
export class ItemsSetting{
public fontColor:string="black";
public backgroundColor:string='transparent';
public outline:enums.Outline=enums.Outline.None;
public textSize:number=10;
}

View File

@ -0,0 +1,11 @@
"Use strict";
import * as enums from "./enums";
export class SlicerHeaderSetting{
public fontColor:string='';
public backgroundColor:string='';
public outline:enums.Outline=enums.Outline.None;
public outlineWeight:number=2;
public outlineColor:string="gray";
}

View File

@ -28,21 +28,11 @@
import { dataViewObjectsParser } from "powerbi-visuals-utils-dataviewutils"; import { dataViewObjectsParser } from "powerbi-visuals-utils-dataviewutils";
import DataViewObjectsParser = dataViewObjectsParser.DataViewObjectsParser; import DataViewObjectsParser = dataViewObjectsParser.DataViewObjectsParser;
import {SlicerHeaderSetting} from "./slicerHeaderSetting";
import {ItemsSetting} from "./itemsSetting";
export class VisualSettings extends DataViewObjectsParser { export class VisualSettings extends DataViewObjectsParser {
public dataPoint: dataPointSettings = new dataPointSettings(); public items: ItemsSetting = new ItemsSetting();
public slicerHeader:SlicerHeaderSetting=new SlicerHeaderSetting();
} }
export class dataPointSettings {
// Default color
public defaultColor: string = "";
// Show all
public showAllDataPoints: boolean = true;
// Fill
public fill: string = "";
// Color saturation
public fillRule: string = "";
// Text Size
public fontSize: number = 12;
}

View File

@ -36,6 +36,7 @@ import VisualObjectInstance = powerbi.VisualObjectInstance;
import DataView = powerbi.DataView; import DataView = powerbi.DataView;
import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject; import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject;
import IVisualEventService = powerbi.extensibility.IVisualEventService; import IVisualEventService = powerbi.extensibility.IVisualEventService;
import SelectionManager=powerbi.extensibility.ISelectionManager;
import * as visualInterfaces from "./visualInterfaces"; import * as visualInterfaces from "./visualInterfaces";
import IVisualHost = powerbi.extensibility.visual.IVisualHost; import IVisualHost = powerbi.extensibility.visual.IVisualHost;
import { VisualSettings } from "../settings/visualSettings"; import { VisualSettings } from "../settings/visualSettings";
@ -43,8 +44,10 @@ import {FilterManager,ManagerFactory,SelectorManager,LayoutManager} from "../man
import * as d3 from "d3"; import * as d3 from "d3";
import { getObject } from "powerbi-visuals-utils-dataviewutils/lib/dataViewObjects"; import { getObject } from "powerbi-visuals-utils-dataviewutils/lib/dataViewObjects";
import { getMeasureIndexOfRole } from "powerbi-visuals-utils-dataviewutils/lib/dataRoleHelper"; import { getMeasureIndexOfRole } from "powerbi-visuals-utils-dataviewutils/lib/dataRoleHelper";
import { selector } from "d3"; import { selector, json } from "d3";
import { debuglog } from "util";
import { EventEmitter } from "events";
import * as models from 'powerbi-models';
export class Visual implements IVisual { export class Visual implements IVisual {
/* /*
SelectorManager: a custom manager, used to manage dropDownSelector, listSelector, calendarSelector, and so on SelectorManager: a custom manager, used to manage dropDownSelector, listSelector, calendarSelector, and so on
@ -52,17 +55,24 @@ export class Visual implements IVisual {
*/ */
private events: IVisualEventService; private events: IVisualEventService;
private settings: VisualSettings; private settings: VisualSettings;
private selectionManager:SelectionManager;
private layoutManager: visualInterfaces.ILayoutManager; private layoutManager: visualInterfaces.ILayoutManager;
private selectorManager: visualInterfaces.ISelectorManager; private selectorManager: visualInterfaces.ISelectorManager&EventEmitter;
private view:SVGElement;
private filterManager:visualInterfaces.IFilterManager; private filterManager:visualInterfaces.IFilterManager;
private selectionManager:powerbi.extensibility.ISelectionManager; private readonly id:number;
private isFirstUpdate:boolean=true;
//private selectionManager:powerbi.extensibility.ISelectionManager;
constructor(options: VisualConstructorOptions) { constructor(options: VisualConstructorOptions) {
console.debug('Visual constructor', options); console.debug('Visual constructor start');
console.debug('options:',options);
this.events = options.host.eventService; this.events = options.host.eventService;
if (document) { if (document) {
this.selectionManager =options.host.createSelectionManager(); //this.selectionManager =options.host.createSelectionManager();
let container=d3.select(options.element).append("div").classed("container",true); let container=d3.select(options.element).append("div").classed("container",true);
//overload context menu
let coverContextMenu=false;
if(coverContextMenu){
d3.select(options.element).on('contextmenu', () => { d3.select(options.element).on('contextmenu', () => {
const mouseEvent: MouseEvent = <MouseEvent>d3.event; const mouseEvent: MouseEvent = <MouseEvent>d3.event;
//const eventTarget: EventTarget = mouseEvent.target; //const eventTarget: EventTarget = mouseEvent.target;
@ -73,18 +83,35 @@ export class Visual implements IVisual {
}); });
//mouseEvent.preventDefault(); //mouseEvent.preventDefault();
}); });
let headerContainer=container.append("div").classed("header-container",true); }
let selectorContainer=container.append("div").classed("selector-container",true);
this.layoutManager=ManagerFactory.CreateLayoutManager(LayoutManager,container); //First, create the filterManager
this.filterManager=ManagerFactory.CreateFilterManager(FilterManager,options.host); this.filterManager=ManagerFactory.CreateFilterManager(FilterManager,options.host);
this.selectorManager=ManagerFactory.CreateSelectorManager(SelectorManager,selectorContainer,this.filterManager); //Then, layoutManager and selectorManager, both use the filterManager
//Lagyoutmanager manage the whole div and the slicer-header
this.layoutManager=ManagerFactory.CreateLayoutManager(LayoutManager,container,this.filterManager);
//SelectorManager manage selectors
let selectorContainer=container.append("div").classed("selector-container",true);
this.selectorManager=ManagerFactory.CreateSelectorManager(SelectorManager,selectorContainer);
//events
this.selectorManager.on(this.selectorManager.stringFieldFilter_event,(selectionValues:string[],applyNow:boolean)=>{
this.filterManager.setFilter_String(selectionValues,applyNow);
});
console.debug('selectorManager stringFieldFilter_event registered');
//set id
this.id=Math.random();
} }
console.debug('end constructor'); console.debug('end constructor');
} }
public update(options: VisualUpdateOptions) { public update(options: VisualUpdateOptions) {
console.debug('visual update start'); console.debug('visual update start');
console.debug('id:',this.id);
console.debug('options:',options);
//console.debug('operationKind',options.operationKind.toString());
this.events.renderingStarted(options); this.events.renderingStarted(options);
this.filterManager.inAdvancedEditMode=options.editMode==powerbi.EditMode.Advanced
this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]); this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]);
let dataView=options.dataViews[0]; let dataView=options.dataViews[0];
let field=dataView.categorical.categories[0]; let field=dataView.categorical.categories[0];
@ -108,16 +135,60 @@ export class Visual implements IVisual {
let height:number=options.viewport.height; let height:number=options.viewport.height;
console.debug("start layoutManager updateView"); console.debug("start layoutManager updateView");
this.layoutManager.update(dataView,width-10,height-10); this.layoutManager.update(dataView,width,height);
console.debug("start selectorManager updateView");
this.selectorManager.updateData(field,defaultSelect,defaultStart,defaultEnd);
console.debug("start filterManager updateView"); console.debug("start filterManager updateView");
this.filterManager.update(field); this.filterManager.update(field);
console.debug("start selectorManager update");
this.selectorManager.updateData(field,defaultSelect,defaultStart,defaultEnd);
this.selectorManager.updateFormat(this.settings,width,height);
console.debug("previous filter",JSON.stringify(options.jsonFilters));
/*
Compare two jsonFilters(filterManager.jsonFilter and options.jsonFilters[0]).
Use method JSON.stringify.
1. filterManager.jsonFilter is null, options.jsonFilter[0] is null
Don't restore
2. filterManager.jsonFilter is Null, options.jsonFilter[0] is not null
Restore
3. filterManager.jsonFilter is not null, options.jsonFilter[0] is null
Don't restore
4. filterManager.jsonFilter != options.jsonFilter[0]
Restore
5. filterManager.jsonFilter == options.jsonFilter[0]
Don't restore
*/
if(options.jsonFilters&&options.jsonFilters[0]&&this.isFirstUpdate){
console.debug('options.jsonFilters[0] is not null');
if((!this.filterManager.jsonFilter)||
(JSON.stringify(this.filterManager.jsonFilter)!=JSON.stringify(options.jsonFilters[0]))){
console.debug('Two filter are different');
let advancedFilter:models.IAdvancedFilter=options.jsonFilters[0] as models.IAdvancedFilter;
console.debug("Cast as advancedFilter:",advancedFilter);
if(advancedFilter.conditions[0].value=='SyinpoSlicer_AdvancedEditMode_true'){
console.debug('previous filter is created in AdvancedEditMode');
}else{
//Sync slicers
console.debug('Sync slicers init or restore a bookmark start');
this.filterManager.setFilter(advancedFilter,false);
let oldSelection:string[]=[];
advancedFilter.conditions.map(function(condition,index){
oldSelection.push(condition.value as string);
});
this.selectorManager.select(oldSelection);
console.debug('Sync slicers init or restore a bookmark end');
}
}else{
console.debug('Two filter are equal');
}//end if
}else{
console.debug('Neednt init a sync slicer or restore a bookmark');
}
console.debug('visual update end');
this.isFirstUpdate=false;
this.filterManager.applyJsonFilter();
this.events.renderingFinished(options); this.events.renderingFinished(options);
} }
private static parseSettings(dataView: DataView): VisualSettings { private static parseSettings(dataView: DataView): VisualSettings {
return <VisualSettings>VisualSettings.parse(dataView); return <VisualSettings>VisualSettings.parse(dataView);
} }

View File

@ -6,35 +6,51 @@ import {VisualSettings} from "../settings/visualSettings";
import * as d3 from "d3"; import * as d3 from "d3";
import { IFilter } from "powerbi-models"; import { IFilter } from "powerbi-models";
type Selection<T extends d3.BaseType> = d3.Selection<T, any, any, any>; type Selection<T extends d3.BaseType> = d3.Selection<T, any, any, any>;
import {EventEmitter} from "events";
import * as models from 'powerbi-models';
/* /*
Main interfaces Main interfaces
*/ */
//IVisualSettings
//ISelectorManager //ISelectorManager
export interface ILayoutManager{ export interface ILayoutManager{
layout:Selection<HTMLElement>; layout:Selection<HTMLElement>;
update(dataView:DataView,width:number,height:number):void; update(dataView:DataView,width:number,height:number):void;
dispose():void;
} }
export interface ILayoutManagerConstructor{ export interface ILayoutManagerConstructor{
new(container:Selection<HTMLElement>):ILayoutManager; new(container:Selection<HTMLElement>,filterManager:IFilterManager):ILayoutManager;
}; }
//ISelectorManager //ISelectorManager
export interface ISelectorManager{ export interface ISelectorManager{
selectorContainer:Selection<HTMLElement>; selectorContainer:Selection<HTMLElement>;
filterManager:IFilterManager; stringFieldFilter_event:string;//filter a string field, give one param: string[]
updateData(field:powerbi.DataViewCategoryColumn,defaultSelect:powerbi.DataViewValueColumn,defaultStart:powerbi.DataViewValueColumn,defaultEnd:powerbi.DataViewValueColumn); dispose():void;
updateData(field:powerbi.DataViewCategoryColumn,defaultSelect:powerbi.DataViewValueColumn,defaultStart:powerbi.DataViewValueColumn,defaultEnd:powerbi.DataViewValueColumn):void;
updateFormat(visualSettings:VisualSettings,width:number,height:number):void;
select(items:string[]):void;
} }
export interface ISelectorManagerConstructor{ export interface ISelectorManagerConstructor{
new(selectorContainer:Selection<HTMLElement>,filterManager:IFilterManager):ISelectorManager; new(selectorContainer:Selection<HTMLElement>):ISelectorManager&EventEmitter;
} }
//IFilterManager //IFilterManager
export interface IFilterManager{ export interface IFilterManager{
jsonFilter:models.IAdvancedFilter;
inAdvancedEditMode:boolean;
update(field:powerbi.DataViewCategoryColumn):void; update(field:powerbi.DataViewCategoryColumn):void;
filterStringField(selection:string[]):void; setFilter_String(selection:string[],applyNow:boolean):void;
setFilter(jsonFilter:models.IAdvancedFilter,applyNow:boolean):void;
applyJsonFilter():void;
clear():void;
dispose():void;
} }
export interface IFilterManagerConstructor{ export interface IFilterManagerConstructor{
new(host:IVisualHost):IFilterManager; new(host:IVisualHost):IFilterManager;

View File

@ -1,5 +1,3 @@
@import "/node_modules/bootstrap/dist/css/bootstrap.min.css";
@import "/node_modules/bootstrap-multiselect/dist/css/bootstrap-multiselect.css";
p { p {
font-size: 20px; font-size: 20px;
font-weight: bold; font-weight: bold;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long