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 ( +
+ + + event => { + if (!events) return; + alert(`clicked: ${JSON.stringify(data)}`); + }} + onMouseLeave={data => event => { + tooltipTimeout = setTimeout(() => { + hideTooltip(); + }, 300); + }} + onMouseMove={data => event => { + if (tooltipTimeout) clearTimeout(tooltipTimeout); + showTooltip({ + tooltipData: data, + tooltipTop: margin.top + yScale(y(data.data)), + tooltipLeft: margin.left + data.width + 75, + }); + }} + /> + ({ + fill: '#a44afe', + fontSize: 11, + textAnchor: 'middle', + })} + /> + ({ + fill: '#a44afe', + fontSize: 11, + textAnchor: 'middle', + })} + /> + +
+ +
+ {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 ( +
+ + + event => { + if (!events) return; + alert(\`clicked: \${JSON.stringify(data)}\`); + }} + onMouseLeave={data => event => { + tooltipTimeout = setTimeout(() => { + hideTooltip(); + }, 300); + }} + onMouseMove={data => event => { + if (tooltipTimeout) clearTimeout(tooltipTimeout); + showTooltip({ + tooltipData: data, + tooltipTop: margin.top + yScale(y(data.data)), + tooltipLeft: margin.left + data.width + 75, + }); + }} + /> + ({ + fill: '#a44afe', + fontSize: 11, + textAnchor: 'middle', + })} + /> + ({ + fill: '#a44afe', + fontSize: 11, + textAnchor: 'middle', + })} + /> + +
+ +
+ {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); + }); +});