curran / d3-area-label

A library for placing labels in areas.
BSD 3-Clause "New" or "Revised" License
75 stars 4 forks source link

d3-area-label

A library for placing labels in areas.

image

image

image

You can use this to position labels on a StreamGraph or Stacked Area Chart.

Example usage:

const labels = svg.selectAll('text').data(stacked)
labels
  .enter().append('text')
    .attr('class', 'area-label')
  .merge(labels)
    .text(d => d.key)
    .attr('transform', d3.areaLabel(area)) // <---------- Call the function like this.

For more details and context, see test/index.html or run the example on bl.ocks.org.

How it Works

The label placement algorithm works as follows:

The set of possible X coordinate to be used as the left edge of the label depends on how interpolate and interpolateResolution are configured. If interpolation is turned off, the original X coordinates from the data points are the only coordinates considered for label placement. For datasets where there are large gaps between X coordinates, we can improve label placement by turning on interpolation, which will generate a certain number (interpolateResolution) of evenly spaced X coordinates and use linear interpolation to compute the corresponding Y coordinates for the top and bottom of the area curve. Cranking up the interpolateResolution value leads to more optimal label placement at the cost of more computation.

Installing

If you use NPM, npm install d3-area-label. Otherwise, download the latest release.

API Reference

# d3.areaLabel([area])

Constructs a new label position generator.

If area is specified, invokes areaLabel.area as well (equivalent to d3.areaLabel().area(area)).

# areaLabel(data)

Invoke the label position generator with the given data array.

This function computes the optimal position and size for a label and returns an SVG transform string.

# areaLabel.area(area)

Sets the x, y0, and y1 accessors applied to the data array from the given area, an instance of d3.area.

# areaLabel.x(x)

If x is specified, sets the x accessor applied to the data array and returns the label position generator. If x is not specified, returns the current x.

# areaLabel.y0(y0)

If y0 is specified, sets the y0 accessor applied to the data array and returns the label position generator. If y0 is not specified, returns the current y0.

# areaLabel.y1(y1)

If y1 is specified, sets the y1 accessor applied to the data array and returns the label position generator. If y1 is not specified, returns the current y1.

# areaLabel.minHeight(minHeight)

The minimum label bounding box height in pixels. Default is 2.

# areaLabel.epsilon(epsilon)

The tolerance within we wish to optimize the bounding box height (in pixels). Default is 0.01;

# areaLabel.maxIterations(maxIterations)

The maximum number of iterations for the bisection method algorithm, which is used to find the maximum height rectangle that fits within the area.

# areaLabel.interpolate(interpolate)

A boolean value that determines whether or not linear interpolation is used for computing label positions.

If set to false, only X coordinates that correspond to data points are considered for use as the leftmost position of the label bounding box. In cases where there is a high number of evenly spaced data points, a value of false works quite well. When there is a low number of data points, a value of false leads to embarassingly pathetic label placements.

If set to true, then a fixed number of X coordinates (interpolateResolution to be exact), not necessarily corresponding to data points, are considered for use as the leftmost position of the label bounding box. The upper and lower Y values for those X values are imputed from the nearest X values from the data using linear interpolation. When there is a low number of data points, a value of true improves label placement by leaps and bounds. A value of true also leads to more expensive computation for placing labels, so if you're encountering performance problems, try setting this to false.

Default is true.

# areaLabel.interpolateResolution(interpolateResolution)

The integer number of possible X positions to check for placing the leftmost edge of the label bounding box. The X extent of the area is subdivided evenly into this many points. When each point is checked, linear interpolation is used to estimate the data value. Default is 200.

This only comes into effect if interpolate is set to true.

# areaLabel.paddingLeft(paddingLeft)

The left padding for labels. This should be a value between 0 and 1. Default is 0.

# areaLabel.paddingRight(paddingRight)

The right padding for labels. This should be a value between 0 and 1. Default is 0.

# areaLabel.paddingTop(paddingTop)

The top padding for labels. This should be a value between 0 and 1. Default is 0.

# areaLabel.paddingBottom(paddingBottom)

The bottom padding for labels. This should be a value between 0 and 1. Default is 0.

# areaLabel.paddingX(paddingX)

A convenience method for simultaneously setting paddingLeft and paddingRight.

# areaLabel.paddingY(paddingY)

A convenience method for simultaneously setting paddingTop and paddingBottom.

# areaLabel.padding(padding)

A convenience method for simultaneously setting paddingX and paddingY.

Thanks

Many thanks to Lee Byron, Noah Veltman, Philippe Rivière, and Adam Pearce for ideas and input.