зеркало из
1
0
Форкнуть 0
* Added bookmarks support


Added bookmarks support

* Code review

* Update documentation

* Fix docs after review
This commit is contained in:
Ilfat Galiev 2018-04-12 11:48:34 +03:00 коммит произвёл Nikita Grachev
Родитель 7568b1bd34
Коммит 73ba661a7d
10 изменённых файлов: 8111 добавлений и 116 удалений

Просмотреть файл

@ -37,3 +37,5 @@ pbiviz start
3. Advanced selection with the Advanced Filter API
- [Adding the Advanced Filter API to the project](doc/AddingAdvancedFilterAPI.md)
- [Using the Advanced Filter API](doc/UsingAdvancedFilterAPI.md)
4. Bookmarks support
- [Adding bookmarks support to the project](doc/AddingBookmarksSuppoprt.md)

Просмотреть файл

@ -0,0 +1,73 @@
# Adding bookmarks support to the project
The main documentation about bookmarks can be found [here](https://github.com/Microsoft/PowerBI-visuals/blob/master/Tutorial/BookmarksSupport.md).
To add bookmarks support for the visual, you should update to version 3.0.0 or higher of `powerbi-visuals-utils-interactivityutils`.
In the update `updateOnRangeSelectonChange` method the visual creates an AdvancedFilter and applies the filter with one or two conditions and calls `applyAdvancedFilter`, where `applyAdvancedFilter` [calls](https://github.com/Microsoft/powerbi-visuals-sampleslicer/blob/master/src/sampleSlicer.ts#L795) `this.visualHost.applyJsonFilter` method.
In each update the visual [inspects and restores](https://github.com/Microsoft/powerbi-visuals-sampleslicer/pull/6/files#diff-5929da3be6a696fb9df5e3571baceb52R356) the persisted filter by using [`FilterManager.restoreFilter`](https://github.com/Microsoft/PowerBI-visuals/blob/master/Tutorial/BookmarksSupport.md#visuals-with-filter).
```typescript
private restoreFilter(data: SampleSlicerData) {
// restore advanced filter from visual properties
let restoredFilter: IAdvancedFilter =
FilterManager.restoreFilter(data && data.slicerSettings.general.filter) as IAdvancedFilter;
// if filter was persisted, the visual retrieves the conditions of the advanced filter
if (restoredFilter) {
restoredFilter.target = this.getCallbacks().getAdvancedFilterColumnTarget();
// reset to default
// modify the values to match the filter values
// in some cases we can receive values with only one condition
if (restoredFilter.conditions.length === 1) {
let value: {
max?: any,
min?: any
} = {};
// get min and max values in the dataset
let convertedValues = data.slicerDataPoints.map( (dataPoint: SampleSlicerDataPoint) => +dataPoint.category );
value.min = d3.min(convertedValues);
value.max = d3.max(convertedValues);
// if some conditions is missing, the visual adds the condition with matching value
let operator = restoredFilter.conditions[0].operator;
if (operator === "LessThanOrEqual" || operator === "LessThan") {
restoredFilter.conditions.push({
operator: "GreaterThan",
value: value.min
});
}
if (operator === "GreaterThanOrEqual" || operator === "GreaterThan") {
restoredFilter.conditions.push({
operator: "LessThan",
value: value.max
});
}
}
// create ValueRange object to apply current filter state to the slicer visual
let rangeValue: ValueRange<number> = <ValueRange<number>>{};
restoredFilter.conditions.forEach( (condition: IAdvancedFilterCondition) => {
let value = condition.value;
let operator = condition.operator;
if (operator === "LessThanOrEqual" || operator === "LessThan") {
rangeValue.max = <number>value;
}
if (operator === "GreaterThanOrEqual" || operator === "GreaterThan") {
rangeValue.min = <number>value;
}
});
// change visual state of slicer
this.behavior.scalableRange.setValue(rangeValue);
// change visual state of text boxes
this.onRangeInputTextboxChange(rangeValue.min.toString(), RangeValueType.Start);
this.onRangeInputTextboxChange(rangeValue.max.toString(), RangeValueType.End);
}
}
```
Resuming: in this method the visual restores the `restoredFilter` object, parses conditions for restoring saved values of the slicer and applies values to slicer and text boxes.
You **should not** persist the filter in the visual properties. Because Power BI saves filter for the visual. And `persistSelectionState`, `restorePersistedRangeSelectionState` and other methods [were removed](https://github.com/Microsoft/powerbi-visuals-sampleslicer/pull/6/files#diff-5929da3be6a696fb9df5e3571baceb52L809).

Просмотреть файл

@ -8,7 +8,7 @@ For <b>discrete</b> cross-visual data-point selection the Sample Slicer visual r
ISelectionHandler holds the state of all discrete (possibly multiple) data-point selections. The handler is updated on each data-point selection event (each mouse click) and does NOT automatically propagate the event to the hosting application. The selection state is only propagated to the host when the method ISelectionHandler.applySelectionFilter() is invoked.
Below is the code executed on each slicer mouse click. The handler is updated with the select/unselect data point event and the complete discrete selection state is flushed to the hosting application. Additionally, the selection state is persisted to the visual's properties so the next time the visual is loaded the selection can be restored.
Below is the code executed on each slicer mouse click. The handler is updated with the select/unselect data point event and the complete discrete selection state is flushed to the hosting application. Additionally, the selection state is persisted to the visual's properties as applyed filter, so the next time the visual is loaded the selection can be restored.
```
/* update selection state */
@ -16,9 +16,6 @@ Below is the code executed on each slicer mouse click. The handler is updated wi
/* send selection state to the host*/
selectionHandler.applySelectionFilter();
/*persiste selection state to properties */
this.persistSelectionState();
```
An instance implementing ISelectionHandler interface is created by InteractivityUtils and supplied to SelectionBehavior class as an argument of SelectionBehavior::bindEvents() method call.

7952
package-lock.json сгенерированный Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "powerbi-visuals-sampleslicer",
"version": "1.1.0",
"version": "1.2.0",
"author": {
"name": "Microsoft"
},
@ -10,6 +10,7 @@
"url": "git+https://github.com/Microsoft/powerbi-visuals-sampleslicer"
},
"scripts": {
"postinstall": "pbiviz update 1.11.0",
"pbiviz": "pbiviz",
"start": "pbiviz start",
"package": "pbiviz package",
@ -23,7 +24,7 @@
"powerbi-visuals-utils-colorutils": "^1.0.0",
"powerbi-visuals-utils-dataviewutils": "^1.2.0",
"powerbi-visuals-utils-formattingutils": "^2.1.0",
"powerbi-visuals-utils-interactivityutils": "^1.0.0",
"powerbi-visuals-utils-interactivityutils": "^3.0.0",
"powerbi-visuals-utils-typeutils": "^1.1.0"
},
"devDependencies": {
@ -35,7 +36,7 @@
"@types/nouislider": "9.0.0",
"jasmine": "2.6.0",
"jasmine-jquery": "2.1.1",
"powerbi-visuals-tools": "1.7.0",
"powerbi-visuals-tools": "1.11.2",
"tslint": "^5.4.3",
"tslint-microsoft-contrib": "^5.0.0",
"typescript": "^2.3.4"

Просмотреть файл

@ -4,12 +4,12 @@
"displayName": "SampleSlicer",
"guid": "SampleSlicer1448559807355",
"visualClassName": "SampleSlicer",
"version": "1.1.0",
"version": "1.2.0",
"description": "Use this slicer to select discrete numeric categories or range of numeric category values.",
"supportUrl": "http://community.powerbi.com",
"gitHubUrl": "https://github.com/Microsoft/powerbi-visuals-sampleslicer"
},
"apiVersion": "1.7.0",
"apiVersion": "1.11.0",
"author": {
"name": "Microsoft"
},

Просмотреть файл

@ -51,6 +51,9 @@ module powerbi.extensibility.visual {
import TextProperties = powerbi.extensibility.utils.formatting.TextProperties;
import textMeasurementService = powerbi.extensibility.utils.formatting.textMeasurementService;
import FilterManager = powerbi.extensibility.utils.filter.FilterManager;
import AppliedFilter = powerbi.extensibility.utils.filter.AppliedFilter;
export const enum RangeValueType {
Start,
End
@ -71,7 +74,6 @@ module powerbi.extensibility.visual {
export interface SampleSlicerCallbacks {
getPersistedSelectionState?: () => powerbi.extensibility.ISelectionId[];
persistSelectionState?: (selectionIds: string[]) => void;
restorePersistedRangeSelectionState?: () => void;
applyAdvancedFilter?: (filter: IAdvancedFilter) => void;
getAdvancedFilterColumnTarget?: () => IFilterColumnTarget;
@ -166,6 +168,7 @@ module powerbi.extensibility.visual {
slicerSettings.general.selection = DataViewObjectsModule.getValue(dataView.metadata.objects, persistedSettingsDataViewObjectPropertyIdentifiers.general.selection, defaultSettings.general.selection);
slicerSettings.general.rangeSelectionStart = DataViewObjectsModule.getValue(dataView.metadata.objects, persistedSettingsDataViewObjectPropertyIdentifiers.general.rangeSelectionStart, defaultSettings.general.selection);
slicerSettings.general.rangeSelectionEnd = DataViewObjectsModule.getValue(dataView.metadata.objects, persistedSettingsDataViewObjectPropertyIdentifiers.general.rangeSelectionEnd, defaultSettings.general.selection);
slicerSettings.general.filter = DataViewObjectsModule.getValue(dataView.metadata.objects, persistedSettingsDataViewObjectPropertyIdentifiers.general.filter, defaultSettings.general.filter);
}
if (searchText) {
@ -194,20 +197,6 @@ module powerbi.extensibility.visual {
this.interactivityService = createInteractivityService(options.host);
this.settings = defaultSettings;
Object.defineProperty(window, "pageXOffset", {
get: function () {
return window.window.pageXOffset;
}
});
Object.defineProperty(window, "pageYOffset", {
get: function () {
return window.window.pageYOffset;
}
});
}
public update(options: VisualUpdateOptions) {
@ -293,6 +282,60 @@ module powerbi.extensibility.visual {
return [];
}
private restoreFilter(data: SampleSlicerData) {
let restoredFilter: IAdvancedFilter =
FilterManager.restoreFilter(data && data.slicerSettings.general.filter) as IAdvancedFilter;
if (restoredFilter) {
restoredFilter.target = this.getCallbacks().getAdvancedFilterColumnTarget();
// reset to default
// this.onRangeInputTextboxChange("", RangeValueType.Start, true);
// this.onRangeInputTextboxChange("", RangeValueType.End, true);
// change value to correspond the filter values
// in some case we can get value with one condition only
if (restoredFilter.conditions.length === 1) {
let value: {
max?: any,
min?: any
} = {};
let convertedValues = data.slicerDataPoints.map( (dataPoint: SampleSlicerDataPoint) => +dataPoint.category );
value.min = d3.min(convertedValues);
value.max = d3.max(convertedValues);
let operator = restoredFilter.conditions[0].operator;
if (operator === "LessThanOrEqual" || operator === "LessThan") {
restoredFilter.conditions.push({
operator: "GreaterThan",
value: value.min
});
}
if (operator === "GreaterThanOrEqual" || operator === "GreaterThan") {
restoredFilter.conditions.push({
operator: "LessThan",
value: value.max
});
}
}
let rangeValue: ValueRange<number> = <ValueRange<number>>{};
restoredFilter.conditions.forEach( (condition: IAdvancedFilterCondition) => {
let value = condition.value;
let operator = condition.operator;
if (operator === "LessThanOrEqual" || operator === "LessThan") {
rangeValue.max = <number>value;
}
if (operator === "GreaterThanOrEqual" || operator === "GreaterThan") {
rangeValue.min = <number>value;
}
});
this.behavior.scalableRange.setValue(rangeValue);
this.onRangeInputTextboxChange(rangeValue.min.toString(), RangeValueType.Start);
this.onRangeInputTextboxChange(rangeValue.max.toString(), RangeValueType.End);
}
}
private updateInternal(resetScrollbarPosition: boolean) {
// convert data to internal representation
let data = SampleSlicer.converter(
@ -307,6 +350,8 @@ module powerbi.extensibility.visual {
return;
}
this.restoreFilter(data);
if (this.slicerData) {
if (this.isSelectionSaved) {
this.isSelectionLoaded = true;
@ -343,7 +388,6 @@ module powerbi.extensibility.visual {
this.updateSliderInputTextboxes();
}
public createInputElement(control: JQuery): JQuery {
let $element: JQuery = $('<input type="text"/>')
.attr("type", "text")
@ -399,7 +443,6 @@ module powerbi.extensibility.visual {
this.$start = this.createInputElement($startControl);
this.$end = this.createInputElement($endControl);
let slicerContainer: Selection<any> = d3.select(this.$root.get(0))
.append('div')
.classed(SampleSlicer.ContainerSelector.className, true)
@ -541,7 +584,7 @@ module powerbi.extensibility.visual {
return value != null ? valueFormatter.format(value, "#") : '';
}
private onRangeInputTextboxChange(inputString: string, rangeValueType: RangeValueType): void {
private onRangeInputTextboxChange(inputString: string, rangeValueType: RangeValueType, supressFilter: boolean = false): void {
// parse input
let inputValue: number;
if (!inputString) {
@ -567,10 +610,13 @@ module powerbi.extensibility.visual {
}
range.max = inputValue;
}
this.behavior.scalableRange.setValue(range);
// trigger range change processing
this.behavior.updateOnRangeSelectonChange();
if (!supressFilter) {
this.behavior.scalableRange.setValue(range);
// trigger range change processing
this.behavior.updateOnRangeSelectonChange();
}
}
private enterSelection(rowSelection: Selection<any>): void {
@ -783,8 +829,6 @@ module powerbi.extensibility.visual {
: textMeasurementService.estimateSvgTextHeight(SampleSlicer.getSampleTextProperties(textSettings.textSize));
}
/**
* Callbacks consumed by the SelectionBehavior class
* */
@ -792,7 +836,7 @@ module powerbi.extensibility.visual {
let callbacks: SampleSlicerCallbacks = {};
callbacks.applyAdvancedFilter = (filter: IAdvancedFilter): void => {
this.visualHost.applyJsonFilter(filter, "general", "filter");
this.visualHost.applyJsonFilter(filter, "general", "filter", FilterAction.merge );
};
callbacks.getAdvancedFilterColumnTarget = (): IFilterColumnTarget => {
@ -806,35 +850,6 @@ module powerbi.extensibility.visual {
return target;
};
callbacks.persistSelectionState = (selectionIds: string[]): void => {
this.visualHost.persistProperties(<VisualObjectInstancesToPersist>{
merge: [{
objectName: "general",
selector: null,
properties: {
selection: selectionIds && JSON.stringify(selectionIds) || "",
rangeSelectionStart: JSON.stringify(this.formatValue(this.behavior.scalableRange.getValue().min)),
rangeSelectionEnd: JSON.stringify(this.formatValue(this.behavior.scalableRange.getValue().max))
}
}]
});
this.isSelectionSaved = true;
};
callbacks.restorePersistedRangeSelectionState = (): void => {
let rangeSelectionStart: string = JSON.parse(this.slicerData.slicerSettings.general.rangeSelectionStart);
let rangeSelectionEnd: string = JSON.parse(this.slicerData.slicerSettings.general.rangeSelectionEnd);
if (rangeSelectionStart) {
this.$start.val(rangeSelectionStart);
this.onRangeInputTextboxChange(rangeSelectionStart, RangeValueType.Start);
}
if (rangeSelectionEnd) {
this.$end.val(rangeSelectionEnd);
this.onRangeInputTextboxChange(rangeSelectionEnd, RangeValueType.End);
}
};
callbacks.getPersistedSelectionState = (): powerbi.extensibility.ISelectionId[] => {
try {
return JSON.parse(this.slicerData.slicerSettings.general.selection) || [];

Просмотреть файл

@ -74,10 +74,6 @@ module powerbi.extensibility.visual {
this.selectionHandler = selectionHandler;
if (!this.options.isSelectionLoaded) {
this.restoreSelectionStateFromPersistedProperties();
}
slicers.on("click", (dataPoint: SampleSlicerDataPoint, index: number) => {
(d3.event as MouseEvent).preventDefault();
@ -88,9 +84,6 @@ module powerbi.extensibility.visual {
/* send selection state to the host*/
selectionHandler.applySelectionFilter();
/*persiste selection state to properties */
this.persistSelectionState();
});
}
@ -110,57 +103,16 @@ module powerbi.extensibility.visual {
}
public clearAllDiscreteSelections() {
/* update state to clear all selections */
this.selectionHandler.handleClearSelection();
/*persiste selection state to properties */
this.persistSelectionState();
if (this.selectionHandler) {
this.selectionHandler.handleClearSelection();
}
}
public clearRangeSelection(): void {
this.scalableRange = new ScalableRange();
}
public restoreSelectionStateFromPersistedProperties(): void {
const savedSelectionIds: ISelectionId[] = this.callbacks.getPersistedSelectionState();
if (savedSelectionIds.length) {
/* clear selection state */
this.selectionHandler.handleClearSelection();
/* restore selection state from persisted properties */
this.dataPoints
.filter(dataPoint => {
return savedSelectionIds.some((selectionId: ISelectionId) => {
return (dataPoint.identity as any).getKey() === selectionId;
});
})
.forEach((dataPoint: SampleSlicerDataPoint) => {
this.selectionHandler.handleSelection(dataPoint, true);
});
/* send selection state to the host */
this.selectionHandler.applySelectionFilter();
} else {
this.callbacks.restorePersistedRangeSelectionState();
}
}
public persistSelectionState(): void {
let selectedIds: ISelectionId[],
selectionIdKeys: string[];
selectedIds = <ISelectionId[]>(<any>this.selectionHandler).selectedIds;
selectionIdKeys = selectedIds.map((selectionId: ISelectionId) => {
return (selectionId as any).getKey();
});
this.callbacks.persistSelectionState(selectionIdKeys);
}
public styleSlicerInputs(slicers: Selection<any>, hasSelection: boolean) {
let settings = this.slicerSettings;
slicers.each(function (dataPoint: SampleSlicerDataPoint) {

Просмотреть файл

@ -34,6 +34,7 @@ module powerbi.extensibility.visual {
rangeSelectionEnd: string;
multiselect: boolean;
selection: string;
filter: any;
};
headerText: {
marginLeft: number;
@ -63,7 +64,8 @@ module powerbi.extensibility.visual {
rangeSelectionStart: null,
rangeSelectionEnd: null,
multiselect: true,
selection: null
selection: null,
filter: null
},
headerText: {
marginLeft: 8,
@ -91,7 +93,8 @@ module powerbi.extensibility.visual {
multiselect: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'multiselect' },
selection: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'selection' },
rangeSelectionStart: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'rangeSelectionStart' },
rangeSelectionEnd: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'rangeSelectionEnd' }
rangeSelectionEnd: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'rangeSelectionEnd' },
filter: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'filter' }
}
};

Просмотреть файл

@ -10,7 +10,7 @@
"declaration": true
},
"files": [
".api/v1.7.0/PowerBI-visuals.d.ts",
".api/v1.11.0/PowerBI-visuals.d.ts",
"node_modules/powerbi-visuals-utils-dataviewutils/lib/index.d.ts",
"node_modules/powerbi-visuals-utils-typeutils/lib/index.d.ts",
"node_modules/powerbi-visuals-utils-svgutils/lib/index.d.ts",