Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(partition): rtl text label support #1433

Merged
merged 22 commits into from
Nov 15, 2021
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 30 additions & 7 deletions integration/tests/test_cases_stories.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Side Public License, v 1.
*/

import { PartitionLayout, SeriesType } from '../../packages/charts/src';
import { eachRotation } from '../helpers';
import { common } from '../page_objects';

Expand All @@ -25,12 +26,34 @@ describe('Test cases stories', () => {
{ waitSelector: '.echReactiveChart_noResults' },
);
});
});

describe('annotation marker rotation', () => {
eachRotation.it(async (rotation) => {
await common.expectChartAtUrlToMatchScreenshot(
`http://localhost:9001/iframe.html?id=test-cases--no-axes-annotation-bug-fix&knob-horizontal marker position=undefined&knob-vertical marker position=undefined&knob-chartRotation=${rotation}`,
);
}, 'should render marker with annotations with %s degree rotations');
describe('annotation marker rotation', () => {
eachRotation.it(async (rotation) => {
await common.expectChartAtUrlToMatchScreenshot(
`http://localhost:9001/iframe.html?id=test-cases--no-axes-annotation-bug-fix&knob-horizontal marker position=undefined&knob-vertical marker position=undefined&knob-chartRotation=${rotation}`,
);
}, 'should render marker with annotations with %s degree rotations');
});

describe('RTL support', () => {
describe.each([SeriesType.Bar, PartitionLayout.treemap, PartitionLayout.sunburst])('%s chart type', (type) => {
describe.each(['HE', 'AR'])('lang %s', (lang) => {
describe.each(['rtl', 'ltr', 'mostly-rtl', 'mostly-ltr'])('%s text', (charSet) => {
it('should render with correct direction', async () => {
await common.expectChartAtUrlToMatchScreenshot(
`http://localhost:9001/?path=/story/test-cases--rtl-text&globals=background:white;theme:light&knob-Chart type=${type}&knob-character set=${charSet}&knob-show legend=true&knob-clockwiseSectors=true&knob-rtl language=${lang}`,
);
});

if (type === PartitionLayout.sunburst) {
it('should render with correct direction - clockwiseSectors', async () => {
await common.expectChartAtUrlToMatchScreenshot(
`http://localhost:9001/?path=/story/test-cases--rtl-text&globals=background:white;theme:light&knob-Chart type=${type}&knob-character set=${charSet}&knob-show legend=true&knob-clockwiseSectors=false&knob-rtl language=${lang}`,
);
});
}
});
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export function shapeViewModel(
const boxedYValues = yValues.map<Box & { value: NonNullable<PrimitiveValue> }>((value) => ({
text: config.yAxisLabel.formatter(value),
value,
isValue: false,
...config.yAxisLabel,
}));

Expand Down Expand Up @@ -150,6 +151,7 @@ export function shapeViewModel(
return {
text: formatter(value, { timeZone: config.timeZone }),
value,
isValue: false,
...config.xAxisLabel,
x: chartDimensions.left + (scaleCallback(value) || 0),
y: cellHeight * pageSize + config.xAxisLabel.fontSize / 2 + config.xAxisLabel.padding,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export const computeChartDimensionsSelector = createCustomCachedSelector(
return {
text: String(value),
value,
isValue: false,
...config.yAxisLabel,
};
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
Radian,
SizeRatio,
} from '../../../../common/geometry';
import { Font, VerticalAlignments } from '../../../../common/text_utils';
import { Font, HorizontalAlignment, VerticalAlignments } from '../../../../common/text_utils';
import { GroupByAccessor } from '../../../../specs';
import { LegendPath } from '../../../../state/actions/legend';
import { ContinuousDomainFocus } from '../../renderer/canvas/partition';
Expand All @@ -30,6 +30,7 @@ import { Config, PartitionLayout } from './config_types';

/** @internal */
export type LinkLabelVM = {
isRTL: boolean;
linkLabels: PointTuples;
translate: PointTuple;
textAlign: CanvasTextAlign;
Expand All @@ -44,6 +45,7 @@ export interface RowBox extends Font {
text: string;
width: Distance;
wordBeginning: Distance;
isValue?: boolean;
nickofthyme marked this conversation as resolved.
Show resolved Hide resolved
}

interface RowAnchor {
Expand Down Expand Up @@ -71,7 +73,8 @@ export interface RowSet {
fontSize: number;
rotation: Radian;
verticalAlignment: VerticalAlignments;
leftAlign: boolean; // might be generalized into horizontalAlign - if needed
horizontalAlignment: HorizontalAlignment;
isRTL: boolean;
container?: any;
clipText?: boolean;
}
Expand Down Expand Up @@ -193,6 +196,14 @@ export const nullShapeViewModel = (specifiedConfig?: Config, diskCenter?: PointO
outerRadius: 0,
});

/** @internal */
export const hasMostlyRTLLabels = (geoms: ShapeViewModel[]): boolean =>
geoms.reduce(
(surplus: number, { rowSets }) =>
surplus + rowSets.reduce((excess: number, { isRTL }) => excess + (isRTL ? 1 : -1), 0),
0,
) > 0;

/** @public */
export type TreeLevel = number;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,16 @@ import {
wrapToTau,
} from '../../../../common/geometry';
import { logarithm } from '../../../../common/math';
import { Box, Font, PartialFont, TextMeasure, VerticalAlignments } from '../../../../common/text_utils';
import {
Box,
Font,
HorizontalAlignment,
PartialFont,
TextMeasure,
VerticalAlignments,
} from '../../../../common/text_utils';
import { integerSnap, monotonicHillClimb } from '../../../../solvers/monotonic_hill_climb';
import { ValueFormatter } from '../../../../utils/common';
import { getOppositeAlignment, isRTLString, ValueFormatter } from '../../../../utils/common';
import { Layer } from '../../specs';
import { Config, Padding } from '../types/config_types';
import {
Expand Down Expand Up @@ -140,6 +147,7 @@ export const getRectangleRowGeometry: GetShapeRowGeometry<RectangleConstruction>
fontSize,
overhang,
);

return {
rowAnchorX: cx + left / 2 - right / 2,
rowAnchorY,
Expand All @@ -163,8 +171,9 @@ function identityRowSet(): RowSet {
fontSize: NaN,
fillTextColor: '',
rotation: NaN,
isRTL: false,
verticalAlignment: VerticalAlignments.middle,
leftAlign: false,
horizontalAlignment: HorizontalAlignment.center,
};
}

Expand All @@ -179,12 +188,11 @@ function getAllBoxes(
return rawTextGetter(node)
.split(' ')
.filter(Boolean)
.map((text) => ({ text, ...sizeInvariantFontShorthand }))
.map<Box>((text) => ({ text, ...sizeInvariantFontShorthand, isValue: false }))
.concat(
valueFormatter(valueGetter(node))
.split(' ')
[valueFormatter(valueGetter(node))]
.filter(Boolean)
.map((text) => ({ text, ...sizeInvariantFontShorthand, ...valueFont })),
.map<Box>((text) => ({ text, ...sizeInvariantFontShorthand, ...valueFont, isValue: true })),
);
}

Expand Down Expand Up @@ -224,6 +232,7 @@ function fill<C>(
leftAlign: boolean,
middleAlign: boolean,
) {
const horizontalAlignment = leftAlign ? HorizontalAlignment.left : HorizontalAlignment.center;
const { maxRowCount, fillLabel } = config;
return (allFontSizes: Pixels[][], textFillOrigin: PointTuple, node: QuadViewModel): RowSet => {
const container = shapeConstructor(node);
Expand Down Expand Up @@ -257,6 +266,7 @@ function fill<C>(
fontFamily,
textColor: node.textColor,
};
const isRtlString = isRTLString(rawTextGetter(node));
const allBoxes = getAllBoxes(rawTextGetter, valueGetter, valueFormatter, sizeInvariantFont, valueFont, node);
const [cx, cy] = textFillOrigin;

Expand All @@ -268,14 +278,15 @@ function fill<C>(
measure,
rotation,
verticalAlignment,
leftAlign,
horizontalAlignment,
container,
getShapeRowGeometry,
cx,
cy,
padding,
node,
clipText,
isRtlString,
),
fillTextColor: node.textColor,
};
Expand All @@ -287,7 +298,7 @@ function tryFontSize<C>(
measure: TextMeasure,
rotation: Radian,
verticalAlignment: VerticalAlignments,
leftAlign: boolean,
horizontalAlignment: HorizontalAlignment,
container: C,
getShapeRowGeometry: GetShapeRowGeometry<C>,
cx: Coordinate,
Expand All @@ -296,8 +307,10 @@ function tryFontSize<C>(
node: ShapeTreeNode,
boxes: Box[],
maxRowCount: number,
clipText?: boolean,
clipText: boolean,
isRTL: boolean,
) {
const adjustedHorizontalAlignment = isRTL ? getOppositeAlignment(horizontalAlignment) : horizontalAlignment;
return function tryFontSizeFn(initialRowSet: RowSet, fontSize: Pixels): { rowSet: RowSet; completed: boolean } {
let rowSet: RowSet = initialRowSet;

Expand Down Expand Up @@ -327,8 +340,9 @@ function tryFontSize<C>(
fillTextColor: '',
rotation,
verticalAlignment,
leftAlign,
horizontalAlignment: adjustedHorizontalAlignment,
clipText,
isRTL,
rows: [...new Array(targetRowCount)].map(() => ({
rowWords: [],
rowAnchorX: NaN,
Expand Down Expand Up @@ -372,7 +386,6 @@ function tryFontSize<C>(
while (measuredBoxes.length > 0 && rowHasRoom) {
// adding box to row
const [currentBox] = measuredBoxes;

const wordBeginning = currentRowLength;
currentRowLength += currentBox.width + wordSpacing;

Expand Down Expand Up @@ -402,20 +415,21 @@ function getRowSet<C>(
measure: TextMeasure,
rotation: Radian,
verticalAlignment: VerticalAlignments,
leftAlign: boolean,
horizontalAlignment: HorizontalAlignment,
container: C,
getShapeRowGeometry: GetShapeRowGeometry<C>,
cx: Coordinate,
cy: Coordinate,
padding: Padding,
node: ShapeTreeNode,
clipText: boolean,
) {
isRtl: boolean,
): RowSet {
const tryFunction = tryFontSize(
measure,
rotation,
verticalAlignment,
leftAlign,
horizontalAlignment,
container,
getShapeRowGeometry,
cx,
Expand All @@ -425,6 +439,7 @@ function getRowSet<C>(
boxes,
maxRowCount,
clipText,
isRtl,
);

// find largest fitting font size
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
trueBearingToStandardPositionAngle,
} from '../../../../common/geometry';
import { cutToLength, fitText, Font, measureOneBoxWidth, TextMeasure } from '../../../../common/text_utils';
import { ValueFormatter } from '../../../../utils/common';
import { isRTLString, ValueFormatter } from '../../../../utils/common';
import { Logger } from '../../../../utils/logger';
import { Point } from '../../../../utils/point';
import { Config, LinkLabelConfig } from '../types/config_types';
Expand Down Expand Up @@ -167,13 +167,20 @@ function nodeToLinkLabel({
[stemToX + west * linkLabel.horizontalStemLength, stemToY],
];

const rawLabelText = rawTextGetter(node);
const isRTL = isRTLString(rawLabelText);
nickofthyme marked this conversation as resolved.
Show resolved Hide resolved

// value text is simple: the full, formatted value is always shown, not truncated
const valueText = valueFormatter(valueGetter(node));
const valueWidth = measureOneBoxWidth(measure, linkLabel.fontSize, { ...valueFont, text: valueText });
const valueWidth = measureOneBoxWidth(measure, linkLabel.fontSize, {
...valueFont,
text: valueText,
isValue: false,
});
const widthAdjustment = valueWidth + 2 * linkLabel.fontSize; // gap between label and value, plus possibly 2em wide ellipsis

// label text removes space allotted for value and gaps, then tries to fit as much as possible
const labelText = cutToLength(rawTextGetter(node), maxTextLength);
const labelText = cutToLength(rawLabelText, maxTextLength);
const allottedLabelWidth = Math.max(
0,
rightSide ? rectWidth - diskCenter.x - translateX - widthAdjustment : diskCenter.x + translateX - widthAdjustment,
Expand All @@ -184,6 +191,7 @@ function nodeToLinkLabel({
: { text: '', width: 0 };

return {
isRTL,
linkLabels,
translate,
text,
Expand Down
Loading