From 3d759644d1c94ea438a11e48b624de4303338885 Mon Sep 17 00:00:00 2001 From: MuJianNan Date: Tue, 5 May 2020 00:53:09 +0800 Subject: [PATCH] I find a new way to support sync slicers and bookmarks --- src/managers/filterManager.ts | 28 ++++++++++---- src/managers/managerFactory.ts | 5 ++- src/managers/selectorManager.ts | 32 ++++++++++----- src/managers/selectors/dropDownSelector.ts | 45 ++++++++++++++++++---- src/managers/selectors/selector.ts | 19 +++++---- src/visual/visual.ts | 35 +++++++++++------ src/visual/visualInterfaces.ts | 14 +++++-- 7 files changed, 128 insertions(+), 50 deletions(-) diff --git a/src/managers/filterManager.ts b/src/managers/filterManager.ts index 827a943..0d7131e 100644 --- a/src/managers/filterManager.ts +++ b/src/managers/filterManager.ts @@ -11,9 +11,23 @@ import { select, json } from "d3"; export class FilterManager implements IFilterManager{ private target: models.IFilterColumnTarget={table:'',column:''}; private host:IVisualHost; + private jsonFilter:powerbi.IFilter; constructor(host:IVisualHost){ this.host=host; } + setFilter(jsonFilter: powerbi.IFilter,applyNow:boolean): void { + 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('filter manager applyJsonFilter end'); + + } public dispose():void{ console.debug('filterManager disposing'); @@ -37,8 +51,8 @@ export class FilterManager implements IFilterManager{ console.debug("queryName.length:",queryName.length); console.debug("filterManager update end, now filter:",this.target); } - public filterStringField(selection:string[]):void{ - console.debug("filterStringField start:",selection); + public setFilter_String(selection:string[],applyNow:boolean):void{ + console.debug("setFilter_String start:",selection); let jsonFilter:models.IBasicFilter={ "$schema": "http://powerbi.com/product/schema#basic", "target": this.target, @@ -47,12 +61,10 @@ export class FilterManager implements IFilterManager{ "values": selection } console.debug("jsonFilter:",jsonFilter); - if(jsonFilter){ - console.debug('apply jsonFilter:',JSON.stringify(jsonFilter)); - this.host.applyJsonFilter(jsonFilter,"general","filter",powerbi.FilterAction.merge); - }else{ - console.debug('not apply jsonFilter:',jsonFilter); + this.jsonFilter=jsonFilter; + if(applyNow){ + this.applyJsonFilter(); } - console.debug("filterStringField end"); + console.debug("setFilter_String end"); } } \ No newline at end of file diff --git a/src/managers/managerFactory.ts b/src/managers/managerFactory.ts index 1100f72..b9e8e76 100644 --- a/src/managers/managerFactory.ts +++ b/src/managers/managerFactory.ts @@ -2,14 +2,15 @@ import * as visualInterfaces from "../visual/visualInterfaces"; import * as d3 from "d3"; import powerbi from "powerbi-visuals-api"; +import { EventEmitter } from "events"; type Selection = d3.Selection; export class ManagerFactory{ public static CreateLayoutManager(classLayoutManager:visualInterfaces.ILayoutManagerConstructor,container:Selection,filterManager:visualInterfaces.IFilterManager):visualInterfaces.ILayoutManager{ return new classLayoutManager(container,filterManager); } - public static CreateSelectorManager(classSelectorManager:visualInterfaces.ISelectorManagerConstructor,container:Selection,filterManager:visualInterfaces.IFilterManager):visualInterfaces.ISelectorManager{ + public static CreateSelectorManager(classSelectorManager:visualInterfaces.ISelectorManagerConstructor,container:Selection):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{ return new classSelectorManager(host); diff --git a/src/managers/selectorManager.ts b/src/managers/selectorManager.ts index b1585a7..737c4f5 100644 --- a/src/managers/selectorManager.ts +++ b/src/managers/selectorManager.ts @@ -1,25 +1,33 @@ "Use strict"; 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 settings from "../settings"; import * as selectors from "./selectors"; type Selection = d3.Selection; +import {EventEmitter} from "events"; /* 负责页面上选择器的展示 */ -export class SelectorManager implements ISelectorManager{ +export class SelectorManager extends EventEmitter implements ISelectorManager { public selectorContainer : Selection; private selector:selectors.ISelector; - public filterManager:IFilterManager; - private categories: powerbi.DataViewCategoryColumn; + + private field: powerbi.DataViewCategoryColumn; private defaultSelect: powerbi.DataViewValueColumn; private defaultStart: powerbi.DataViewValueColumn; private defaultEnd: powerbi.DataViewValueColumn; - constructor(selectorContainer:Selection,filterManager:IFilterManager){ + + //events + public stringFieldFilter_event: string="stringFieldFilter_event"; + + constructor(selectorContainer:Selection){ + super(); console.debug("selectorManager constructor start"); this.selectorContainer=selectorContainer; - this.filterManager=filterManager; + } + select(items: string[]): void { + this.selector.select(items); } updateFormat(visualSettings:settings.VisualSettings,width:number,height:number): void { @@ -28,6 +36,7 @@ export class SelectorManager implements ISelectorManager{ if(visualSettings&&visualSettings.items){ this.selector.updateFormat(visualSettings.items,width,height); } + console.debug('selectorManager updateFormat end'); } @@ -46,18 +55,21 @@ export class SelectorManager implements ISelectorManager{ let fieldType=field.source.type; if(fieldType.text){ let dropDownSelectorContainer=this.selectorContainer.append("div").classed("dropDown-selector-container",true); - this.selector=new selectors.DropDownSelector(dropDownSelectorContainer,this.filterManager); + this.selector=new selectors.DropDownSelector(dropDownSelectorContainer); }else if(fieldType.numeric){ let dropDownSelectorContainer=this.selectorContainer.append("div").classed("dropDown-selector-container",true); - this.selector=new selectors.DropDownSelector(dropDownSelectorContainer,this.filterManager); + this.selector=new selectors.DropDownSelector(dropDownSelectorContainer); }else if(fieldType.dateTime){ let dropDownSelectorContainer=this.selectorContainer.append("div").classed("dropDown-selector-container",true); - this.selector=new selectors.DropDownSelector(dropDownSelectorContainer,this.filterManager); + this.selector=new selectors.DropDownSelector(dropDownSelectorContainer); }else{ 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); }; } \ No newline at end of file diff --git a/src/managers/selectors/dropDownSelector.ts b/src/managers/selectors/dropDownSelector.ts index 0691a77..8fa31ce 100644 --- a/src/managers/selectors/dropDownSelector.ts +++ b/src/managers/selectors/dropDownSelector.ts @@ -2,10 +2,23 @@ import { Selector } from "./selector"; import * as settings from "../../settings"; import powerbi from "powerbi-visuals-api"; import * as d3 from "d3"; -import { IFilterManager } from "../../visual/visualInterfaces"; +import { selection } from "d3"; type Selection = d3.Selection; 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) { @@ -61,12 +74,11 @@ export class DropDownSelector extends Selector { console.debug('dropDownViewManager', 'createView start'); this.container = container; this.dropDown = this.container.append("select").classed("dropDown-selector", true).classed("selector", true);//.attr("multiple",false); - let filterManager: IFilterManager = this.filterManager; + let dropDownSelector=this; //on change, filter 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); @@ -78,8 +90,8 @@ export class DropDownSelector extends Selector { } if (selectedValues.length == 0) { } else { - console.debug("selection.length", selectedValues.length); - filterManager.filterStringField(selectedValues); + console.debug("selection", selectedValues); + dropDownSelector.emit(dropDownSelector.stringFieldFilter_event,selectedValues,true); } console.debug("this", this.selectedOptions); }); @@ -95,6 +107,9 @@ export class DropDownSelector extends Selector { 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]) { @@ -116,19 +131,33 @@ export class DropDownSelector extends Selector { console.debug('newDefaultSelect:', newDefaultSelect); return (d.toString() == newDefaultSelect) ? 'selected' : null; }); - //this.manager.filterManager.filterStringField([newDefaultSelect]);//No matter what, filter after defaultSelection changing console.debug('Set defaultSelection end'); } else { console.debug('defaultSelection not reset, start update options'); - options.enter().append("option").text(function (d) { return d.toString(); }).attr('label', function (d) { return d.toString(); }).classed('dropDown-option', true);//add + options=options.enter().append("option").text(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); - this.dropDown.dispatch('change'); console.debug('dropDownViewManager', 'update end'); } } \ No newline at end of file diff --git a/src/managers/selectors/selector.ts b/src/managers/selectors/selector.ts index 77490cd..e21d911 100644 --- a/src/managers/selectors/selector.ts +++ b/src/managers/selectors/selector.ts @@ -1,19 +1,24 @@ import powerbi from "powerbi-visuals-api"; import * as settings from "../../settings"; -import {IFilterManager} from "../../visual/visualInterfaces"; import * as d3 from "d3"; +import { EventEmitter } from "events"; type Selection = d3.Selection; -export interface ISelector{ +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 implements ISelector{ +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){ @@ -34,14 +39,14 @@ export abstract class Selector implements ISelector{ console.debug('checkFieldAndDefaultSelectionChange end, result:',flag); return flag; } - constructor(container:Selection,filterManager: IFilterManager){ + constructor(container:Selection){ + super(); console.debug("Abstract selector:","constructor start"); - this.filterManager=filterManager; this.createView(container); } - public abstract updateFormat(itemsSetting: settings.ItemsSetting,width:number,height:number); + public abstract select(items: string[]):void; + public abstract updateFormat(itemsSetting: settings.ItemsSetting,width:number,height:number):void; protected abstract createView(container:Selection):void; public abstract dispose():void; - protected readonly filterManager:IFilterManager; public abstract update(field: powerbi.DataViewCategoryColumn, defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn):void; } \ No newline at end of file diff --git a/src/visual/visual.ts b/src/visual/visual.ts index aca0e27..a844e1e 100644 --- a/src/visual/visual.ts +++ b/src/visual/visual.ts @@ -46,6 +46,7 @@ import { getObject } from "powerbi-visuals-utils-dataviewutils/lib/dataViewObjec import { getMeasureIndexOfRole } from "powerbi-visuals-utils-dataviewutils/lib/dataRoleHelper"; import { selector, json } from "d3"; import { debuglog } from "util"; +import { EventEmitter } from "events"; export class Visual implements IVisual { /* @@ -56,7 +57,7 @@ export class Visual implements IVisual { private settings: VisualSettings; private selectionManager:SelectionManager; private layoutManager: visualInterfaces.ILayoutManager; - private selectorManager: visualInterfaces.ISelectorManager; + private selectorManager: visualInterfaces.ISelectorManager&EventEmitter; private filterManager:visualInterfaces.IFilterManager; private readonly id:number; private isFirstUpdate:boolean=true; @@ -91,8 +92,13 @@ export class Visual implements IVisual { 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,this.filterManager); + 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(); } @@ -100,7 +106,9 @@ export class Visual implements IVisual { } public update(options: VisualUpdateOptions) { - console.debug('visual update start, id=',this.id); + console.debug('visual update start'); + console.debug('id:',this.id); + //console.debug('operationKind',options.operationKind.toString()); this.events.renderingStarted(options); this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]); let dataView=options.dataViews[0]; @@ -130,18 +138,23 @@ export class Visual implements IVisual { console.debug("start filterManager updateView"); this.filterManager.update(field); - console.debug("previous filter",JSON.stringify(options.jsonFilters)); - if(this.isFirstUpdate&&options.jsonFilters){ - //Sync slicers - console.debug('Sync slicers start'); - } console.debug("start selectorManager update"); - console.debug("isFirstUpdate:",this.isFirstUpdate); this.selectorManager.updateData(field,defaultSelect,defaultStart,defaultEnd); this.selectorManager.updateFormat(this.settings,width,height); - - console.debug('visual update end'); + console.debug("previous filter",JSON.stringify(options.jsonFilters)); + if(this.isFirstUpdate&&options.jsonFilters&&options.jsonFilters[0]){ + //Sync slicers + console.debug('Sync slicers init end'); + this.filterManager.setFilter(options.jsonFilters[0],false); + this.selectorManager.select(options.jsonFilters[0]['values']) + console.debug('Sync slicers init end'); + }else{ + console.debug('Neednt init a sync slicer'); + } + console.debug("isFirstUpdate:",this.isFirstUpdate); this.isFirstUpdate=false; + console.debug('visual update end'); + this.filterManager.applyJsonFilter(); this.events.renderingFinished(options); } private static parseSettings(dataView: DataView): VisualSettings { diff --git a/src/visual/visualInterfaces.ts b/src/visual/visualInterfaces.ts index 7bdf9ee..f9b9bf9 100644 --- a/src/visual/visualInterfaces.ts +++ b/src/visual/visualInterfaces.ts @@ -6,6 +6,9 @@ import {VisualSettings} from "../settings/visualSettings"; import * as d3 from "d3"; import { IFilter } from "powerbi-models"; type Selection = d3.Selection; +import {EventEmitter} from "events"; +import * as models from 'powerbi-models'; + /* Main interfaces */ @@ -27,20 +30,23 @@ export interface ILayoutManagerConstructor{ export interface ISelectorManager{ selectorContainer:Selection; - filterManager:IFilterManager; - + stringFieldFilter_event:string;//filter a string field, give one param: string[] 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{ - new(selectorContainer:Selection,filterManager:IFilterManager):ISelectorManager; + new(selectorContainer:Selection):ISelectorManager&EventEmitter; } //IFilterManager export interface IFilterManager{ update(field:powerbi.DataViewCategoryColumn):void; - filterStringField(selection:string[]):void; + setFilter_String(selection:string[],applyNow:boolean):void; + setFilter(jsonFilter:powerbi.IFilter,applyNow:boolean):void; + applyJsonFilter():void; clear():void; dispose():void; }