diff --git a/packages/demo/examples/01-xy-chart/HorizontalBarChart.jsx b/packages/demo/examples/01-xy-chart/HorizontalBarChart.jsx
new file mode 100644
index 00000000..1f839428
--- /dev/null
+++ b/packages/demo/examples/01-xy-chart/HorizontalBarChart.jsx
@@ -0,0 +1,127 @@
+/* eslint react/prop-types: 0 */
+import React from 'react';
+import { timeParse, timeFormat } from 'd3-time-format';
+
+import {
+ XYChart,
+ CrossHair,
+ XAxis,
+ YAxis,
+ theme,
+ withScreenSize,
+ BarSeries,
+ PatternLines,
+} from '@data-ui/xy-chart';
+
+import colors, { allColors } from '@data-ui/theme/lib/color';
+
+import { timeSeriesData } from './data';
+
+export const parseDate = timeParse('%Y%m%d');
+export const formatDate = timeFormat('%b %d');
+export const formatYear = timeFormat('%Y');
+export const dateFormatter = date => formatYear(parseDate(date));
+
+const categoryHorizontalData = timeSeriesData.map((d, i) => ({
+ x: d.y,
+ y: i + 1,
+}));
+
+const categoryData = timeSeriesData.map((d, i) => ({
+ x: i + 1,
+ y: d.y,
+}));
+
+class HorizontalBarChartExample extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ direction: 'horizontal',
+ };
+ }
+
+ renderControls() {
+ return (
+
+ );
+ }
+
+ render() {
+ const { screenWidth } = this.props;
+ const { direction } = this.state;
+ const categoryScale = { type: 'band', paddingInner: 0.15 };
+ const valueScale = { type: 'linear' };
+ const horizontal = direction === 'horizontal';
+
+ return (
+
+ {this.renderControls()}
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default withScreenSize(HorizontalBarChartExample);
diff --git a/packages/demo/examples/01-xy-chart/index.jsx b/packages/demo/examples/01-xy-chart/index.jsx
index 3dfebf86..7637c8ad 100644
--- a/packages/demo/examples/01-xy-chart/index.jsx
+++ b/packages/demo/examples/01-xy-chart/index.jsx
@@ -37,6 +37,7 @@ import AreaDifferenceSeriesExample from './AreaDifferenceSeriesExample';
import { BoxPlotSeriesExample, BoxPlotViolinPlotSeriesExample } from './StatsSeriesExample';
import BrushableLineChart from './BrushableLineChart';
import BrushableLinkedLineCharts from './BrushableLinkedLineCharts';
+import HorizontalBarChartExample from './HorizontalBarChart';
import {
circlePackData,
@@ -102,6 +103,11 @@ export default {
),
},
+ {
+ description: 'HorizontalBarChartExample',
+ components: [BarSeries, CrossHair],
+ example: () => ,
+ },
{
description: 'LineSeries',
components: [LineSeries, CrossHair],
diff --git a/packages/xy-chart/src/chart/XYChart.jsx b/packages/xy-chart/src/chart/XYChart.jsx
index ad523d08..57dfd4fd 100644
--- a/packages/xy-chart/src/chart/XYChart.jsx
+++ b/packages/xy-chart/src/chart/XYChart.jsx
@@ -268,7 +268,6 @@ class XYChart extends React.PureComponent {
} = this.state;
const { numXTicks, numYTicks } = this.getNumTicks(innerWidth, innerHeight);
- const barWidth = xScale.barWidth || (xScale.bandwidth && xScale.bandwidth()) || 0;
const CrossHairs = []; // ensure these are the top-most layer
let Brush = null;
let xAxisOrientation;
@@ -329,7 +328,6 @@ class XYChart extends React.PureComponent {
return React.cloneElement(Child, {
xScale,
yScale,
- barWidth,
onClick:
Child.props.onClick ||
(Child.props.disableMouseEvents ? undefined : this.handleClick),
@@ -407,7 +405,9 @@ class XYChart extends React.PureComponent {
left:
xScale(getX(tooltipData.datum) || 0) +
(xScale.bandwidth ? xScale.bandwidth() / 2 : 0),
- top: yScale(getY(tooltipData.datum) || 0),
+ top:
+ yScale(getY(tooltipData.datum) || 0) +
+ (yScale.bandwidth ? yScale.bandwidth() / 2 : 0),
xScale,
yScale,
}),
diff --git a/packages/xy-chart/src/series/BarSeries.jsx b/packages/xy-chart/src/series/BarSeries.jsx
index 38c83d61..880dd721 100644
--- a/packages/xy-chart/src/series/BarSeries.jsx
+++ b/packages/xy-chart/src/series/BarSeries.jsx
@@ -11,20 +11,20 @@ import sharedSeriesProps from '../utils/sharedSeriesProps';
const propTypes = {
...sharedSeriesProps,
- barWidth: PropTypes.number,
data: barSeriesDataShape.isRequired,
fill: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
fillOpacity: PropTypes.oneOfType([PropTypes.func, PropTypes.number]),
stroke: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
strokeWidth: PropTypes.oneOfType([PropTypes.func, PropTypes.number]),
+ horizontal: PropTypes.bool,
};
const defaultProps = {
- barWidth: null,
fill: themeColors.default,
fillOpacity: null,
stroke: '#FFFFFF',
strokeWidth: 1,
+ horizontal: false,
};
const x = d => d.x;
@@ -34,7 +34,6 @@ const noEventsStyles = { pointerEvents: 'none' };
export default class BarSeries extends React.PureComponent {
render() {
const {
- barWidth,
data,
disableMouseEvents,
fill,
@@ -46,24 +45,32 @@ export default class BarSeries extends React.PureComponent {
onClick,
onMouseMove,
onMouseLeave,
+ horizontal,
} = this.props;
+ if (!xScale || !yScale) return null;
+ const valueScale = horizontal ? xScale : yScale;
+ const categoryScale = horizontal ? yScale : xScale;
+ const barWidth =
+ categoryScale.barWidth || (categoryScale.bandwidth && categoryScale.bandwidth()) || 0;
+ const valueField = horizontal ? x : y;
+ const categoryField = horizontal ? y : x;
- if (!xScale || !yScale || !barWidth) return null;
-
- const maxHeight = (yScale.range() || [0])[0];
- const offset = xScale.offset || 0;
+ const maxBarLength = Math.max(...valueScale.range());
+ const offset = categoryScale.offset || 0;
return (
{data.map((d, i) => {
- const barHeight = maxHeight - yScale(y(d));
+ const barLength = horizontal
+ ? valueScale(valueField(d))
+ : maxBarLength - valueScale(valueField(d));
const color = d.fill || callOrValue(fill, d, i);
- const barX = xScale(x(d)) - offset;
+ const barPosition = categoryScale(categoryField(d)) - offset;
return (
- isDefined(d.y) && (
+ isDefined(horizontal ? d.x : d.y) && (
d && d.x;
const xString = d => getX(d).toString();
+const getY = d => d && d.y;
+const yString = d => getY(d).toString();
export default function collectScalesFromProps(props) {
const { xScale: xScaleObject, yScale: yScaleObject, children } = props;
@@ -42,20 +44,26 @@ export default function collectScalesFromProps(props) {
Children.forEach(children, Child => {
// Child-specific scales or adjustments here
const name = componentName(Child);
- if (isBarSeries(name) && xScaleObject.type !== 'band') {
- const dummyBand = getScaleForAccessor({
- allData,
- minAccessor: xString,
- maxAccessor: xString,
- type: 'band',
- rangeRound: [0, innerWidth],
- paddingOuter: 1,
- });
+ if (isBarSeries(name)) {
+ const { horizontal } = Child.props;
+ const categoryScaleObject = horizontal ? yScaleObject : xScaleObject;
+ if (categoryScaleObject.type !== 'band') {
+ const categoryScale = horizontal ? yScale : xScale;
+ const range = horizontal ? innerHeight : innerWidth;
+ const dummyBand = getScaleForAccessor({
+ allData,
+ minAccessor: horizontal ? yString : xString,
+ maxAccessor: horizontal ? yString : xString,
+ type: 'band',
+ rangeRound: [0, range],
+ paddingOuter: 1,
+ });
- const offset = dummyBand.bandwidth() / 2;
- xScale.range([offset, innerWidth - offset]);
- xScale.barWidth = dummyBand.bandwidth();
- xScale.offset = offset;
+ const offset = dummyBand.bandwidth() / 2;
+ categoryScale.range([offset, range - offset]);
+ categoryScale.barWidth = dummyBand.bandwidth();
+ categoryScale.offset = offset;
+ }
}
if (isCirclePackSeries(name)) {
yScale.domain([-innerHeight / 2, innerHeight / 2]);
diff --git a/packages/xy-chart/test/series/BarSeries.test.js b/packages/xy-chart/test/series/BarSeries.test.js
index e2296910..c9be42f5 100644
--- a/packages/xy-chart/test/series/BarSeries.test.js
+++ b/packages/xy-chart/test/series/BarSeries.test.js
@@ -62,6 +62,60 @@ describe('', () => {
expect(barSeries.find(Bar)).toHaveLength(mockData.length - 1);
});
+ it('should render bar width correctly for horizontal barchart', () => {
+ const maxWidth = 500;
+ const maxHeight = 10;
+ const wrapper = shallow(
+
+ ({
+ x: mockData.length - i,
+ y: d.date,
+ }))}
+ horizontal
+ />
+ ,
+ );
+ const barSeries = wrapper.find(BarSeries).dive();
+ expect(
+ barSeries
+ .find(Bar)
+ .first()
+ .props().width,
+ ).toBe(maxWidth);
+ });
+
+ it('should not render bars for null data for horizontal barchart', () => {
+ const wrapper = shallow(
+
+ ({
+ x: i === 0 ? null : d.num,
+ y: d.date,
+ }))}
+ horizontal
+ />
+ ,
+ );
+ const barSeries = wrapper.find(BarSeries).dive();
+ expect(barSeries.find(Bar)).toHaveLength(mockData.length - 1);
+ });
+
it('should work with time or band scales', () => {
const timeWrapper = shallow(