forked from wireservice/leather
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Working GroupedBars implementation. wireservice#26
- Loading branch information
Showing
8 changed files
with
236 additions
and
9 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import leather | ||
from datetime import date | ||
|
||
data = [ | ||
(1, 'Hello', 'first'), | ||
(5, 'World', 'first'), | ||
(7, 'Hello', 'second'), | ||
(4, 'Goodbye', 'second'), | ||
(7, 'Hello', 'third'), | ||
(3, 'Goodbye', 'third'), | ||
(4, 'Yellow', 'third') | ||
] | ||
|
||
chart = leather.Chart('Bars') | ||
chart.add_grouped_bars(data) | ||
chart.to_svg('examples/charts/grouped_bars.svg') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
#!/usr/bin/env python | ||
|
||
import six | ||
import xml.etree.ElementTree as ET | ||
|
||
from leather import theme | ||
from leather.shapes.base import Shape | ||
|
||
|
||
class CategoryShape(Shape): | ||
""" | ||
Base class for shapes that can be used to render data :class:`.CategorySeries`. | ||
Extends the base :class:`.Shape` class. | ||
""" | ||
def legend_dimension(self): | ||
return self._legend_dimension | ||
|
||
def legend_labels(self, series, palette): | ||
""" | ||
Generate a dictionary of labels mappeds to colors for the legend. | ||
""" | ||
label_colors = [] | ||
legend_dimension = self._legend_dimension | ||
|
||
seen = set() | ||
legend_values = [v for v in series.values(self._legend_dimension) if v not in seen and not seen.add(v)] | ||
|
||
colors = list(palette) | ||
color_count = len(colors) | ||
|
||
for i, value in enumerate(legend_values): | ||
label_colors.append((value, colors[i % color_count])) | ||
|
||
return label_colors | ||
|
||
def legend_to_svg(self, series, palette): | ||
""" | ||
Render the legend entries for these shapes. | ||
""" | ||
label_colors = self.legend_labels(series, palette) | ||
item_groups = [] | ||
|
||
if hasattr(self, '_stroke_color'): | ||
if self._stroke_color: | ||
if callable(self._stroke_color): | ||
# TODO | ||
stroke_color = 'black' | ||
else: | ||
stroke_color = self._stroke_color | ||
else: | ||
stroke_color = next(palette) | ||
else: | ||
stroke_color = None | ||
|
||
bubble_width = theme.legend_bubble_size + theme.legend_bubble_offset | ||
|
||
for label, fill_color in label_colors: | ||
text = six.text_type(label) if label is not None else 'Unnamed label' | ||
text_width = (len(text) + 4) * theme.legend_font_char_width | ||
|
||
item_width = text_width + bubble_width | ||
|
||
# Group | ||
item_group = ET.Element('g') | ||
|
||
# Bubble | ||
bubble = ET.Element('rect', | ||
x=six.text_type(0), | ||
y=six.text_type(-theme.legend_font_char_height + theme.legend_bubble_offset), | ||
width=six.text_type(theme.legend_bubble_size), | ||
height=six.text_type(theme.legend_bubble_size) | ||
) | ||
|
||
if fill_color: | ||
bubble.set('fill', fill_color) | ||
elif stroke_color: | ||
bubble.set('fill', stroke_color) | ||
|
||
item_group.append(bubble) | ||
|
||
# Label | ||
label = ET.Element('text', | ||
x=six.text_type(bubble_width), | ||
y=six.text_type(0), | ||
fill=theme.legend_color | ||
) | ||
label.set('font-family', theme.legend_font_family) | ||
label.set('font-size', six.text_type(theme.legend_font_size)) | ||
label.text = text | ||
|
||
item_group.append(label) | ||
|
||
item_groups.append((item_group, item_width)) | ||
|
||
return item_groups |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
#!/usr/bin/env python | ||
|
||
import xml.etree.ElementTree as ET | ||
|
||
import six | ||
|
||
from leather.series import CategorySeries | ||
from leather.shapes.category import CategoryShape | ||
from leather.utils import issequence, Y, Z | ||
from leather import theme | ||
|
||
|
||
class GroupedBars(CategoryShape): | ||
""" | ||
Render a categorized series of data as grouped bars. | ||
:param fill_color: | ||
A sequence of colors to fill the bars. If the sequence is shorter than | ||
the number of values in any category, the colors will be repeated. | ||
""" | ||
def __init__(self, fill_color=None): | ||
self._fill_color = fill_color | ||
self._legend_dimension = Y | ||
|
||
def validate_series(self, series): | ||
""" | ||
Verify this shape can be used to render a given series. | ||
""" | ||
if issequence(self._fill_color) and len(series.categories()) > len(self._fill_color.keys()): | ||
raise ValueError('fill_color must have an element for every category in the series.') | ||
|
||
def to_svg(self, width, height, x_scale, y_scale, series, palette): | ||
""" | ||
Render bars to SVG elements. | ||
""" | ||
group = ET.Element('g') | ||
group.set('class', 'series grouped-bars') | ||
|
||
zero_x = x_scale.project(0, 0, width) | ||
|
||
if self._fill_color: | ||
fill_color = self._fill_color | ||
else: | ||
fill_color = list(palette) | ||
|
||
label_colors = self.legend_labels(series, fill_color) | ||
|
||
categories = series.categories() | ||
category_counts = {c: series.values(Z).count(c) for c in categories} | ||
seen_counts = {c: 0 for c in categories} | ||
|
||
# Bars display "top-down" | ||
for i, d in enumerate(series.data()): | ||
if d.x is None or d.y is None: | ||
continue | ||
|
||
y1, y2 = y_scale.project_interval(d.z, height, 0) | ||
|
||
group_height = (y1 - y2) / category_counts[d.z] | ||
y1 = y2 + (group_height * (seen_counts[d.z] + 1)) - 1 | ||
y2 = y2 + (group_height * seen_counts[d.z]) | ||
|
||
proj_x = x_scale.project(d.x, 0, width) | ||
|
||
if d.x < 0: | ||
bar_x = proj_x | ||
bar_width = zero_x - proj_x | ||
else: | ||
bar_x = zero_x | ||
bar_width = proj_x - zero_x | ||
|
||
color = dict(label_colors)[d.y] | ||
seen_counts[d.z] += 1 | ||
|
||
group.append(ET.Element('rect', | ||
x=six.text_type(bar_x), | ||
y=six.text_type(y2), | ||
width=six.text_type(bar_width), | ||
height=six.text_type(y1 - y2), | ||
fill=color | ||
)) | ||
|
||
return group |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters