Skip to content

Commit

Permalink
feat(partition): add 4.5 contrast for text in partition slices (#608)
Browse files Browse the repository at this point in the history
Updates partition charts to allow for adaptive text color to meet acceptable color contrast ratio. For more information on text contrast see [docs](https://github.com/elastic/elastic-charts/blob/master/docs/0-Intro/1-Overview.mdx#background-colors-and-text-contrast).

BREAKING CHANGE
Adds `background` to the `Theme` object with the `BackgroundStyle` type that accepts a `color` value as a `string. Note text color might be different after changes from this PR, see docs on how to disable `textContrast` in the partition chart config.

Closes #606
  • Loading branch information
rshen91 committed Jun 8, 2020
1 parent 0729f79 commit eded2ac
Show file tree
Hide file tree
Showing 90 changed files with 1,069 additions and 180 deletions.
2 changes: 1 addition & 1 deletion .playground/playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License. */

import React from 'react';
import { Example } from '../stories/treemap/6_custom_style';
import { Example } from '../stories/stylings/20_partition_background';

export class Playground extends React.Component {
render() {
Expand Down
10 changes: 8 additions & 2 deletions api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@ export interface AxisStyle {
tickLabelPadding?: number;
}

// @public
export interface BackgroundStyle {
color: string;
}

// Warning: (ae-forgotten-export) The symbol "SpecRequiredProps" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "SpecOptionalProps" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "BarSeries" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
Expand Down Expand Up @@ -1403,6 +1408,7 @@ export interface Theme {
areaSeriesStyle: AreaSeriesStyle;
// (undocumented)
axes: AxisConfig;
background: BackgroundStyle;
barSeriesStyle: BarSeriesStyle;
bubbleSeriesStyle: BubbleSeriesStyle;
chartMargins: Margins;
Expand Down Expand Up @@ -1534,8 +1540,8 @@ export interface XYChartSeriesIdentifier extends SeriesIdentifier {

// Warnings were encountered during analysis:
//
// src/chart_types/partition_chart/layout/types/config_types.ts:117:5 - (ae-forgotten-export) The symbol "TimeMs" needs to be exported by the entry point index.d.ts
// src/chart_types/partition_chart/layout/types/config_types.ts:118:5 - (ae-forgotten-export) The symbol "AnimKeyframe" needs to be exported by the entry point index.d.ts
// src/chart_types/partition_chart/layout/types/config_types.ts:120:5 - (ae-forgotten-export) The symbol "TimeMs" needs to be exported by the entry point index.d.ts
// src/chart_types/partition_chart/layout/types/config_types.ts:121:5 - (ae-forgotten-export) The symbol "AnimKeyframe" needs to be exported by the entry point index.d.ts
// src/chart_types/partition_chart/specs/index.ts:47:13 - (ae-forgotten-export) The symbol "NodeColorAccessor" needs to be exported by the entry point index.d.ts
// src/commons/series_id.ts:37:3 - (ae-forgotten-export) The symbol "SeriesKey" needs to be exported by the entry point index.d.ts

Expand Down
166 changes: 166 additions & 0 deletions docs/0-Intro/1-Overview.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
import { Chart, Datum, Partition, PartitionLayout, Settings } from '../../src';
import { mocks } from '../../src/mocks/hierarchical/index';
import { config } from '../../src/chart_types/partition_chart/layout/config/config';
import { ShapeTreeNode } from '../../src/chart_types/partition_chart/layout/types/viewmodel_types';
import {
categoricalFillColor,
colorBrewerCategoricalStark9,
countryLookup,
indexInterpolatedFillColor,
interpolatorCET2s,
interpolatorTurbo,
productLookup,
regionLookup,
} from '../../stories/utils/utils';
import { Meta, Story } from "@storybook/addon-docs/blocks";

<Meta title="Introduction/Elastic Charts Overview" />
Expand Down Expand Up @@ -255,3 +269,155 @@ type PointStyleAccessor = (
```

> Note: When overriding bar or point styles be mindful of performance and these accessor functions will be call on every bar/point is every series. Precomputing any expensive task before rendering.
### Background Colors and Text Contrast
You can provide the `backgroundColor` of the container that the chart will be placed onto. You can set the `textContrast` to a boolean value or a number. The default `textContrast` is set to 4.5 but you can always disable this or set your own numerical amount.

> Note: This functionality is currently available for Partition charts. Please see the partition background and partition label stories.
```js
config: {
fillLabel: {
textInvertible: true,
textContrast: true, // can also be set to a number
}
}
```
`textInvertible` will have to be set to true for `textContrast` to be set as well. To see an example of where this applies, please see the Partitions Background story within Stylings. Charts are included below but are static.
If you have `textInvertible` set to true, but do not have `textContrast` set to true, then the red slices, Europe, North America, and Asia, will have white text:

<Chart className="story-chart">
<Settings theme={{background : {color: `rgba(155, 155, 155, 1)`}}} />
<Partition
id="spec_1"
data={mocks.miniSunburst}
valueAccessor={(d) => d.exportVal}
valueFormatter={(d) => `$${config.fillLabel.valueFormatter(Math.round(d / 1000000000))}\xa0Bn`}
layers={[
{
groupByRollup: (d) => d.sitc1,
nodeLabel: (d) => productLookup[d].name,
shape: {
fillColor: (d) => {
return categoricalFillColor(colorBrewerCategoricalStark9, 0.7)(d.sortIndex);
},
},
},
{
groupByRollup: (d) => countryLookup[d.dest].continentCountry.substr(0, 2),
nodeLabel: (d) => regionLookup[d].regionName,
shape: {
fillColor: (d) => {
return categoricalFillColor(colorBrewerCategoricalStark9, 0.5)(d.parent.sortIndex);
},
},
},
{
groupByRollup: (d) => d.dest,
nodeLabel: (d) => countryLookup[d].name,
shape: {
fillColor: (d) => {
return categoricalFillColor(colorBrewerCategoricalStark9, 0.3)(d.parent.parent.sortIndex);
},
},
},
]}
config={{
partitionLayout: PartitionLayout.sunburst,
linkLabel: {
maxCount: 0,
fontSize: 14,
},
fontFamily: 'Arial',
fillLabel: {
valueFormatter: (d) => `$${config.fillLabel.valueFormatter(Math.round(d / 1000000000))}\xa0Bn`,
fontStyle: 'italic',
textInvertible: true,
textContrast: false,
fontWeight: 900,
valueFont: {
fontFamily: 'Menlo',
fontStyle: 'normal',
fontWeight: 100,
},
},
margin: { top: 0, bottom: 0, left: 0, right: 0 },
minFontSize: 1,
idealFontSizeJump: 1.1,
outerSizeRatio: 1,
emptySizeRatio: 0,
circlePadding: 4,
backgroundColor: 'rgba(229,229,229,1)',
}}
/>
</Chart>



Now if you set the `textContrast` to true as well, these slices also become black in text color:

<Chart className="story-chart">
<Settings theme={{background : {color: `rgba(155, 155, 155, 1)`}}} />
<Partition
id="spec_1"
data={mocks.miniSunburst}
valueAccessor={(d) => d.exportVal}
valueFormatter={(d) => `$${config.fillLabel.valueFormatter(Math.round(d / 1000000000))}\xa0Bn`}
layers={[
{
groupByRollup: (d) => d.sitc1,
nodeLabel: (d) => productLookup[d].name,
shape: {
fillColor: (d) => {
return categoricalFillColor(colorBrewerCategoricalStark9, 0.7)(d.sortIndex);
},
},
},
{
groupByRollup: (d) => countryLookup[d.dest].continentCountry.substr(0, 2),
nodeLabel: (d) => regionLookup[d].regionName,
shape: {
fillColor: (d) => {
return categoricalFillColor(colorBrewerCategoricalStark9, 0.5)(d.parent.sortIndex);
},
},
},
{
groupByRollup: (d) => d.dest,
nodeLabel: (d) => countryLookup[d].name,
shape: {
fillColor: (d) => {
return categoricalFillColor(colorBrewerCategoricalStark9, 0.3)(d.parent.parent.sortIndex);
},
},
},
]}
config={{
partitionLayout: PartitionLayout.sunburst,
linkLabel: {
maxCount: 0,
fontSize: 14,
},
fontFamily: 'Arial',
fillLabel: {
valueFormatter: (d) => `$${config.fillLabel.valueFormatter(Math.round(d / 1000000000))}\xa0Bn`,
fontStyle: 'italic',
textInvertible: true,
textContrast: true,
fontWeight: 900,
valueFont: {
fontFamily: 'Menlo',
fontStyle: 'normal',
fontWeight: 100,
},
},
margin: { top: 0, bottom: 0, left: 0, right: 0 },
minFontSize: 1,
idealFontSizeJump: 1.1,
outerSizeRatio: 1,
emptySizeRatio: 0,
circlePadding: 4,
backgroundColor: 'rgba(229,229,229,1)',
}}
/>
</Chart>
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.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@
"@storybook/react": "5.2.8",
"@storybook/source-loader": "^5.3.9",
"@storybook/theming": "^5.2.8",
"@types/chroma-js": "^2.0.0",
"@types/classnames": "^2.2.7",
"@types/color": "^3.0.1",
"@types/core-js": "^2.5.2",
"@types/d3-array": "^1.2.6",
"@types/d3-collection": "^1.0.8",
Expand Down Expand Up @@ -170,6 +172,7 @@
"webpack-dev-server": "^3.3.1"
},
"dependencies": {
"chroma-js": "^2.1.0",
"@popperjs/core": "^2.4.0",
"classnames": "^2.2.6",
"d3-array": "^1.2.4",
Expand Down
4 changes: 3 additions & 1 deletion src/chart_types/partition_chart/layout/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,9 @@ export const configMetadata = {
fillLabel: {
type: 'group',
values: {
textColor: { dflt: '#000000', type: 'color' },
textColor: { type: 'color', dflt: '#000000' },
textInvertible: { dflt: false, type: 'boolean' },
textContrast: { dflt: false, type: 'boolean' || 'number' },
...fontSettings,
valueGetter: {
dflt: sumValueGetter,
Expand Down Expand Up @@ -221,6 +222,7 @@ export const configMetadata = {
},
textColor: { dflt: '#000000', type: 'color' },
textInvertible: { dflt: false, type: 'boolean' },
textContrast: { dflt: false, type: 'boolean' || 'number' },
textOpacity: { dflt: 1, min: 0, max: 1, type: 'number' },
minimumStemLength: {
dflt: 0,
Expand Down
3 changes: 3 additions & 0 deletions src/chart_types/partition_chart/layout/types/config_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ export type PerSidePadding = PerSideDistance;

export type Padding = Pixels | Partial<PerSidePadding>;

export type TextContrast = boolean | number;

interface LabelConfig extends Font {
textColor: Color;
textInvertible: boolean;
textContrast: TextContrast;
textOpacity: Ratio;
valueFormatter: ValueFormatter;
valueFont: PartialFont;
Expand Down
2 changes: 2 additions & 0 deletions src/chart_types/partition_chart/layout/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export interface Font {
fontVariant: FontVariant;
fontWeight: FontWeight;
fontFamily: FontFamily;
textColor: string;
textOpacity: number;
}

export type PartialFont = Partial<Font>;
Expand Down
23 changes: 18 additions & 5 deletions src/chart_types/partition_chart/layout/types/viewmodel_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,19 @@ import { Font } from './types';
import { config, ValueGetterName } from '../config/config';
import { ArrayNode, HierarchyOfArrays } from '../utils/group_by_rollup';
import { Color } from '../../../../utils/commons';
import { LinkLabelsViewModelSpec } from '../viewmodel/link_text_layout';
import { VerticalAlignments } from '../viewmodel/viewmodel';

/** @internal */
export type LinkLabelVM = {
link: PointTuples;
linkLabels: PointTuples;
translate: PointTuple;
textAlign: CanvasTextAlign;
text: string;
valueText: string;
width: Distance;
valueWidth: Distance;
verticalOffset: Distance;
labelFontSpec: Font;
valueFontSpec: Font;
};

/** @internal */
Expand Down Expand Up @@ -95,19 +94,33 @@ export type ShapeViewModel = {
config: Config;
quadViewModel: QuadViewModel[];
rowSets: RowSet[];
linkLabelViewModels: LinkLabelVM[];
linkLabelViewModels: LinkLabelsViewModelSpec;
outsideLinksViewModel: OutsideLinksViewModel[];
diskCenter: PointObject;
pickQuads: PickFunction;
outerRadius: number;
};

const defaultFont: Font = {
fontStyle: 'normal',
fontVariant: 'normal',
fontFamily: '',
fontWeight: 'normal',
textColor: 'black',
textOpacity: 1,
};

/** @internal */
export const nullShapeViewModel = (specifiedConfig?: Config, diskCenter?: PointObject): ShapeViewModel => ({
config: specifiedConfig || config,
quadViewModel: [],
rowSets: [],
linkLabelViewModels: [],
linkLabelViewModels: {
linkLabels: [],
labelFontSpec: defaultFont,
valueFontSpec: defaultFont,
strokeColor: '',
},
outsideLinksViewModel: [],
diskCenter: diskCenter || { x: 0, y: 0 },
pickQuads: () => [],
Expand Down
21 changes: 21 additions & 0 deletions src/chart_types/partition_chart/layout/utils/__mocks__/calcs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License. */
const module = jest.requireActual('../calcs.ts');

export const getBackgroundWithContainerColorFromUser = jest.fn(module.getBackgroundWithContainerColorFromUser);
export const makeHighContrastColor = jest.fn(module.makeHighContrastColor);
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License. */

const module = jest.requireActual('../d3_utils.ts');
const module = jest.requireActual('../color_library_wrappers.ts');

export const defaultColor = module.defaultColor;
export const transparentColor = module.transparentColor;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License. */
const module = jest.requireActual('../../viewmodel/fill_text_layout.ts');

export const getTextColorIfTextInvertible = jest.fn(module.getTextColorIfTextInvertible);
Loading

0 comments on commit eded2ac

Please sign in to comment.