Creating a Plot

This section goes over the steps required to create a plot, configure it, and add data and graphics to it.

Title and Axis Labels

In [ ]:
new Plot(title: "We Will Control the Title", xLabel: "Horizontal", yLabel: "Vertical")

Lines

There are multiple ways to specify the points of the line using Groovy. All the following lines achieve the same result.

In [ ]:
// just provide lists of x's and y's
new Plot().add(new Line(x: [0, 1, 2, 3, 4, 5], y: [0, 1, 6, 5, 2, 8]))

// Groovy range works
new Plot().add(new Line(x: (0..5), y: [0, 1, 6, 5, 2, 8]))

// use '<<' left-shift to save some key strokes
new Plot() << new Line(x: (0..5), y: [0, 1, 6, 5, 2, 8])

// the constructor of class Line is overloaded to take 1 or 2 lists
// if an Line was returned, and empty plot is automatically generated
new Line((0..5), [0, 1, 6, 5, 2, 8])
                        
// if x is not provided, a default list of x values (0..5) will be used
new Line([0, 1, 6, 5, 2, 8])

You may change the rendering properties of the lines, by specifying the corresponding parameters. E.g. width, color, style, interpolation, etc.

In [ ]:
def plot = new Plot(title: "Setting line properties")
def ys = [0, 1, 6, 5, 2, 8]
def ys2 = [0, 2, 7, 6, 3, 8]
plot << new Line(y: ys, width: 10, color: Color.red)
plot << new Line(y: ys, width: 3, color: Color.yellow)
plot << new Line(y: ys, width: 4, color: new Color(33, 87, 141), style: StrokeType.DASH, interpolation: 0)
plot << new Line(y: ys2, width: 2, color: new Color(212, 57, 59), style: StrokeType.DOT)
plot << new Line(y: [5, 0], x: [0, 5], style: StrokeType.LONGDASH)
plot << new Line(y: [4, 0], x: [0, 5], style: StrokeType.DASHDOT)

Stems

Stems are vertical line segments. All the rendering properties for lines apply to stems.

In [ ]:
def plot = new Plot();
def y1 = [1.5, 1, 6, 5, 2, 8]
def cs = [Color.black, Color.red, Color.gray, Color.green, Color.blue, Color.pink]
def ss = [StrokeType.SOLID, StrokeType.SOLID, StrokeType.DASH, StrokeType.DOT, StrokeType.DASHDOT, StrokeType.LONGDASH]
plot << new Stems(y: y1, color: cs, style: ss, width: 5)

Draw points at the top / bottom of stems to make stem bases

In [ ]:
def plot = new Plot(title: "Setting the base of Stems")
def ys = [3, 5, 2, 3, 7]
def y2s = [2.5, -1.0, 3.5, 2.0, 3.0]
plot << new Stems(y: ys, width: 2, base: y2s)
plot << new Points(y: ys)

Bars

You can set the width and color of the bars. You can set the color property for each single bar element using a list Bar colors are fill colors, To change the outline color, use outlineColor. Bar width is in terms of the data domain.

For Bar Charts, see the other tutorial on Category Plots.

In [ ]:
def plot = new Plot(title: "Bars")
def cs = [new Color(255, 0, 0, 128)] * 5 // transparent bars
cs[3] = Color.red // set color of a single bar, solid colored bar
plot << new Bars(x: (1..5), y: [3, 5, 2, 3, 7], color: cs, outlineColor: Color.black, width: 0.3)

Points

You can change the size and shape of points. Points also support outlineColor.

In [ ]:
def shapes = [ShapeType.SQUARE,
              ShapeType.CIRCLE,
              ShapeType.TRIANGLE,
              ShapeType.DOWNTRIANGLE,
              ShapeType.DIAMOND,
              ShapeType.LEVEL,
              ShapeType.VLEVEL,
              ShapeType.LINECROSS,
              ShapeType.CROSS,
              ShapeType.DCROSS]

def plot = new Plot(title: "Point Shapes")
def y = [6, 7, 12, 11, 8, 14]
shapes.each {
    plot << new Points(y: y, size: 20, shape: it)
    y = y.collect { it - 2 }
}
plot
In [ ]:
def plot = new Plot(title: "Changing Point Size, Color")
def y1 = [6, 7, 12, 11, 8, 14]
def y2 = y1.collect { it - 2 }
def y3 = y2.collect { it - 2 }
def y4 = y3.collect { it - 2 }
plot << new Points(y: y1)
plot << new Points(y: y2, size: 10, color: Color.black)
plot << new Points(y: y3, size: 15, color: Color.orange, outlineColor: Color.black)
//plot << new Points(y: y4, size: 20, outlineColor: Color.red, fill: false)

You can also set point properties using lists.

In [ ]:
def plot = new Plot(title: "Changing point properties with list", yBound:[1.5, 5.5])
def cs = [Color.black, Color.red, Color.orange, Color.green, Color.blue, Color.pink]
def ss = [6.0, 9.0, 12.0, 15.0, 18.0, 21.0]
def fs = [false, false, false, true, false, false]
plot << [new Points(y: [5] * 6, size: 12.0, color: cs),
         new Points(y: [4] * 6, size: 12.0, color: Color.gray, outlineColor: cs),
         new Points(y: [3] * 6, size: ss, color: Color.red),
         new Points(y: [2] * 6, size: 12.0, color: Color.black, fill: fs, outlineColor: Color.black)]

Areas

In [ ]:
def plot = new Plot()
def y = [3, 5, 2, 3]
def x0 = [0, 1, 2, 3]
def x1 = [3, 4, 5, 8]
plot << new Area(x: x0, y: y)
plot << new Area(x: x1, y: y, color: new Color(128, 128, 128, 50), interpolation: 0)

You can also set bases for the areas.

In [ ]:
def p = new Plot()
p << new Line(y: [3, 6, 12, 24], displayName: "Median")
p << new Area(y: [4, 8, 16, 32], base: [2, 4, 8, 16],
              color: new Color(255, 0, 0, 50), displayName: "Q1 to Q3")

Stacking

You can combine plot items that have a base property (Bars, Stems, Area).

In [ ]:
def y1 = [1,5,3,2,3]
def y2 = [7,2,4,1,3]
def p = new Plot(title: 'Plot with XYStacker', initHeight: 200)
def a1 = new Area(y: y1, displayName: 'y1')
def a2 = new Area(y: y2, displayName: 'y2')
p << XYStacker.stack([a1, a2])

Constant Lines

In [ ]:
def p = new Plot ()
p << new Line(y: [-1, 1])
p << new ConstantLine(x: 0.65, style: StrokeType.DOT, color: Color.blue)
p << new ConstantLine(y: 0.1, style: StrokeType.DASHDOT, color: Color.blue)
p << new ConstantLine(x: 0.3, y: 0.4, color: Color.gray, width: 5, showLabel: true)

Constant Bands

In [ ]:
new Plot() << new Line(y: [-3, 1, 3, 4, 5]) << new ConstantBand(x: [1, 2], y: [1, 3])

You can change bands colors and use Infinity for values

In [ ]:
def p = new Plot() 
p << new Line(x: [-3, 1, 2, 4, 5], y: [4, 2, 6, 1, 5])
p << new ConstantBand(x: [Double.NEGATIVE_INFINITY, 1], color: new Color(128, 128, 128, 50))
p << new ConstantBand(x: [1, 2])
p << new ConstantBand(x: [4, Double.POSITIVE_INFINITY])

Text

In [ ]:
def plot = new Plot()
def xs = (1..10)
def ys = [8.6, 6.1, 7.4, 2.5, 0.4, 0.0, 0.5, 1.7, 8.4, 1]
def label = { i ->
  if (ys[i] > ys[i+1] && ys[i] > ys[i-1]) return "max"
  if (ys[i] < ys[i+1] && ys[i] < ys[i-1]) return "min"
  if (ys[i] > ys[i-1]) return "rising"
  if (ys[i] < ys[i-1]) return "falling"
  return ""
}
for (i = 0; i < xs.size(); i++) {
  if (i > 0 && i < xs.size()-1)
    plot << new Text(x: xs[i], y: ys[i], text: label(i),  pointerAngle: -i/3.0)
}
plot << new Line(x: xs, y: ys)
plot << new Points(x: xs, y: ys)

Legend

ToolTip

The Beaker Plot includes tool tips. Hover the changing points of the line or the bars to see them in the plot below.

You can interact with the tooltips.

  • Stick a tooltip: Click while a tooltip is visible, and it will remain until you close it by clicking the X.
  • Click and drag a tooltip to increase its visibility.
  • When you zoom and pan, the tooltip will try to keep its place within the plot when you later navigate.

Legend box

The Beaker Plots include a legend box by default. You may drag the legend box around just as you drag the tool tips.

The legend box includes the control panel features that allow you to show / hide data groups by toggling the checkboxes. You may turn off this feature by using an “omitCheckboxes” property (in this case there would be no checkbox to show / hide data).

Use “displayName” to show the data’s legend. The legend will automatically appear when a “displayName” property presents. However, you my turn the legend off explicitly by setting “showLegend”.

If you do not specify the “displayName” property, or the “displayName” is an empty string, the data will not appear in the legend box.

By default the legend is placed in the top-right corner. To change its position you can specify a “legendPosition” property. You can use predefined values (TOP, TOP_LEFT, etc..) or provide an array of coordinates (x, y).

Also you can change legend’s layout to horizontal by setting a “legendLayout” property.

Cursor hint

The cursor hint allows you to quickly identify the x / y values of the mouse location.Use “crosshair” property to enable the cursor hint. 

The Crosshair object has properties similar to a Line, including color, width and style

TRY IT NOW!

Now it would be a good time for you to try the above interactions in the following demo plot. Make sure you try out the following things:

  • Click a line point / bar to stick its tool tip.
  • Drag a tool tip around.
  • Show / hide the line or the bar group using the legend box
  • Change the legend box layout to horizontal and position to top-left
In [ ]:
def ch = new Crosshair(color: new Color(255, 128, 5), width: 2, style: StrokeType.DOT)
pp = new Plot(crosshair: ch, omitCheckboxes: true,
                  legendLayout: LegendLayout.HORIZONTAL, legendPosition: LegendPosition.TOP)
def x = [1, 4, 6, 8, 10]
def y = [3, 6, 4, 5, 9]
pp << new Line(displayName: "Line", x: x, y: y, width: 3)
pp << new Bars(displayName: "Bar", x: (1..10), y: [2, 2, 4, 4, 2, 2, 0, 2, 2, 4], width: 0.5)
pp << new Points(x: x, y: y, size: 10, toolTip: {xs, ys -> "x = " + xs + ", y = " + ys })
In [ ]:
pp.setLegendPosition(LegendPosition.RIGHT);
OutputCell.HIDDEN
In [ ]:
import com.twosigma.beakerx.fileloader.CSV
rates = new CSV().read("../resources/data/interest-rates.csv")
def size = rates.size()
(0 ..< size).each{row = rates[it]; row.spread = row.y10 - row.m3}

Simple Time Plot

Using SimpleTimePlot you can create a time plot based on the complex table data. To do this just specify table and table column names for plot, like in the example at the top of this notebook.

Data for time axis is taken from 'time' column. To change this behavior use 'timeColumn' parameter.

By default lines are used to draw the plot, but you can also add points using the parameter 'displayPoints'. The 'displayNames' property give sthe names of the lines, as displayedin the legend. To specify custom colors use the 'colors' parameter, and give it a list with colors in a variety of formats.

In [ ]:
new SimpleTimePlot(rates, ["y1", "y10"], // column names
                   timeColumn : "time", // time is default value for a timeColumn
                   yLabel: "Price", 
                   displayNames: ["1 Year", "10 Year"],
                   colors : [[216, 154, 54], '#aabbcc'],
                   displayLines: false, // no lines (true by default)
                   displayPoints: true) // show points (false by default))

Second Y Axis

The plot can have two y-axes. Just add a YAxis to the plot object, and specify its label. Then for data that should be scaled according to this second axis, specify the property yAxis with a value that coincides with the label given. You can use upperMargin and lowerMargin to restrict the range of the data leaving more white, perhaps for the data on the other axis.

In [ ]:
def p = new TimePlot(xLabel: "Time", yLabel: "Interest Rates")
p << new YAxis(label: "Spread", upperMargin: 4)
p << new Area(x: rates.time, y: rates.spread, displayName: "Spread",
              yAxis: "Spread", color: new Color(180, 50, 50, 128))
p << new Line(x: rates.time, y: rates.m3, displayName: "3 Month")
p << new Line(x: rates.time, y: rates.y10, displayName: "10 Year")

Logarithmic Scale

The plots support log scale for both axes, independently. To add log scale you need to specify set the logX (for x-axis) or logY (for y-axis) property to true. By default a base 10 is used. To change this, use properties xLogBase and yLogBase.

In [ ]:
def points = 100;
def logBase = 10;
def expys = [];
def xs = [];
for(int i = 0; i < points; i++){
  xs[i] = i / 15.0;
  expys[i] = Math.exp(xs[i]); 
}

def cplot = new CombinedPlot(xLabel: "Linear");
def logYPlot = new Plot(title: "Linear x, Log y", yLabel: "Log", logY: true, yLogBase: logBase);
logYPlot << new Line(x: xs, y: expys, displayName: "f(x) = exp(x)");
logYPlot << new Line(x: xs, y: xs, displayName: "g(x) = x");
cplot.add(logYPlot, 3);

// works for 2nd Y axis too:
// logYPlot << new YAxis(label: "Right Log Y-Axis", log: true, logBase: logBase);

def linearYPlot = new Plot(title: "Linear x, Linear y", yLabel: "Linear");
linearYPlot << new Line(x: xs, y: expys, displayName: "f(x) = exp(x)");
linearYPlot << new Line(x: xs, y: xs, displayName: "g(x) = x");
cplot.add(linearYPlot, 3);

cplot
In [ ]:
def points = 100;
def logBase = 10;
def expys = [];
def xs = [];
for(int i = 0; i < points; i++){
  xs[i] = i /15
  expys[i] = Math.exp(xs[i]);
}

def plot = new Plot(title: "Log x, Log y", xLabel: "Log", yLabel: "Log",
                    logX: true, xLogBase: logBase, logY: true, yLogBase: logBase);

plot << new Line(x: xs, y: expys, displayName: "f(x) = exp(x)");
plot << new Line(x: xs, y: xs, displayName: "f(x) = x");

plot

Date Objects for the Time Coordinate

For Time plots you can provide x coordinate as:

  • a list of numbers (milliseconds), or
  • a list of java.util.Date objects, or
  • a list of java.util.Calendar objects, or
  • a list of java.time.Instant objects, or
  • a list of java.time.LocalDateTime objects, or
  • a list of java.time.LocalTime objects.
In [ ]:
def cal = Calendar.getInstance();
cal.add(Calendar.HOUR, -1)

def today = new Date();
def millis = today.time;
def hour = 1000 * 60 * 60;

def plot = new TimePlot(
  timeZone: new SimpleTimeZone(10800000, "America/New_York")
);
//list of milliseconds
plot << new Points(x:(0..10).collect{millis + hour * it}, y:(0..10), size: 10, displayName: "milliseconds");
//list of java.util.Date objects
plot << new Points(x:(0..10).collect{cal.add(Calendar.HOUR, 1); cal.getTime()}, y:(0..10), size: 4, displayName: "date objects");

Nanosecond Resolution

Handling time with nanosecond resolution is easy in languages like Java and Groovy because they support 64-bit integers. Numbers in JavaScript however are limited to 53 bits. Beaker's plotting library can handle these large numbers, just use the NanoPlot class.

In [ ]:
def today  = new Date()
def millis = today.time
def nanos  = millis * 1000 * 1000g // g makes it arbitrary precision
def np = new NanoPlot()
np << new Points(x:(0..10).collect{nanos + 7 * it}, y:(0..10))

More Formatting Controls

You can remove the tick labels from either or both axes.

In [ ]:
p = new Plot(title: "No Tick Labels", xTickLabelsVisible: false, yTickLabelsVisible: false)
p << new Line([0, 1, 6, 5, 2, 8])

And add arbitrary styles to various elements:

In [ ]:
r = new Random()
p = new Plot(title: "Advanced Plot Styling",
         labelStyle: "font-size:32px; font-weight: bold; font-family: courier; fill: green;",
         gridLineStyle:  "stroke: purple; stroke-width: 3;",
         titleStyle: "color: green;"
        )
p << new Points(x: (1..1000).collect { r.nextGaussian() * 10.0d },
                y: (1..1000).collect { r.nextGaussian() * 20.0d })

Rasters

In [ ]:
import java.nio.file.Files
byte[] picture = Files.readAllBytes(new File("../resources/img/widgetArch.png").toPath());
def p =  new Plot();
// x y width height are coordinates, opacity is a double in 0~1

// image can be loaded via bytes, filepath, or url
p << new Rasters(x: [-10,3], y: [3,1.5], width: [6,5], height:[10,8], opacity: [1,0.5], dataString: picture);
//p << new Rasters(x: -1, y: 4.5, width: 5, height: 8, opacity:0.5, filePath: "../resources/img/widgetArch.png");
p << new Rasters(x: [-4], y: [10.5], width: [7], height: [2], opacity:[1], fileUrl: "https://www.twosigma.com/static/img/twosigma.png");

// a list of images!
def x = [-8, -5, -3, -2, -1, 1, 2, 4, 6, 8]
def y = [4, 5, 1, 2, 0 ,3, 6, 4, 5, 9]
def width = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
def opacity = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
p << new Rasters(x: x, y: y, width:width, height:width, opacity:opacity,fileUrl: "http://icons.iconarchive.com/icons/paomedia/small-n-flat/1024/sign-check-icon.png")
In [ ]:
def plot = new Plot(title: "Setting 2nd Axis bounds")
def ys = [0, 2, 4, 6, 15, 10]
def ys2 = [-40, 50, 6, 4, 2, 0]
def ys3 = [3, 6, 3, 6, 70, 6]
plot << new YAxis(label:"Spread")
plot << new Line(y: ys)
plot << new Line(y: ys2, yAxis: "Spread")
plot.getYAxes()[0].setBound(1,5);
plot.getYAxes()[1].setBound(3,6) // this should change the bounds of the 2nd, right axis
plot
In [ ]:
new Line(y: [4, 0], x: [0, 5])

Y Axis 0 point auto inclusion

You can include y: 0 point by using yAutoRangeIncludesZero property.

In [ ]:
new Plot(yAutoRangeIncludesZero: true) << new Line(y: [5, 10])

Limit X and Y Bounds

In [ ]:
new Plot(xBound: [7, 9], yBound: [6, 9]) << new Line(x: (1..8), y: (1..8)) << new Line(x: (8..10), y: [8, 7, 6])
In [ ]:
new Plot(xBound: [7, 9]) << new Line(x: (1..8), y: (1..8)) << new Line(x: (8..10), y: [8, 7, 6])
In [ ]:
new Plot(yBound: [6, 9]) << new Line(x: (1..8), y: (1..8)) << new Line(x: (8..10), y: [8, 7, 6])

Margins

You can add margins to the plot using:

  • xLowerMargin,
  • xUpperMargin,
  • yLowerMargin,
  • yUpperMargin

properties.

In [ ]:
p = new Plot(title: "Margins", 
             xLowerMargin: 1, xUpperMargin: 1, 
             yLowerMargin: 1, yUpperMargin: 1) 
p << new Line(x: (1..10), y: (5..14))