# Tick Locators¶

When you create a figure in Toyplot, you begin by creating a canvas, add coordinate systems, and add data to the coordinate systems in the form of marks. The coordinate system axes map data from its domain to a range on the canvas, and generate ticks in domain-space with tick labels as an integral part of the process.

To generate tick locations and tick labels, individual coordinate system axes delegate to the tick locator classes in the toyplot.locator module. Each tick locator class is responsible for generating a collection of tick locations from the range of values in an axis domain, and there are several different classes available that implement different strategies for generating “good” tick locations. If you don’t specify any tick locators when creating axes, sensible defaults will be chosen for you. For example:

[1]:

import numpy
x = numpy.arange(20)
y = numpy.linspace(0, 1, len(x)) ** 2

[2]:

import toyplot
canvas, axes, mark = toyplot.plot(x, y, width=300)


Note that the X and Y axes in this plot have sensible ticks that use round numbers. In this case the algorithm for identifying “good” tick values is provided by Toyplot’s default toyplot.locator.Extended locator.

However, notice that the X axis has a tick at the value $$20$$, even though the domain of the data values is $$[0, 19]$$. Toyplot always expands the visible domain to include every tick generated by a locator. If you need to change this behavior, there are several approaches: first, you can configure toyplot.locator.Extended to never generate ticks outside the data domain:

[3]:

canvas, axes, mark = toyplot.plot(x, y, width=300)
axes.x.ticks.locator = toyplot.locator.Extended(only_inside=True)


Notice that this can have the side effect of altering the number and spacing of ticks. Let’s say instead that we prefer to always have ticks that include the exact minimum and maximum data domain values, and evenly divide the rest of the domain. In this case, we can override the default choice of locator with the toyplot.locator.Uniform tick locator:

[4]:

canvas, axes, mark = toyplot.plot(x, y, width=300)
axes.x.ticks.locator = toyplot.locator.Uniform(count=5)


A third alternative is to use explicit tick locators to explicitly specify the ticks to generate for an axis. Explicit tick locators are discussed in detail below.

In the meantime, we can also override the default formatting string used to generate the locator labels:

[5]:

canvas, axes, mark = toyplot.plot(x, y, width=300)
axes.x.ticks.locator = toyplot.locator.Uniform(count=5, format="{:.2f}")


Anytime you use log scale axes in a plot, Toyplot automatically uses the toyplot.locator.Log locator to provide ticks that are evenly-spaced in the logarithmic domain:

[6]:

canvas, axes, mark = toyplot.plot(x, y, xscale="log10", width=300)


If you don’t like the “superscript” notation that the Log locator produces, you could replace it with your own locator and custom format:

[7]:

canvas, axes, mark = toyplot.plot(x, y, xscale="log10", width=300)
axes.x.ticks.locator = toyplot.locator.Log(base=10, format="{base}^{exponent}")


Or even display raw tick values:

[8]:

canvas, axes, mark = toyplot.plot(x, y, xscale="log2", width=300)
axes.x.ticks.locator = toyplot.locator.Log(base=2, format="{:.0f}")


Although you might not think of Table Coordinates as needing tick locators, when you use toyplot.matrix() or toyplot.canvas.Canvas.matrix() to visualize a matrix, it generates a table visualization that uses toyplot.locator.Integer locators to generate row and column labels:

[9]:

numpy.random.seed(1234)
canvas, table = toyplot.matrix(numpy.random.random((5, 5)), width=300)


By default the Integer locator generates a tick/label for every integer in the range $$[0, N)$$ … as you visualize larger matrices, you’ll find that a label for every row and column becomes crowded, in which case you can override the default step parameter to space-out the labels:

[10]:

canvas, table = toyplot.matrix(numpy.random.random((50, 50)), width=400, step=5)


## Explicit Locators¶

For the ultimate flexibility in positioning tick locations and labels, you can use the toyplot.locator.Explicit locator. With it, you can specify an explicit set of labels, and a set of $$[0, N)$$ integer locations will be created to match. This is particularly useful if you are working with categorical data:

[11]:

fruits = ["Apples", "Oranges", "Kiwi", "Miracle Fruit", "Durian"]
counts = [452, 347, 67, 21, 5]

canvas, axes, mark = toyplot.bars(counts, width=400, height=300)
axes.x.ticks.locator = toyplot.locator.Explicit(labels=fruits)


Note that in the above example the implicit $$[0, N)$$ tick locations match the implicit $$[0, N)$$ X coordinates that are generated for each bar when you don’t supply any X coordinates of your own. This is by design!

You can also use Explicit locators with a list of tick locations, and a set of tick labels will be generated using a format string. For example:

[12]:

x = numpy.linspace(0, 2 * numpy.pi)
y = numpy.sin(x)
locations=[0, numpy.pi/2, numpy.pi, 3*numpy.pi/2, 2*numpy.pi]

canvas, axes, mark = toyplot.plot(x, y, width=500, height=300)
axes.x.ticks.locator = toyplot.locator.Explicit(locations=locations, format="{:.2f}")


Finally, you can supply both locations and labels to an Explicit locator:

[13]:

labels = ["0", u"\u03c0 / 2", u"\u03c0", u"3\u03c0 / 2", u"2\u03c0"]

canvas, axes, mark = toyplot.plot(x, y, width=500, height=300)
axes.x.ticks.locator = toyplot.locator.Explicit(locations=locations, labels=labels)


## Timestamp Locators¶

Toyplot includes the toyplot.locator.Timestamp locator which can be used to provide human-consumable date-time labels when your data’s domain is timestamps (seconds since the Unix epoch, i.e. midnight, January 1st, 1970, UTC). As an example, let’s load some real-world data containing date-time information:

[14]:

import toyplot.data
data = toyplot.data.commute()
data[:6]

[14]:

DatetimeNameValueUnits
2014-05-01T14:08:53.587607ZStatus Since DTC Cleared0011110100110
2014-05-01T14:08:53.656972ZFuel System Status0000
2014-05-01T14:08:53.734393ZCoolant Temperature13C
2014-05-01T14:08:53.804349ZShort Term Fuel Trim0.0%
2014-05-01T14:08:53.873862ZLong Term Fuel Trim-3.125%

Our first step will be to convert the string datetimes from the file into true numeric timestamps. Note that for this example we’re using Arrow, a library that improves upon the builtin datetime functionality in Python. Note that Arrow is used in the timestamp locator implementation, so it must already be installed for the following examples to work:

[15]:

import arrow
timestamps = numpy.array([arrow.get(datetime).timestamp() for datetime in data["Datetime"]])


Now, we can plot the data using the timestamps as our independent variable:

[16]:

observations = numpy.logical_and(data["Name"] == "Vehicle Speed", data["Value"] != "NODATA")
x = timestamps[observations]
y = data["Value"][observations]

[17]:

canvas, axes, mark = toyplot.plot(
x, y, label="Vehicle Speed", xlabel="Time", ylabel="km/h",
width=600, height=300)


As you would expect, the timestamps make very unfriendly tick labels. Let’s use the timestamp locator instead:

[18]:

canvas, axes, mark = toyplot.plot(
x, y, label="Vehicle Speed", xlabel="Time", ylabel="km/h",
width=600, height=300)
axes.x.ticks.show = True
axes.x.ticks.locator = toyplot.locator.Timestamp()


By default, the timestamp locator chooses a “good” time interval based on the data domain - for this data, it chose to create ticks at five minute intervals. In addition, the locator also chooses a default format based on the interval - in this case, month/day hour:minute. This is a significant improvement over raw timestamps, but before we continue, we need to address timezone issues.

As alluded to above, in Toyplot all timestamps must always be UTC timestamps, without exception … that is to say that the numeric values to be displayed by the timestamp locator must be the number of seconds since Midnight, January 1st, 1970, UTC time. Our current example data already contained UTC datetimes (note the “Z” timezone in the data file), so no special conversion was necessary. If for some reason you’re working with datetimes that aren’t UTC, you’ll need to take their timezone into consideration when converting them to timestamps for display by Toyplot … and as an aside: you need to run, not walk to anyone collecting data using local timestamps, and make them stop!

Since all Toyplot timestamps are UTC, the times displayed by the timestamp locator are also UTC by default. If you prefer to display times using a specific timezone, you can specify it when creating the locator.

An Important Note on Timezone Names

The arrow library (and thus Toyplot) relies on the underlying operating system for its timezone information. The following examples use “US/Mountain” to identify a timezone, and should work on most posix-like systems including Linux and OSX. Unfortunately, Windows operating systems use a different naming scheme; if you are using Windows, you will need to substitute a different name, such as “US Mountain Standard Time”.

[19]:

canvas, axes, mark = toyplot.plot(
x, y, label="Vehicle Speed", xlabel="Time (US/Mountain)", ylabel="km/h",
width=600, height=300)
axes.x.ticks.show = True
axes.x.ticks.locator = toyplot.locator.Timestamp(timezone="US/Mountain")


Note that the hour in this data changed from 14:00 to 8:00, which makes more sense, since this data came from a morning commute. You could also specify a timezone of “utc” (the default) to explicitly document in the code that no timezone conversion is taking place. Finally, you may use “local” to automatically display the data using whichever timezone is local to the host running the code … but be careful with this option, since it means that the the content of a figure could change based on where your laptop was when you generated it!

With timezones out of the way, let’s focus on the labels. The current set is a little crowded, so let’s look at different ways to clean things up. First, we can use the count attribute to request that the locator choose an interval that produces a specific number of ticks:

[20]:

canvas, axes, mark = toyplot.plot(
x, y, label="Vehicle Speed", xlabel="Time (US/Mountain)", ylabel="km/h",
width=600, height=300)
axes.x.ticks.show = True
axes.x.ticks.locator = toyplot.locator.Timestamp(timezone="US/Mountain", count=5)


Note that in most cases the locator won’t produce the exact number of ticks requested (we got lucky in this case), it will choose an interval that produces the closest match.

Alternatively, we might choose to accept the default tick count and alter the tick format, to save space:

[21]:

canvas, axes, mark = toyplot.plot(
x, y, label="Vehicle Speed", xlabel="Time (US/Mountain)", ylabel="km/h",
width=600, height=300)
axes.x.ticks.show = True
axes.x.ticks.locator = toyplot.locator.Timestamp(
timezone="US/Mountain", format="{0:HH}:{0:mm}")


The individual tick values are passed to the format function as arrow.arrow.Arrow objects, so you can use any of the arrow attributes and formatting tokens in the format string, as we’ve done here.

Or, we might force a specific interval if it had special meaning for our domain, such as the number of seven-minute workouts we could have completed during our commute:

[22]:

canvas, axes, mark = toyplot.plot(
x, y, label="Vehicle Speed", xlabel="Time (US/Mountain)", ylabel="km/h",
width=600, height=300)
axes.x.ticks.show = True
axes.x.ticks.locator = toyplot.locator.Timestamp(
timezone="US/Mountain", interval=(7, "minutes"))


Toyplot supports common intervals in both singular and plural forms from seconds to millennia.

Finally, we might go in the opposite direction and show more detail in the timestamps. This is often a case where you’ll want to adjust the orientation of the labels so they don’t overlap (note in the following example that we had to make the canvas larger and position the axes manually on the canvas to make room for the angled labels - and we manually placed the x axis label to avoid overlap):

[23]:

canvas = toyplot.Canvas(width=600, height=350)
axes = canvas.cartesian(bounds=(80, -80, 50, -120), label="Vehicle Speed", ylabel="km/h")
axes.plot(x, y)
axes.x.ticks.show = True
axes.x.ticks.locator = toyplot.locator.Timestamp(
timezone="US/Mountain", format="{0:MMMM} {0:d}, {0:YYYY} {0:h}:{0:mm} {0:a}")
axes.x.ticks.labels.angle = 30
canvas.text(300, 320, "Time (US/Mountain)", style={"font-weight":"bold"});