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 3 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.
24 changes: 17 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,21 @@ 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');
});

it.each([SeriesType.Bar, PartitionLayout.treemap, PartitionLayout.sunburst])(
'should render %s chart with rtl text',
async (type) => {
await common.expectChartAtUrlToMatchScreenshot(
`http://localhost:9001/?path=/story/test-cases--rtl-text&globals=background:white;theme:light&knob-Chart type=${type}&knob-show legend=true&knob-use rtl text=true`,
);
},
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
import { logarithm } from '../../../../common/math';
import { Box, Font, PartialFont, TextMeasure, VerticalAlignments } from '../../../../common/text_utils';
import { integerSnap, monotonicHillClimb } from '../../../../solvers/monotonic_hill_climb';
import { ValueFormatter } from '../../../../utils/common';
import { isRTL, ValueFormatter } from '../../../../utils/common';
import { Layer } from '../../specs';
import { Config, Padding } from '../types/config_types';
import {
Expand Down Expand Up @@ -168,6 +168,8 @@ function identityRowSet(): RowSet {
};
}

const getWords = (s: string) => (isRTL(s) ? s.split(' ').reverse() : s.split(' '));

function getAllBoxes(
rawTextGetter: RawTextGetter,
valueGetter: ValueGetterFunction,
Expand All @@ -176,13 +178,11 @@ function getAllBoxes(
valueFont: PartialFont,
node: ShapeTreeNode,
): Box[] {
return rawTextGetter(node)
.split(' ')
return getWords(rawTextGetter(node))
.filter(Boolean)
.map((text) => ({ text, ...sizeInvariantFontShorthand }))
.concat(
valueFormatter(valueGetter(node))
.split(' ')
getWords(valueFormatter(valueGetter(node)))
.filter(Boolean)
.map((text) => ({ text, ...sizeInvariantFontShorthand, ...valueFont })),
);
Expand Down
6 changes: 4 additions & 2 deletions packages/charts/src/common/text_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { $Values as Values } from 'utility-types';

import { ArrayEntry } from '../chart_types/partition_chart/layout/utils/group_by_rollup';
import { integerSnap, monotonicHillClimb } from '../solvers/monotonic_hill_climb';
import { Datum } from '../utils/common';
import { Datum, isRTL } from '../utils/common';
import { Color } from './colors';
import { Pixels, Rectangle } from './geometry';

Expand Down Expand Up @@ -114,7 +114,9 @@ export function measureOneBoxWidth(measure: TextMeasure, fontSize: number, box:

/** @internal */
export function cutToLength(s: string, maxLength: number) {
return s.length <= maxLength ? s : `${s.slice(0, Math.max(0, maxLength - 1))}…`; // ellipsis is one char
const prefix = isRTL(s) ? '…' : ''; // ellipsis is one char
const postfix = isRTL(s) ? '' : '…'; // ellipsis is one char
return s.length <= maxLength ? s : `${prefix}${s.slice(0, Math.max(0, maxLength - 1))}${postfix}`;
}

/** @internal */
Expand Down
8 changes: 8 additions & 0 deletions packages/charts/src/components/legend/_legend_item.scss
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,12 @@ $legendItemHeight: #{$euiFontSizeXS * $euiLineHeight};
&--hidden {
color: $euiColorDarkShade;
}

&--rtl {
direction: rtl;

.echLegendItem__label {
text-align: right;
}
}
}
3 changes: 2 additions & 1 deletion packages/charts/src/components/legend/legend_item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
onLegendItemOverAction,
onToggleDeselectSeriesAction,
} from '../../state/actions/legend';
import { LayoutDirection } from '../../utils/common';
import { isRTL, LayoutDirection } from '../../utils/common';
import { deepEqual } from '../../utils/fast_deep_equal';
import { LegendLabelOptions } from '../../utils/themes/theme';
import { Color as ItemColor } from './color';
Expand Down Expand Up @@ -182,6 +182,7 @@ export class LegendListItem extends Component<LegendItemProps, LegendItemState>
if (isItemHidden) return null;

const itemClassNames = classNames('echLegendItem', {
'echLegendItem--rtl': isRTL(label),
'echLegendItem--hidden': isSeriesHidden,
'echLegendItem--vertical': positionConfig.direction === LayoutDirection.Vertical,
});
Expand Down
36 changes: 26 additions & 10 deletions packages/charts/src/mocks/hierarchical/dimension_codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,32 @@

/** @internal */
export const productDimension = [
{ sitc1: '0', name: 'Food and live animals' },
{ sitc1: '1', name: 'Beverages and tobacco' },
{ sitc1: '2', name: 'Crude materials, inedible, except fuels' },
{ sitc1: '3', name: 'Mineral fuels, lubricants and related materials' },
{ sitc1: '4', name: 'Animal and vegetable oils, fats and waxes' },
{ sitc1: '5', name: 'Chemicals and related products' },
{ sitc1: '6', name: 'Manufactured goods classified chiefly by material' },
{ sitc1: '7', name: 'Machinery and transport equipment' },
{ sitc1: '8', name: 'Miscellaneous manufactured articles' },
{ sitc1: '9', name: 'Commodities and transactions not classified elsewhere' },
{ sitc1: '0', name: 'Food and live animals', nameAR: 'الغذاء والحيوانات الحية' },
{ sitc1: '1', name: 'Beverages and tobacco', nameAR: 'المشروبات والتبغ' },
{
sitc1: '2',
name: 'Crude materials, inedible, except fuels',
nameAR: 'المواد الخام غير الصالحة للأكل عدا المحروقات',
},
{
sitc1: '3',
name: 'Mineral fuels, lubricants and related materials',
nameAR: 'الوقود المعدني وزيوت التشحيم والمواد ذات الصلة',
},
{ sitc1: '4', name: 'Animal and vegetable oils, fats and waxes', nameAR: 'زيوت ودهون وشموع حيوانية ونباتية' },
{ sitc1: '5', name: 'Chemicals and related products', nameAR: 'الكيماويات والمنتجات ذات الصلة' },
{
sitc1: '6',
name: 'Manufactured goods classified chiefly by material',
nameAR: 'البضائع المصنعة مصنفة بشكل رئيسي حسب المادة',
},
{ sitc1: '7', name: 'Machinery and transport equipment', nameAR: 'الالات ومعدات النقل' },
{ sitc1: '8', name: 'Miscellaneous manufactured articles', nameAR: 'المواد المصنعة المتنوعة' },
{
sitc1: '9',
name: 'Commodities and transactions not classified elsewhere',
nameAR: 'السلع والمعاملات غير المصنفة في مكان آخر',
},
];

/** @internal */
Expand Down
9 changes: 9 additions & 0 deletions packages/charts/src/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,15 @@ export function isUniqueArray<B, T>(arr: B[], extractor?: (value: B) => T) {
})();
}

/** @internal */
export function isRTL(s: string) {
const ltrChars =
'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' +
'\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF';
const rtlChars = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC';
return new RegExp(`^[^${ltrChars}]*[${rtlChars}]`).test(s);
}

nickofthyme marked this conversation as resolved.
Show resolved Hide resolved
/**
* Returns defined value type if not null nor undefined
*
Expand Down
106 changes: 106 additions & 0 deletions storybook/stories/test_cases/7_rtl_text.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { boolean, select } from '@storybook/addon-knobs';
import numeral from 'numeral';
import React from 'react';

import {
Chart,
Datum,
Partition,
PartitionLayout,
Settings,
SeriesType,
BarSeries,
Axis,
Position,
ScaleType,
} from '@elastic/charts';
import { config } from '@elastic/charts/src/chart_types/partition_chart/layout/config';
import { arrayToLookup, hueInterpolator } from '@elastic/charts/src/common/color_calcs';
import { mocks } from '@elastic/charts/src/mocks/hierarchical';
import { productDimension } from '@elastic/charts/src/mocks/hierarchical/dimension_codes';
import { palettes } from '@elastic/charts/src/mocks/hierarchical/palettes';

import { useBaseTheme } from '../../use_base_theme';

const productLookup = arrayToLookup((d: Datum) => d.sitc1, productDimension);

// style calcs
const interpolatorCET2s = hueInterpolator(palettes.CET2s.map(([r, g, b]) => [r, g, b, 0.7]));

const defaultFillColor = (colorMaker: any) => (d: any, i: number, a: any[]) => colorMaker(i / (a.length + 1));

export const Example = () => {
const useRtl = boolean('use rtl text', true);
const showLegend = boolean('show legend', true);
const type = select(
'Chart type',
{
Bar: SeriesType.Bar,
Treemap: PartitionLayout.treemap,
Sunburst: PartitionLayout.sunburst,
},
PartitionLayout.treemap,
);

const renderSeries = () =>
type === SeriesType.Bar ? (
<>
<Axis id="x" position={Position.Bottom} tickFormat={(d) => numeral(d).format('0.0 a')} />
<Axis id="y" position={Position.Left} />
<BarSeries
id="bar"
data={mocks.pie}
xScaleType={ScaleType.Ordinal}
xAccessor={({ sitc1 }) => (useRtl ? productLookup[sitc1].nameAR : productLookup[sitc1].name)}
yAccessors={[(d: Datum) => d.exportVal]}
splitSeriesAccessors={[({ sitc1 }) => (useRtl ? productLookup[sitc1].nameAR : productLookup[sitc1].name)]}
/>
</>
) : (
<Partition
id="partition"
data={mocks.pie}
valueAccessor={(d: Datum) => d.exportVal as number}
valueFormatter={(d: number) => `$${config.fillLabel.valueFormatter(Math.round(d / 1000000000))}\u00A0Bn`}
layers={[
{
groupByRollup: (d: Datum) => d.sitc1,
nodeLabel: (d: Datum) => (useRtl ? productLookup[d].nameAR : productLookup[d].name),
fillLabel: {
valueFormatter: (d: number) => numeral(d).format('0.0 a'),
},
shape: {
fillColor: defaultFillColor(interpolatorCET2s),
},
},
]}
config={{
partitionLayout: type,
}}
/>
);

return (
<Chart>
<Settings
rotation={type === SeriesType.Bar ? 90 : 0}
debugState
showLegend={showLegend}
baseTheme={useBaseTheme()}
/>
{renderSeries()}
</Chart>
);
};

Example.parameters = {
background: { default: 'white' },
};
1 change: 1 addition & 0 deletions storybook/stories/test_cases/test_cases.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export { Example as noAxesAnnotationBugFix } from './3_no_axes_annotation.story'
export { Example as filterZerosInLogFitDomain } from './4_filter_zero_values_log.story';
export { Example as legendScrollBarSizing } from './5_legend_scroll_bar_sizing.story';
export { Example as accessibilityCustomizations } from './6_a11y_custom_description.story';
export { Example as rtlText } from './7_rtl_text.story';