diff --git a/packages/vx-demo/components/gallery.js b/packages/vx-demo/components/gallery.js
index 69658489e..b91fb09dd 100644
--- a/packages/vx-demo/components/gallery.js
+++ b/packages/vx-demo/components/gallery.js
@@ -17,6 +17,7 @@ import MultiLine from '../components/tiles/multiline';
import Axis from '../components/tiles/axis';
import BarGroup from '../components/tiles/bargroup';
import BarStack from '../components/tiles/barstack';
+import BarStackHorizontal from '../components/tiles/barstackhorizontal';
import Heatmap from '../components/tiles/heatmap';
import LineRadial from '../components/tiles/lineradial';
import Pies from '../components/tiles/pie';
@@ -99,6 +100,7 @@ export default class Gallery extends React.Component {
const t21 = this.state.dimensions[20] || [8, 300];
const t22 = this.state.dimensions[21] || [8, 300];
const t23 = this.state.dimensions[22] || [8, 300];
+ const t24 = this.state.dimensions[23] || [8, 300];
return (
@@ -680,6 +682,30 @@ export default class Gallery extends React.Component {
+
+
+ this.nodes.add(d)}
+ style={{
+ background: '#FAF7E9',
+ }}
+ >
+
+
+
+
+
Bar Stack Horizontal
+
+
+
+
+
{}
{}
diff --git a/packages/vx-demo/components/tiles/barstackhorizontal.js b/packages/vx-demo/components/tiles/barstackhorizontal.js
new file mode 100644
index 000000000..b97a94ba8
--- /dev/null
+++ b/packages/vx-demo/components/tiles/barstackhorizontal.js
@@ -0,0 +1,181 @@
+import React from 'react';
+import { BarStackHorizontal } from '@vx/shape';
+import { Group } from '@vx/group';
+import { AxisBottom, AxisLeft } from '@vx/axis';
+import { cityTemperature } from '@vx/mock-data';
+import { scaleBand, scaleLinear, scaleOrdinal } from '@vx/scale';
+import { timeParse, timeFormat } from 'd3-time-format';
+import { withTooltip, Tooltip } from '@vx/tooltip';
+import { LegendOrdinal } from '@vx/legend';
+import { extent, max } from 'd3-array';
+
+export default withTooltip(
+ ({
+ width,
+ height,
+ events = false,
+ margin = {
+ top: 40,
+ left: 0,
+ },
+ tooltipOpen,
+ tooltipLeft,
+ tooltipTop,
+ tooltipData,
+ hideTooltip,
+ showTooltip
+ }) => {
+ if (width < 10) return null;
+
+ const data = cityTemperature.slice(0, 12);
+ const keys = Object.keys(data[0]).filter(d => d !== 'date');
+ const parseDate = timeParse('%Y%m%d');
+ const format = timeFormat('%b %d');
+ const formatDate = date => format(parseDate(date));
+
+ // accessors
+ const y = d => d.date;
+ const x = d => d.value;
+
+ const totals = data.reduce((ret, cur) => {
+ const t = keys.reduce((dailyTotal, k) => {
+ dailyTotal += +cur[k];
+ return dailyTotal;
+ }, 0);
+ ret.push(t);
+ return ret;
+ }, []);
+
+ // bounds
+ const xMax = width;
+ const yMax = height - margin.top - 100;
+
+ // // scales
+ const xScale = scaleLinear({
+ rangeRound: [0, xMax],
+ domain: [0, max(totals)],
+ nice: true,
+ });
+ const yScale = scaleBand({
+ rangeRound: [yMax, 0],
+ domain: data.map(y),
+ padding: 0.2,
+ tickFormat: () => val => formatDate(val)
+ });
+ const zScale = scaleOrdinal({
+ domain: keys,
+ range: ['#6c5efb', '#c998ff', '#a44afe']
+ });
+
+ let tooltipTimeout;
+
+ return (
+
+
+
+
+
+ {tooltipOpen &&
+
+
+
+ {tooltipData.key}
+
+
+
+ {tooltipData.data[tooltipData.key]}℉
+
+
+
+ {tooltipData.xFormatted}
+
+
+ }
+
+ );
+ }
+);
\ No newline at end of file
diff --git a/packages/vx-demo/pages/barstackhorizontal.js b/packages/vx-demo/pages/barstackhorizontal.js
new file mode 100644
index 000000000..17ccca445
--- /dev/null
+++ b/packages/vx-demo/pages/barstackhorizontal.js
@@ -0,0 +1,192 @@
+import React from 'react';
+import Show from '../components/show';
+import BarStackHorizontal from '../components/tiles/barstackhorizontal';
+
+export default () => {
+ return (
+
+ {`import React from 'react';
+import { BarStackHorizontal } from '@vx/shape';
+import { Group } from '@vx/group';
+import { AxisBottom, AxisLeft } from '@vx/axis';
+import { cityTemperature } from '@vx/mock-data';
+import { scaleBand, scaleLinear, scaleOrdinal } from '@vx/scale';
+import { timeParse, timeFormat } from 'd3-time-format';
+import { withTooltip, Tooltip } from '@vx/tooltip';
+import { LegendOrdinal } from '@vx/legend';
+import { extent, max } from 'd3-array';
+
+export default withTooltip(
+ ({
+ width,
+ height,
+ events = false,
+ margin = {
+ top: 40,
+ left: 0,
+ },
+ tooltipOpen,
+ tooltipLeft,
+ tooltipTop,
+ tooltipData,
+ hideTooltip,
+ showTooltip
+ }) => {
+ if (width < 10) return null;
+
+ const data = cityTemperature.slice(0, 12);
+ const keys = Object.keys(data[0]).filter(d => d !== 'date');
+ const parseDate = timeParse('%Y%m%d');
+ const format = timeFormat('%b %d');
+ const formatDate = date => format(parseDate(date));
+
+ // accessors
+ const y = d => d.date;
+ const x = d => d.value;
+
+ const totals = data.reduce((ret, cur) => {
+ const t = keys.reduce((dailyTotal, k) => {
+ dailyTotal += +cur[k];
+ return dailyTotal;
+ }, 0);
+ ret.push(t);
+ return ret;
+ }, []);
+
+ // bounds
+ const xMax = width;
+ const yMax = height - margin.top - 100;
+
+ // // scales
+ const xScale = scaleLinear({
+ rangeRound: [0, xMax],
+ domain: [0, max(totals)],
+ nice: true,
+ });
+ const yScale = scaleBand({
+ rangeRound: [yMax, 0],
+ domain: data.map(y),
+ padding: 0.2,
+ tickFormat: () => val => formatDate(val)
+ });
+ const zScale = scaleOrdinal({
+ domain: keys,
+ range: ['#6c5efb', '#c998ff', '#a44afe']
+ });
+
+ let tooltipTimeout;
+
+ return (
+
+
+
+
+
+ {tooltipOpen &&
+
+
+
+ {tooltipData.key}
+
+
+
+ {tooltipData.data[tooltipData.key]}℉
+
+
+
+ {tooltipData.xFormatted}
+
+
+ }
+
+ );
+ }
+);
+`}
+
+ );
+};
diff --git a/packages/vx-shape/build/index.js b/packages/vx-shape/build/index.js
new file mode 100644
index 000000000..df49d0515
--- /dev/null
+++ b/packages/vx-shape/build/index.js
@@ -0,0 +1,202 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _Arc = require('./shapes/Arc');
+
+Object.defineProperty(exports, 'Arc', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_Arc).default;
+ }
+});
+
+var _Pie = require('./shapes/Pie');
+
+Object.defineProperty(exports, 'Pie', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_Pie).default;
+ }
+});
+
+var _Line = require('./shapes/Line');
+
+Object.defineProperty(exports, 'Line', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_Line).default;
+ }
+});
+
+var _LinePath = require('./shapes/LinePath');
+
+Object.defineProperty(exports, 'LinePath', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_LinePath).default;
+ }
+});
+
+var _LineRadial = require('./shapes/LineRadial');
+
+Object.defineProperty(exports, 'LineRadial', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_LineRadial).default;
+ }
+});
+
+var _LinkHorizontal = require('./shapes/LinkHorizontal');
+
+Object.defineProperty(exports, 'LinkHorizontal', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_LinkHorizontal).default;
+ }
+});
+
+var _LinkVertical = require('./shapes/LinkVertical');
+
+Object.defineProperty(exports, 'LinkVertical', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_LinkVertical).default;
+ }
+});
+
+var _LinkRadial = require('./shapes/LinkRadial');
+
+Object.defineProperty(exports, 'LinkRadial', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_LinkRadial).default;
+ }
+});
+
+var _Area = require('./shapes/Area');
+
+Object.defineProperty(exports, 'Area', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_Area).default;
+ }
+});
+
+var _AreaClosed = require('./shapes/AreaClosed');
+
+Object.defineProperty(exports, 'AreaClosed', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_AreaClosed).default;
+ }
+});
+
+var _AreaStack = require('./shapes/AreaStack');
+
+Object.defineProperty(exports, 'AreaStack', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_AreaStack).default;
+ }
+});
+
+var _Bar = require('./shapes/Bar');
+
+Object.defineProperty(exports, 'Bar', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_Bar).default;
+ }
+});
+
+var _BarGroup = require('./shapes/BarGroup');
+
+Object.defineProperty(exports, 'BarGroup', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_BarGroup).default;
+ }
+});
+
+var _BarStack = require('./shapes/BarStack');
+
+Object.defineProperty(exports, 'BarStack', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_BarStack).default;
+ }
+});
+
+var _BarStackHorizontal = require('./shapes/BarStackHorizontal');
+
+Object.defineProperty(exports, 'BarStackHorizontal', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_BarStackHorizontal).default;
+ }
+});
+
+var _Stack = require('./shapes/Stack');
+
+Object.defineProperty(exports, 'Stack', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_Stack).default;
+ }
+});
+
+var _callOrValue = require('./util/callOrValue');
+
+Object.defineProperty(exports, 'callOrValue', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_callOrValue).default;
+ }
+});
+
+var _stackOffset = require('./util/stackOffset');
+
+Object.defineProperty(exports, 'stackOffset', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_stackOffset).default;
+ }
+});
+Object.defineProperty(exports, 'STACK_OFFSETS', {
+ enumerable: true,
+ get: function get() {
+ return _stackOffset.STACK_OFFSETS;
+ }
+});
+Object.defineProperty(exports, 'STACK_OFFSET_NAMES', {
+ enumerable: true,
+ get: function get() {
+ return _stackOffset.STACK_OFFSET_NAMES;
+ }
+});
+
+var _stackOrder = require('./util/stackOrder');
+
+Object.defineProperty(exports, 'stackOrder', {
+ enumerable: true,
+ get: function get() {
+ return _interopRequireDefault(_stackOrder).default;
+ }
+});
+Object.defineProperty(exports, 'STACK_ORDERS', {
+ enumerable: true,
+ get: function get() {
+ return _stackOrder.STACK_ORDERS;
+ }
+});
+Object.defineProperty(exports, 'STACK_ORDER_NAMES', {
+ enumerable: true,
+ get: function get() {
+ return _stackOrder.STACK_ORDER_NAMES;
+ }
+});
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
\ No newline at end of file
diff --git a/packages/vx-shape/src/index.js b/packages/vx-shape/src/index.js
index 010f77563..ccf71594d 100644
--- a/packages/vx-shape/src/index.js
+++ b/packages/vx-shape/src/index.js
@@ -12,6 +12,7 @@ export { default as AreaStack } from './shapes/AreaStack';
export { default as Bar } from './shapes/Bar';
export { default as BarGroup } from './shapes/BarGroup';
export { default as BarStack } from './shapes/BarStack';
+export { default as BarStackHorizontal } from './shapes/BarStackHorizontal';
export { default as Stack } from './shapes/Stack';
export { default as callOrValue } from './util/callOrValue';
export {
diff --git a/packages/vx-shape/src/shapes/BarStackHorizontal.js b/packages/vx-shape/src/shapes/BarStackHorizontal.js
new file mode 100644
index 000000000..aeb552eb2
--- /dev/null
+++ b/packages/vx-shape/src/shapes/BarStackHorizontal.js
@@ -0,0 +1,77 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+import { Group } from '@vx/group';
+import Bar from './Bar';
+import { stack as d3stack } from 'd3-shape';
+
+export default function BarStackHorizontal({
+ data,
+ className,
+ top,
+ left,
+ y,
+ xScale,
+ yScale,
+ zScale,
+ keys,
+ height,
+ ...restProps
+}) {
+ const series = d3stack().keys(keys)(data);
+ const format = yScale.tickFormat ? yScale.tickFormat() : d => d;
+ const bandwidth = yScale.bandwidth();
+ const step = yScale.step();
+ const paddingInner = yScale.paddingInner();
+ const paddingOuter = yScale.paddingOuter();
+ return (
+
+ {series &&
+ series.map((s, i) => {
+ return (
+
+ {s.map((d, ii) => {
+ const barWidth = xScale(d[1]) - xScale(d[0]);
+ return (
+
+ );
+ })}
+
+ );
+ })}
+
+ );
+}
+
+BarStackHorizontal.propTypes = {
+ data: PropTypes.array.isRequired,
+ y: PropTypes.func.isRequired,
+ xScale: PropTypes.func.isRequired,
+ yScale: PropTypes.func.isRequired,
+ zScale: PropTypes.func.isRequired,
+ keys: PropTypes.array.isRequired,
+ className: PropTypes.string,
+ top: PropTypes.number,
+ left: PropTypes.number,
+};
diff --git a/packages/vx-shape/test/BarStackHorizontal.test.js b/packages/vx-shape/test/BarStackHorizontal.test.js
new file mode 100644
index 000000000..95db2e112
--- /dev/null
+++ b/packages/vx-shape/test/BarStackHorizontal.test.js
@@ -0,0 +1,66 @@
+import React from 'react';
+import { BarStackHorizontal } from '../src';
+import { shallow } from 'enzyme';
+
+const yScale = jest.fn();
+yScale.bandwidth = jest.fn();
+yScale.step = jest.fn();
+yScale.paddingInner = jest.fn();
+yScale.paddingOuter = jest.fn();
+
+describe('', () => {
+ test('it should be defined', () => {
+ expect(BarStackHorizontal).toBeDefined();
+ });
+
+ test('it should have className .vx-bar-stack-horizontal', () => {
+ const wrapper = shallow(
+ d}
+ xScale={d => d}
+ yScale={yScale}
+ zScale={d => d}
+ keys={[]}
+ />,
+ );
+ expect(wrapper.prop('className')).toEqual('vx-bar-stack-horizontal');
+ });
+
+ test('it should set className prop', () => {
+ const wrapper = shallow(
+ d}
+ xScale={d => d}
+ yScale={yScale}
+ zScale={d => d}
+ keys={[]}
+ />,
+ );
+ expect(wrapper.prop('className')).toEqual('vx-bar-stack-horizontal test');
+ });
+
+ test('it should set top & left props', () => {
+ const wrapper = shallow(
+ d}
+ xScale={d => d}
+ yScale={yScale}
+ zScale={d => d}
+ keys={[]}
+ />,
+ );
+ expect(wrapper.prop('top')).toEqual(2);
+ expect(wrapper.prop('left')).toEqual(3);
+ });
+});