Skip to content

Commit 2602026

Browse files
committed
Added TopRight and BottomRight positions for Legend
1 parent 703d759 commit 2602026

4 files changed

Lines changed: 82 additions & 36 deletions

File tree

src/legend/legend.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,58 @@ export function isLeft(orientation: LegendPosition): boolean {
4646
}
4747
}
4848

49+
export function isRight(orientation: LegendPosition): boolean {
50+
switch (orientation) {
51+
case LegendPosition.Right:
52+
case LegendPosition.RightCenter:
53+
return true;
54+
default:
55+
return false;
56+
}
57+
}
58+
4959
export function isTop(orientation: LegendPosition): boolean {
5060
switch (orientation) {
5161
case LegendPosition.Top:
5262
case LegendPosition.TopCenter:
63+
case LegendPosition.TopRight:
64+
return true;
65+
default:
66+
return false;
67+
}
68+
}
69+
70+
export function isBottom(orientation: LegendPosition): boolean {
71+
switch (orientation) {
72+
case LegendPosition.Bottom:
73+
case LegendPosition.BottomCenter:
74+
case LegendPosition.BottomRight:
75+
return true;
76+
default:
77+
return false;
78+
}
79+
}
80+
81+
export function isTopOrBottom(orientation: LegendPosition): boolean {
82+
return isTop(orientation) || isBottom(orientation);
83+
}
84+
85+
export function isCentered(orientation: LegendPosition): boolean {
86+
switch (orientation) {
87+
case LegendPosition.TopCenter:
88+
case LegendPosition.BottomCenter:
89+
case LegendPosition.LeftCenter:
90+
case LegendPosition.RightCenter:
91+
return true;
92+
default:
93+
return false;
94+
}
95+
}
96+
97+
export function isRightAligned(orientation: LegendPosition): boolean {
98+
switch (orientation) {
99+
case LegendPosition.TopRight:
100+
case LegendPosition.BottomRight:
53101
return true;
54102
default:
55103
return false;

src/legend/legendInterfaces.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export enum LegendPosition {
3939
BottomCenter,
4040
RightCenter,
4141
LeftCenter,
42+
TopRight,
43+
BottomRight,
4244
}
4345

4446
export interface ISelectableDataPoint{

src/legend/legendPosition.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,5 @@ export const topCenter: string = "TopCenter";
3232
export const bottomCenter: string = "BottomCenter";
3333
export const leftCenter: string = "LeftCenter";
3434
export const rightCenter: string = "RightCenter";
35+
export const topRight: string = "TopRight";
36+
export const bottomRight: string = "BottomRight";

src/legend/svgLegend.ts

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
} from "powerbi-visuals-utils-svgutils";
4040

4141
import { ILegend, LegendData, LegendDataPoint, LegendPosition } from "./legendInterfaces";
42+
import { isBottom, isRight, isTopOrBottom, isCentered, isRightAligned } from "./legend";
4243

4344
import * as Markers from "./markers";
4445

@@ -168,14 +169,11 @@ export class SVGLegend implements ILegend {
168169
"width", legendViewport.width || (orientation === LegendPosition.None ? 0 : this.parentViewport.width)
169170
);
170171

171-
const isRight = orientation === LegendPosition.Right || orientation === LegendPosition.RightCenter,
172-
isBottom = orientation === LegendPosition.Bottom || orientation === LegendPosition.BottomCenter;
173-
174172
this.svg.style(
175-
"margin-left", isRight ? (this.parentViewport.width - legendViewport.width) + "px" : null
173+
"margin-left", isRight(orientation) ? (this.parentViewport.width - legendViewport.width) + "px" : null
176174
);
177175
this.svg.style(
178-
"margin-top", isBottom ? (this.parentViewport.height - legendViewport.height) + "px" : null,
176+
"margin-top", isBottom(orientation) ? (this.parentViewport.height - legendViewport.height) + "px" : null,
179177
);
180178
}
181179

@@ -185,6 +183,8 @@ export class SVGLegend implements ILegend {
185183
case LegendPosition.Bottom:
186184
case LegendPosition.TopCenter:
187185
case LegendPosition.BottomCenter:
186+
case LegendPosition.TopRight:
187+
case LegendPosition.BottomRight:
188188
const pixelHeight = PixelConverter.fromPointToPixel(this.data && this.data.fontSize
189189
? this.data.fontSize
190190
: SVGLegend.DefaultFontSizeInPt);
@@ -259,7 +259,7 @@ export class SVGLegend implements ILegend {
259259

260260
// Adding back the workaround for Legend Left/Right position for Map
261261
const mapControls = this.element.getElementsByClassName("mapControl");
262-
if (mapControls.length > 0 && !this.isTopOrBottom(this.orientation)) {
262+
if (mapControls.length > 0 && !isTopOrBottom(this.orientation)) {
263263
for (let i = 0; i < mapControls.length; ++i) {
264264
const element = <HTMLElement>mapControls[i];
265265
element.style.display = "inline-block";
@@ -274,18 +274,22 @@ export class SVGLegend implements ILegend {
274274

275275
const group = this.group;
276276

277-
// transform the wrapping group if position is centered
278-
if (this.isCentered(this.orientation)) {
277+
// transform the wrapping group if position is centered or right-aligned
278+
if (isCentered(this.orientation)) {
279279
let centerOffset = 0;
280-
if (this.isTopOrBottom(this.orientation)) {
280+
if (isTopOrBottom(this.orientation)) {
281281
centerOffset = Math.max(0, (this.parentViewport.width - this.visibleLegendWidth) / 2);
282282
group.attr("transform", svgManipulation.translate(centerOffset, 0));
283283
}
284284
else {
285-
centerOffset = Math.max((this.parentViewport.height - this.visibleLegendHeight) / 2);
285+
centerOffset = Math.max(0, (this.parentViewport.height - this.visibleLegendHeight) / 2);
286286
group.attr("transform", svgManipulation.translate(0, centerOffset));
287287
}
288288
}
289+
else if (isRightAligned(this.orientation)) {
290+
const rightOffset = Math.max(0, this.parentViewport.width - this.visibleLegendWidth);
291+
group.attr("transform", svgManipulation.translate(rightOffset, 0));
292+
}
289293
else {
290294
group.attr("transform", null);
291295
}
@@ -444,7 +448,7 @@ export class SVGLegend implements ILegend {
444448
const hasTitle = !!title;
445449

446450
if (hasTitle) {
447-
const isHorizontal = this.isTopOrBottom(this.orientation);
451+
const isHorizontal = isTopOrBottom(this.orientation);
448452

449453
const textProperties = SVGLegend.getTextProperties(title, this.data.fontSize, this.data.fontFamily);
450454
let text = title;
@@ -498,7 +502,7 @@ export class SVGLegend implements ILegend {
498502
let navArrows: NavigationArrow[];
499503
let numberOfItems: number;
500504

501-
if (this.isTopOrBottom(this.orientation)) {
505+
if (isTopOrBottom(this.orientation)) {
502506
navArrows = this.isScrollable ? this.calculateHorizontalNavigationArrowsLayout(title) : [];
503507
numberOfItems = this.calculateHorizontalLayout(dataPoints, title, navArrows);
504508
}
@@ -786,6 +790,20 @@ export class SVGLegend implements ILegend {
786790
}
787791

788792
this.visibleLegendWidth = occupiedWidth;
793+
794+
// When the legend is right-aligned (TopRight/BottomRight):
795+
// - If everything fits, the group is translated to the right edge in
796+
// drawLegendInternal and items render right-aligned with no arrow.
797+
// - If items overflow, native visuals fall back to left-aligned rendering
798+
// with the "next" arrow at the right edge of the viewport — matching the
799+
// standard Top/Bottom overflow behavior. We achieve that here by setting
800+
// visibleLegendWidth to the full parent width so the translation in
801+
// drawLegendInternal becomes 0; the arrow keeps its default x set by
802+
// updateNavigationArrowLayout (parentViewport.width - LegendArrowWidth).
803+
if (isRightAligned(this.orientation) && numberOfItems !== dataPointsLength) {
804+
this.visibleLegendWidth = this.parentViewport.width;
805+
}
806+
789807
this.updateNavigationArrowLayout(navigationArrows, dataPointsLength, numberOfItems);
790808

791809
return numberOfItems;
@@ -955,30 +973,6 @@ export class SVGLegend implements ILegend {
955973
.attr("transform", (d: NavigationArrow) => d.rotateTransform);
956974
}
957975

958-
private isTopOrBottom(orientation: LegendPosition): boolean {
959-
switch (orientation) {
960-
case LegendPosition.Top:
961-
case LegendPosition.Bottom:
962-
case LegendPosition.BottomCenter:
963-
case LegendPosition.TopCenter:
964-
return true;
965-
default:
966-
return false;
967-
}
968-
}
969-
970-
private isCentered(orientation: LegendPosition): boolean {
971-
switch (orientation) {
972-
case LegendPosition.BottomCenter:
973-
case LegendPosition.LeftCenter:
974-
case LegendPosition.RightCenter:
975-
case LegendPosition.TopCenter:
976-
return true;
977-
default:
978-
return false;
979-
}
980-
}
981-
982976
public reset(): void { }
983977

984978
private static getTextProperties(

0 commit comments

Comments
 (0)