############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Chart Element Processing """ import reportlab.lib.formatters from reportlab.graphics import shapes from reportlab.graphics.charts import barcharts, lineplots, piecharts from reportlab.graphics.charts import spider, doughnut from z3c.rml import attr, directive, interfaces, occurence # Patches against Reportlab 2.0 lineplots.Formatter = reportlab.lib.formatters.Formatter class PropertyItem(directive.RMLDirective): def process(self): attrs = dict(self.getAttributeValues()) self.parent.dataList.append(attrs) class PropertyCollection(directive.RMLDirective): propertyName = None def processAttributes(self): prop = getattr(self.parent.context, self.propertyName) # Get global properties for name, value in self.getAttributeValues(): setattr(prop, name, value) def process(self): self.processAttributes() # Get item specific properties prop = getattr(self.parent.context, self.propertyName) self.dataList = [] self.processSubDirectives() for index, data in enumerate(self.dataList): for name, value in data.items(): setattr(prop[index], name, value) class IText(interfaces.IRMLDirectiveSignature): """Draw a text on the chart.""" x = attr.Measurement( title=u'X-Coordinate', description=(u'The X-coordinate of the lower-left position of the ' u'text.'), required=True) y = attr.Measurement( title=u'Y-Coordinate', description=(u'The Y-coordinate of the lower-left position of the ' u'text.'), required=True) angle = attr.Float( title=u'Rotation Angle', description=(u'The angle about which the text will be rotated.'), required=False) text = attr.TextNode( title=u'Text', description=u'The text to be printed.', required=True) fontName = attr.String( title=u'Font Name', description=u'The name of the font.', required=False) fontSize = attr.Measurement( title=u'Font Size', description=u'The font size for the text.', required=False) fillColor = attr.Color( title=u'Fill Color', description=u'The color in which the text will appear.', required=False) textAnchor = attr.Choice( title=u'Text Anchor', description=u'The position in the text to which the coordinates refer.', choices=('start', 'middle', 'end', 'boxauto'), required=False) class Text(directive.RMLDirective): signature = IText def process(self): attrs = dict(self.getAttributeValues()) string = shapes.String( attrs.pop('x'), attrs.pop('y'), attrs.pop('text')) angle = attrs.pop('angle', 0) for name, value in attrs.items(): setattr(string, name, value) group = shapes.Group(string) group.translate(0,0) group.rotate(angle) self.parent.parent.drawing.add(group) class ITexts(interfaces.IRMLDirectiveSignature): """A set of texts drawn on the chart.""" occurence.containing( occurence.ZeroOrMore('text', IText) ) class Texts(directive.RMLDirective): signature = ITexts factories = {'text': Text} class Series(directive.RMLDirective): def process(self): attrs = self.getAttributeValues(valuesOnly=True) self.parent.data.append(attrs[0]) class Data(directive.RMLDirective): series = None def process(self): self.data = [] self.factories = {'series': self.series} self.processSubDirectives() self.parent.context.data = self.data class ISeries1D(interfaces.IRMLDirectiveSignature): """A one-dimensional series.""" values = attr.TextNodeSequence( title=u'Values', description=u"Numerical values representing the series' data.", value_type=attr.Float(), required=True) class Series1D(Series): signature = ISeries1D class IData1D(interfaces.IRMLDirectiveSignature): """A 1-D data set.""" occurence.containing( occurence.OneOrMore('series', ISeries1D) ) class Data1D(Data): signature = IData1D series = Series1D class ISingleData1D(interfaces.IRMLDirectiveSignature): """A 1-D data set.""" occurence.containing( occurence.One('series', ISeries1D) ) class SingleData1D(Data1D): signature = ISingleData1D def process(self): self.data = [] self.factories = {'series': self.series} self.processSubDirectives() self.parent.context.data = self.data[0] class ISeries2D(interfaces.IRMLDirectiveSignature): """A two-dimensional series.""" values = attr.TextNodeGrid( title=u'Values', description=u"Numerical values representing the series' data.", value_type=attr.Float(), columns=2, required=True) class Series2D(Series): signature = ISeries2D class IData2D(interfaces.IRMLDirectiveSignature): """A 2-D data set.""" occurence.containing( occurence.OneOrMore('series', ISeries2D) ) class Data2D(Data): signature = IData2D series = Series2D class IBar(interfaces.IRMLDirectiveSignature): """Define the look of a bar.""" strokeColor = attr.Color( title=u'Stroke Color', description=u'The color in which the bar border is drawn.', required=False) strokeWidth = attr.Measurement( title=u'Stroke Width', description=u'The width of the bar border line.', required=False) fillColor = attr.Color( title=u'Fill Color', description=u'The color with which the bar is filled.', required=False) class Bar(PropertyItem): signature = IBar class IBars(IBar): """Collection of bar subscriptions.""" occurence.containing( occurence.ZeroOrMore('bar', IBar) ) class Bars(PropertyCollection): signature = IBars propertyName = 'bars' factories = {'bar': Bar} class ILabelBase(interfaces.IRMLDirectiveSignature): dx = attr.Measurement( title=u'Horizontal Extension', description=(u'The width of the label.'), required=False) dy = attr.Measurement( title=u'Vertical Extension', description=(u'The height of the label.'), required=False) angle = attr.Float( title=u'Angle', description=(u'The angle to rotate the label.'), required=False) boxAnchor = attr.Choice( title=u'Box Anchor', description=(u'The position relative to the label.'), choices=('nw','n','ne','w','c','e','sw','s','se', 'autox', 'autoy'), required=False) boxStrokeColor = attr.Color( title=u'Box Stroke Color', description=(u'The color of the box border line.'), required=False) boxStrokeWidth = attr.Measurement( title=u'Box Stroke Width', description=u'The width of the box border line.', required=False) boxFillColor = attr.Color( title=u'Box Fill Color', description=(u'The color in which the box is filled.'), required=False) boxTarget = attr.Text( title=u'Box Target', description=u'The box target.', required=False) fillColor = attr.Color( title=u'Fill Color', description=(u'The color in which the label is filled.'), required=False) strokeColor = attr.Color( title=u'Stroke Color', description=(u'The color of the label.'), required=False) strokeWidth = attr.Measurement( title=u'Stroke Width', description=u'The width of the label line.', required=False) fontName = attr.String( title=u'Font Name', description=u'The font used to print the value.', required=False) fontSize = attr.Measurement( title=u'Font Size', description=u'The size of the value text.', required=False) leading = attr.Measurement( title=u'Leading', description=(u'The height of a single text line. It includes ' u'character height.'), required=False) width = attr.Measurement( title=u'Width', description=u'The width the label.', required=False) maxWidth = attr.Measurement( title=u'Maximum Width', description=u'The maximum width the label.', required=False) height = attr.Measurement( title=u'Height', description=u'The height the label.', required=False) textAnchor = attr.Choice( title=u'Text Anchor', description=u'The position in the text to which the coordinates refer.', choices=('start', 'middle', 'end', 'boxauto'), required=False) visible = attr.Boolean( title=u'Visible', description=u'A flag making the label text visible.', required=False) leftPadding = attr.Measurement( title=u'Left Padding', description=u'The size of the padding on the left side.', required=False) rightPadding = attr.Measurement( title=u'Right Padding', description=u'The size of the padding on the right side.', required=False) topPadding = attr.Measurement( title=u'Top Padding', description=u'The size of the padding on the top.', required=False) bottomPadding = attr.Measurement( title=u'Bottom Padding', description=u'The size of the padding on the bottom.', required=False) class IPositionLabelBase(ILabelBase): x = attr.Measurement( title=u'X-Coordinate', description=(u'The X-coordinate of the lower-left position of the ' u'label.'), required=False) y = attr.Measurement( title=u'Y-Coordinate', description=(u'The Y-coordinate of the lower-left position of the ' u'label.'), required=False) class ILabel(IPositionLabelBase): """A label for the chart on an axis.""" text = attr.TextNode( title=u'Text', description=u'The label text to be displayed.', required=True) class Label(PropertyItem): signature = ILabel class IBarLabels(ILabelBase): """A set of labels for a bar chart""" occurence.containing( occurence.ZeroOrMore('label', ILabel) ) class BarLabels(PropertyCollection): signature = IBarLabels propertyName = 'barLabels' factories = {'label': Label} name = 'barLabels' class ILabels(IPositionLabelBase): """A set of labels of an axis.""" occurence.containing( occurence.ZeroOrMore('label', ILabel) ) class Labels(PropertyCollection): signature = ILabels propertyName = 'labels' factories = {'label': Label} class IAxis(interfaces.IRMLDirectiveSignature): occurence.containing( occurence.ZeroOrMore('labels', ILabels) ) visible = attr.Boolean( title=u'Visible', description=u'When true, draw the entire axis with all details.', required=False) visibleAxis = attr.Boolean( title=u'Visible Axis', description=u'When true, draw the axis line.', required=False) visibleTicks = attr.Boolean( title=u'Visible Ticks', description=u'When true, draw the axis ticks on the line.', required=False) visibleLabels = attr.Boolean( title=u'Visible Labels', description=u'When true, draw the axis labels.', required=False) visibleGrid = attr.Boolean( title=u'Visible Grid', description=u'When true, draw the grid lines for the axis.', required=False) strokeWidth = attr.Measurement( title=u'Stroke Width', description=u'The width of axis line and ticks.', required=False) strokeColor = attr.Color( title=u'Stroke Color', description=u'The color in which the axis line and ticks are drawn.', required=False) strokeDashArray = attr.Sequence( title=u'Stroke Dash Array', description=u'The dash array that is used for the axis line and ticks.', value_type=attr.Float(), required=False) gridStrokeWidth = attr.Measurement( title=u'Grid Stroke Width', description=u'The width of the grid lines.', required=False) gridStrokeColor = attr.Color( title=u'Grid Stroke Color', description=u'The color in which the grid lines are drawn.', required=False) gridStrokeDashArray = attr.Sequence( title=u'Grid Stroke Dash Array', description=u'The dash array that is used for the grid lines.', value_type=attr.Float(), required=False) gridStart = attr.Measurement( title=u'Grid Start', description=(u'The start of the grid lines with respect to the ' u'axis origin.'), required=False) gridEnd = attr.Measurement( title=u'Grid End', description=(u'The end of the grid lines with respect to the ' u'axis origin.'), required=False) style = attr.Choice( title=u'Style', description=u'The plot style of the common categories.', choices=('parallel', 'stacked', 'parallel_3d'), required=False) class Axis(directive.RMLDirective): signature = IAxis name = '' factories = {'labels': Labels} def process(self): self.context = axis = getattr(self.parent.context, self.name) for name, value in self.getAttributeValues(): setattr(axis, name, value) self.processSubDirectives() class IName(interfaces.IRMLDirectiveSignature): """A category name""" text = attr.TextNode( title=u'Text', description=u'The text value that is the name.', required=True) class Name(directive.RMLDirective): signature = IName def process(self): text = self.getAttributeValues(valuesOnly=True)[0] self.parent.names.append(text) class ICategoryNames(interfaces.IRMLDirectiveSignature): """A list of category names.""" occurence.containing( occurence.OneOrMore('name', IName), ) class CategoryNames(directive.RMLDirective): signature = ICategoryNames factories = {'name': Name} def process(self): self.names = [] self.processSubDirectives() self.parent.context.categoryNames = self.names class ICategoryAxis(IAxis): """An axis displaying categories (instead of numerical values).""" occurence.containing( occurence.ZeroOrOne('categoryNames', ICategoryNames), *IAxis.queryTaggedValue('directives', ()) ) categoryNames = attr.Sequence( title=u'Category Names', description=u'A simple list of category names.', value_type=attr.Text(), required=False) joinAxis = attr.Boolean( title=u'Join Axis', description=u'When true, both axes join together.', required=False) joinAxisPos = attr.Measurement( title=u'Join Axis Position', description=u'The position at which the axes should join together.', required=False) reverseDirection = attr.Boolean( title=u'Reverse Direction', description=u'A flag to reverse the direction of category names.', required=False) labelAxisMode = attr.Choice( title=u'Label Axis Mode', description=u'Defines the relative position of the axis labels.', choices=('high', 'low', 'axis'), required=False) tickShift = attr.Boolean( title=u'Tick Shift', description=(u'When true, place the ticks in the center of a ' u'category instead the beginning and end.'), required=False) class CategoryAxis(Axis): signature = ICategoryAxis name = 'categoryAxis' factories = Axis.factories.copy() factories.update({ 'categoryNames': CategoryNames, }) class IXCategoryAxis(ICategoryAxis): """X-Category Axis""" tickUp = attr.Measurement( title=u'Tick Up', description=u'Length of tick above the axis line.', required=False) tickDown = attr.Measurement( title=u'Tick Down', description=u'Length of tick below the axis line.', required=False) joinAxisMode = attr.Choice( title=u'Join Axis Mode', description=u'Mode for connecting axes.', choices=('bottom', 'top', 'value', 'points', 'None'), required=False) class XCategoryAxis(CategoryAxis): signature = IXCategoryAxis class IYCategoryAxis(ICategoryAxis): """Y-Category Axis""" tickLeft = attr.Measurement( title=u'Tick Left', description=u'Length of tick left to the axis line.', required=False) tickRight = attr.Measurement( title=u'Tick Right', description=u'Length of tick right to the axis line.', required=False) joinAxisMode = attr.Choice( title=u'Join Axis Mode', description=u'Mode for connecting axes.', choices=('bottom', 'top', 'value', 'points', 'None'), required=False) class YCategoryAxis(CategoryAxis): signature = IYCategoryAxis class IValueAxis(IAxis): forceZero = attr.Boolean( title=u'Force Zero', description=u'When set, the range will contain the origin.', required=False) minimumTickSpacing = attr.Measurement( title=u'Minimum Tick Spacing', description=u'The minimum distance between ticks.', required=False) maximumTicks = attr.Integer( title=u'Maximum Ticks', description=u'The maximum number of ticks to be shown.', required=False) labelTextFormat = attr.String( title=u'Label Text Format', description=u'Formatting string for axis labels.', required=False) labelTextPostFormat = attr.Text( title=u'Label Text Post Format', description=u'An additional formatting string.', required=False) labelTextScale = attr.Float( title=u'Label Text Scale', description=u'The sclaing factor for the label tick values.', required=False) valueMin = attr.Float( title=u'Minimum Value', description=u'The smallest value on the axis.', required=False) valueMax = attr.Float( title=u'Maximum Value', description=u'The largest value on the axis.', required=False) valueStep = attr.Float( title=u'Value Step', description=u'The step size between ticks', required=False) valueSteps = attr.Sequence( title=u'Step Sizes', description=u'List of step sizes between ticks.', value_type = attr.Float(), required=False) rangeRound = attr.Choice( title=u'Range Round', description=u'Method to be used to round the range values.', choices=('none', 'both', 'ceiling', 'floor'), required=False) zrangePref = attr.Float( title=u'Zero Range Preference', description=u'Zero range axis limit preference.', required=False) class ValueAxis(Axis): signature = IValueAxis name = 'valueAxis' class IXValueAxis(IValueAxis): """X-Value Axis""" tickUp = attr.Measurement( title=u'Tick Up', description=u'Length of tick above the axis line.', required=False) tickDown = attr.Measurement( title=u'Tick Down', description=u'Length of tick below the axis line.', required=False) joinAxis = attr.Boolean( title=u'Join Axis', description=u'Whether to join the axes.', required=False) joinAxisMode = attr.Choice( title=u'Join Axis Mode', description=u'Mode for connecting axes.', choices=('bottom', 'top', 'value', 'points', 'None'), required=False) joinAxisPos = attr.Measurement( title=u'Join Axis Position', description=u'The position in the plot at which to join the axes.', required=False) class XValueAxis(ValueAxis): signature = IXValueAxis class LineXValueAxis(XValueAxis): name = 'xValueAxis' class IYValueAxis(IValueAxis): """Y-Value Axis""" tickLeft = attr.Measurement( title=u'Tick Left', description=u'Length of tick left to the axis line.', required=False) tickRight = attr.Measurement( title=u'Tick Right', description=u'Length of tick right to the axis line.', required=False) joinAxis = attr.Boolean( title=u'Join Axis', description=u'Whether to join the axes.', required=False) joinAxisMode = attr.Choice( title=u'Join Axis Mode', description=u'Mode for connecting axes.', choices=('bottom', 'top', 'value', 'points', 'None'), required=False) joinAxisPos = attr.Measurement( title=u'Join Axis Position', description=u'The position in the plot at which to join the axes.', required=False) class YValueAxis(ValueAxis): signature = IYValueAxis class LineYValueAxis(YValueAxis): name = 'yValueAxis' class ILineLabels(IPositionLabelBase): """A set of labels of an axis.""" occurence.containing( occurence.ZeroOrMore('label', ILabel) ) class LineLabels(PropertyCollection): signature = ILineLabels propertyName = 'lineLabels' factories = {'label': Label} class ILineBase(interfaces.IRMLDirectiveSignature): strokeWidth = attr.Measurement( title=u'Stroke Width', description=u'The width of the plot line.', required=False) strokeColor = attr.Color( title=u'Stroke Color', description=u'The color of the plot line.', required=False) strokeDashArray = attr.Sequence( title=u'Stroke Dash Array', description=u'The dash array of the plot line.', value_type = attr.Float(), required=False) symbol = attr.Symbol( title=u'Symbol', description=u'The symbol to be used for every data point in the plot.', required=False) class ILine(ILineBase): """A line description of a series of a line plot.""" name = attr.Text( title=u'Name', description=u'The name of the line.', required=False) class Line(PropertyItem): signature = ILine class ILines(ILineBase): """The set of all line descriptions in the line plot.""" occurence.containing( occurence.OneOrMore('line', ILine), ) class Lines(PropertyCollection): signature = ILines propertyName = 'lines' factories = {'line': Line} class ISliceLabel(ILabelBase): """The label of a slice within a bar chart.""" text = attr.TextNode( title=u'Text', description=u'The label text to be displayed.', required=True) class SliceLabel(Label): signature = ISliceLabel def process(self): for name, value in self.getAttributeValues(): self.parent.context['label_'+name] = value # Now we do not have simple labels anymore self.parent.parent.parent.context.simpleLabels = False class ISlicePointer(interfaces.IRMLDirectiveSignature): """A pointer to a slice in a pie chart.""" strokeColor = attr.Color( title=u'Stroke Color', description=u'The color of the pointer line.', required=False) strokeWidth = attr.Measurement( title=u'Stroke Width', description=u'The wodth of the pointer line.', required=False) elbowLength = attr.Measurement( title=u'Elbow Length', description=u'The length of the final segment of the pointer.', required=False) edgePad = attr.Measurement( title=u'Edge Padding', description=u'The padding between between the pointer label and box.', required=False) piePad = attr.Measurement( title=u'Pie Padding', description=u'The padding between between the pointer label and chart.', required=False) class SlicePointer(directive.RMLDirective): signature = ISlicePointer def process(self): for name, value in self.getAttributeValues(): self.parent.context['label_pointer_'+name] = value class ISliceBase(interfaces.IRMLDirectiveSignature): strokeWidth = attr.Measurement( title=u'Stroke Width', description=u'The wodth of the slice line.', required=False) fillColor = attr.Color( title=u'Fill Color', description=u'The fill color of the slice.', required=False) strokeColor = attr.Color( title=u'Stroke Color', description=u'The color of the pointer line.', required=False) strokeDashArray = attr.Sequence( title=u'Stroke Dash Array', description=u'Teh dash array of the slice borderline.', value_type=attr.Float(), required=False) popout = attr.Measurement( title=u'Popout', description=u'The distance of how much the slice should be popped out.', required=False) fontName = attr.String( title=u'Font Name', description=u'The font name of the label.', required=False) fontSize = attr.Measurement( title=u'Font Size', description=u'The font size of the label.', required=False) labelRadius = attr.Measurement( title=u'Label Radius', description=(u'The radius at which the label should be placed around ' u'the pie.'), required=False) class ISlice(ISliceBase): """A slice in a pie chart.""" occurence.containing( occurence.ZeroOrOne('label', ISliceLabel), occurence.ZeroOrOne('pointer', ISlicePointer), ) swatchMarker = attr.Symbol( required=False) class Slice(directive.RMLDirective): signature = ISlice factories = { 'label': SliceLabel, 'pointer': SlicePointer} def process(self): self.context = attrs = dict(self.getAttributeValues()) self.processSubDirectives() self.parent.context.append(attrs) class ISlice3D(ISlice): """A 3-D slice of a 3-D pie chart.""" fillColorShaded = attr.Color( title=u'Fill Color Shade', description=u'The shade used for the fill color.', required=False) class Slice3D(Slice): signature = ISlice3D factories = {} # Sigh, the 3-D Pie does not support advanced slice labels. :-( # 'label': SliceLabel} class ISlices(ISliceBase): """The collection of all 2-D slice descriptions.""" occurence.containing( occurence.OneOrMore('slice', ISlice), ) class Slices(directive.RMLDirective): signature = ISlices factories = {'slice': Slice} def process(self): # Get global slice properties for name, value in self.getAttributeValues(): setattr(self.parent.context.slices, name, value) # Get slice specific properties self.context = slicesData = [] self.processSubDirectives() for index, sliceData in enumerate(slicesData): for name, value in sliceData.items(): setattr(self.parent.context.slices[index], name, value) class ISlices3D(ISliceBase): """The collection of all 3-D slice descriptions.""" occurence.containing( occurence.OneOrMore('slice', ISlice3D), ) fillColorShaded = attr.Color( required=False) class Slices3D(Slices): signature = ISlices3D factories = {'slice': Slice3D} class ISimpleLabel(IName): """A simple label""" class SimpleLabel(Name): signature = ISimpleLabel class ISimpleLabels(interfaces.IRMLDirectiveSignature): """A set of simple labels for a chart.""" occurence.containing( occurence.OneOrMore('label', ISimpleLabel), ) class SimpleLabels(directive.RMLDirective): signature = ISimpleLabels factories = {'label': Name} def process(self): self.names = [] self.processSubDirectives() self.parent.context.labels = self.names class IStrandBase(interfaces.IRMLDirectiveSignature): strokeWidth = attr.Measurement( title=u'Stroke Width', description=u'The line width of the strand.', required=False) fillColor = attr.Color( title=u'Fill Color', description=u'The fill color of the strand area.', required=False) strokeColor= attr.Color( title=u'Stroke Color', description=u'The color of the strand line.', required=False) strokeDashArray = attr.Sequence( title=u'Stroke Dash Array', description=u'The dash array of the strand line.', value_type=attr.Float(), required=False) symbol = attr.Symbol( title=u'Symbol', description=u'The symbol to use to mark the strand.', required=False) symbolSize = attr.Measurement( title=u'Symbol Size', description=u'The size of the strand symbol.', required=False) class IStrand(IStrandBase): """A strand in the spider diagram""" name = attr.Text( title=u'Name', description=u'The name of the strand.', required=False) class Strand(PropertyItem): signature = IStrand class IStrands(IStrand): """A collection of strands.""" occurence.containing( occurence.OneOrMore('strand', IStrand) ) class Strands(PropertyCollection): signature = IStrands propertyName = 'strands' attrs = IStrandBase factories = {'strand': Strand} class IStrandLabelBase(ILabelBase): _text = attr.TextNode( title=u'Text', description=u'The label text of the strand.', required=False) row = attr.Integer( title=u'Row', description=u'The row of the strand label', required=False) col = attr.Integer( title=u'Column', description=u'The column of the strand label.', required=False) format = attr.String( title=u'Format', description=u'The format string for the label.', required=False) class IStrandLabel(IStrandLabelBase): """A label for a strand.""" dR = attr.Float( title=u'Radial Shift', description=u'The radial shift of the label.', required=False) class StrandLabel(Label): signature = IStrandLabel class IStrandLabels(IStrandLabelBase): """A set of strand labels.""" occurence.containing( occurence.OneOrMore('label', IStrandLabel) ) class StrandLabels(PropertyCollection): signature = IStrandLabels propertyName = 'strandLabels' factories = {'label': StrandLabel} def process(self): self.processAttributes() # Get item specific properties prop = getattr(self.parent.context, self.propertyName) self.dataList = [] self.processSubDirectives() for data in self.dataList: row = data.pop('row') col = data.pop('col') for name, value in data.items(): setattr(prop[row, col], name, value) class ISpoke(interfaces.IRMLDirectiveSignature): """A spoke in the spider diagram.""" strokeWidth = attr.Measurement( title=u'Stroke Width', description=u"The width of the spoke's line.", required=False) fillColor = attr.Color( title=u'Fill Color', description=u"The fill color of the spoke's area.", required=False) strokeColor= attr.Color( title=u'Stroke Color', description=u'The color of the spoke line.', required=False) strokeDashArray = attr.Sequence( title=u'Stroke Dash Array', description=u'The dash array of the spoke line.', value_type=attr.Float(), required=False) labelRadius = attr.Measurement( title=u'Label Radius', description=u'The radius of the label arouns the spoke.', required=False) visible = attr.Boolean( title=u'Visible', description=u'When true, the spoke line is drawn.', required=False) class Spoke(PropertyItem): signature = ISpoke class ISpokes(ISpoke): """A collection of spokes.""" occurence.containing( occurence.OneOrMore('spoke', ISpoke) ) class Spokes(PropertyCollection): signature = ISpokes propertyName = 'spokes' factories = {'spoke': Spoke} class ISpokeLabelBase(ILabelBase): pass class ISpokeLabel(ISpokeLabelBase): """A label for a spoke.""" _text = attr.TextNode( title=u'Text', description=u'The text of the spoke (label).', required=False) class SpokeLabel(Label): signature = ISpokeLabel class ISpokeLabels(ISpokeLabelBase): """A set of spoke labels.""" occurence.containing( occurence.OneOrMore('label', ISpokeLabel) ) class SpokeLabels(PropertyCollection): signature = ISpokeLabels propertyName = 'spokeLabels' factories = {'label': SpokeLabel} class IChart(interfaces.IRMLDirectiveSignature): occurence.containing( occurence.ZeroOrOne('texts', ITexts), ) # Drawing Options dx = attr.Measurement( title=u'Drawing X-Position', description=u'The x-position of the entire drawing on the canvas.', required=False) dy = attr.Measurement( title=u'Drawing Y-Position', description=u'The y-position of the entire drawing on the canvas.', required=False) dwidth = attr.Measurement( title=u'Drawing Width', description=u'The width of the entire drawing', required=False) dheight = attr.Measurement( title=u'Drawing Height', description=u'The height of the entire drawing', required=False) angle = attr.Float( title=u'Angle', description=u'The orientation of the drawing as an angle in degrees.', required=False) # Plot Area Options x = attr.Measurement( title=u'Chart X-Position', description=u'The x-position of the chart within the drawing.', required=False) y = attr.Measurement( title=u'Chart Y-Position', description=u'The y-position of the chart within the drawing.', required=False) width = attr.Measurement( title=u'Chart Width', description=u'The width of the chart.', required=False) height = attr.Measurement( title=u'Chart Height', description=u'The height of the chart.', required=False) strokeColor = attr.Color( title=u'Stroke Color', description=u'Color of the chart border.', required=False) strokeWidth = attr.Measurement( title=u'Stroke Width', description=u'Width of the chart border.', required=False) fillColor = attr.Color( title=u'Fill Color', description=u'Color of the chart interior.', required=False) debug = attr.Boolean( title=u'Debugging', description=u'A flag that when set to True turns on debug messages.', required=False) class Chart(directive.RMLDirective): signature = IChart factories = { 'texts': Texts } attrMapping = {} def createChart(self, attributes): raise NotImplementedError def process(self): attrs = dict(self.getAttributeValues(attrMapping=self.attrMapping)) angle = attrs.pop('angle', 0) x, y = attrs.pop('dx'), attrs.pop('dy') self.drawing = shapes.Drawing(attrs.pop('dwidth'), attrs.pop('dheight')) self.context = chart = self.createChart(attrs) self.processSubDirectives() group = shapes.Group(chart) group.translate(0,0) group.rotate(angle) self.drawing.add(group) manager = attr.getManager(self, interfaces.ICanvasManager) self.drawing.drawOn(manager.canvas, x, y) class IBarChart(IChart): """Creates a two-dimensional bar chart.""" occurence.containing( occurence.One('data', IData1D), occurence.ZeroOrOne('bars', IBars), occurence.ZeroOrOne('categoryAxis', ICategoryAxis), occurence.ZeroOrOne('valueAxis', IValueAxis), occurence.ZeroOrOne('barLabels', IBarLabels), *IChart.queryTaggedValue('directives', ()) ) direction = attr.Choice( title=u'Direction', description=u'The direction of the bars within the chart.', choices=('horizontal', 'vertical'), default='horizontal', required=False) useAbsolute = attr.Boolean( title=u'Use Absolute Spacing', description=u'Flag to use absolute spacing values.', default=False, required=False) barWidth = attr.Measurement( title=u'Bar Width', description=u'The width of an individual bar.', default=10, required=False) groupSpacing = attr.Measurement( title=u'Group Spacing', description=u'Width between groups of bars.', default=5, required=False) barSpacing = attr.Measurement( title=u'Bar Spacing', description=u'Width between individual bars.', default=0, required=False) barLabelFormat = attr.String( title=u'Bar Label Text Format', description=u'Formatting string for bar labels.', required=False) class BarChart(Chart): signature = IBarChart nameBase = 'BarChart' factories = Chart.factories.copy() factories.update({ 'data': Data1D, 'bars': Bars, 'barLabels': BarLabels, }) def createChart(self, attrs): direction = attrs.pop('direction') # Setup sub-elements based on direction if direction == 'horizontal': self.factories['categoryAxis'] = YCategoryAxis self.factories['valueAxis'] = XValueAxis else: self.factories['categoryAxis'] = XCategoryAxis self.factories['valueAxis'] = YValueAxis # Generate the chart chart = getattr( barcharts, direction.capitalize()+self.nameBase)() for name, value in attrs.items(): setattr(chart, name, value) return chart class IBarChart3D(IBarChart): """Creates a three-dimensional bar chart.""" occurence.containing( *IBarChart.queryTaggedValue('directives', ()) ) thetaX = attr.Float( title=u'Theta-X', description=u'Fraction of dx/dz.', required=False) thetaY = attr.Float( title=u'Theta-Y', description=u'Fraction of dy/dz.', required=False) zDepth = attr.Measurement( title=u'Z-Depth', description=u'Depth of an individual series/bar.', required=False) zSpace = attr.Measurement( title=u'Z-Space', description=u'Z-Gap around a series/bar.', required=False) class BarChart3D(BarChart): signature = IBarChart3D nameBase = 'BarChart3D' attrMapping = {'thetaX': 'theta_x', 'thetaY': 'theta_y'} class ILinePlot(IChart): """A line plot.""" occurence.containing( occurence.One('data', IData2D), occurence.ZeroOrOne('lines', ILines), occurence.ZeroOrOne('xValueAxis', IXValueAxis), occurence.ZeroOrOne('yValueAxis', IYValueAxis), occurence.ZeroOrOne('lineLabels', ILineLabels), *IChart.queryTaggedValue('directives', ()) ) reversePlotOrder = attr.Boolean( title=u'Reverse Plot Order', description=u'When true, the coordinate system is reversed.', required=False) lineLabelNudge = attr.Measurement( title=u'Line Label Nudge', description=u'The distance between the data point and its label.', required=False) lineLabelFormat = attr.String( title=u'Line Label Format', description=u'Formatting string for data point labels.', required=False) joinedLines = attr.Boolean( title=u'Joined Lines', description=u'When true, connect all data points with lines.', required=False) class LinePlot(Chart): signature = ILinePlot factories = Chart.factories.copy() factories.update({ 'data': Data2D, 'lines': Lines, 'xValueAxis': LineXValueAxis, 'yValueAxis': LineYValueAxis, 'lineLabels': LineLabels, }) def createChart(self, attrs): # Generate the chart chart = lineplots.LinePlot() for name, value in attrs.items(): setattr(chart, name, value) return chart class ILinePlot3D(ILinePlot): """Creates a three-dimensional line plot.""" occurence.containing( *ILinePlot.queryTaggedValue('directives', ()) ) thetaX = attr.Float( title=u'Theta-X', description=u'Fraction of dx/dz.', required=False) thetaY = attr.Float( title=u'Theta-Y', description=u'Fraction of dy/dz.', required=False) zDepth = attr.Measurement( title=u'Z-Depth', description=u'Depth of an individual series/bar.', required=False) zSpace = attr.Measurement( title=u'Z-Space', description=u'Z-Gap around a series/bar.', required=False) class LinePlot3D(LinePlot): signature = ILinePlot3D nameBase = 'LinePlot3D' attrMapping = {'thetaX': 'theta_x', 'thetaY': 'theta_y'} def createChart(self, attrs): # Generate the chart chart = lineplots.LinePlot3D() for name, value in attrs.items(): setattr(chart,name, value) return chart class IPieChart(IChart): """A pie chart.""" occurence.containing( occurence.One('data', ISingleData1D), occurence.ZeroOrOne('slices', ISlices), occurence.ZeroOrOne('labels', ISimpleLabels), *IChart.queryTaggedValue('directives', ()) ) startAngle = attr.Integer( title=u'Start Angle', description=u'The start angle in the chart of the first slice ' u'in degrees.', required=False) direction = attr.Choice( title=u'Direction', description=u'The direction in which the pie chart will be built.', choices=('clockwise', 'anticlockwise'), required=False) checkLabelOverlap = attr.Boolean( title=u'Check Label Overlap', description=(u'When true, check and attempt to fix standard ' u'label overlaps'), required=False) pointerLabelMode = attr.Choice( title=u'Pointer Label Mode', description=(u'The location relative to the slace the label should ' u'be placed.'), choices={'none': None, 'leftright': 'LeftRight', 'leftandright': 'LeftAndRight'}, required=False) sameRadii = attr.Boolean( title=u'Same Radii', description=u'When true, make x/y radii the same.', required=False) orderMode = attr.Choice( title=u'Order Mode', description=u'', choices=('fixed', 'alternate'), required=False) xradius = attr.Measurement( title=u'X-Radius', description=u'The radius of the X-directions', required=False) yradius = attr.Measurement( title=u'Y-Radius', description=u'The radius of the Y-directions', required=False) class PieChart(Chart): signature = IPieChart chartClass = piecharts.Pie factories = Chart.factories.copy() factories.update({ 'data': SingleData1D, 'slices': Slices, 'labels': SimpleLabels, }) def createChart(self, attrs): # Generate the chart chart = self.chartClass() for name, value in attrs.items(): setattr(chart, name, value) return chart class IPieChart3D(IPieChart): """A 3-D pie chart.""" occurence.containing( occurence.One('slices', ISlices3D), *IChart.queryTaggedValue('directives', ()) ) perspective = attr.Float( title=u'Perspsective', description=u'The flattening parameter.', required=False) depth_3d = attr.Measurement( title=u'3-D Depth', description=u'The depth of the pie.', required=False) angle_3d = attr.Float( title=u'3-D Angle', description=u'The view angle in the Z-coordinate.', required=False) class PieChart3D(PieChart): signature = IPieChart3D chartClass = piecharts.Pie3d factories = PieChart.factories.copy() factories.update({ 'slices': Slices3D, }) class ISpiderChart(IChart): """A spider chart.""" occurence.containing( occurence.One('data', IData1D), occurence.ZeroOrOne('strands', IStrands), occurence.ZeroOrOne('strandLabels', IStrandLabels), occurence.ZeroOrOne('spokes', ISpokes), occurence.ZeroOrOne('spokeLabels', ISpokeLabels), occurence.ZeroOrOne('labels', ISimpleLabels), *IChart.queryTaggedValue('directives', ()) ) startAngle = attr.Integer( title=u'Start Angle', description=u'The start angle in the chart of the first strand ' u'in degrees.', required=False) direction = attr.Choice( title=u'Direction', description=u'The direction in which the spider chart will be built.', choices=('clockwise', 'anticlockwise'), required=False) class SpiderChart(Chart): signature = ISpiderChart factories = Chart.factories.copy() factories.update({ 'data': Data1D, 'strands': Strands, 'strandLabels': StrandLabels, 'spokes': Spokes, 'spokeLabels': SpokeLabels, 'labels': SimpleLabels, }) def createChart(self, attrs): # Generate the chart chart = spider.SpiderChart() for name, value in attrs.items(): setattr(chart, name, value) return chart