Skip to content

Commit

Permalink
Merge pull request #73 from williaster/chris--falsy-tooltip
Browse files Browse the repository at this point in the history
add support to hide tooltips, key => seriesKey to avoid React prop clash.
  • Loading branch information
williaster authored Nov 9, 2017
2 parents 49733e9 + f88831a commit de7642e
Show file tree
Hide file tree
Showing 12 changed files with 63 additions and 23 deletions.
12 changes: 6 additions & 6 deletions packages/demo/examples/01-xy-chart/ResponsiveXYChart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ export const formatDate = timeFormat('%b %d');
export const dateFormatter = date => formatDate(parseDate(date));

// this is a little messy to handle all cases across series types
function renderTooltip({ datum, key, color }) {
function renderTooltip({ datum, seriesKey, color }) {
const { x, x0, y, value } = datum;
let xVal = x || x0;
if (typeof xVal === 'string') {
xVal = parseDate(xVal) === null ? xVal : dateFormatter(xVal);
} else if (typeof xVal !== 'string' && Number(xVal) > 1000000) {
xVal = formatDate(xVal);
}
const yVal = key && datum[key] ? datum[key] : (y || value || '--');
const yVal = seriesKey && datum[seriesKey] ? datum[seriesKey] : (y || value || '--');
return (
<div>
{key &&
{seriesKey &&
<div>
<strong style={{ color }}>{key}</strong>
<strong style={{ color }}>{seriesKey}</strong>
</div>}
<div>
<strong style={{ color }}>x </strong>
Expand All @@ -44,8 +44,8 @@ function ResponsiveXYChart({ screenWidth, children, ...rest }) {
return (
<XYChart
theme={theme}
width={Math.min(1000, screenWidth / 1.5)}
height={Math.min(1000 / 2, screenWidth / 1.5 / 2)}
width={Math.min(700, screenWidth / 1.5)}
height={Math.min(700 / 2, screenWidth / 1.5 / 2)}
renderTooltip={renderTooltip}
{...rest}
>
Expand Down
4 changes: 2 additions & 2 deletions packages/histogram/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ tickValues | PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.
### Tooltips
Tooltips are supported for histogram `BarSeries`. The _easiest_ way to use tooltips out of the box is by passing a `renderTooltip` function to `<Histogram />` as shown in the above example. This function takes an object with the shape `{ event, datum, data, color }` as input and should return the inner contents of the tooltip (not the tooltip container!) as shown above. `datum` corresponds to the _binned_ data point, see the above-specified shapes which depend on whether your bins are categorical or numeric. `color` represents the bar fill.
Tooltips are supported for histogram `BarSeries`. The _easiest_ way to use tooltips out of the box is by passing a `renderTooltip` function to `<Histogram />` as shown in the above example. This function takes an object with the shape `{ event, datum, data, color }` as input and should return the inner contents of the tooltip (not the tooltip container!) as shown above. `datum` corresponds to the _binned_ data point, see the above-specified shapes which depend on whether your bins are categorical or numeric. `color` represents the bar fill. If this function returns a `falsy` value, a tooltip will not be rendered.
Under the covers this will wrap the `<Histogram />` component in the exported `<WithTooltip />` HOC, which wraps the `svg` in a `<div />` and handles the positioning and rendering of an HTML-based tooltip with the contents returned by `renderTooltip()`. This tooltip is aware of the bounds of its container and should position itself "smartly".
Expand All @@ -169,7 +169,7 @@ Name | Type | Default | Description
------------ | ------------- | ------- | ----
children | PropTypes.func or PropTypes.object | - | Child function (to call) or element (to clone) with onMouseMove, onMouseLeave, and tooltipData props/keys
className | PropTypes.string | - | Class name to add to the `<div>` container wrapper
renderTooltip | PropTypes.func.isRequired | - | Renders the _contents_ of the tooltip, signature of `({ event, data, datum, color }) => node`
renderTooltip | PropTypes.func.isRequired | - | Renders the _contents_ of the tooltip, signature of `({ event, data, datum, color }) => node`. If this function returns a `falsy` value, a tooltip will not be rendered.
styles | PropTypes.object | {} | Styles to add to the `<div>` container wrapper
TooltipComponent | PropTypes.func or PropTypes.object | `@vx`'s `TooltipWithBounds` | Component (not instance) to use as the tooltip container component. It is passed `top` and `left` numbers for positioning
tooltipTimeout | PropTypes.number | 200 | Timeout in ms for the tooltip to hide upon calling `onMouseLeave`
Expand Down
4 changes: 3 additions & 1 deletion packages/network/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ See the demo at <a href="https://williaster.github.io/data-ui" target="_blank">w


### Tooltips
Tooltips are controlled with the `renderTooltip` function passed to `<Network />`. This function takes an object with the shape `{ event, index, id, data }` as input and is expected to return the inner _contents_ of the tooltip (not the tooltip container!) as shown above. Under the covers this will wrap the `<Network />` component in the exported `<WithTooltip />` HOC, which wraps the `svg` in a `<div />` and handles the positioning and rendering of an HTML-based tooltip with the contents returned by `renderTooltip()`. This tooltip is aware of the bounds of its container and should position itself "smartly".
Tooltips are controlled with the `renderTooltip` function passed to `<Network />`. This function takes an object with the shape `{ event, index, id, data }` as input and is expected to return the inner _contents_ of the tooltip (not the tooltip container!) as shown above. If this function returns a `falsy` value, a tooltip will not be rendered.

Under the covers this will wrap the `<Network />` component in the exported `<WithTooltip />` HOC, which wraps the `svg` in a `<div />` and handles the positioning and rendering of an HTML-based tooltip with the contents returned by `renderTooltip()`. This tooltip is aware of the bounds of its container and should position itself "smartly".

### Roadmap
- more layout algorithms
Expand Down
6 changes: 4 additions & 2 deletions packages/radial-chart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ export default () => (
```

### Tooltips
The _easiest_ way to use tooltips out of the box is by passing a `renderTooltip` function to `<RadialChart />`. This function takes an object with the shape `{ event, datum, data, fraction }` as input and should return the inner contents of the tooltip (not the tooltip container!) as shown above. Under the covers this will wrap the `<RadialChart />` component in the exported `<WithTooltip />` HOC, which wraps the `svg` in a `<div />` and handles the positioning and rendering of an HTML-based tooltip with the contents returned by `renderTooltip()`. This tooltip is aware of the bounds of its container and should position itself "smartly".
The _easiest_ way to use tooltips out of the box is by passing a `renderTooltip` function to `<RadialChart />`. This function takes an object with the shape `{ event, datum, data, fraction }` as input and should return the inner contents of the tooltip (not the tooltip container!) as shown above. If this function returns a `falsy` value, a tooltip will not be rendered.

Under the covers this will wrap the `<RadialChart />` component in the exported `<WithTooltip />` HOC, which wraps the `svg` in a `<div />` and handles the positioning and rendering of an HTML-based tooltip with the contents returned by `renderTooltip()`. This tooltip is aware of the bounds of its container and should position itself "smartly".

If you'd like more customizability over tooltip rendering you can do either of the following:

Expand All @@ -74,7 +76,7 @@ Name | Type | Default | Description
------------ | ------------- | ------- | ----
children | PropTypes.func or PropTypes.object | - | Child function (to call) or element (to clone) with onMouseMove, onMouseLeave, and tooltipData props/keys
className | PropTypes.string | - | Class name to add to the `<div>` container wrapper
renderTooltip | PropTypes.func.isRequired | - | Renders the _contents_ of the tooltip, signature of `({ event, data, datum, fraction }) => node`
renderTooltip | PropTypes.func.isRequired | - | Renders the _contents_ of the tooltip, signature of `({ event, data, datum, fraction }) => node`. If this function returns a `falsy` value, a tooltip will not be rendered.
styles | PropTypes.object | {} | Styles to add to the `<div>` container wrapper
TooltipComponent | PropTypes.func or PropTypes.object | `@vx`'s `TooltipWithBounds` | Component (not instance) to use as the tooltip container component. It is passed `top` and `left` numbers for positioning
tooltipTimeout | PropTypes.number | 200 | Timeout in ms for the tooltip to hide upon calling `onMouseLeave`
Expand Down
9 changes: 7 additions & 2 deletions packages/shared/src/enhancer/WithTooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ class WithTooltip extends React.PureComponent {
tooltipData,
};

const tooltipContent = renderTooltip
&& tooltipOpen
&& TooltipComponent
&& renderTooltip(tooltipData);

return (
<div style={styles} className={className}>

Expand All @@ -102,13 +107,13 @@ class WithTooltip extends React.PureComponent {
? children(childProps)
: React.cloneElement(React.Children.only(children), childProps)}

{tooltipOpen && TooltipComponent && renderTooltip &&
{Boolean(tooltipContent) &&
<TooltipComponent
key={Math.random()}
top={tooltipTop}
left={tooltipLeft}
>
{renderTooltip(tooltipData)}
{tooltipContent}
</TooltipComponent>}

{HoverStyles && <HoverStyles />}
Expand Down
33 changes: 32 additions & 1 deletion packages/shared/test/enhancer/WithTooltip.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,37 @@ describe('<WithTooltip />', () => {
expect(wrapper.find('#test').length).toBe(1);
});

test('it should not render a tooltip if renderTooltip returns a falsy value', () => {
const renderTooltip = jest.fn();
renderTooltip.mockReturnValue(<div id="test" />);

let mouseMove;
const wrapper = mount(
<WithTooltip
TooltipComponent={({ children }) => <div id="tooltip">{children}</div>}
renderTooltip={renderTooltip}
>
{({ onMouseMove }) => {
mouseMove = onMouseMove;
return <svg />;
}}
</WithTooltip>,
);

mouseMove({});
wrapper.update();
expect(renderTooltip).toHaveBeenCalledTimes(1);
expect(wrapper.find('#tooltip').length).toBe(1);
expect(wrapper.find('#test').length).toBe(1);

renderTooltip.mockReturnValue(null);
mouseMove({});
wrapper.update();
expect(renderTooltip).toHaveBeenCalledTimes(2);
expect(wrapper.find('#tooltip').length).toBe(0);
expect(wrapper.find('#test').length).toBe(0);
});

test('it should hide the value of renderTooltip on mouse leave', () => {
jest.useFakeTimers(); // needed for mouseLeave timeout

Expand Down Expand Up @@ -126,7 +157,7 @@ describe('<WithTooltip />', () => {
let mouseMove;
const wrapper = mount(
<WithTooltip
renderTooltip={() => null}
renderTooltip={() => 'test'}
TooltipComponent={Tooltip}
>
{({ onMouseMove }) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/sparkline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ Name | Type | Default | Description
------------ | ------------- | ------- | ----
children | PropTypes.func or PropTypes.object | - | Child function (to call) or element (to clone) with onMouseMove, onMouseLeave, and tooltipData props/keys
className | PropTypes.string | - | Class name to add to the `<div>` container wrapper
renderTooltip | PropTypes.func.isRequired | - | Renders the _contents_ of the tooltip, signature of `({ event, data, datum, color }) => node`
renderTooltip | PropTypes.func.isRequired | - | Renders the _contents_ of the tooltip, signature of `({ event, data, datum, color }) => node`. If this function returns a `falsy` value, a tooltip will not be rendered.
styles | PropTypes.object | {} | Styles to add to the `<div>` container wrapper
TooltipComponent | PropTypes.func or PropTypes.object | `@vx`'s `TooltipWithBounds` | Component (not instance) to use as the tooltip container component. It is passed `top` and `left` numbers for positioning
tooltipTimeout | PropTypes.number | 200 | Timeout in ms for the tooltip to hide upon calling `onMouseLeave`
Expand Down
4 changes: 2 additions & 2 deletions packages/xy-chart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ Note that only `x` values are needed for `CirclePackSeries`, `y` values are comp


### Tooltips
Tooltips are supported for all series types, but how you configure them will likely depend on which series combinations you're using and how much customization you need. The _easiest_ way to use tooltips out of the box is by passing a `renderTooltip` function to `<XYChart />` as shown in the above example. This function takes an object with the shape `{ event, datum, data, color }` as input and should return the inner contents of the tooltip (not the tooltip container!) as shown above.
Tooltips are supported for all series types, but how you configure them will likely depend on which series combinations you're using and how much customization you need. The _easiest_ way to use tooltips out of the box is by passing a `renderTooltip` function to `<XYChart />` as shown in the above example. This function takes an object with the shape `{ event, datum, data, color [, seriesKey] }` as input and should return the inner contents of the tooltip (not the tooltip container!) as shown above. If this function returns a `falsy` value, a tooltip will not be rendered.

Under the covers this will wrap the `<XYChart />` component in the exported `<WithTooltip />` HOC, which wraps the `<svg />` in a `<div />` and handles the positioning and rendering of an HTML-based tooltip with the contents returned by `renderTooltip()`. This tooltip is aware of the bounds of its container and should position itself "smartly".

Expand All @@ -202,7 +202,7 @@ Name | Type | Default | Description
------------ | ------------- | ------- | ----
children | PropTypes.func or PropTypes.object | - | Child function (to call) or element (to clone) with onMouseMove, onMouseLeave, and tooltipData props/keys
className | PropTypes.string | - | Class name to add to the `<div>` container wrapper
renderTooltip | PropTypes.func.isRequired | - | Renders the _contents_ of the tooltip, signature of `({ event, data, datum, color }) => node`
renderTooltip | PropTypes.func.isRequired | - | Renders the _contents_ of the tooltip, signature of `({ event, data, datum, color }) => node`. If this function returns a `falsy` value, a tooltip will not be rendered.
styles | PropTypes.object | {} | Styles to add to the `<div>` container wrapper
TooltipComponent | PropTypes.func or PropTypes.object | `@vx`'s `TooltipWithBounds` | Component (not instance) to use as the tooltip container component. It is passed `top` and `left` numbers for positioning
tooltipTimeout | PropTypes.number | 200 | Timeout in ms for the tooltip to hide upon calling `onMouseLeave`
Expand Down
2 changes: 1 addition & 1 deletion packages/xy-chart/src/series/GroupedBarSeries.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export default class GroupedBarSeries extends React.PureComponent {
strokeWidth={strokeWidth}
onMouseMove={onMouseMove && (d => (event) => {
const { key, data: datum } = d;
onMouseMove({ event, data, datum, key, color: zScale(key) });
onMouseMove({ event, data, datum, seriesKey: key, color: zScale(key) });
})}
onMouseLeave={onMouseLeave && (() => onMouseLeave)}
/>
Expand Down
2 changes: 1 addition & 1 deletion packages/xy-chart/src/series/StackedBarSeries.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export default class StackedBarSeries extends React.PureComponent {
strokeWidth={strokeWidth}
onMouseMove={onMouseMove && (d => (event) => {
const { data: datum, key } = d;
onMouseMove({ event, data, datum, key, color: zScale(key) });
onMouseMove({ event, data, datum, seriesKey: key, color: zScale(key) });
})}
onMouseLeave={onMouseLeave && (() => onMouseLeave)}
/>
Expand Down
4 changes: 2 additions & 2 deletions packages/xy-chart/test/GroupedBarSeries.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('<GroupedBarSeries />', () => {
});
});

test('it should call onMouseMove({ datum, data, event, key, color }) and onMouseLeave() on trigger', () => {
test('it should call onMouseMove({ datum, data, event, seriesKey, color }) and onMouseLeave() on trigger', () => {
const fills = ['magenta', 'maplesyrup', 'banana'];
const stackKeys = ['a', 'b', 'c'];
const onMouseMove = jest.fn();
Expand All @@ -95,7 +95,7 @@ describe('<GroupedBarSeries />', () => {
expect(args.data).toBe(mockData);
expect(args.datum).toBe(mockData[0]);
expect(args.event).toBeDefined();
expect(stackKeys.includes(args.key)).toBe(true);
expect(stackKeys.includes(args.seriesKey)).toBe(true);
expect(fills.includes(args.color)).toBe(true);

bar.simulate('mouseleave');
Expand Down
4 changes: 2 additions & 2 deletions packages/xy-chart/test/StackedBarSeries.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('<GroupedBarSeries />', () => {
});
});

test('it should call onMouseMove({ datum, data, event, key, color }) and onMouseLeave() on trigger', () => {
test('it should call onMouseMove({ datum, data, event, seriesKey, color }) and onMouseLeave() on trigger', () => {
const fills = ['magenta', 'maplesyrup', 'banana'];
const stackKeys = ['a', 'b', 'c'];
const onMouseMove = jest.fn();
Expand All @@ -95,7 +95,7 @@ describe('<GroupedBarSeries />', () => {
expect(args.data).toEqual(mockData);
expect(args.datum).toBe(mockData[0]);
expect(args.event).toBeDefined();
expect(stackKeys.includes(args.key)).toBe(true);
expect(stackKeys.includes(args.seriesKey)).toBe(true);
expect(fills.includes(args.color)).toBe(true);

bar.simulate('mouseleave');
Expand Down

0 comments on commit de7642e

Please sign in to comment.