Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Working on native charts support

  • Loading branch information...
commit 670ae8040de0afcaeaac04f910e9e51a69533b6d 1 parent 21b976b
@marinho authored
View
1  examples/pdf2jpg
@@ -18,4 +18,5 @@ convert inline-detail-report.pdf inline-detail-report.jpg
convert inline-detail-report-half-height.pdf inline-detail-report-half-height.jpg
convert additional-fonts.pdf additional-fonts.jpg
convert report-with-barcodes.pdf report-with-barcodes.jpg
+convert report-with-charts.pdf report-with-charts.jpg
View
BIN  examples/report-with-charts.pdf
Binary file not shown
View
2  geraldo/__init__.py
@@ -31,7 +31,7 @@
- tests - a package with automated doc tests.
"""
-VERSION = (0, 4, 'alpha-6')
+VERSION = (0, 4, 'alpha-7')
def get_version():
return '%d.%d-%s'%VERSION
View
1  geraldo/base.py
@@ -18,6 +18,7 @@
BAND_WIDTH = 'band-width'
BAND_HEIGHT = 'band-height'
CROSS_COLS = 'cross-cols'
+CROSS_ROWS = 'cross-rows'
class GeraldoObject(object):
"""Base class inherited by all report classes, including band, subreports,
View
447 geraldo/charts.py
@@ -0,0 +1,447 @@
+from reportlab.graphics.shapes import Drawing, String
+
+from reportlab.graphics.charts.barcharts import HorizontalBarChart as OriginalHorizBarChart
+from reportlab.graphics.charts.barcharts import VerticalBarChart as OriginalVertBarChart
+from reportlab.graphics.charts.barcharts import HorizontalBarChart3D as OriginalHorizBarChart3D
+from reportlab.graphics.charts.barcharts import VerticalBarChart3D as OriginalVertBarChart3D
+from reportlab.graphics.charts.doughnut import Doughnut as OriginalDoughnutChart
+from reportlab.graphics.charts.linecharts import HorizontalLineChart as OriginalLineChart
+from reportlab.graphics.charts.piecharts import Pie as OriginalPieChart
+from reportlab.graphics.charts.spider import SpiderChart as OriginalSpiderChart
+from reportlab.graphics.charts.legends import LineLegend
+
+from utils import cm, memoize, get_attr_value
+from cross_reference import CrossReferenceMatrix, CROSS_COLS, CROSS_ROWS
+from graphics import Graphic
+
+DEFAULT_TITLE_HEIGHT = 1*cm
+
+class BaseChart(Graphic):
+ """Abstract chart class"""
+
+ chart_class = None
+ title = None
+ colors = None
+ _width = 8*cm
+ _height = 7*cm
+ rows_attribute = None
+ cols_attribute = None
+ cell_attribute = None
+ action = 'first'
+ data = None
+ chart_style = None # Additional chart attributes
+ axis_labels = None
+ axis_labels_angle = None
+ legend_labels = False
+ values_labels = ' %s '
+ replace_none_by_zero = True
+ summarize_by = None # Can be None, CROSS_ROWS or CROSS_COLS
+
+ def __init__(self, **kwargs):
+ # Set instance attributes
+ for k,v in kwargs.items():
+ if k == 'style':
+ setattr(self, 'chart_style', v)
+ else:
+ setattr(self, k, v)
+
+ # Prepare the title
+ if self.title:
+ self.title = isinstance(self.title, dict) and self.title or {'text': self.title}
+ self.title.setdefault('fontSize', 14)
+ self.title.setdefault('textAnchor', 'middle')
+ self.title.setdefault('height', DEFAULT_TITLE_HEIGHT)
+
+ # Prepare the colors
+ if self.colors == False:
+ self.legend_labels = None
+ elif not self.colors:
+ self.colors = self.get_available_colors()
+
+ # Prepare chart additional kwargs
+ self.chart_style = self.chart_style or {}
+
+ def clone(self):
+ new = super(BaseChart, self).clone()
+
+ new.chart_class = self.chart_class
+ new.title = self.title
+ new.colors = self.colors
+ new.rows_attribute = self.rows_attribute
+ new.cols_attribute = self.cols_attribute
+ new.cell_attribute = self.cell_attribute
+ new.action = self.action
+ new.data = self.data
+ new.chart_style = self.chart_style
+ new.axis_labels = self.axis_labels
+ new.axis_labels_angle = self.axis_labels_angle
+ new.legend_labels = self.legend_labels
+ new.values_labels = self.values_labels
+ new.replace_none_by_zero = self.replace_none_by_zero
+ new.summarize_by = self.summarize_by
+
+ return new
+
+ # DRAWING METHODS
+
+ @memoize
+ def get_available_colors(self):
+ """Returns a list of available colors"""
+ import random
+ from reportlab.lib.colors import getAllNamedColors
+
+ # Get reportlab available colors
+ colors = getAllNamedColors()
+
+ # Remove bad colors
+ colors.pop('white', None)
+ colors.pop('black', None)
+
+ # Returns only the colors values (without their names)
+ colors = colors.values()
+
+ # Shuffle colors list
+ random.shuffle(colors)
+
+ return colors
+
+ def get_drawing(self, chart):
+ """Create and returns the drawing, to be generated"""
+
+ drawing = Drawing(self.width, self.height)
+
+ # Make the title
+ title = self.make_title(drawing)
+
+ # Make the legend
+ legend = self.make_legend(drawing, chart)
+
+ # Setting chart dimensions
+ chart.height = self.height
+ chart.width = self.width
+
+ if title:
+ chart.height -= self.title.get('height', DEFAULT_TITLE_HEIGHT)
+ self.top += self.title.get('height', DEFAULT_TITLE_HEIGHT)
+
+ if legend:
+ chart.width = legend.getBounds()[0]
+
+ # Setting additional chart attributes
+ self.set_chart_style(chart)
+
+ # Adds the chart to drawing to return
+ drawing.add(chart)
+
+ # Resizes to make sure everything is fitting
+ drawing = drawing.resized()
+
+ return drawing
+
+ def make_legend(self, drawing, chart):
+ if not self.legend_labels:
+ return
+
+ # Get legend labels
+ labels = self.get_legend_labels()
+
+ # Legend object
+ legend = LineLegend()
+ #legend.fontName = 'Times-Roman'
+ #legend.fontSize = 12
+
+ legend.colorNamePairs = zip(self.colors[:len(labels)], labels)
+ legend.columnMaximum = len(legend.colorNamePairs)
+ legend.deltay = 5
+ legend.alignment = 'right'
+ legend.x = drawing.width
+ legend.y = drawing.height - (self.title and self.title.get('height', DEFAULT_TITLE_HEIGHT) or 0)
+
+ drawing.add(legend)
+
+ return legend
+
+ def get_legend_labels(self):
+ # Use same axis if is summarizing
+ if self.summarize_by:
+ return self.get_axis_labels()
+
+ # Base labels
+ if isinstance(self.legend_labels, (tuple,list)):
+ labels = self.legend_labels
+ else:
+ labels = self.get_cross_data().rows()
+
+ # Calculated labels
+ if callable(self.legend_labels):
+ labels = [self.legend_labels(self, label, num) for num, label in enumerate(labels)]
+ elif isinstance(self.legend_labels, basestring):
+ labels = [self.get_cross_data().first(self.legend_labels, col=label) for label in labels]
+
+ return map(unicode, labels)
+
+ def get_axis_labels(self):
+ # Base labels
+ if isinstance(self.axis_labels, (tuple,list)):
+ labels = self.axis_labels
+ elif self.summarize_by == CROSS_ROWS:
+ labels = self.get_cross_data().rows()
+ else:
+ labels = self.get_cross_data().cols()
+
+ # Calculated labels
+ if callable(self.axis_labels):
+ labels = [self.axis_labels(self, label, num) for num, label in enumerate(labels)]
+ elif isinstance(self.axis_labels, basestring):
+ if self.summarize_by == CROSS_ROWS:
+ labels = [self.get_cross_data().first(self.axis_labels, row=label) for label in labels]
+ else:
+ labels = [self.get_cross_data().first(self.axis_labels, col=label) for label in labels]
+
+ return map(unicode, labels)
+
+ def make_title(self, drawing):
+ if not self.title:
+ return
+
+ # Make the dict with kwargs
+ kwargs = self.title.copy()
+ kwargs.setdefault('x', drawing.width / 2)
+ kwargs.setdefault('y', drawing.height)
+
+ # Make the string
+ title = String(**kwargs)
+
+ drawing.add(title)
+
+ return title
+
+ # CHART METHODS
+
+ def get_cross_data(self, data=None):
+ if not getattr(self, '_cross_data', None):
+ data = data or self.data
+
+ # Transforms data to cross-reference matrix
+ if isinstance(data, basestring):
+ data = get_attr_value(self.instance, data)
+
+ if not isinstance(data, CrossReferenceMatrix):
+ if self.rows_attribute and self.cols_attribute:
+ data = CrossReferenceMatrix(data, self.rows_attribute, self.cols_attribute)
+
+ self._cross_data = data
+
+ return self._cross_data
+
+ def get_data(self):
+ data = self.data
+
+ # Returns nothing data is empty
+ if not data:
+ data = self.report.queryset # TODO: Change to support current objects
+ # list (for subreports and groups)
+
+ # Transforms data to cross-reference matrix
+ data = self.get_cross_data(data)
+
+ # Summarize data or get its matrix (after it is a Cross-Reference Matrix)
+ if self.summarize_by == CROSS_ROWS:
+ data = data.summarize_rows(self.cell_attribute, self.action)
+ elif self.summarize_by == CROSS_COLS:
+ data = data.summarize_cols(self.cell_attribute, self.action)
+ else:
+ data = data.matrix(self.cell_attribute, self.action)
+
+ def none_to_zero(value):
+ if value is None:
+ value = 0
+ elif isinstance(value, (list, tuple)):
+ value = [cell or 0 for cell in value]
+
+ return value
+
+ # Replace None to Zero
+ if self.replace_none_by_zero:
+ data = map(none_to_zero, data)
+
+ return data
+
+ def set_chart_attributes(self, chart):
+ # Cols (Y) labels - Y axis
+ if self.axis_labels:
+ chart.categoryAxis.categoryNames = self.get_axis_labels()
+
+ if self.axis_labels_angle is not None:
+ chart.categoryAxis.labels.angle = self.axis_labels_angle
+ chart.categoryAxis.labels.boxAnchor = 'ne'
+
+ def set_chart_style(self, chart):
+ # Setting additional chart attributes
+ if self.chart_style:
+ for k,v in self.chart_style.items():
+ setattr(chart, k, v)
+
+ def create_chart(self):
+ chart = self.chart_class()
+
+ return chart
+
+ def render(self):
+ # Make data matrix
+ data = self.get_data()
+
+ if not data:
+ return
+
+ # Creates the chart instance
+ chart = self.create_chart()
+ chart.data = data
+
+ # Sets additional attributes
+ self.set_chart_attributes(chart)
+
+ return self.get_drawing(chart)
+
+class BaseMatrixChart(BaseChart):
+ """Abstract chart class to support matrix charts"""
+
+ def get_data(self):
+ data = super(BaseMatrixChart, self).get_data()
+
+ if data and self.summarize_by:
+ data = [data]
+
+ return data
+
+class LineChart(BaseMatrixChart):
+ chart_class = OriginalLineChart
+
+ def set_chart_attributes(self, chart):
+ super(LineChart, self).set_chart_attributes(chart)
+
+ # Cells labels
+ if self.values_labels:
+ self.chart_style.setdefault('lineLabelFormat', self.values_labels)
+ else:
+ self.chart_style.pop('lineLabelFormat', None)
+
+ # Set the line colors
+ if self.colors:
+ for num, color in enumerate(self.colors):
+ try:
+ chart.lines[num].strokeColor = color
+ except IndexError:
+ break
+
+class BarChart(BaseMatrixChart):
+ chart_class = None
+ horizontal = False # If is not horizontal, is because it is vertical (default)
+ is3d = False
+
+ def __init__(self, *args, **kwargs):
+ super(BarChart, self).__init__(*args, **kwargs)
+
+ # Chart class varies depending on attributes
+ if not self.chart_class:
+ if self.horizontal and self.is3d:
+ self.chart_class = OriginalHorizBarChart3D
+ elif self.horizontal:
+ self.chart_class = OriginalHorizBarChart
+ elif self.is3d:
+ self.chart_class = OriginalVertBarChart3D
+ else:
+ self.chart_class = OriginalVertBarChart
+
+ def clone(self):
+ new = super(BarChart, self).clone()
+
+ new.horizontal = self.horizontal
+ new.is3d = self.is3d
+
+ return new
+
+ def set_chart_attributes(self, chart):
+ super(BarChart, self).set_chart_attributes(chart)
+
+ # Cells labels
+ if self.values_labels:
+ self.chart_style.setdefault('barLabelFormat', self.values_labels)
+
+ # Label orientation
+ if self.horizontal:
+ chart.barLabels.boxAnchor = 'w'
+ else:
+ chart.barLabels.boxAnchor = 's'
+ else:
+ self.chart_style.pop('barLabelFormat', None)
+
+ # Set bar strokes
+ chart.bars.strokeWidth = 0
+
+ # Set the bar colors
+ if self.colors:
+ for num, color in enumerate(self.colors):
+ try:
+ chart.bars[num].fillColor = color
+ except IndexError:
+ break
+
+class SpiderChart(BaseMatrixChart):
+ chart_class = OriginalSpiderChart
+
+ def set_chart_attributes(self, chart):
+ # Chart labels
+ chart.labels = self.get_axis_labels()
+
+ # Set the strands colors
+ if self.colors:
+ for num, color in enumerate(self.colors):
+ try:
+ chart.strands[num].fillColor = color
+ except IndexError:
+ break
+
+class PieChart(BaseChart):
+ chart_class = OriginalPieChart
+ slice_popout = None
+
+ def __init__(self, **kwargs):
+ super(PieChart, self).__init__(**kwargs)
+
+ # Force default value for summarize
+ if not self.summarize_by:
+ self.summarize_by = CROSS_ROWS
+
+ def set_chart_attributes(self, chart):
+ chart.labels = self.get_axis_labels()
+
+ # Sets the slice colors
+ if self.colors:
+ for num, color in enumerate(self.colors):
+ try:
+ chart.slices[num].fillColor = color
+ except IndexError:
+ break
+
+ # Sets the slice to popout
+ pos = -1
+ if self.slice_popout == True:
+ data = self.get_data()
+ pos = data.index(max(data))
+ elif isinstance(self.slice_popout, int):
+ pos = self.slice_popout
+ elif callable(self.slice_popout):
+ pos = self.slice_popout(self, chart)
+
+ if pos >= 0:
+ chart.slices[pos].popout = 20
+
+ def clone(self):
+ new = super(PieChart, self).clone()
+ new.slice_popout = self.slice_popout
+ return new
+
+class DoughnutChart(PieChart):
+ chart_class = OriginalDoughnutChart
+
View
61 geraldo/cross_reference.py
@@ -7,7 +7,7 @@
import random
from utils import get_attr_value, memoize
-from base import ReportBand, GeraldoObject, CROSS_COLS
+from base import ReportBand, GeraldoObject, CROSS_COLS, CROSS_ROWS
RANDOM_ROW_DEFAULT = RANDOM_COL_DEFAULT = ''.join([random.choice([chr(c) for c in range(48, 120)]) for i in range(100)])
@@ -43,7 +43,7 @@ class CrossReferenceMatrix(object):
cols_attr = None
def __init__(self, objects_list, rows_attribute, cols_attribute):
- self.objects_list = objects_list
+ self.objects_list = objects_list or []
self.rows_attr = rows_attribute
self.cols_attr = cols_attribute
@@ -106,22 +106,69 @@ def distinct_count(self, cell, row=RANDOM_ROW_DEFAULT, col=RANDOM_COL_DEFAULT):
@memoize
def first(self, cell, row=RANDOM_ROW_DEFAULT, col=RANDOM_COL_DEFAULT):
- return self.values(cell, row, col)[0]
+ try:
+ return self.values(cell, row, col)[0]
+ except IndexError:
+ return None
@memoize
def last(self, cell, row=RANDOM_ROW_DEFAULT, col=RANDOM_COL_DEFAULT):
- return self.values(cell, row, col)[-1]
+ try:
+ return self.values(cell, row, col)[-1]
+ except IndexError:
+ return None
@memoize
- def matrix(self, cell, func='values'):
+ def matrix(self, cell, func='values', show_rows=False, show_cols=False):
ret = []
- ret.append([''] + self.cols())
+ # Show column names if argument requires
+ if show_cols:
+ # Show the 0,0 cell (row/column relation)
+ prep = show_rows and [''] or []
+
+ ret.append(prep + self.cols())
+
+ func = getattr(self, func)
+
+ for row in self.rows():
+ # Show rows values if argument requires
+ prep = show_rows and [row] or []
+ ret.append(prep + [func(cell, row, col) for col in self.cols()])
+
+ return ret
+
+ @memoize
+ def summarize_rows(self, cell, func='values', show_rows=False):
+ ret = []
func = getattr(self, func)
for row in self.rows():
- ret.append([row] + [func(cell, row, col) for col in self.cols()])
+ val = func(cell, row)
+
+ # Show rows values if argument requires
+ if show_rows:
+ ret.append([row, val])
+ else:
+ ret.append(val)
+
+ return ret
+
+ @memoize
+ def summarize_cols(self, cell, func='values', show_cols=False):
+ ret = []
+
+ func = getattr(self, func)
+
+ for col in self.cols():
+ val = func(cell, col=col)
+
+ # Show cols values if argument requires
+ if show_cols:
+ ret.append([col, val])
+ else:
+ ret.append(val)
return ret
View
5 geraldo/generators/base.py
@@ -8,6 +8,7 @@
from geraldo.base import GeraldoObject, ManyElements
from geraldo.cache import CACHE_BY_QUERYSET, CACHE_BY_RENDER, CACHE_DISABLED,\
make_hash_key, get_cache_backend
+from geraldo.charts import BaseChart
class ReportPage(GeraldoObject):
rect = None
@@ -265,6 +266,9 @@ def render_element(self, element, current_object, band, band_rect, temp_top,
graphic.left = band_rect['left'] + self.calculate_size(graphic.left)
graphic.top = top_position - self.calculate_size(graphic.top) - self.calculate_size(graphic.height)
self.wrap_barcode_on(barcode, graphic.width, graphic.height)
+ elif isinstance(element, BaseChart):
+ graphic.left = band_rect['left'] + self.calculate_size(graphic.left)
+ graphic.top = top_position - self.calculate_size(graphic.top) - self.calculate_size(graphic.height)
# Sets element height as the highest
temp_height = self.calculate_size(element.top) + self.calculate_size(graphic.height)
@@ -287,6 +291,7 @@ def render_element(self, element, current_object, band, band_rect, temp_top,
self.render_element(el, current_object, band, band_rect, temp_top,
highest_height, top_position)
+
def render_band(self, band, top_position=None, left_position=None,
update_top=True, current_object=None):
"""Generate a band having the current top position or informed as its
View
10 geraldo/generators/pdf.py
@@ -25,6 +25,7 @@
Ellipse, Image
from geraldo.barcodes import BarCode
from geraldo.cache import make_hash_key, get_cache_backend, CACHE_DISABLED
+from geraldo.charts import BaseChart
class PDFGenerator(ReportGenerator):
"""This is a generator to output a PDF using ReportLab library with
@@ -466,7 +467,14 @@ def generate_graphic(self, graphic, canvas=None):
)
elif isinstance(graphic, BarCode):
barcode = graphic.render()
- barcode.drawOn(canvas, graphic.left, graphic.top)
+
+ if barcode:
+ barcode.drawOn(canvas, graphic.left, graphic.top)
+ elif isinstance(graphic, BaseChart):
+ drawing = graphic.render()
+
+ if drawing:
+ drawing.drawOn(canvas, graphic.left, graphic.top)
else:
return
View
197 geraldo/tests/27-charts.txt
@@ -1,20 +1,201 @@
CHARTS
======
-Test to implement charts generation on Geraldo, with no dependency to MatPlotLib,
-Cairoplot or any other reports generator.
+Test to implement charts generation on Geraldo, with no dependency on MatPlotLib,
+Cairoplot or any other charts generator.
Even if being the most powerful charting library, MatPlotLib is very hard to
-implement on a report, and since Geraldo already depends on ReportLab and it
-has already a good charting generation kit, the best choice is use it.
+implement on a report, and since Geraldo already depends on ReportLab and it has
+already a good charting generation kit, the best choice is use it.
>>> from reportlab.graphics import charts
-There are 2D and 3D charts, and there are many types of them. Besides that, there
-are some different ways to get data to draw them.
+Chart Classes
+-------------
-So, firstly we are going to list the chart types below:
+The chart base class inherits from Graphic.
+
+ >>> from geraldo.charts import BaseChart, Graphic
+ >>> issubclass(BaseChart, Graphic)
+ True
+
+There are many chart types, but for a while we are going to support only the most
+common, below:
+
+- Bars (horizontal and vertical, with optional support to 3D)
+
+ >>> from geraldo.charts import BarChart
+
+ >>> hasattr(BarChart, 'is3d')
+ True
+
+ >>> hasattr(BarChart, 'horizontal')
+ True
+
+- Line
+
+ >>> from geraldo.charts import LineChart
- Pie
-- Bar
+
+ >>> from geraldo.charts import PieChart
+
+- Doughnut
+
+ >>> from geraldo.charts import DoughnutChart
+
+- Spider
+
+ >>> from geraldo.charts import SpiderChart
+
+Data to test
+------------
+
+ >>> from geraldo.cross_reference import CrossReferenceMatrix, CROSS_ROWS, CROSS_COLS
+
+ >>> holidays = [
+ ... {'name': 'New year', 'type': 'tradition', 'month': 1, 'day': 1, 'month_name': 'jan'},
+ ... {'name': "Sao Paulo's Birthday", 'type': 'civic', 'month': 1, 'day': 25, 'month_name': 'jan'},
+ ... {'name': 'Carnival', 'type': 'tradition', 'month': 2, 'day': 15, 'month_name': 'feb'},
+ ... {'name': 'Easter', 'type': 'religious', 'month': 3, 'day': 20, 'month_name': 'mar'},
+ ... {'name': 'Discovering Day', 'type': 'civic', 'month': 4, 'day': 22, 'month_name': 'apr'},
+ ... {'name': 'Day of Work', 'type': 'civic', 'month': 5, 'day': 1, 'month_name': 'may'},
+ ... {'name': "Tarsila's Birthday", 'type': 'tradition', 'month': 5, 'day': 9, 'month_name': 'may'},
+ ... {'name': 'Corpus Christi', 'type': 'religious', 'month': 6, 'day': 12, 'month_name': 'jun'},
+ ... {'name': "Marinho's Birthday", 'type': 'tradition', 'month': 7, 'day': 10, 'month_name': 'jul'},
+ ... {'name': "Leticia's Birthday", 'type': 'tradition', 'month': 8, 'day': 10, 'month_name': 'ago'},
+ ... {'name': 'Independence Day', 'type': 'civic', 'month': 9, 'day': 7, 'month_name': 'sep'},
+ ... {'name': 'Election Day', 'type': 'civic', 'month': 10, 'day': 3, 'month_name': 'oct'},
+ ... {'name': "Santa Maria's Day", 'type': 'religious', 'month': 10, 'day': 12, 'month_name': 'oct'},
+ ... {'name': "Goiania's Birthday", 'type': 'civic', 'month': 10, 'day': 24, 'month_name': 'oct'},
+ ... {'name': 'Republic Day', 'type': 'civic', 'month': 11, 'day': 15, 'month_name': 'nov'},
+ ... {'name': 'Christmas', 'type': 'religious', 'month': 12, 'day': 25, 'month_name': 'dec'},
+ ... ]
+
+ >>> cities = [
+ ... {'city': 'New York City', 'state': 'NY', 'state_name': 'New York', 'capital': False, 'population': 8363710, 'area': 468.9, 'government': 'Mayor', 'holidays': holidays},
+ ... {'city': 'Albany', 'state': 'NY', 'state_name': 'New York', 'capital': True, 'population': 95658, 'area': 21.8, 'government': 'Mayor', 'holidays': []},
+ ... {'city': 'Austin', 'state': 'TX', 'state_name': 'Texas', 'capital': True, 'population': 757688, 'area': 296.2, 'government': 'Council-manager', 'holidays': holidays},
+ ... {'city': 'Dallas', 'state': 'TX', 'state_name': 'Texas', 'capital': False, 'population': 1279910, 'area': 385.0, 'government': 'Council-manager', 'holidays': holidays},
+ ... {'city': 'Houston', 'state': 'TX', 'state_name': 'Texas', 'capital': False, 'population': 2242193, 'area': 601.7, 'government': 'Mayor-council', 'holidays': None},
+ ... {'city': 'San Francisco', 'state': 'CA', 'state_name': 'California', 'capital': False, 'population': 808976, 'area': 231.92, 'government': 'Mayor-council', 'holidays': holidays},
+ ... {'city': 'Los Angeles', 'state': 'CA', 'state_name': 'California', 'capital': False, 'population': 3833995, 'area': 498.3, 'government': 'Mayor-council', 'holidays': None},
+ ... {'city': 'Sacramento', 'state': 'CA', 'state_name': 'California', 'capital': True, 'population': 463794, 'area': 99.2, 'government': 'Mayor-council', 'holidays': None},
+ ... {'city': 'Seattle', 'state': 'WA', 'state_name': 'Washington', 'capital': False, 'population': 602000, 'area': 142.5, 'government': 'Mayor-council', 'holidays': holidays},
+ ... {'city': 'Washington', 'state': 'DC', 'state_name': 'DC', 'capital': True, 'population': 3602000, 'area': 242.5, 'government': 'Mayor-council', 'holidays': None},
+ ... ]
+
+ >>> #cities = CrossReferenceMatrix(cities, 'capital', 'state')
+ >>> #holidays = CrossReferenceMatrix(holidays, 'type', 'month')
+
+Report class
+------------
+
+ >>> from geraldo import Report, ObjectValue, DetailBand
+ >>> from geraldo.utils import cm
+
+ >>> class MatrixChartsReport(Report):
+ ... title = 'Rendering Matrix Charts'
+ ... borders = {'all': True}
+ ...
+ ... class band_begin(DetailBand):
+ ... height=12*cm
+ ... elements = [
+ ... PieChart(top=1.3*cm, left=1*cm, height=3*cm, width=5*cm,
+ ... cols_attribute='state', rows_attribute='government',
+ ... cell_attribute='population', action='sum', legend_labels=True,
+ ... slice_popout=2),
+ ... DoughnutChart(top=1.3*cm, left=10*cm, height=3*cm, width=5*cm,
+ ... cols_attribute='state', rows_attribute='government',
+ ... cell_attribute='population', action='sum', summarize_by=CROSS_COLS,
+ ... slice_popout=True),
+ ... LineChart(top=7*cm, left=1*cm, height=3*cm, width=5*cm,
+ ... cols_attribute='state', rows_attribute='government',
+ ... cell_attribute='population', action='sum', legend_labels=True),
+ ... SpiderChart(top=7*cm, left=12*cm, height=3*cm, width=5*cm,
+ ... rows_attribute='capital', cols_attribute='state',
+ ... cell_attribute='area', action='sum'),
+ ... ]
+ ...
+ ... class band_detail(DetailBand):
+ ... height=12*cm
+ ... elements = [
+ ... ObjectValue(attribute_name='city', left=0.2*cm, style={'fontSize': 12}),
+ ... LineChart(top=1.3*cm, left=3*cm, height=3*cm, width=5*cm,
+ ... data='holidays', cols_attribute='type', rows_attribute='month',
+ ... cell_attribute='day', action='count', summarize_by=CROSS_COLS),
+ ... BarChart(top=1.3*cm, left=10*cm, height=3*cm, width=8*cm,
+ ... data='holidays', rows_attribute='type', cols_attribute='month',
+ ... cell_attribute='day', action='count'),
+ ... BarChart(top=6.3*cm, left=0.5*cm, height=3*cm, width=8*cm,
+ ... data='holidays', rows_attribute='type', cols_attribute='month',
+ ... cell_attribute='day', action='count', horizontal=True,
+ ... axis_labels=True, axis_labels_angle=-20, summarize_by=CROSS_ROWS),
+ ... BarChart(top=8.3*cm, left=12*cm, height=3*cm, width=5*cm,
+ ... data='holidays', rows_attribute='type', cols_attribute='month',
+ ... cell_attribute='day', action='count', is3d=True, axis_labels=True,
+ ... axis_labels_angle=80, summarize_by=CROSS_ROWS),
+ ... ]
+ ... borders = {'top': True}
+
+Generating the result report
+
+ >>> report = MatrixChartsReport(queryset=cities)
+
+PDF generation
+
+ >>> import os
+ >>> cur_dir = os.path.dirname(os.path.abspath(__file__))
+
+ >>> from geraldo.generators import PDFGenerator
+ >>> report.generate_by(PDFGenerator, filename=os.path.join(cur_dir, 'output/report-with-charts.pdf'))
+
+Other important elements on this topic
+--------------------------------------
+
+- Axis, Legends, Title and Colors
+
+All of above are supported by the following attributes:
+
+ >>> hasattr(BaseChart, 'axis_labels')
+ True
+
+ >>> hasattr(BaseChart, 'axis_labels_angle')
+ True
+
+ >>> hasattr(BaseChart, 'legend_labels')
+ True
+
+ >>> hasattr(BaseChart, 'title')
+ True
+
+ >>> hasattr(BaseChart, 'colors')
+ True
+
+About Colors, it should supports color sets (TODO)
+
+- Grid (TODO)
+
+Not yet supported
+
+- Transparecy (TODO: enhance)
+
+The transparency is made with colors, like this:
+
+ >>> from reportlab.lib.colors import Color, red
+ >>> red_transparent = Color(red.red, red.green, red.blue, alpha=0.7)
+
+- Gradients and Shadows (TODO)
+
+For a while, we won't support gradient filling and shadows, because ReportLab doesn't,
+and we will must decide between improve ReportLab or just use SVG to do it.
+
+- Markers (TODO)
+ - Empty Square
+ - Filled Square
+ - Filled Diamond
+ - Empty Circle
+ - Filled Circle
+ - Smiley
+
View
4 geraldo/widgets.py
@@ -125,7 +125,9 @@ def get_object_value(self, instance=None):
value = get_attr_value(instance, self.attribute_name)
- # For method attributes
+ # For method attributes --- FIXME: check what does this code here, because
+ # get_attr_value has a code to do that, using
+ # callable() checking
if type(value) == types.MethodType:
value = value()
Please sign in to comment.
Something went wrong with that request. Please try again.