class IAggregateDescription(Interface): """ Mixin of fields related to aggregating multiple data points into a single descriptive aggretate point. All fields optional, and only considered relevant to aggregation of data from multiple sources or samples. 'distribution' attribute would have values that look like: [{ "value": 75.0, "sample_size": 8 }, { "value": 80, "sample_size": 10}] This data is sufficient to compute: - The sum of sample sizes. - The weighted mean. - The original numerator values as value/100.0 * sample_size - Presuming the value looks like a percentage. - The arithmetic mean. - Distribution plot. - Median, quartile boundary, and therefore box plot. """ distribution = schema.List( title=_(u'Distribution data'), description=_(u'List of dict containing value, sample size for each ' u'sample in aggregation.'), required=False, value_type=schema.Dict(key_type=schema.BytesLine(), value_type=schema.Object( schema=Interface, description=u'Value, may be int or float')))
class INamedDataSequence(form.Schema, IDataSeries, ISeriesDisplay): """Named category seqeuence with embedded data stored as content""" form.fieldset( 'display', label=u"Display settings", fields=[ 'color', 'show_trend', 'trend_width', 'trend_color', 'display_precision', 'point_labels', ], ) input = schema.Text( title=_(u'Data input'), description=_(u'Comma-separated records, one per line ' u'(name, numeric value, [note], [URL]). ' u'Note and URL are optional.'), default=u'', required=False, ) # data field to store CSV source: form.omitted('data') data = schema.List( title=_(u'Data'), description=_(u'Data points for series: name, value; values are ' u'either whole/integer or decimal numbers.'), value_type=schema.Object(schema=INamedDataPoint, ), readonly=True, )
class ITimeSeriesCollection(IDataCollection): """ Time series interface for configured range of time for all data series contained. Adds date range configuration to collection. """ start = schema.Date( title=_(u'Start date'), required=False, ) end = schema.Date( title=_(u'End date'), required=False, ) frequency = schema.Choice( title=u'Chart frequency', vocabulary=FREQ_VOCAB, default='monthly', ) @invariant def validate_start_end(obj): if not (obj.start is None or obj.end is None) and obj.start > obj.end: raise Invalid(_(u"Start date cannot be after end date."))
class ISeriesQuickStyles(form.Schema): """Interface for quick line styles (commonly used) for style book""" form.widget(color=NativeColorFieldWidget) color = schema.TextLine( title=_(u'Series color'), description=_(u'If omitted, color will be selected from defaults.'), required=False, default=u'Auto', ) marker_style = schema.Choice( title=_(u'Marker style'), description=_(u'Shape/type of the point-value marker.'), vocabulary=SimpleVocabulary([ SimpleTerm(value=u'diamond', title=u'Diamond'), SimpleTerm(value=u'circle', title=u'Circle'), SimpleTerm(value=u'square', title=u'Square'), SimpleTerm(value=u'x', title=u'X'), SimpleTerm(value=u'plus', title=u'Plus sign'), SimpleTerm(value=u'dash', title=u'Dash'), SimpleTerm(value=u'triangle-up', title=u'Triangle (upward)'), SimpleTerm(value=u'triangle-down', title=u'Triangle (downward)'), ]), default=u'square', )
class INamedBase(Interface): """Mix-in schema for name field""" name = schema.TextLine( title=_(u'Name'), description=_(u'Series-unique name or category for data point.'), required=True, ) def identity(): """return self.name"""
class IMeasureSeriesProvider(form.Schema, IDataSeries, ILineDisplay): form.widget(measure=ContentTreeFieldWidget) measure = schema.Choice( title=u'Bound measure', description=u'Measure definition that defines a function to apply ' u'to a dataset of forms to obtain a computed value for ' u'each as a data-point.', source=UUIDSourceBinder( portal_type=MEASURE_DEFINITION_TYPE, ), ) dataset = schema.Choice( title=u'Data set (collection)', description=u'Select a dataset that enumerates which forms are ' u'considered part of the data set to query for data. ' u'You must select a dataset within the same measure ' u'group in which the bound measure definition is ' u'contained.', source=MeasureGroupParentBinder( portal_type='uu.formlibrary.setspecifier', ), required=False, ) summarization_strategy = schema.Choice( title=u'Summarization strategy', description=u'How should data be summarized into a single value ' u'when multiple competing values for date or name ' u'are found in the data stream provided by the measure ' u'and data set? For example you may average or sum ' u'the multiple values, take the first or last, ' u'or you may choose to treat such competing values as ' u'a conflict, and omit any value on duplication.', vocabulary=VOCAB_SUMMARIZATION, default='AVG', ) form.omitted('data') data = schema.List( title=_(u'Data'), description=_(u'Data points computed from bound dataset, measure ' u'selected. Should return an empty list if any ' u'bindings are missing. ' u'Whether the data point key/identity type is a date ' u'or a name will depend on the type of chart ' u'containing this data provider.'), value_type=schema.Object( schema=IDataPoint, ), readonly=True, )
class IChartStyleBook(IChartDisplay): """ Style book for charts, can contain (as folder) ILineStyle items, in an ordered manner. """ goal = schema.Float( title=_(u'Goal'), description=_(u'Common goal value as decimal number. If each ' u'series has different respective goals, edit ' u'those goals on each series.'), required=False, ) # hide fields that are per-chart-specific form.omitted('x_label') form.omitted('y_label')
class IDataPoint(IAggregateDescription): """Data point contains single value and optional note and URI""" value = schema.Float( title=_(u'Number value'), description=_(u'Decimal number value.'), default=0.0, ) note = schema.Text( title=_(u'Note'), description=_(u'Note annotating the data value for this point.'), required=False, ) uri = schema.BytesLine( title=_(u'URI'), description=_(u'URI/URL or identifier to source of data'), required=False, ) sample_size = schema.Int( title=_(u'Sample size (N)'), description=u'Sample size, may be computed denominator of a ' u'population or subpopulation sampled.', required=False, ) def identity(): """
class IDateBase(Interface): """Mix-in schema with date field""" date = schema.Date( title=_(u'Date'), required=True, ) def identity(): """return self.date"""
class ISeriesDisplay(form.Schema): """ Common display settings for visualizing a series as either a bar or line chart. """ form.widget(color=ColorpickerFieldWidget) color = schema.TextLine( title=_(u'Series color'), description=_(u'If omitted, color will be selected from defaults.'), required=False, default=u'Auto', ) show_trend = schema.Bool( title=_(u'Show trend-line?'), description=_(u'Display a linear trend line? If enabled, uses ' u'configuration options specified.'), default=False, ) trend_width = schema.Int( title=_(u'Trend-line width'), description=_(u'Line width of trend-line in pixel units.'), default=2, ) form.widget(trend_color=ColorpickerFieldWidget) trend_color = schema.TextLine( title=_(u'Trend-line color'), description=_(u'If omitted, color will be selected from defaults.'), required=False, default=u'Auto', ) display_precision = schema.Int( title=u'Digits after decimal point (display precision)?', description=u'When displaying a decimal value, how many places ' u'beyond the decimal point should be displayed in ' u'output? Default: two digits after the decimal point.', default=1, ) point_labels = schema.Choice( title=u'Show point labels?', description=u'Show labels above data-point markers for this series?', default='defer', vocabulary=SimpleVocabulary([ SimpleTerm('defer', title=u'Defer to chart default'), SimpleTerm('show', title=u'Show labels'), SimpleTerm('omit', title=u'Omit labels'), ]) )
class INamedSeriesChart(IBaseChart, IDataCollection, IChartDisplay): """ Named/categorical chart: usually a bar chart with x-axis containing categorical enumerated names/labels, and Y-axis representing values for that label. """ chart_type = schema.Choice( title=_(u'Chart type'), description=_(u'Type of chart to display.'), vocabulary=SimpleVocabulary([ SimpleTerm(value=u'bar', title=u'Bar chart'), SimpleTerm(value=u'stacked', title=u'Stacked bar chart'), ]), default=u'bar', ) def series(): """
class ITimeDataSequence(form.Schema, IDataSeries, ILineDisplay): """Content item interface for a data series stored as content""" input = schema.Text( title=_(u'Data input'), description=_(u'Comma-separated records, one per line ' u'(date, numeric value, [note], [URL]). ' u'Note and URL are optional. Date ' u'should be in MM/DD/YYYY format.'), default=u'', required=False, ) label_default = schema.Choice( title=_(u'Label default'), description=_(u'Default format for X-Axis labels.'), default='locale', vocabulary=DATE_AXIS_LABEL_CHOICES, ) form.omitted('data') data = schema.List( title=_(u'Data'), description=_(u'Data points for time series: date, value; values are ' u'either whole/integer or decimal numbers.'), value_type=schema.Object( schema=ITimeSeriesDataPoint, ), readonly=True, )
class IDataReport(form.Schema, IOrderedContainer, IAttributeUUID): """ Ordered container/folder of contained charts providing ITimeSeriesChart. """ title = schema.TextLine( title=_(u'Title'), description=_(u'Report title; may be displayed in output.'), required=False, ) description = schema.Text( title=_(u'Description'), description=_(u'Textual description of the report.'), required=False, ) timeseries_default_type = schema.Choice( title=u'Default plot type for time-series', vocabulary=VOCAB_PLOT_TYPES, default=u'line', )
class IDataSeries(form.Schema): """Iterable of IDataPoint objects""" title = schema.TextLine( title=_(u'Title'), description=_(u'Name of data series; may be displayed as a label.'), required=True, ) def __iter__(): """ Return iterable of date, number data point objects providing (at least) IDataPoint. """ def __len__(): """Return number of data points""" def display_value(point): """Return normalized string display value for point""" def excluded(): """
class IDataPoint(Interface): """Data point contains single value and optional note and URI""" value = schema.Float( title=_(u'Number value'), description=_(u'Decimal number value.'), default=0.0, ) note = schema.Text( title=_(u'Note'), description=_(u'Note annotating the data value for this point.'), required=False, ) uri = schema.BytesLine( title=_(u'URI'), description=_(u'URI/URL or identifier to source of data'), required=False, ) def identity(): """
class IBaseChart(form.Schema, ILocation, IAttributeUUID, IDataCollection, IChartDisplay): """Base chart (content item) interface""" form.omitted('__name__') form.fieldset('goal', label=u'Goal', fields=[ 'show_goal', 'goal', 'goal_color', ]) form.fieldset('legend', label=u'Axes & Legend', fields=[ 'legend_placement', 'legend_location', 'x_label', 'y_label', 'units', ]) form.fieldset('display', label=u"Display", fields=[ 'width', 'width_units', 'height', 'height_units', 'chart_styles', 'point_labels', ]) form.fieldset( 'about', label=u"About", fields=['info'], ) directives.widget('stylebook', CustomRootRelatedWidget, pattern_options={ 'selectableTypes': ['uu.chart.stylebook'], 'maximumSelectionSize': 1, 'baseCriteria': [{ 'i': 'portal_type', 'o': 'plone.app.querystring.operation.string.is', 'v': 'uu.chart.stylebook', }] }) stylebook = schema.BytesLine( title=u'Bound theme', description=u'If a theme is bound, any updates to that theme ' u'will OVER-WRITE display configuration saved ' u'on this chart.', required=False, constraint=is_content_uuid) info = RichText( title=_(u'Informative notes'), description=_(u'This allows any rich text and may contain free-form ' u'notes about this chart; displayed in report output.'), required=False, )
def validate_start_end(obj): if not (obj.start is None or obj.end is None) and obj.start > obj.end: raise Invalid(_(u"Start date cannot be after end date."))
class IBaseChart( form.Schema, ILocation, IAttributeUUID, IDataCollection, IChartDisplay): """Base chart (content item) interface""" form.omitted('__name__') form.fieldset( 'goal', label=u'Goal', fields=[ 'show_goal', 'goal', 'goal_color', ] ) form.fieldset( 'legend', label=u'Axes & Legend', fields=[ 'legend_placement', 'legend_location', 'x_label', 'y_label', 'units', ] ) form.fieldset( 'display', label=u"Display", fields=[ 'width', 'width_units', 'height', 'height_units', 'chart_styles', 'point_labels', ] ) form.fieldset( 'about', label=u"About", fields=['info'], ) form.widget(stylebook=ContentTreeFieldWidget) stylebook = schema.Choice( title=u'Bound stylebook', description=u'If a stylebook is bound, any updates to that style ' u'book will OVER-WRITE display configuration saved ' u'on this chart.', source=UUIDSourceBinder( portal_type=STYLEBOOK_TYPE, ), required=False, ) info = RichText( title=_(u'Informative notes'), description=_(u'This allows any rich text and may contain free-form ' u'notes about this chart; displayed in report output.'), required=False, )
class ILineDisplayCore(form.Schema, ISeriesDisplay): """ Mixin interface for display-line configuration metadata for series line. """ line_width = schema.Int( title=_(u'Line width'), description=_(u'Width/thickness of line in pixel units.'), default=2, ) marker_style = schema.Choice( title=_(u'Marker style'), description=_(u'Shape/type of the point-value marker.'), vocabulary=SimpleVocabulary([ SimpleTerm(value=u'diamond', title=u'Diamond'), SimpleTerm(value=u'circle', title=u'Circle'), SimpleTerm(value=u'square', title=u'Square'), SimpleTerm(value=u'x', title=u'X'), SimpleTerm(value=u'plus', title=u'Plus sign'), SimpleTerm(value=u'dash', title=u'Dash'), SimpleTerm(value=u'filledDiamond', title=u'Filled diamond'), SimpleTerm(value=u'filledCircle', title=u'Filled circle'), SimpleTerm(value=u'filledSquare', title=u'Filled square'), ]), default=u'square', ) marker_size = schema.Float( title=_(u'Marker size'), description=_(u'Size of the marker (diameter or circle, length of ' u'edge of square, etc) in decimal pixels.'), required=False, default=9.0, ) marker_width = schema.Int( title=_(u'Marker line width'), description=_(u'Line width of marker in pixel units for ' u'non-filled markers.'), required=False, default=2, ) form.widget(marker_color=ColorpickerFieldWidget) marker_color = schema.TextLine( title=_(u'Marker color'), description=_(u'If omitted, color will be selected from defaults.'), required=False, default=u'Auto', ) break_lines = schema.Bool( title=u'Break lines?', description=u'When a value is missing for name or date on the ' u'X axis, should the line be broken/discontinuous ' u'such that no line runs through the empty/null ' u'value? This defaults to True, which means that ' u'no line will run from adjacent values through the ' u'missing value.', default=True, )
class IChartDisplay(form.Schema): """ Display configuration for chart settings (as a whole). """ width = schema.Int( title=_(u'Width'), description=_(u'Display width of chart, including Y-axis labels, ' u'grid, and legend (if applicable) in units ' u'configured.'), default=100, ) width_units = schema.Choice( title=_(u'Units of width'), vocabulary=WIDTH_UNITS, default='%', ) height = schema.Int( title=_(u'Height'), description=_(u'Display height of chart in units configured ' u'(either as percentage of width, or in pixels).'), default=50, ) height_units = schema.Choice( title=_(u'Units of height'), vocabulary=HEIGHT_UNITS, default='2:1', ) show_goal = schema.Bool( title=_(u'Show goal-line?'), description=_(u'If defined, show (constant horizontal) goal line?'), default=False, ) form.widget(goal_color=ColorpickerFieldWidget) goal_color = schema.TextLine( title=_(u'Goal-line color'), description=_(u'If omitted, color will be selected from defaults.'), required=False, default=u'Auto', ) form.order_after(chart_type='description') chart_type = schema.Choice( title=_(u'Chart type'), description=_(u'Type of chart to display.'), vocabulary=VOCAB_PLOT_TYPES, default=u'line', ) legend_placement = schema.Choice( title=_(u'Legend placement'), description=_( u'Where to place legend in relationship to the grid.' u'Note: the legend is disabled for less than two ' u'series/line unless the tabular legend is selected.' ), vocabulary=SimpleVocabulary(( SimpleTerm(value=None, token=str(None), title=u'Legend disabled'), SimpleTerm(value='outside', title=_(u'Outside grid')), SimpleTerm(value='inside', title=_(u'Inside grid')), SimpleTerm(value='tabular', title=_( u'Tabular legend with data, below plot')), )), required=False, default='outside', ) legend_location = schema.Choice( title=_(u'Legend location'), description=_(u'Select a directional position for legend. ' u'This setting is ignored if either the tabular ' u'legend placement is selected or if the ' u'legend is hidden (for less than two series).'), vocabulary=SimpleVocabulary(( SimpleTerm(value='nw', title=_(u'Top left')), SimpleTerm(value='n', title=_(u'Top')), SimpleTerm(value='ne', title=_(u'Top right')), SimpleTerm(value='e', title=_(u'Right')), SimpleTerm(value='se', title=_(u'Bottom right')), SimpleTerm(value='s', title=_(u'Bottom')), SimpleTerm(value='sw', title=_(u'Bottom left')), SimpleTerm(value='w', title=_(u'Left')), )), required=False, default='e', # right hand side ) x_label = schema.TextLine( title=_(u'X axis label'), default=u'', required=False, ) y_label = schema.TextLine( title=_(u'Y axis label'), default=u'', required=False, ) form.widget(chart_styles=TextAreaFieldWidget) chart_styles = schema.Bytes( title=_(u'Chart styles'), description=_(u'CSS stylesheet rules for chart (optional).'), required=False, ) point_labels = schema.Choice( title=u'Show point labels?', description=u'Show labels above data-point markers? ' u'This may be overridden on individual lines/series.', default='show', vocabulary=SimpleVocabulary([ SimpleTerm('show', title=u'Show labels'), SimpleTerm('omit', title=u'Omit labels'), ]) )
class IDataCollection(Interface): """ Collection of one or more (related) data series and associated metadata. Usually the logical component of a chart with multiple data series. """ title = schema.TextLine( title=_(u'Title'), description=_(u'Data collection name or title; may be displayed ' u'in legend.'), required=False, ) description = schema.Text( title=_(u'Description'), description=_(u'Textual description of the data collection.'), required=False, ) units = schema.TextLine( title=_(u'Units'), description=_(u'Common set of units of measure for the data ' u'series in this collection. If the units ' u'for series are not shared, then define ' u'respective units on the series themselves. ' u'May be displayed as part of Y-axis label.'), required=False, ) goal = schema.Float( title=_(u'Goal'), description=_(u'Common goal value as decimal number. If each ' u'series has different respective goals, edit ' u'those goals on each series.'), required=False, ) range_min = schema.Float( title=_(u'Range minimum'), description=_(u'Minimum anticipated value of any data point ' u'(optional).'), required=False, ) range_max = schema.Float( title=_(u'Range maximum'), description=_(u'Maximum anticipated value of any data point ' u'(optional).'), required=False, ) def series(): """ return a iterable of IDataSeries objects. """ def identities(): """