Custom Components

Many of the plots in the documentation apply fluent methods to a plot before rendering, for example:

import com.cibo.evilplot.plot._
import com.cibo.evilplot.numeric.Point

LinePlot(Seq(Point(1, 1), Point(3, 3)))
  .xAxis().yAxis()
  .xGrid().yGrid()
  .hline(2d)
  .title("An example plot")
  .render()

In this plot, xAxis, yAxis, xGrid, yGrid, hline, and title, all add plot components to the plot. You can see the javadoc for all the built-in components here by filtering for the “Implicits” traits.

Building a Custom Component

You can build your own components by extending plot.compoments.PlotComponent. They are added to a plot using the plot’s component method.

Every PlotComponent instance has a Position indicating how it should be rendered relative to the plot.

Position.Overlay
The component occupies the same space as the plot and is rendered in front of it.
Position.Background
Background is similar to Overlay but is rendered behind the plot.
Position.Left, Position.Top, Position.Right, Position.Bottom
Components with these positions are rendered outside of the plot space and placed next to the left, top, right, or bottom edge of the plot. Each component added at a position will be rendered outside previous components at that position (further from the plot). You can see this in the plot below where three Label components are added, all at Position.Top.
LinePlot(Seq(Point(1, 1), Point(3, 3)))
  .xAxis().yAxis()
  .xGrid().yGrid()
  .hline(2d)
  .topLabel("First Label")
  .topLabel("Second Label")
  .topLabel("Third Label")
  .render()

Here’s a sample custom component that adds a marker to the right side of our example plot.

import com.cibo.evilplot.geometry.{Align, Drawable, Extent, Polygon, Text}
import com.cibo.evilplot.plot.aesthetics.Theme
import com.cibo.evilplot.plot.components.{PlotComponent, Position}

case class RightSideMarker(
  msg: String,
  value: Double
) extends PlotComponent {
  val position = Position.Right

  private val triangle: Drawable = Polygon(Seq(Point(0, 0), Point(20, 0), Point(10, 20)))
  private val marker: Drawable = Align.middle(
    triangle.rotated(90),
    Text(msg).padAll(5)
  ).reduce(beside)

  override def size(plot: Plot): Extent = marker.extent

  def render(plot: Plot, extent: Extent)(implicit theme: Theme): Drawable = {
    val yoffset = plot.ytransform(plot, extent)(value) - (marker.extent.height / 2)
    marker.translate(y = yoffset)
  }
}
LinePlot(Seq(Point(1, 1), Point(3, 3)))
  .xAxis().yAxis()
  .xGrid().yGrid()
  .hline(2d)
  .title("An example plot")
  .component(RightSideMarker("marker at 2", 2))
  .render()