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

feat: add option to toggle value labels on bar charts #182

Merged
merged 42 commits into from
May 4, 2019
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
ce3175d
feat(bar_series): toggle value label on bars
emmacunningham Apr 15, 2019
68966c3
chore: merge master into feat/value-labels
emmacunningham Apr 22, 2019
33a2a9a
feat(bar_geometries): format displayValue in bar geometries
emmacunningham Apr 22, 2019
bda7d30
feat(bar_geometries): add style for displayValue in theme
emmacunningham Apr 22, 2019
dc2ff10
docs(bars): add display value style to story
emmacunningham Apr 22, 2019
f06b440
feat: add alternatingDisplayValues prop
emmacunningham Apr 24, 2019
ab48247
feat: reposition value labels if overflow
emmacunningham Apr 24, 2019
e637104
test(rendering_props_utils): add buildBarValueProps tests
emmacunningham Apr 24, 2019
f8d3dbc
test(utils): add showValueLable prop test
emmacunningham Apr 24, 2019
5d97a7b
test: add alternating bar value test
emmacunningham Apr 24, 2019
8ce4aab
chore: resolve merge conflict with master
markov00 Apr 24, 2019
6a077c7
chore: fix merge conflicts with master
emmacunningham Apr 24, 2019
9938ead
refactor(bar values): move to separate rendering layer
emmacunningham May 1, 2019
c5b580f
feat(bar values): add debug rect
emmacunningham May 1, 2019
b0ddd16
feat(bar values): center values relative to bar element
emmacunningham May 1, 2019
53f1911
feat(bar values): adjust positioning for 180 deg rotation
emmacunningham May 1, 2019
7727e52
feat(bar values): adjust position dependent on chart rotation
emmacunningham May 1, 2019
294f03d
feat(bar values): fix positioning when chart is rotated
emmacunningham May 2, 2019
3bff5ba
fix: merge barSeriesStyle before computing render geometries
emmacunningham May 2, 2019
94ed4a3
test(bar values): fix new props parameters
emmacunningham May 2, 2019
e3b93e9
chore: merge master into feat/value-labels
emmacunningham May 2, 2019
cd0e350
feat(bar values): hide values if clipped at chart edges
emmacunningham May 2, 2019
e85926f
refactor(bar values): move settings into own interface
emmacunningham May 2, 2019
0da9b54
feat(bar values): add prop for containing value within bar
emmacunningham May 2, 2019
c09e0e8
feat(bar values): toggle visibility of clipped values with prop
emmacunningham May 2, 2019
f796397
fix(bar values): fix value to top of bar for rotated charts
emmacunningham May 2, 2019
9df0d4c
fix(bar values): set displayValue iff showValueLabel
emmacunningham May 2, 2019
2225efd
docs(bars): add ability to switch frozen data sources
emmacunningham May 2, 2019
a099904
feat(bar values): adjust dimensions for rotated contained within bar
emmacunningham May 2, 2019
0329b4c
test: fix parameters in test
emmacunningham May 2, 2019
1e19411
feat(bar values): hide if text too large
emmacunningham May 2, 2019
7ef7808
fix(bar values): account for offsets when hiding clipped values
emmacunningham May 2, 2019
8ef1d1f
fix(bar values): hide clipped value when rotated wiht offset
emmacunningham May 3, 2019
da8e366
test: fix bar value props tests
emmacunningham May 3, 2019
079dc8e
docs(bar values): add split & stacked series
emmacunningham May 3, 2019
d0f8f79
refactor(bar values): move logic to helper functions for easier testing
emmacunningham May 3, 2019
6274f0b
test(bar value): add tests for bar value clipping
emmacunningham May 3, 2019
11bd4c0
test(bar value): add test for bar value clip dimensions
emmacunningham May 3, 2019
5901969
test(bar values): add tests for rotation
emmacunningham May 3, 2019
7e5c7aa
chore: merge branch 'master' into feat/value-labels
emmacunningham May 3, 2019
ecc5314
test(bars): add tests for renderBars for displayValue
emmacunningham May 3, 2019
77e45e9
test(utils): add tests for bar displayValue
emmacunningham May 3, 2019
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
23 changes: 19 additions & 4 deletions src/components/react_canvas/bar_geometries.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Group as KonvaGroup } from 'konva';
import React from 'react';
import { Group, Rect } from 'react-konva';
import { Group, Rect, Text } from 'react-konva';
import { animated, Spring } from 'react-spring/renderprops-konva.cjs';
import { LegendItem } from '../../lib/series/legend';
import { BarGeometry, getGeometryStyle } from '../../lib/series/rendering';
import { BarSeriesStyle, SharedGeometryStyle } from '../../lib/themes/theme';
import { buildBarProps } from './utils/rendering_props_utils';
import { buildBarProps, buildBarValueProps } from './utils/rendering_props_utils';

interface BarGeometriesDataProps {
animated?: boolean;
Expand Down Expand Up @@ -48,7 +48,7 @@ export class BarGeometries extends React.PureComponent<
sharedStyle,
} = this.props;
return bars.map((bar, index) => {
const { x, y, width, height, color, seriesStyle } = bar;
const { displayValue, x, y, width, height, color, seriesStyle } = bar;
const border = seriesStyle ? seriesStyle.border : style.border;
const customOpacity = seriesStyle ? seriesStyle.opacity : undefined;

Expand All @@ -70,6 +70,15 @@ export class BarGeometries extends React.PureComponent<

// min border depending on bar width bars with white border
const borderEnabled = border.visible && width > border.strokeWidth * 7;

const displayValueProps = buildBarValueProps({
x,
y,
width,
height,
displayValueStyle: style.displayValue!, // displayValue is guaranteed on style as part of the merged theme
});

if (this.props.animated) {
return (
<Group key={index}>
Expand All @@ -91,6 +100,7 @@ export class BarGeometries extends React.PureComponent<
return <animated.Rect {...barProps} />;
}}
</Spring>
{displayValue && <Text text={displayValue} {...displayValueProps} />}
</Group>
);
} else {
Expand All @@ -106,7 +116,12 @@ export class BarGeometries extends React.PureComponent<
borderEnabled,
geometryStyle,
});
return <Rect {...barProps} />;
return (
<React.Fragment key={index}>
<Rect {...barProps} />
{displayValue && <Text text={displayValue} {...displayValueProps} />}
</React.Fragment>
);
}
});
}
Expand Down
56 changes: 56 additions & 0 deletions src/components/react_canvas/utils/rendering_props_utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
buildLinePointProps,
buildLineProps,
buildPointStyleProps,
buildBarValueProps,
} from './rendering_props_utils';

describe('[canvas] Area Geometries props', () => {
Expand Down Expand Up @@ -392,4 +393,59 @@ describe('[canvas] Bar Geometries', () => {
opacity: 0.5,
});
});

test('can build bar value props', () => {
const valueArguments = {
x: 10,
y: 20,
width: 30,
height: 40,
displayValueStyle: {
fill: 'fill',
fontFamily: 'ff',
fontSize: 10,
padding: 5,
},
};

const basicProps = buildBarValueProps(valueArguments);
expect(basicProps).toEqual({
x: 10,
y: 20,
width: 30,
align: 'center',
fill: 'fill',
fontFamily: 'ff',
fontSize: 10,
padding: 5,
});

valueArguments.height = 2;
const insufficientHeightBarProps = buildBarValueProps(valueArguments);
expect(insufficientHeightBarProps).toEqual({
x: 10,
y: 5,
width: 30,
align: 'center',
fill: 'fill',
fontFamily: 'ff',
fontSize: 10,
padding: 5,
});

valueArguments.y = 4;
valueArguments.height = 20;
valueArguments.displayValueStyle.padding = -5;
const chartOverflowBarProps = buildBarValueProps(valueArguments);
expect(chartOverflowBarProps).toEqual({
x: 10,
y: 4,
width: 30,
align: 'center',
fill: 'fill',
fontFamily: 'ff',
fontSize: 10,
padding: 5,
});
});
});
39 changes: 38 additions & 1 deletion src/components/react_canvas/utils/rendering_props_utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GeometryStyle } from '../../../lib/series/rendering';
import { AreaStyle, LineStyle, PointStyle } from '../../../lib/themes/theme';
import { AreaStyle, LineStyle, PointStyle, TextStyle } from '../../../lib/themes/theme';
import { GlobalKonvaElementProps } from '../globals';

export interface PointStyleProps {
Expand Down Expand Up @@ -153,6 +153,43 @@ export function buildBarProps({
};
}

export function buildBarValueProps({
x,
y,
width,
height,
displayValueStyle,
}: {
x: number;
y: number;
width: number;
height: number;
displayValueStyle: TextStyle;
}): TextStyle & {
x: number;
y: number;
width: number;
align: string;
} {
const { fontSize, padding } = displayValueStyle;
const displayValueTextHeight = fontSize + padding;
const displayValueY = height >= displayValueTextHeight ? y : y - displayValueTextHeight;

// if padding is less than 0, then text will appear above bar
// this checks if there is enough space above the bar to render the value
// if not, render the value within the bar
const textPadding = (padding < 0 && y < fontSize) ? -padding : padding;

return {
x,
y: displayValueY,
width,
align: 'center',
...displayValueStyle,
padding: textPadding,
};
}

export function buildLinePointProps({
lineIndex,
pointIndex,
Expand Down
106 changes: 103 additions & 3 deletions src/lib/series/rendering.bars.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { computeSeriesDomains } from '../../state/utils';
import { identity } from '../utils/commons';
import { getGroupId, getSpecId } from '../utils/ids';
import { ScaleType } from '../utils/scales/scales';
import { renderBars } from './rendering';
import { computeXScale, computeYScales } from './scales';
import { BarSeriesSpec } from './specs';

const SPEC_ID = getSpecId('spec_1');
const GROUP_ID = getGroupId('group_1');

Expand All @@ -14,19 +16,114 @@ describe('Rendering bars', () => {
groupId: GROUP_ID,
seriesType: 'bar',
yScaleToDataExtent: false,
data: [[0, 10], [1, 5]],
data: [[-200, 0], [0, 10], [1, 5]], // first datum should be skipped as it's out of domain
xAccessor: 0,
yAccessors: [1],
xScaleType: ScaleType.Ordinal,
yScaleType: ScaleType.Linear,
};
const barSeriesMap = new Map();
barSeriesMap.set(SPEC_ID, barSeriesSpec);
const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map());
const customDomain = [0, 1];
const barSeriesDomains = computeSeriesDomains(barSeriesMap, new Map(), customDomain);
const xScale = computeXScale(barSeriesDomains.xDomain, barSeriesMap.size, 0, 100);
const yScales = computeYScales(barSeriesDomains.yDomain, 100, 0);

test('Can render two bars', () => {
test('Can render two bars within domain', () => {
const { barGeometries } = renderBars(
0,
barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0].data,
xScale,
yScales.get(GROUP_ID)!,
'red',
SPEC_ID,
[],
);

expect(barGeometries[0]).toEqual({
x: 0,
y: 0,
width: 50,
height: 100,
color: 'red',
value: {
accessor: 'y1',
x: 0,
y: 10,
},
geometryId: {
specId: SPEC_ID,
seriesKey: [],
},
});
expect(barGeometries[1]).toEqual({
x: 50,
y: 50,
width: 50,
height: 50,
color: 'red',
value: {
accessor: 'y1',
x: 1,
y: 5,
},
geometryId: {
specId: SPEC_ID,
seriesKey: [],
},
});
expect(barGeometries.length).toBe(2);
});
test('Can render bars with value labels', () => {
const valueFormatter = identity;
const { barGeometries } = renderBars(
0,
barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0].data,
xScale,
yScales.get(GROUP_ID)!,
'red',
SPEC_ID,
[],
valueFormatter,
);

expect(barGeometries[0]).toEqual({
x: 0,
y: 0,
width: 50,
height: 100,
color: 'red',
value: {
accessor: 'y1',
x: 0,
y: 10,
},
geometryId: {
specId: SPEC_ID,
seriesKey: [],
},
displayValue: 10,
});
expect(barGeometries[1]).toEqual({
x: 50,
y: 50,
width: 50,
height: 50,
color: 'red',
value: {
accessor: 'y1',
x: 1,
y: 5,
},
geometryId: {
specId: SPEC_ID,
seriesKey: [],
},
displayValue: 5,
});
});
test('Can render bars with alternating value labels', () => {
const valueFormatter = identity;
const { barGeometries } = renderBars(
0,
barSeriesDomains.formattedDataSeries.nonStacked[0].dataSeries[0].data,
Expand All @@ -35,6 +132,8 @@ describe('Rendering bars', () => {
'red',
SPEC_ID,
[],
valueFormatter,
true,
);
expect(barGeometries[0]).toEqual({
x: 0,
Expand All @@ -51,6 +150,7 @@ describe('Rendering bars', () => {
specId: SPEC_ID,
seriesKey: [],
},
displayValue: 10,
});
expect(barGeometries[1]).toEqual({
x: 50,
Expand Down
13 changes: 13 additions & 0 deletions src/lib/series/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { CurveType, getCurveFactory } from './curves';
import { LegendItem } from './legend';
import { DataSeriesDatum } from './series';
import { belongsToDataSeries } from './series_utils';
import { TickFormatter } from './specs';

export interface GeometryId {
specId: SpecId;
Expand Down Expand Up @@ -53,6 +54,7 @@ export interface BarGeometry {
width: number;
height: number;
color: string;
displayValue?: any;
geometryId: GeometryId;
value: GeometryValue;
seriesStyle?: CustomBarSeriesStyle;
Expand Down Expand Up @@ -173,6 +175,8 @@ export function renderBars(
color: string,
specId: SpecId,
seriesKey: any[],
valueFormatter?: TickFormatter,
alternatingValueLabel?: boolean,
seriesStyle?: CustomBarSeriesStyle,
): {
barGeometries: BarGeometry[];
Expand Down Expand Up @@ -210,7 +214,16 @@ export function renderBars(
}
const x = xScale.scale(datum.x) + xScale.bandwidth * orderIndex;
const width = xScale.bandwidth;

const formattedDisplayValue = valueFormatter ? valueFormatter(initialY1) : undefined;

// only show displayValue for even bars if showOverlappingValue is false
const displayValue = alternatingValueLabel ?
(barGeometries.length % 2 === 0 ? formattedDisplayValue : undefined)
: formattedDisplayValue;

const barGeometry: BarGeometry = {
displayValue,
x,
y, // top most value
width,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/series/specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export interface SeriesSpec {
hideInLegend?: boolean;
/** Index per series to sort by */
sortIndex?: number;
/** Show value label in chart element */
showValueLabel?: boolean;
/** If value labels are shown, skips every other label */
alternatingValueLabel?: boolean;
}

export type CustomSeriesColorsMap = Map<DataSeriesColorsValues, string>;
Expand Down
7 changes: 7 additions & 0 deletions src/lib/themes/dark_theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ export const DARK_THEME: Theme = {
strokeWidth: 2,
visible: false,
},
displayValue: {
fontSize: 10,
fontStyle: 'normal',
fontFamily: `'Open Sans', Helvetica, Arial, sans-serif`,
padding: 5,
fill: 'white',
},
},
sharedStyle: DEFAULT_GEOMETRY_STYLES,
scales: {
Expand Down
Loading