Victory 0.12.0: The One True Tooltip
Victory 0.12.0: The One True Tooltip
19 September 2016
We are pleased to announce that the latest release of Victory finally includes tooltips! This feature has been in demand for a while, but nailing down an implementation strategy proved challenging. No other chart element exhibits the range of behaviors or visual possibilities as the simple tooltip. The One True Tooltip was impossible to build, because it was impossible to define. Instead we built a set of components that could be configured and combined to create The One True Tooltip however it might look.
<VictoryTooltip/>
<VictoryChart>
<VictoryBar
labelComponent={<VictoryTooltip/>}
data={[
{x: 2, y: 5, label: "right-side-up"},
{x: 4, y: -6, label: "upside-down"},
{x: 6, y: 4, label: "tiny"},
{x: 8, y: -5, label: "or a little \n BIGGER"},
{x: 10, y: 7, label: "automatically"}
]}
style={{
data: {width: 15, fill: "tomato"}
}}
/>
</VictoryChart>
The easiest way to add a tooltip to a chart is to replace the default label component with a tooltip like labelComponent={<VictoryTooltip/>}
Any Victory component that uses labels will pass in the appropriate text to your tooltip component. By default, tooltips are automatically sized to accomodate label text, and oriented correctly for each data point. onMouseOver
and onMouseOut
event handlers are also automatically registered for each data point.
Customizing VictoryTooltip
Default behaviors are incredibly convenient until they get in the way. VictoryTooltip
is written to be configurable, extendable, and replaceable. Users can alter props directly on the component like labelComponent={<VictoryTooltip height={75} orientation="top" flyoutStyle={{...}}/>}
. Custom tooltip containers can be created by replacing the default flyoutCompoment
with a custom path, or any other svg
element. Event handlers can even been replaced by extending VictoryTooltip
and overriding its static defaultEvents
array.
<Flyout/>
By default, VictoryTooltip
uses a component called Flyout
to render the tooltip container. Flyout
renders a path element that is calculated based on x
, y
, width
, height
, orientation
, cornerRadius
, pointerLength
and pointerWidth
. Because the path is calculated rather than scaled or transformed from a hard-coded path, a wide variety of container shapes is possible with minor configuration.
If something completely custom is required, a completely custom component can be suplied to VictoryTooltip
. Here’s an example of an extremely silly custom tooltip.
The custom HandPointer
component renders emoji hands rather than a path element.
class HandPointer extends React.Component {
static propTypes = {
x: React.PropTypes.number,
y: React.PropTypes.number,
orientation: React.PropTypes.string
};
render() {
const size = 70;
const pointer = this.props.orientation === "top" ? emoji : emoji;
const offset = this.props.orientation === "top" ? 5 : 55;
const x = this.props.x - size / 2;
const y = this.props.y + offset;
return (
<text x={x} y={y} fontSize={size}>
{pointer}
</text>
);
}
}
Once written, the custom component is supplied to VictoryTooltip
like <VictoryTooltip flyoutComponent={<HandPointer/>}/>
, and is useable just like any other tooltip.
<VictoryVoronoiTooltip/>
We wrote VictoryVoronoiTooltip
to improve the behavior of tooltips on components that are not easy to interact with, like line components or very small scatter points. VictoryVoronoiTooltip
has more in common with full data components like VictoryBar
than it does with simple label components, as it renders both data and labels. The data rendered by VictoryVoronoiTooltip
is a transparent voronoi diagram, with each polygon corresponding to a closest data point. This creates a much larger interactive area for each data point, and results in more fluid tooltip interactions.
To add a voronoi tooltip to a chart, simply provide it with data and labels like so:
<VictoryChart>
<VictoryLine
data={[
{x: 1, y: -5},
{x: 2, y: 4},
{x: 3, y: 2},
{x: 4, y: 3},
{x: 5, y: 1},
{x: 6, y: -3},
{x: 7, y: 3}
]}
/>
<VictoryVoronoiTooltip
labels={(d) => `x: ${d.x} \n y: ${d.y}`}
data={[
{x: 1, y: -5},
{x: 2, y: 4},
{x: 3, y: 2},
{x: 4, y: 3},
{x: 5, y: 1},
{x: 6, y: -3},
{x: 7, y: 3}
]}
/>
</VictoryChart>
We decided it was a little tedious to supply the same data to each component, so we made an enhancement to the VictoryGroup
wrapper component so that it can supply data to all its children. The equivalent example using VictoryGroup
looks like this:
<VictoryChart>
<VictoryGroup
data={[
{x: 1, y: -5},
{x: 2, y: 4},
{x: 3, y: 2},
{x: 4, y: 3},
{x: 5, y: 1},
{x: 6, y: -3},
{x: 7, y: 3}
]}
>
<VictoryLine/>
<VictoryVoronoiTooltip
labels={(d) => `x: ${d.x} \n y: ${d.y}`}
/>
</VictoryGroup>
</VictoryChart>
Much cleaner!
Customizing VictoryVoronoiTooltip
VictoryVoronoiTooltip
is an instance of the VictoryVoronoi
component that uses VictoryTooltip
as a default labelComponent
. Because of this modular assembly, any customizations that are possible for VictoryTooltip
are easily applied to VictoryVoronoiTooltip
by simply replacing components. To specify props such as cornerRadius
and pointerLength
from VictoryVoronoiTooltip
, add them directly to the label component labelComponent
like so: labelComponent={<VictoryTooltip cornerRadius={10} pointerLength={20} .../>}
Supporting Features
Supporting tooltips was the main goal of this release, but the work required to complete this feature resulted in a few cool features and enhancements.
VictoryVoronoi
Compoment
VictoryVoronoi
is exposed as its own component rather than being an intrinsic part of VictoryVoronoiTooltip
. Read more about this component in our docs.
Enhanced VictoryGroup
wrapper
The enhancements made to VictoryGroup
are convenient for sharing data and styles between child components, but they also make it possible to stack components as a set. Read more about when to use VictoryGroup
here.
Arbitrary component events with defaultEvents
VictoryTooltip
attaches events to whatever component uses it by exposing a static defaultEvents
array. Now any component that is included by another as a dataComponent
, labelComponent
etc., can register defaultEvents
and target elements in the component including it. For example, the default events supplied by VictoryTooltip
look like this:
static defaultEvents = [{
target: "data",
eventHandlers: {
onMouseOver: () => {
return {
target: "labels",
mutation: () => {
return { active: true };
}
};
},
onMouseOut: () => {
return {
target: "labels",
mutation: () => {
return { active: false };
}
};
}
}
}];
All rendered components are exported from VictoryCore
To make Victory more flexible and easier to extend we’ve exported all small, rendered components (i.e. Point
, Bar
, Flyout
etc.). We hope this change will make Victory even more fun to use!