Skip to content

Commit

Permalink
Add optional axis titles. Closes #34.
Browse files Browse the repository at this point in the history
  • Loading branch information
onyxfish committed May 20, 2016
1 parent 4a8f63d commit c365482
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 27 deletions.
61 changes: 53 additions & 8 deletions leather/axis.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import six

from leather import svg
from leather import theme


Expand All @@ -21,19 +22,39 @@ class Axis(object):
is the total number of ticks. The return value of the function will
be used for display instead of the original tick value.
"""
def __init__(self, ticks=None, tick_formatter=None):
def __init__(self, ticks=None, tick_formatter=None, name=None):
self._ticks = ticks or theme.default_ticks
self._tick_formatter = tick_formatter
self._tick_formatter = tick_formatter or (lambda value, i, tick_count: value)
self._name = name

def _estimate_left_tick_width(self, scale):
"""
Estimate the y axis space used by tick labels.
"""
ticks = scale.ticks(self._ticks)
tick_count = len(ticks)
max_len = 0

for i, value in enumerate(ticks):
max_len = max(max_len, len(self._tick_formatter(value, i, tick_count)))

return max_len * theme.tick_font_char_width

def estimate_label_margin(self, scale, orient):
"""
Estimate the space needed for the tick labels.
"""
margin = 0

if orient == 'left':
max_len = max(len(six.text_type(t)) for t in scale.ticks(self._ticks))
return max_len * theme.tick_font_char_width
margin += self._estimate_left_tick_width(scale) + (theme.tick_size * 2)
elif orient == 'bottom':
return theme.tick_font_char_height
margin += theme.tick_font_char_height + (theme.tick_size * 2)

if self._name:
margin += theme.axis_title_font_char_height + theme.axis_title_gap

return margin

def to_svg(self, width, height, scale, orient):
"""
Expand All @@ -42,6 +63,32 @@ def to_svg(self, width, height, scale, orient):
group = ET.Element('g')
group.set('class', 'axis ' + orient)

# Axis title
if orient == 'left':
title_x = -(self._estimate_left_tick_width(scale) + theme.axis_title_gap)
title_y = height / 2
dy=''
transform = svg.rotate(270, title_x, title_y)
elif orient == 'bottom':
title_x = width / 2
title_y = height + theme.tick_font_char_height + (theme.tick_size * 2) + theme.axis_title_gap
dy='1em'
transform = ''

title = ET.Element('text',
x=six.text_type(title_x),
y=six.text_type(title_y),
dy=dy,
fill=theme.axis_title_color,
transform=transform
)
title.set('text-anchor', 'middle')
title.set('font-family', theme.axis_title_font_family)
title.text = self._name

group.append(title)

# Ticks
if orient == 'left':
label_x = -(theme.tick_size * 2)
x1 = -theme.tick_size
Expand Down Expand Up @@ -112,9 +159,7 @@ def to_svg(self, width, height, scale, orient):
label.set('text-anchor', text_anchor)
label.set('font-family', theme.tick_font_family)

if self._tick_formatter:
value = self._tick_formatter(value, i, tick_count)

value = self._tick_formatter(value, i, tick_count)
label.text = six.text_type(value)

tick_group.append(label)
Expand Down
40 changes: 22 additions & 18 deletions leather/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,41 +31,45 @@ def __init__(self, title=None):
self._scales = [None, None]
self._axes = [None, None]

def _set_scale(self, dimension, scale):
"""
Set a new :class:`.Scale` for this chart.
"""
self._scales[dimension] = scale

def set_x_scale(self, scale):
"""
Set the X :class:`.Scale` for this chart.
"""
self._set_scale(X, scale)
self._scales[X] = scale

def set_y_scale(self, scale):
"""
Set the Y :class:`.Scale` for this chart.
"""
self._set_scale(Y, scale)
self._scales[Y] = scale

def _set_axis(self, dimension, axis):
def set_x_axis(self, axis):
"""
Set a new :class:`.Axis` for this chart.
Set a new :class:`.Axis` class for this chart.
"""
self._axes[dimension] = axis
self._axes[X] = axis

def set_x_axis(self, axis):
def set_y_axis(self, axis):
"""
Set the X :class:`.Axis` for this chart.
Set the Y :class:`.Axis` class for this chart.
"""
self._set_axis(X, axis)
self._axes[Y] = axis

def set_y_axis(self, axis):
def add_x_axis(self, ticks=None, tick_formatter=None, name=None):
"""
Create and add an X :class:`.Axis`.
If you want to use a custom axis class use :meth:`.set_x_axis` instead.
"""
Set the Y :class:`.Axis` for this chart.
self._axes[X] = Axis(ticks, tick_formatter, name)

def add_y_axis(self, ticks=None, tick_formatter=None, name=None):
"""
Create and add an Y :class:`.Axis`.
If you want to use a custom axis class use :meth:`.set_y_axis` instead.
"""
self._set_axis(Y, axis)
self._axes[Y] = Axis(ticks, tick_formatter, name)

def add_series(self, series):
"""
Expand Down Expand Up @@ -200,7 +204,7 @@ def to_svg_group(self, width=None, height=None):
label.text = six.text_type(self._title)

header_group.append(label)
header_margin += theme.title_font_char_height
header_margin += theme.title_font_char_height + theme.title_gap

margin_group.append(header_group)

Expand Down
7 changes: 7 additions & 0 deletions leather/svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,10 @@ def translate(x, y):
Generate an SVG transform statement representing a simple translation.
"""
return 'translate(%i %i)' % (x, y)

def rotate(deg, x, y):
"""
Generate an SVG transform statement representing rotation around a given
point.
"""
return 'rotate(%i %i %i)' % (deg, x, y)
25 changes: 24 additions & 1 deletion leather/theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,34 @@
title_font_size = 16

#: Approximate glyph height of the title font
title_font_char_height = 18
title_font_char_height = 16

#: Approximate glyph width of the title font
title_font_char_width = 9

#: Gap between title and rest of chart
title_gap = 4

# AXIS

#: Axis title text color
axis_title_color = '#333'

#: Axis title font
axis_title_font_family = 'Monaco'

#: Axis title font size
axis_title_font_size = 14

#: Approximate glyph height of the axis title font
axis_title_font_char_height = 14

#: Approximate glyph width of the axis title font
axis_title_font_char_width = 8

#: Gap between axis title and rest of chart
axis_title_gap = 8

# TICKS

#: Default number of ticks to display
Expand Down
2 changes: 2 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@
data = list(reader)[:10]

chart = leather.Chart('Test')
chart.add_x_axis(name='Test X Axis name')
chart.add_y_axis(name='The Y Axis has arrived')
chart.add_bars(data, x=1, y=0)
chart.to_svg('test.svg')

0 comments on commit c365482

Please sign in to comment.