Compare commits

..

2 Commits

26 changed files with 241 additions and 749 deletions

View File

@ -1,26 +0,0 @@
# 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,6 +1,4 @@
{
"supportsSynchronizingFilterState": true,
"advancedEditModeSupport": 1,
"dataRoles": [
{
"displayName": "Field",
@ -48,7 +46,7 @@
"objects": {
"general": {
"displayName": "General",
"displayNameKey": "Formatting-General",
"displayNameKey": "formattingGeneral",
"properties": {
"filter": {
"type": {
@ -64,12 +62,11 @@
}
}
},
"slicerHeader": {
"displayName": "Slicer header",
"displayNameKey":"Formatting-SicerHeader",
"dataPoint": {
"displayName": "Data colors",
"properties": {
"fontColor": {
"displayName": "Font color",
"defaultColor": {
"displayName": "Default color",
"type": {
"fill": {
"solid": {
@ -78,79 +75,6 @@
}
}
},
"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": {
"displayName": "Show all",
"type": {
@ -182,90 +106,6 @@
}
}
}
},
"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": [

56
package-lock.json generated
View File

@ -53,6 +53,23 @@
}
}
},
"@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": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-5.7.2.tgz",
@ -286,11 +303,10 @@
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz",
"integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ=="
},
"@types/node": {
"version": "13.13.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.4.tgz",
"integrity": "sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA==",
"dev": true
"@types/jquery": {
"version": "2.0.54",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-2.0.54.tgz",
"integrity": "sha512-D/PomKwNkDfSKD13DEVQT/pq2TUjN54c6uB341fEZanIzkjfGe7UaFuuaLZbpEiS5j7Wk2MUHAZqZIoECw29lg=="
},
"ansi-styles": {
"version": "3.2.1",
@ -322,6 +338,26 @@
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
"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": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -780,6 +816,11 @@
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
},
"jquery": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz",
"integrity": "sha1-LInWiJterFIqfuoywUUhVZxsvwI="
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -905,11 +946,6 @@
"resolved": "https://registry.npmjs.org/powerbi-visuals-utils-dataviewutils/-/powerbi-visuals-utils-dataviewutils-2.2.1.tgz",
"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": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",

View File

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

View File

@ -18,7 +18,10 @@
"icon": "assets/icon.png"
},
"externalJS": [
"./src/cover.js"
"node_modules/jquery/dist/jquery.min.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",
"capabilities": "capabilities.json",

View File

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

3
src/coverJquery.js Normal file
View File

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

View File

@ -11,48 +11,9 @@ import { select, json } from "d3";
export class FilterManager implements IFilterManager{
private target: models.IFilterColumnTarget={table:'',column:''};
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){
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);
@ -67,33 +28,17 @@ export class FilterManager implements IFilterManager{
console.debug("queryName.length:",queryName.length);
console.debug("filterManager update end, now filter:",this.target);
}
public setFilter_String(selection:string[],applyNow:boolean):void{
console.debug("setFilter_String start:",selection);
let conditions:models.IAdvancedFilterCondition[]=[];
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",
public filterStringField(selection:string[]):void{
console.debug("filterStringField start:",selection);
let jsonFilter:models.IBasicFilter={
"$schema": "http://powerbi.com/product/schema#basic",
"target": this.target,
"filterType": models.FilterType.Advanced,
"conditions":conditions,
"logicalOperator":"Or"
"filterType": models.FilterType.Basic,
"operator":"In",
"values": selection
}
console.debug("jsonFilter:",jsonFilter);
this._jsonFilter=jsonFilter;
if(applyNow){
this.applyJsonFilter();
}
console.debug("setFilter_String end");
this.host.applyJsonFilter(jsonFilter?jsonFilter:null,"general","filter",powerbi.FilterAction.merge);
console.debug("filterStringField end");
}
}

View File

@ -11,24 +11,15 @@ export class LayoutManager implements ILayoutManager{
// constructor(layout:Selection<HTMLElement>){
// this.layout=layout;
// }
headerContainer:Selection<HTMLDivElement>
header: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.header=this.layout.append('div').attr('header',true);
this.header.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{
this.layout.style("width",width+"px").style("height",height+"px");
//this.layout.style("width",width+"px").style("height",height+"px");
}
}

View File

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

View File

@ -1,75 +1,136 @@
"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 {ISelectorManager,ISelectorManagerConstructor} from "../visual/visualInterfaces";
import {ISelectorManager,ISelectorManagerConstructor, IFilterManager} from "../visual/visualInterfaces";
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>;
import {EventEmitter} from "events";
/*
*/
export class SelectorManager extends EventEmitter implements ISelectorManager {
export class SelectorManager implements ISelectorManager{
public selectorContainer : Selection<HTMLElement>;
private selector:selectors.ISelector;
private field: powerbi.DataViewCategoryColumn;
private selector:ISelector;
public filterManager:IFilterManager;
private categories: powerbi.DataViewCategoryColumn;
private defaultSelect: powerbi.DataViewValueColumn;
private defaultStart: powerbi.DataViewValueColumn;
private defaultEnd: powerbi.DataViewValueColumn;
//events
public stringFieldFilter_event: string="stringFieldFilter_event";
constructor(selectorContainer:Selection<HTMLElement>){
super();
constructor(selectorContainer:Selection<HTMLElement>,filterManager:IFilterManager){
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 {
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){
public switchSelector<T extends ISelector>(classSelector:new ()=>T){
let newSelector=new classSelector();
this.selector?.dispose();
this.selector=newSelector;
}
updateData(field: powerbi.DataViewCategoryColumn, defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn){
updateData(categories: powerbi.DataViewCategoryColumn, defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn){
if(!this.selector){
let fieldType=field.source.type;
let fieldType=categories.source.type;
if(fieldType.text){
let dropDownSelectorContainer=this.selectorContainer.append("div").classed("dropDown-selector-container",true);
this.selector=new selectors.DropDownSelector(dropDownSelectorContainer);
this.selector=new DropDownSelector(this);
}else if(fieldType.numeric){
let dropDownSelectorContainer=this.selectorContainer.append("div").classed("dropDown-selector-container",true);
this.selector=new selectors.DropDownSelector(dropDownSelectorContainer);
this.selector=new DropDownSelector(this);
}else if(fieldType.dateTime){
let dropDownSelectorContainer=this.selectorContainer.append("div").classed("dropDown-selector-container",true);
this.selector=new selectors.DropDownSelector(dropDownSelectorContainer);
this.selector=new CalendarSelector(this);
}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);
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<HTMLElement>;
protected createView() {
console.debug('dropDownViewManager','createView start');
this.dropDown=this.manager.selectorContainer.append('div').classed('dropDown-selector',true)
.append('select').attr('id','dropDown-select').attr('multiple','true');
console.debug('dropDownViewManager','createView end');
}
public dispose(){
this.dropDown.remove();
}
public update(field: powerbi.DataViewCategoryColumn, defaultSelect: powerbi.DataViewValueColumn, defaultStart: powerbi.DataViewValueColumn, defaultEnd: powerbi.DataViewValueColumn) {
console.debug('dropDownViewManager','update start');
let filterManager:IFilterManager=this.manager.filterManager;
$('#dropDown-select').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:200,
includeSelectAllOption:true
});
let options=[];
field.values.map((item)=>{
options.push({
label:item.toString()
});
});
$('#dropDown-select').multiselect('dataprovider',options);
$('.dropDown-selector').css('overflow','visible').css('z-index',1000).css('position','relative');
$('.dropdown-menu').css('overflow','flow').css('z-index',1000).css('position','relative');
//$('#dropDown-select').multiselect('rebuild');
console.debug("dropDown:",this.dropDown);
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

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

View File

@ -1,168 +0,0 @@
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

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

View File

@ -1,52 +0,0 @@
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

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

View File

@ -1,38 +0,0 @@
"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;
}

View File

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

View File

@ -1,8 +0,0 @@
"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

@ -1,11 +0,0 @@
"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,11 +28,21 @@
import { dataViewObjectsParser } from "powerbi-visuals-utils-dataviewutils";
import DataViewObjectsParser = dataViewObjectsParser.DataViewObjectsParser;
import {SlicerHeaderSetting} from "./slicerHeaderSetting";
import {ItemsSetting} from "./itemsSetting";
export class VisualSettings extends DataViewObjectsParser {
public items: ItemsSetting = new ItemsSetting();
public slicerHeader:SlicerHeaderSetting=new SlicerHeaderSetting();
public dataPoint: dataPointSettings = new dataPointSettings();
}
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,7 +36,6 @@ import VisualObjectInstance = powerbi.VisualObjectInstance;
import DataView = powerbi.DataView;
import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject;
import IVisualEventService = powerbi.extensibility.IVisualEventService;
import SelectionManager=powerbi.extensibility.ISelectionManager;
import * as visualInterfaces from "./visualInterfaces";
import IVisualHost = powerbi.extensibility.visual.IVisualHost;
import { VisualSettings } from "../settings/visualSettings";
@ -44,10 +43,8 @@ import {FilterManager,ManagerFactory,SelectorManager,LayoutManager} from "../man
import * as d3 from "d3";
import { getObject } from "powerbi-visuals-utils-dataviewutils/lib/dataViewObjects";
import { getMeasureIndexOfRole } from "powerbi-visuals-utils-dataviewutils/lib/dataRoleHelper";
import { selector, json } from "d3";
import { debuglog } from "util";
import { EventEmitter } from "events";
import * as models from 'powerbi-models';
import { selector } from "d3";
export class Visual implements IVisual {
/*
SelectorManager: a custom manager, used to manage dropDownSelector, listSelector, calendarSelector, and so on
@ -55,63 +52,41 @@ export class Visual implements IVisual {
*/
private events: IVisualEventService;
private settings: VisualSettings;
private selectionManager:SelectionManager;
private layoutManager: visualInterfaces.ILayoutManager;
private selectorManager: visualInterfaces.ISelectorManager&EventEmitter;
private selectorManager: visualInterfaces.ISelectorManager;
private view:SVGElement;
private filterManager:visualInterfaces.IFilterManager;
private readonly id:number;
private isFirstUpdate:boolean=true;
//private selectionManager:powerbi.extensibility.ISelectionManager;
private selectionManager:powerbi.extensibility.ISelectionManager;
constructor(options: VisualConstructorOptions) {
console.debug('Visual constructor start');
console.debug('options:',options);
console.debug('Visual constructor', options);
this.events = options.host.eventService;
if (document) {
//this.selectionManager =options.host.createSelectionManager();
this.selectionManager =options.host.createSelectionManager();
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', () => {
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();
});
}
// d3.select(options.element).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();
// });
let headerContainer=container.append("div").classed("header-container",true);
let selectorContainer=container.append("div").classed("selector-container",true);
//First, create the filterManager
this.filterManager=ManagerFactory.CreateFilterManager(FilterManager,options.host);
//Then, layoutManager and selectorManager, both use the filterManager
//Lagyoutmanager manage the whole div and the slicer-header
//Then, layoutManager and selectorManager
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();
this.selectorManager=ManagerFactory.CreateSelectorManager(SelectorManager,selectorContainer,this.filterManager);
}
console.debug('end constructor');
}
public update(options: VisualUpdateOptions) {
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.filterManager.inAdvancedEditMode=options.editMode==powerbi.EditMode.Advanced
this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]);
let dataView=options.dataViews[0];
let field=dataView.categorical.categories[0];
@ -135,60 +110,16 @@ export class Visual implements IVisual {
let height:number=options.viewport.height;
console.debug("start layoutManager updateView");
this.layoutManager.update(dataView,width,height);
this.layoutManager.update(dataView,width-10,height-10);
console.debug("start selectorManager updateView");
this.selectorManager.updateData(field,defaultSelect,defaultStart,defaultEnd);
console.debug("start filterManager updateView");
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);
}
private static parseSettings(dataView: DataView): VisualSettings {
return <VisualSettings>VisualSettings.parse(dataView);
}

View File

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

View File

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