Skip to content

Commit 96b9937

Browse files
authored
Allows to select any date range even if Force Selection options are selected (#61)
* 30381 Timeline. Force selection * 30396 Timeline should allow selection on cursor drag event * 30400 Timeline blinks if it auto turns off Force Selection * 30406 Update package-lock.json to align its version to package.json version
1 parent e9cf3e6 commit 96b9937

File tree

6 files changed

+92
-81
lines changed

6 files changed

+92
-81
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 2.0.2
2+
* Allows to select any date range even if Force Selection options are selected
3+
14
## 2.0.1
25
* Fixes race condition that happened if we change granularity and select any date range. For users this issue looked like blinking issue
36

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "powerbi-visuals-timeline",
3-
"version": "2.0.1",
3+
"version": "2.0.2",
44
"description": "Timeline slicer is a graphical date range selector used as a filtering component in the report canvas",
55
"repository": {
66
"type": "git",

pbiviz.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"visual": {
33
"name": "Timeline",
4-
"displayName": "Timeline 2.0.1",
4+
"displayName": "Timeline 2.0.2",
55
"guid": "Timeline1447991079100",
66
"visualClassName": "Timeline",
7-
"version": "2.0.1",
7+
"version": "2.0.2",
88
"description": "Timeline slicer is a graphical date range selector used as a filtering component in the report canvas",
99
"supportUrl": "https://community.powerbi.com",
1010
"gitHubUrl": "https://github.com/Microsoft/powerbi-visuals-timeline"

src/visual.ts

Lines changed: 70 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -560,8 +560,6 @@ export class Timeline implements powerbi.extensibility.visual.IVisual {
560560
private cursorGroupSelection: D3Selection<any, any, any, any>;
561561
private selectorSelection: D3Selection<any, any, any, any>;
562562

563-
private selectionManager: powerbi.extensibility.ISelectionManager;
564-
565563
private options: powerbi.extensibility.visual.VisualUpdateOptions;
566564
private dataView: powerbi.DataView;
567565

@@ -581,30 +579,16 @@ export class Timeline implements powerbi.extensibility.visual.IVisual {
581579

582580
private selectedGranulaPos: number = null;
583581

582+
private isForceSelectionReset: boolean = false;
583+
584584
private cursorDragBehavior = d3Drag<any, ICursorDataPoint>()
585585
.subject((cursorDataPoint: ICursorDataPoint) => {
586586
cursorDataPoint.x = cursorDataPoint.selectionIndex * this.timelineProperties.cellWidth;
587587

588588
return cursorDataPoint;
589589
})
590-
.on("drag", (cursorDataPoint: ICursorDataPoint) => {
591-
if (this.settings.forceSelection.currentPeriod
592-
|| this.settings.forceSelection.latestAvailableDate
593-
) {
594-
return;
595-
}
596-
597-
this.onCursorDrag(cursorDataPoint);
598-
})
599-
.on("end", () => {
600-
if (this.settings.forceSelection.currentPeriod
601-
|| this.settings.forceSelection.latestAvailableDate
602-
) {
603-
return;
604-
}
605-
606-
this.onCursorDragEnd();
607-
});
590+
.on("drag", this.onCursorDrag.bind(this))
591+
.on("end", this.onCursorDragEnd.bind(this));
608592

609593
constructor(options: powerbi.extensibility.visual.VisualConstructorOptions) {
610594
const element: HTMLElement = options.element;
@@ -614,7 +598,6 @@ export class Timeline implements powerbi.extensibility.visual.IVisual {
614598
this.initialized = false;
615599
this.locale = this.host.locale;
616600

617-
this.selectionManager = this.host.createSelectionManager();
618601
this.localizationManager = this.host.createLocalizationManager();
619602

620603
this.timelineProperties = {
@@ -634,13 +617,7 @@ export class Timeline implements powerbi.extensibility.visual.IVisual {
634617
this.rootSelection = d3Select(element)
635618
.append("div")
636619
.classed("timeline-component", true)
637-
.on("click", () => {
638-
if (!this.settings.forceSelection.currentPeriod
639-
&& !this.settings.forceSelection.latestAvailableDate
640-
) {
641-
this.clear();
642-
}
643-
});
620+
.on("click", () => this.clearUserSelection());
644621

645622
this.headerSelection = this.rootSelection
646623
.append("svg")
@@ -658,14 +635,13 @@ export class Timeline implements powerbi.extensibility.visual.IVisual {
658635
this.addElements();
659636
}
660637

661-
public clear(): void {
662-
if (this.initialized) {
663-
this.selectionManager.clear();
664-
665-
if (this.timelineData) {
666-
this.clearSelection(this.timelineData.filterColumnTarget);
667-
}
638+
public clearUserSelection(): void {
639+
if (!this.initialized || !this.timelineData) {
640+
return;
668641
}
642+
643+
this.clearSelection(this.timelineData.filterColumnTarget);
644+
this.toggleForceSelectionOptions();
669645
}
670646

671647
public doesPeriodSlicerRectPositionNeedToUpdate(granularity: GranularityType): boolean {
@@ -757,33 +733,44 @@ export class Timeline implements powerbi.extensibility.visual.IVisual {
757733
const datePeriod: ITimelineDatePeriodBase = this.datePeriod;
758734

759735
const granularity = this.settings.granularity.granularity;
760-
const currentForceSelection: boolean = this.settings.forceSelection.currentPeriod;
761-
const latestAvailableDate: boolean = this.settings.forceSelection.latestAvailableDate;
762-
const isUserSelection: boolean = !currentForceSelection && !latestAvailableDate;
736+
737+
const isCurrentPeriodSelected: boolean = !this.isForceSelectionReset && this.settings.forceSelection.currentPeriod;
738+
const isLatestAvailableDateSelected: boolean = !this.isForceSelectionReset && this.settings.forceSelection.latestAvailableDate;
739+
const isForceSelected: boolean = !this.isForceSelectionReset && (isCurrentPeriodSelected || isLatestAvailableDateSelected);
740+
741+
this.isForceSelectionReset = false; // Reset it to default state to allow re-enabling Force Selection
742+
763743
const target: IFilterColumnTarget = this.timelineData.filterColumnTarget;
764744

765745
let currentForceSelectionResult = { startDate: null, endDate: null };
766746

767-
if (currentForceSelection) {
747+
if (isCurrentPeriodSelected) {
768748
currentForceSelectionResult = ({
769749
endDate: filterDatePeriod.endDate,
770750
startDate: filterDatePeriod.startDate,
771751
} = Timeline.selectCurrentPeriod(datePeriod, granularity, this.calendar));
772752
}
773-
if (latestAvailableDate && (!currentForceSelection ||
774-
(currentForceSelection && !currentForceSelectionResult.startDate && !currentForceSelectionResult.endDate))) {
753+
if (isLatestAvailableDateSelected
754+
&& (
755+
!isCurrentPeriodSelected
756+
|| (isCurrentPeriodSelected
757+
&& !currentForceSelectionResult.startDate
758+
&& !currentForceSelectionResult.endDate
759+
)
760+
)
761+
) {
775762
filterDatePeriod.endDate = adaptedDataEndDate;
776763
({
777764
endDate: filterDatePeriod.endDate,
778765
startDate: filterDatePeriod.startDate,
779766
} = Timeline.selectPeriod(datePeriod, granularity, this.calendar, this.datePeriod.endDate));
780767
}
781768

782-
const filterWasChanged: boolean =
769+
const wasFilterChanged: boolean =
783770
String(this.prevFilteredStartDate) !== String(filterDatePeriod.startDate) ||
784771
String(this.prevFilteredEndDate) !== String(filterDatePeriod.endDate);
785772

786-
if ((!isUserSelection && filterWasChanged)) {
773+
if (isForceSelected && wasFilterChanged) {
787774
this.applyDatePeriod(filterDatePeriod.startDate, filterDatePeriod.endDate, target);
788775
}
789776

@@ -876,25 +863,16 @@ export class Timeline implements powerbi.extensibility.visual.IVisual {
876863
.selectAll(Timeline.TimelineSelectors.CellRect.selectorName)
877864
.data(dataPoints);
878865

879-
const clickHandler = (dataPoint: ITimelineDataPoint, index: number) => {
880-
// If something from Force Selection settings group is enabled, any user filters has no sense
881-
if (this.settings.forceSelection.currentPeriod || this.settings.forceSelection.latestAvailableDate) {
882-
return;
883-
}
884-
885-
const event: MouseEvent = require("d3").event as MouseEvent;
886-
887-
event.stopPropagation();
888-
889-
this.onCellClickHandler(dataPoint, index, event.altKey || event.shiftKey);
890-
};
866+
cellsSelection
867+
.exit()
868+
.remove();
891869

892870
cellsSelection
893871
.enter()
894872
.append("rect")
895873
.classed(Timeline.TimelineSelectors.CellRect.className, true)
896-
.on("click", clickHandler)
897-
.on("touchstart", clickHandler)
874+
.on("click", this.handleClick.bind(this))
875+
.on("touchstart", this.handleClick.bind(this))
898876
.merge(cellsSelection)
899877
.attr("x", (dataPoint: ITimelineDataPoint) => {
900878
const position: number = totalX;
@@ -910,10 +888,6 @@ export class Timeline implements powerbi.extensibility.visual.IVisual {
910888
});
911889

912890
this.fillCells(this.settings);
913-
914-
cellsSelection
915-
.exit()
916-
.remove();
917891
}
918892

919893
public renderCursors(
@@ -1175,15 +1149,18 @@ export class Timeline implements powerbi.extensibility.visual.IVisual {
11751149

11761150
public onCursorDragEnd(): void {
11771151
this.setSelection(this.timelineData);
1152+
this.toggleForceSelectionOptions();
11781153
}
11791154

1180-
private addElements(): void {
1181-
this.rootSelection.on("click", () => {
1182-
if (!this.settings.forceSelection.currentPeriod && !this.settings.forceSelection.latestAvailableDate) {
1183-
this.clear();
1184-
}
1185-
});
1155+
private handleClick(dataPoint: ITimelineDataPoint, index: number): void {
1156+
const event: MouseEvent = require("d3").event as MouseEvent;
1157+
1158+
event.stopPropagation();
11861159

1160+
this.onCellClickHandler(dataPoint, index, event.altKey || event.shiftKey);
1161+
}
1162+
1163+
private addElements(): void {
11871164
this.rangeTextSelection = this.headerSelection
11881165
.append("g")
11891166
.classed(Timeline.TimelineSelectors.RangeTextArea.className, true)
@@ -1537,7 +1514,9 @@ export class Timeline implements powerbi.extensibility.visual.IVisual {
15371514
);
15381515

15391516
this.renderTimeRangeText(timelineData, this.settings.rangeHeader);
1517+
15401518
this.setSelection(timelineData);
1519+
this.toggleForceSelectionOptions();
15411520
}
15421521

15431522
private scrollAutoFocusFunc(selectedGranulaPos: number): void {
@@ -1547,4 +1526,28 @@ export class Timeline implements powerbi.extensibility.visual.IVisual {
15471526

15481527
this.mainSvgWrapperSelection.node().scrollLeft = selectedGranulaPos - this.horizontalAutoScrollingPositionOffset;
15491528
}
1529+
1530+
private toggleForceSelectionOptions(): void {
1531+
const isForceSelectionTurnedOn: boolean = this.settings.forceSelection.currentPeriod
1532+
|| this.settings.forceSelection.latestAvailableDate;
1533+
1534+
if (isForceSelectionTurnedOn) {
1535+
this.turnOffForceSelectionOptions();
1536+
}
1537+
}
1538+
1539+
private turnOffForceSelectionOptions(): void {
1540+
this.host.persistProperties({
1541+
merge: [{
1542+
objectName: "forceSelection",
1543+
properties: {
1544+
currentPeriod: false,
1545+
latestAvailableDate: false,
1546+
},
1547+
selector: null,
1548+
}],
1549+
});
1550+
1551+
this.isForceSelectionReset = true;
1552+
}
15501553
}

test/visual.test.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -223,14 +223,14 @@ describe("Timeline", () => {
223223

224224
visualBuilder.update(dataView);
225225

226-
spyOn(visualBuilder.visualObject, "clear");
226+
spyOn(visualBuilder.visualObject, "clearUserSelection");
227227
});
228228

229229
it("click - event", (done) => {
230230
d3Click(visualBuilder.rootElement, 0, 0);
231231

232232
renderTimeout(() => {
233-
expect(visualBuilder.visualObject.clear).toHaveBeenCalled();
233+
expect(visualBuilder.visualObject.clearUserSelection).toHaveBeenCalled();
234234

235235
done();
236236
});
@@ -847,19 +847,21 @@ describe("Timeline", () => {
847847
colorSel);
848848
});
849849

850-
it("current enabled -- impossible to make user selection", () => {
850+
it("user selection is allowed if forceSelection.currentPeriod is enabled", () => {
851851
const currentDate: Date = new Date();
852852
const startDateRange: Date = new Date(currentDate.getFullYear() - 1, 0, 1);
853853
const endDateRange: Date = new Date(currentDate.getFullYear() + 1, 11, 31);
854+
854855
const color: string = "#ABCDEF";
855-
const colorSel: string = "#AAAAAA";
856+
const selectedColor: string = "#AAAAAA";
856857

857858
defaultDataViewBuilder.setDateRange(startDateRange, endDateRange);
858859

859860
dataView = defaultDataViewBuilder.getDataView();
861+
860862
dataView.metadata.objects = {
861863
cells: {
862-
fillSelected: getSolidColorStructuralObject(colorSel),
864+
fillSelected: getSolidColorStructuralObject(selectedColor),
863865
fillUnselected: getSolidColorStructuralObject(color),
864866
},
865867
forceSelection: {
@@ -877,22 +879,24 @@ describe("Timeline", () => {
877879

878880
assertColorsMatch(
879881
lastCell.css("fill"),
880-
color);
882+
selectedColor,
883+
);
881884
});
882885

883-
it("latest enabled -- impossible to make user selection", () => {
886+
it("user selection is allowed if forceSelection.latestAvailableDate is enabled", () => {
884887
const currentDate: Date = new Date();
885888
const startDateRange: Date = new Date(currentDate.getFullYear() - 1, 0, 1);
886889
const endDateRange: Date = new Date(currentDate.getFullYear() + 1, 11, 31);
890+
887891
const color: string = "#ABCDEF";
888-
const colorSel: string = "#AAAAAA";
892+
const selectedColor: string = "#AAAAAA";
889893

890894
defaultDataViewBuilder.setDateRange(startDateRange, endDateRange);
891895

892896
dataView = defaultDataViewBuilder.getDataView();
893897
dataView.metadata.objects = {
894898
cells: {
895-
fillSelected: getSolidColorStructuralObject(colorSel),
899+
fillSelected: getSolidColorStructuralObject(selectedColor),
896900
fillUnselected: getSolidColorStructuralObject(color),
897901
},
898902
forceSelection: {
@@ -910,7 +914,8 @@ describe("Timeline", () => {
910914

911915
assertColorsMatch(
912916
firstCell.css("fill"),
913-
color);
917+
selectedColor,
918+
);
914919
});
915920

916921
it(`current period for 'week' granularity`, () => {

0 commit comments

Comments
 (0)