The Toyplot Tutorial¶

Getting Started¶ Welcome! This tutorial will introduce you to the basics of working with Toyplot. Note: this tutorial was created in a [Jupyter](http://www.ipython.org) notebook and assumes that you’re following-along in a notebook of your own. If you aren’t using a notebook, you should read the user guide section on Rendering for some important information on how to display your figures. To begin, we’re going to import numpy (so we can create data to use for our figures), and the main toyplot module: [1]: import numpy import toyplot For our first figure, let’s create a simple set of X and Y coordinates that we can plot: [2]: x = numpy.linspace(0, 10) y = x ** 2 Now, let’s put Toyplot to work … [3]: canvas = toyplot.Canvas(width=300, height=300) axes = canvas.cartesian() mark = axes.plot(x, y) 0 5 10 0 50 100 Short as it is, this example demonstrates several key features of Toyplot: Toyplot figures are beautiful, scalable, embeddable, and interactive. For example: Click or tap anywhere in the figure, and note the coordinates that update along each axis. Carefully place the cursor on top of the plotted line, and open a context menu - in the popup that appears, you can choose to save the figure data to a CSV file from your browser. Note that Toyplot produces a clean, aesthetically pleasing figure that is crisp at any scale and free of chartjunk “out-of-the-box”.

Every Toyplot figure begins with a canvas - a drawing area upon which the caller adds marks. Note that the width and height of the canvas have been specified in CSS pixels, which are always equal to 1/96th of an inch, and converted to device units when the canvas is rendered .

- a drawing area upon which the caller adds marks. Note that the width and height of the canvas have been specified in CSS pixels, which are always equal to 1/96th of an inch, and converted to device units when the canvas is . A Toyplot figure typically contains one-or-more coordinate systems which map a data domain to a range of canvas pixels.

which map a data domain to a range of canvas pixels. Marks are added to coordinate systems using the factory functions they provide. In this example, the plot function adds a plot mark using the supplied coordinates. Note that the cartesian axes been automatically sized to the data’s domain.

Styles¶ Let’s say that you wanted to alter the above figure to make the plotted line blue and dashed. To do so, simply override the default style information when creating the plot: [4]: canvas = toyplot.Canvas(width=300, height=300) axes = canvas.cartesian() mark = axes.plot(x, y, style={"stroke":"blue", "stroke-dasharray":"2, 2"}) 0 5 10 0 50 100 In this case, you can see that the style information is a dictionary of key-value properties that alter how a mark is rendered. To avoid reinventing the wheel, Toyplot uses Cascading Style Sheets (CSS) to specify styles. If you’re familiar with web development, you already know CSS. If not, this tutorial will cover many of most useful CSS properties for Toyplot as we go, and there are many learning resources for CSS online. Every mark you add to a figure will have at least one (and possibly more than one) set of styles that control its appearance.

Plotting¶ Let’s continue with the previous example. As a shortcut, you can omit the X coordinates when using the plot command, and a set of coordinates in the range $[0, M)$ will be provided (compare the following X axis with the previous two plots to see the difference): [5]: canvas = toyplot.Canvas(width=300, height=300) axes = canvas.cartesian() mark = axes.plot(y) 0 10 20 30 40 50 0 50 100 If you add multiple plots to a set of axes, each automatically receives a different color: [6]: x = numpy.linspace(0, 10, 100) y1 = numpy.sin(x) y2 = numpy.cos(x) y3 = numpy.sin(x) + numpy.cos(x) [7]: canvas = toyplot.Canvas(width=600, height=300) axes = canvas.cartesian() mark1 = axes.plot(x, y1) mark2 = axes.plot(x, y2) mark3 = axes.plot(x, y3) 0 5 10 -1 0 1 As we’ve already seen, we can use the “stroke” style to override the default color of each plot; in addition, the “stroke-width” and “stroke-opacity” styles are useful properties for (de)emphasizing individual plots: [8]: canvas = toyplot.Canvas(width=600, height=300) axes = canvas.cartesian() mark1 = axes.plot(x, y1, style={"stroke-width":1, "stroke-opacity":0.6}) mark2 = axes.plot(x, y2, style={"stroke-width":1, "stroke-opacity":0.6}) mark3 = axes.plot(x, y3, style={"stroke":"blue"}) 0 5 10 -1 0 1

Palettes¶ Before proceeding, let’s take a moment to look at how the default color for a mark is assigned. When we add multiple marks to a set of axes, each mark gets a different color. These default colors are all drawn from a palette - an ordered collection of RGBA colors. For example, here’s Toyplot’s default palette: [9]: import toyplot.color toyplot.color.Palette() [9]: Note: Like canvases, palettes are automatically rendered in Jupyter notebooks, in this case as a collection of color swatches. You should observe that the order of colors in the palette match the order of the colors that were assigned to our plots as they were added to their axes. You could create a custom palette by passing a sequence of colors to the toyplot.color.Palette constructor, but Toyplot already comes with a builtin collection of high-quality palettes from [Color Brewer](http://colorbrewer2.org), which we will use in the examples that follow. For more detail on colors in Toyplot, see the Colors section of the user guide.

Filled Regions¶ You can use fill to display a region bounded by two sets of Y coordinates. This can be a handy way to visualize data distributions: [10]: numpy.random.seed(1234) observations = numpy.random.normal(size=(50, 50)) x = numpy.linspace(0, 1, len(observations)) y1 = numpy.min(observations, axis=1) y2 = numpy.max(observations, axis=1) [11]: canvas = toyplot.Canvas(width=400, height=300) axes = canvas.cartesian() mark = axes.fill(x, y1, y2) 0.0 0.5 1.0 -4 -2 0 2 4 Use the “fill” style (not to be confused with the fill command) to control the color of the shaded region. You might also want to change the fill-opacity or add a stroke using styles: [12]: style={"fill":"steelblue", "fill-opacity":0.5, "stroke":toyplot.color.black} canvas = toyplot.Canvas(width=400, height=300) axes = canvas.cartesian() mark = axes.fill(x, y1, y2, style=style) 0.0 0.5 1.0 -4 -2 0 2 4 If you omit one of the boundaries it will default to \(y = 0\): [13]: canvas = toyplot.Canvas(width=400, height=300) axes = canvas.cartesian() mark = axes.fill(x, y2) 0.0 0.5 1.0 0 1 2 3 As with plots, if you omit the X coordinates, they will default to the range \([0, M)\): [14]: canvas = toyplot.Canvas(width=400, height=300) axes = canvas.cartesian() mark = axes.fill(y2) 0 10 20 30 40 50 0 1 2 3 Toyplot also makes it easy to define multiple sets of boundaries, by passing an \(M \times N\) matrix as input, where \(M\) is the number of observations, and \(N\) is the number of boundaries: [15]: boundaries = numpy.column_stack( (numpy.min(observations, axis=1), numpy.percentile(observations, 25, axis=1), numpy.percentile(observations, 50, axis=1), numpy.percentile(observations, 75, axis=1), numpy.max(observations, axis=1))) [16]: canvas = toyplot.Canvas(width=400, height=300) axes = canvas.cartesian() mark = axes.fill(boundaries) 0 10 20 30 40 50 -4 -2 0 2 4 This introduces an important new concept: you can think of fill (and other types of) marks as containers for collections of series, where in this case, \(N\) boundaries define \(N-1\) series. This distinction is important because we can control the styles of individual series, not just the mark as a whole. So, if we want to override the default colors for the fill regions, we can do it using the mark’s global “fill” style (with a contrasting stroke to display the boundaries between series): [17]: canvas = toyplot.Canvas(width=400, height=300) axes = canvas.cartesian() mark = axes.fill(boundaries, style={"fill":"steelblue", "stroke":"white"}) 0 10 20 30 40 50 -4 -2 0 2 4 … or we can do it using the “color” argument: [18]: canvas = toyplot.Canvas(width=400, height=300) axes = canvas.cartesian() mark = axes.fill(boundaries, color="steelblue", style={"stroke":"white"}) 0 10 20 30 40 50 -4 -2 0 2 4 The advantage of the latter is that the “color” argument can specify a single color value as we’ve seen, or a sequence of color values, one-per-series. And, you can combine those per-series color values with global styles in intuitive ways: [19]: color = ["red", "green", "blue", "yellow"] [20]: canvas = toyplot.Canvas(width=400, height=300) axes = canvas.cartesian() mark = axes.fill(boundaries, color=color, style={"stroke":toyplot.color.black}) 0 10 20 30 40 50 -4 -2 0 2 4 The “opacity” and “title” arguments can also be specified on a per-series basis (hover the mouse over the fill regions in the following figure to see the title as a popup): [21]: color = ["blue", "blue", "red", "red"] opacity = [0.1, 0.2, 0.2, 0.1] title = ["1st Quartile", "2nd Quartile", "3rd Quartile", "4th Quartile"] [22]: style={"stroke":toyplot.color.black} canvas = toyplot.Canvas(width=400, height=300) axes = canvas.cartesian() mark = axes.fill(boundaries, color=color, opacity=opacity, title=title, style=style) 1st Quartile 2nd Quartile 3rd Quartile 4th Quartile 0 10 20 30 40 50 -4 -2 0 2 4 In the preceding examples you defined the fill regions by explicitly specifying their boundaries … as an alternative, you can generate fills by specifying the magnitudes (the heights) of each region (note that in this case \(N\) heights define \(N\) series): [23]: numpy.random.seed(1234) samples = numpy.linspace(0, 4 * numpy.pi, 100) frequency = lambda: numpy.random.normal() phase = lambda: numpy.random.normal() amplitude = lambda: numpy.random.uniform(0.1, 1) wave = lambda: numpy.sin(phase() + (frequency() * samples)) signal = lambda: amplitude() * (2 + wave()) heights = numpy.column_stack([signal() for i in range(10)]) [24]: canvas = toyplot.Canvas(width=500, height=300) axes = canvas.cartesian() m = axes.fill(heights, baseline="stacked") 0 50 100 0 4 8 12 If you pass a sequence of scalar values instead of colors to the “color” argument, the values will be mapped to colors using a linear mapping and a default diverging colormap: [25]: color = numpy.arange(heights.shape[1]) [26]: canvas = toyplot.Canvas(width=500, height=300) axes = canvas.cartesian() m = axes.fill(heights, baseline="stacked", color=color) 0 50 100 0 4 8 12 Of course, you’re free to supply your own colormap instead: [27]: colormap = toyplot.color.brewer.map("BlueGreenBrown") [28]: canvas = toyplot.Canvas(width=500, height=300) axes = canvas.cartesian() m = axes.fill(heights, baseline="stacked", color=(color, colormap)) 0 50 100 0 4 8 12 … note that the baseline parameter is what signals that the inputs are magnitudes instead of boundaries. You can also change the baseline parameter to create various types of streamgraph: [29]: canvas = toyplot.Canvas(width=500, height=300) axes = canvas.cartesian() m = axes.fill(heights, baseline="symmetric", color=(color, colormap)) 0 50 100 -5 0 5 [30]: canvas = toyplot.Canvas(width=500, height=300) axes = canvas.cartesian() m = axes.fill(heights, baseline="wiggle", color=(color, colormap)) 0 50 100 -4 0 4 8

Barplots¶ Of course, you can’t have a plotting library without barplots … [31]: heights = numpy.linspace(1, 10, 10) ** 2 [32]: canvas = toyplot.Canvas(width=300, height=300) axes = canvas.cartesian() mark = axes.bars(heights) 0 5 10 0 50 100 By default the bars are centered on integer X coordinates in the range \([0, M)\) - but we can specify our own X coordinates to suit: [33]: x = numpy.linspace(-2, 2, 20) y = 5 - (x ** 2) canvas = toyplot.Canvas(width=300, height=300) axes = canvas.cartesian() mark = axes.bars(x, y) -2 -1 0 1 2 0 1 2 3 4 5 As a convenience, you can pass the output from numpy.histogram() directly to toyplot.coordinates.Cartesian.bars() : [34]: numpy.random.seed(1234) population = numpy.random.normal(size=10000) [35]: canvas = toyplot.Canvas(width=300, height=300) axes = canvas.cartesian() bars = axes.bars(numpy.histogram(population, 20)) -4 -2 0 2 4 0 500 1000 1500 As with fill marks, Toyplot allows you to stack multiple sets of bars by passing an \(M \times N\) matrix as input, where \(M\) is the number of observations, and \(N\) is the number of series: [36]: heights1 = numpy.linspace(1, 10, 10) ** 1.1 heights2 = numpy.linspace(1, 10, 10) ** 1.3 heights3 = numpy.linspace(1, 10, 10) ** 1.4 heights4 = numpy.linspace(1, 10, 10) ** 1.5 heights = numpy.column_stack((heights1, heights2, heights3, heights4)) [37]: canvas = toyplot.Canvas(width=300, height=300) axes = canvas.cartesian() mark = axes.bars(heights) 0 5 10 0 30 60 90 As before, we can style the bars globally and use the “color”, “opacity”, and “title” arguments to specify constant or per-series behavior: [38]: color = ["red", "green", "blue", "yellow"] title = ["Series 1", "Series 2", "Series 3", "Series 4"] style = {"stroke":toyplot.color.black} [39]: canvas = toyplot.Canvas(width=300, height=300) axes = canvas.cartesian() bars = axes.bars(heights, color=color, title=title, style=style) Series 1 Series 1 Series 1 Series 1 Series 1 Series 1 Series 1 Series 1 Series 1 Series 1 Series 2 Series 2 Series 2 Series 2 Series 2 Series 2 Series 2 Series 2 Series 2 Series 2 Series 3 Series 3 Series 3 Series 3 Series 3 Series 3 Series 3 Series 3 Series 3 Series 3 Series 4 Series 4 Series 4 Series 4 Series 4 Series 4 Series 4 Series 4 Series 4 Series 4 0 5 10 0 30 60 90 However, with bars we can take these concepts even further to specify per-datum quantities. That is, the color, opacity, and title arguments can accept data that will apply to every individual bar in the plot. For the following example, we generate a per-datum set of random values to map to the color, and also use them as the bar titles (hover over the bars to see the titles): [40]: color = numpy.random.random(heights.shape) colormap = toyplot.color.diverging.map("BlueRed") [41]: canvas = toyplot.Canvas(width=300, height=300) axes = canvas.cartesian() bars = axes.bars(heights, color=(color, colormap), title=color, style=style) 0.67118036649 0.133532633774 0.554120573978 0.251703973957 0.920589748011 0.775928866558 0.134720437283 0.482583685071 0.0818899345124 0.392665903306 0.888308887867 0.990166325071 0.912418077543 0.198153492971 0.952017634264 0.828330445405 0.377302102634 0.172044457353 0.00248295021781 0.462878739418 0.542452965488 0.935960207527 0.605218065236 0.16363800051 0.521272904472 0.0211591344526 0.715489179551 0.505326966788 0.928841016595 0.0682873232196 0.945011062943 0.352675253593 0.402109446772 0.867865068738 0.542207909699 0.780171143565 0.960964938879 0.187286086371 0.664078390361 0.356551345028 0 5 10 0 30 60 90

Scatterplots¶ Next on our whirlwind tour of marks is the scatterplot : [42]: x = numpy.linspace(0, 2 * numpy.pi) y1 = numpy.sin(x) y2 = numpy.cos(x) [43]: canvas = toyplot.Canvas(width=500, height=300) axes = canvas.cartesian() mark = axes.scatterplot(x, y1) 0 2 4 6 -1.0 -0.5 0.0 0.5 1.0 As you might expect, you can omit the X coordinates for a scatterplot, and they will fall in to the range \([0, M)\): [44]: canvas = toyplot.Canvas(width=500, height=300) axes = canvas.cartesian() mark = axes.scatterplot(y1) 0 10 20 30 40 50 -1.0 -0.5 0.0 0.5 1.0 And as we’ve seen before, you can pass multiple series in a single call: [45]: series = numpy.column_stack((y1, y2)) canvas = toyplot.Canvas(width=500, height=300) axes = canvas.cartesian() mark = axes.scatterplot(series) 0 10 20 30 40 50 -1.0 -0.5 0.0 0.5 1.0 And as expected, you can control attributes like color, size, and opacity on a global, per-series, or per-datum basis: [46]: color = numpy.random.random(series.shape) palette = toyplot.color.brewer.map("Oranges") size = [16, 9] canvas = toyplot.Canvas(width=500, height=300) axes = canvas.cartesian() mark = axes.scatterplot(series, color=(color, palette), size=size) 0 10 20 30 40 50 -1.0 -0.5 0.0 0.5 1.0

Markers¶ You can choose from a variety of marker shapes for scatterplots and line plots, also specified either globally, per-series, or per-datum, and specify styles for the markers: [47]: mstyle={"stroke":toyplot.color.black} canvas = toyplot.Canvas(width=500, height=300) axes = canvas.cartesian() mark = axes.scatterplot(series, size=10, marker=["^", "o"], mstyle=mstyle) 0 10 20 30 40 50 -1.0 -0.5 0.0 0.5 1.0 In addition to the basic marker shapes, you can create your own by adding text labels, also with their own styles: [48]: marker = [ toyplot.marker.create(shape="o", label="1"), toyplot.marker.create(shape="o", label="2"), ] mlstyle = {"fill":"white"} [49]: canvas = toyplot.Canvas(width=500, height=300) axes = canvas.cartesian() mark = axes.scatterplot(series, size=15, marker=marker, mstyle=mstyle, mlstyle=mlstyle) 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 0 10 20 30 40 50 -1.0 -0.5 0.0 0.5 1.0 See the user guide section on Markers for additional detail on the available marker shapes, styles, and how to create your own custom markers.

Plots Revisited¶ Now that we’ve seen how bar, fill, and scatter plots can accept multiple series’ worth of data in a single call, let’s revisit our earlier line plots, and see that the same is true: [50]: x = numpy.linspace(0, 10, 100) y1 = numpy.sin(x) y2 = numpy.cos(x) y3 = numpy.sin(x) + numpy.cos(x) series = numpy.column_stack((y1, y2, y3)) [51]: canvas = toyplot.Canvas(width=600, height=300) axes = canvas.cartesian() mark = axes.plot(x, series) 0 5 10 -1 0 1

Axes¶ So far, we’ve created a default set of axes in each of the preceeding examples, and called methods on the axes to add marks to the canvas. The reason we explicitly create axes in Toyplot (instead of simply adding marks to the canvas directly) is that it allows us to have multiple axes on a single canvas: [52]: canvas = toyplot.Canvas(600, 300) axes = canvas.cartesian(grid=(1, 2, 0)) mark = axes.plot(x, y1) axes = canvas.cartesian(grid=(1, 2, 1)) mark = axes.plot(x, y2) 0 5 10 -1.0 -0.5 0.0 0.5 1.0 0 5 10 -1.0 -0.5 0.0 0.5 1.0 In addition to positioning axes on the canvas, there are many properties that control their behavior. For example, Toyplot axes include builtin support for labels: [53]: canvas = toyplot.Canvas(300, 300) axes = canvas.cartesian(label="Toyplot User Growth", xlabel="Days", ylabel="Users") mark = axes.plot(x, 40 + x ** 2) 0 5 10 Days 50 100 150 Users Toyplot User Growth Similarly, we can specify minimum and maximum values for each axis - for example, if we wanted the previous figure to include \(y = 0\): [54]: canvas = toyplot.Canvas(300, 300) axes = canvas.cartesian(label="Toyplot User Growth", xlabel="Days", ylabel="Users", ymin=0) mark = axes.plot(x, 40 + x ** 2) 0 5 10 Days 0 50 100 150 Users Toyplot User Growth We can also specify logarithmic scales for axes: [55]: x = numpy.linspace(-1000, 1000) [56]: toyplot.plot(x, x, marker="o", xscale="linear", yscale="log", width=500); -1000 -500 0 500 1000 -10 3 -10 2 -10 1 -10 0 0 10 0 10 1 10 2 10 3 There are many more properties that control axes positioning and behavior - for more details, see Canvas Layout and Cartesian Coordinates in the user guide.