Victory 0.12.0: The One True Tooltip


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/>

biggertooltip
<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.

screen-shot-2016-09-14-at-11-02-44-am

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.

emojitooltip

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.

voronoitooltip

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!

See the Victory source code.
Read the Victory docs.


We Are Formidable

Formidable is a Seattle-based consultancy and open-source shop, with an emphasis on Node.js and React.js. We deploy a mixture of consulting, staff augmentation, and training to level up teams and solve engineering problems. Whether it’s transitioning walmart.com to React, moving speedtest.net off Flash, or helping a startup build and scale an MVP, we’re ready to help teams of any size.

Interested in hiring or working for us? Get in touch or view our Careers Page.