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 33 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
8 changes: 6 additions & 2 deletions src/components/react_canvas/bar_geometries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface BarGeometriesDataState {
export class BarGeometries extends React.PureComponent<
BarGeometriesDataProps,
BarGeometriesDataState
> {
> {
static defaultProps: Partial<BarGeometriesDataProps> = {
animated: false,
};
Expand Down Expand Up @@ -101,7 +101,11 @@ export class BarGeometries extends React.PureComponent<
borderEnabled: border.visible,
geometryStyle,
});
return <Rect {...barProps} />;
return (
<React.Fragment key={index}>
<Rect {...barProps} />
</React.Fragment>
);
}
});
}
Expand Down
63 changes: 63 additions & 0 deletions src/components/react_canvas/bar_values.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';
import { Group, Rect, Text } from 'react-konva';
import { BarGeometry } from '../../lib/series/rendering';
import { Rotation } from '../../lib/series/specs';
import { DisplayValueStyle } from '../../lib/themes/theme';
import { Dimensions } from '../../lib/utils/dimensions';
import { buildBarValueProps } from './utils/rendering_props_utils';

interface BarValuesProps {
chartDimensions: Dimensions;
chartRotation: Rotation;
debug: boolean;
bars: BarGeometry[];
displayValueStyle: DisplayValueStyle;
}

export class BarValues extends React.PureComponent<BarValuesProps> {
render() {
const { chartDimensions } = this.props;

return (
<Group x={chartDimensions.left} y={chartDimensions.top}>
{this.renderBarValues()}
</Group>
);
}

private renderBarValues = () => {
const { bars, displayValueStyle, debug, chartRotation, chartDimensions } = this.props;
return bars.map((bar, index) => {
const { displayValue, x, y, height, width } = bar;
if (!displayValue) {
return;
}

const key = `bar-value-${index}`;
const displayValueProps = buildBarValueProps({
x,
y,
barHeight: height,
barWidth: width,
displayValueStyle,
displayValue,
chartRotation,
chartDimensions,
});

const debugProps = {
...displayValueProps,
stroke: 'violet',
strokeWidth: 1,
fill: 'transparent',
};

return (
<Group key={key}>
{debug && <Rect {...debugProps} />}
{displayValue && <Text {...displayValueProps} />}
</Group>
);
});
}
}
31 changes: 26 additions & 5 deletions src/components/react_canvas/reactive_chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Annotation } from './annotation';
import { AreaGeometries } from './area_geometries';
import { Axis } from './axis';
import { BarGeometries } from './bar_geometries';
import { BarValues } from './bar_values';
import { Grid } from './grid';
import { LineGeometries } from './line_geometries';

Expand Down Expand Up @@ -198,6 +199,22 @@ class Chart extends React.Component<ReactiveChartProps, ReactiveChartState> {
return annotationComponents;
}

renderBarValues = () => {
const { debug, chartDimensions, geometries, chartTheme, chartRotation } = this.props.chartStore!;
if (!geometries) {
return;
}
const props = {
debug,
chartDimensions,
chartRotation,
bars: geometries.bars,
// displayValue is guaranteed on style as part of the merged theme
displayValueStyle: chartTheme.barSeriesStyle.displayValue!,
};
return <BarValues {...props} />;
}

renderBrushTool = () => {
const { brushing, brushStart, brushEnd } = this.state;
const { chartDimensions, chartRotation, chartTransform } = this.props.chartStore!;
Expand Down Expand Up @@ -360,19 +377,23 @@ class Chart extends React.Component<ReactiveChartProps, ReactiveChartState> {
<Layer hitGraphEnabled={false} listening={false}>
{this.renderAnnotations()}
</Layer>

<Layer hitGraphEnabled={false} listening={false} {...layerClippings}>
{this.renderBarValues()}
</Layer>
</Stage>
</div>
);
}

private renderDebugChartBorders = () => {
const { chartDimensions, chartRotation, chartTransform } = this.props.chartStore!;
const { chartDimensions } = this.props.chartStore!;
return (
<Rect
x={chartDimensions.left + chartTransform.x}
y={chartDimensions.top + chartTransform.y}
width={[90, -90].includes(chartRotation) ? chartDimensions.height : chartDimensions.width}
height={[90, -90].includes(chartRotation) ? chartDimensions.width : chartDimensions.height}
x={chartDimensions.left}
y={chartDimensions.top}
width={chartDimensions.width}
height={chartDimensions.height}
stroke="red"
strokeWidth={4}
listening={false}
Expand Down
72 changes: 72 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 @@ -3,10 +3,12 @@ import {
buildAreaPointProps,
buildAreaProps,
buildBarProps,
buildBarValueProps,
buildLinePointProps,
buildLineProps,
buildPointStyleProps,
} from './rendering_props_utils';
import { Rotation } from '../../../lib/series/specs';

describe('[canvas] Area Geometries props', () => {
test('can build area point props', () => {
Expand Down Expand Up @@ -392,4 +394,74 @@ describe('[canvas] Bar Geometries', () => {
opacity: 0.5,
});
});

// TODO: fix these tests
test.skip('can build bar value props', () => {
const valueArguments = {
x: 10,
y: 20,
barWidth: 30,
barHeight: 40,
displayValueStyle: {
fill: 'fill',
fontFamily: 'ff',
fontSize: 10,
padding: 5,
offsetX: 0,
offsetY: 0,
},
displayValue: {
text: 'foo',
width: 10,
height: 10,
},
chartDimensions: {
width: 10,
height: 10,
top: 0,
left: 0,
},
chartRotation: 0 as Rotation,
};

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

valueArguments.barHeight = 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.barHeight = 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,
});
});
});
127 changes: 126 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,7 @@
import { GeometryStyle } from '../../../lib/series/rendering';
import { AreaStyle, LineStyle, PointStyle } from '../../../lib/themes/theme';
import { Rotation } from '../../../lib/series/specs';
import { AreaStyle, DisplayValueStyle, LineStyle, PointStyle } from '../../../lib/themes/theme';
import { Dimensions } from '../../../lib/utils/dimensions';
import { GlobalKonvaElementProps } from '../globals';

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

export function buildBarValueProps({
x,
y,
barHeight,
barWidth,
displayValueStyle,
displayValue,
chartRotation,
chartDimensions,
}: {
x: number;
y: number;
barHeight: number;
barWidth: number;
displayValueStyle: DisplayValueStyle;
displayValue: {
text: string;
width: number;
height: number;
hideClippedValue?: boolean;
isValueContainedInElement?: boolean;
};
chartRotation: Rotation;
chartDimensions: Dimensions;
}): DisplayValueStyle & {
x: number;
y: number;
align: string;
text: string;
width: number;
height: number;
} {
const chartHeight = chartDimensions.height;
const chartWidth = chartDimensions.width;
const { padding } = displayValueStyle;
const elementHeight = displayValue.isValueContainedInElement ? barHeight : displayValue.height;

const displayValueHeight = elementHeight + padding;
const displayValueWidth = displayValue.width + padding;

const displayValueY = barHeight >= displayValueHeight ? y : y - displayValueHeight;
const displayValueX = displayValueWidth > barWidth ?
x - Math.abs(barWidth - displayValueWidth) / 2
: x + Math.abs(barWidth - displayValueWidth) / 2;

const rotatedDisplayValueX = displayValueHeight > barWidth ?
x - Math.abs(barWidth - displayValueHeight) / 2
: x + Math.abs(barWidth - displayValueHeight) / 2;

const displayValueOffsetY = displayValueStyle.offsetY || 0;
const displayValueOffsetX = displayValueStyle.offsetX || 0;

const props = {
align: 'center',
verticalAlign: 'top',
...displayValueStyle,
text: displayValue.text,
width: displayValueWidth,
height: displayValueHeight,
offsetY: displayValueOffsetY,
x: displayValueX,
y: displayValueY,
};

switch (chartRotation) {
case 0:
props.x = displayValueX;
props.y = displayValueY;
break;
case 180:
props.x = chartWidth - displayValueX - displayValueWidth;
props.y = chartHeight - displayValueY - displayValueHeight;
props.verticalAlign = 'bottom';
break;
case 90:
props.x = (barHeight >= displayValueWidth) ?
chartWidth - displayValueY - displayValueWidth
: chartWidth - displayValueY;
props.y = rotatedDisplayValueX;
props.verticalAlign = 'middle';

if (displayValue.isValueContainedInElement) {
props.x = chartWidth - y - barHeight;
props.y = x;
props.width = barHeight >= displayValueWidth ? barHeight : 0;
props.height = displayValue.height <= barWidth ? barWidth : 0;
props.align = 'right';
}
break;
case -90:
props.x = (barHeight >= displayValueWidth) ? displayValueY : displayValueY - displayValueWidth;
props.y = chartHeight - rotatedDisplayValueX - displayValueHeight;
props.verticalAlign = 'middle';

if (displayValue.isValueContainedInElement) {
props.x = y;
props.y = chartHeight - x - barWidth;
props.width = barHeight >= displayValueWidth ? barHeight : 0;
props.height = displayValue.height <= barWidth ? barWidth : 0;
props.align = 'left';
}
break;
}

const clipHeight = displayValue.isValueContainedInElement ? displayValue.height : props.height;
const clipWidth = displayValue.isValueContainedInElement ? displayValue.width : props.width;

const clipOffsetY = chartRotation === 180 ? barHeight - displayValue.height : 0;
const clipOffsetX = chartRotation === 90 ? barHeight - displayValue.width : 0;

const isOverflowX = props.x + clipWidth - displayValueOffsetX > chartWidth
|| props.x + clipOffsetX - displayValueOffsetX < 0;
const isOverflowY = props.y + clipHeight - displayValueOffsetY > chartHeight
|| props.y + clipOffsetY - displayValueOffsetY < 0;

if (displayValue.hideClippedValue && (isOverflowX || isOverflowY)) {
props.width = 0;
props.height = 0;
}

return props;
}

export function buildLinePointProps({
lineIndex,
pointIndex,
Expand Down
Loading