/usr/share/pyshared/graphy/formatters.py is in python-graphy 1.0+dfsg-3.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | #!/usr/bin/python2.4
#
# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This module contains various formatters which can help format a chart
object. To use these, add them to your chart's list of formatters. For
example:
chart.formatters.append(InlineLegend)
chart.formatters.append(LabelSeparator(right=8))
Feel free to write your own formatter. Formatters are just callables that
modify the chart in some (hopefully useful) way. For example, the AutoColor
formatter makes sure each DataSeries has a color applied to it. The formatter
should take the chart to format as its only argument.
(The formatters work on a deepcopy of the user's chart, so modifications
shouldn't leak back into the user's original chart)
"""
def AutoLegend(chart):
"""Automatically fill out the legend based on series labels. This will only
fill out the legend if is at least one series with a label.
"""
chart._show_legend = False
labels = []
for series in chart.data:
if series.label is None:
labels.append('')
else:
labels.append(series.label)
chart._show_legend = True
if chart._show_legend:
chart._legend_labels = labels
class AutoColor(object):
"""Automatically add colors to any series without colors.
Object attributes:
colors: The list of colors (hex strings) to cycle through. You can modify
this list if you don't like the default colors.
"""
def __init__(self):
# TODO: Add a few more default colors.
# TODO: Add a default styles too, so if you don't specify color or
# style, you get a unique set of colors & styles for your data.
self.colors = ['0000ff', 'ff0000', '00dd00', '000000']
def __call__(self, chart):
index = -1
for series in chart.data:
if series.style.color is None:
index += 1
if index >= len(self.colors):
index = 0
series.style.color = self.colors[index]
class AutoScale(object):
"""If you don't set min/max on the dependent axes, this fills them in
automatically by calculating min/max dynamically from the data.
You can set just min or just max and this formatter will fill in the other
value for you automatically. For example, if you only set min then this will
set max automatically, but leave min untouched.
Charts can have multiple dependent axes (chart.left & chart.right, for
example.) If you set min/max on some axes but not others, then this formatter
copies your min/max to the un-set axes. For example, if you set up min/max on
only the right axis then your values will be automatically copied to the left
axis. (if you use different min/max values for different axes, the
precendence is undefined. So don't do that.)
"""
def __init__(self, buffer=0.05):
"""Create a new AutoScale formatter.
Args:
buffer: percentage of extra space to allocate around the chart's axes.
"""
self.buffer = buffer
def __call__(self, chart):
"""Format the chart by setting the min/max values on its dependent axis."""
if not chart.data:
return # Nothing to do.
min_value, max_value = chart.GetMinMaxValues()
if None in (min_value, max_value):
return # No data. Nothing to do.
# Honor user's choice, if they've picked min/max.
for axis in chart.GetDependentAxes():
if axis.min is not None:
min_value = axis.min
if axis.max is not None:
max_value = axis.max
buffer = (max_value - min_value) * self.buffer # Stay away from edge.
for axis in chart.GetDependentAxes():
if axis.min is None:
axis.min = min_value - buffer
if axis.max is None:
axis.max = max_value + buffer
class LabelSeparator(object):
"""Adjust the label positions to avoid having them overlap. This happens for
any axis with minimum_label_spacing set.
"""
def __init__(self, left=None, right=None, bottom=None):
self.left = left
self.right = right
self.bottom = bottom
def __call__(self, chart):
self.AdjustLabels(chart.left, self.left)
self.AdjustLabels(chart.right, self.right)
self.AdjustLabels(chart.bottom, self.bottom)
def AdjustLabels(self, axis, minimum_label_spacing):
if minimum_label_spacing is None:
return
if len(axis.labels) <= 1: # Nothing to adjust
return
if axis.max is not None and axis.min is not None:
# Find the spacing required to fit all labels evenly.
# Don't try to push them farther apart than that.
maximum_possible_spacing = (axis.max - axis.min) / (len(axis.labels) - 1)
if minimum_label_spacing > maximum_possible_spacing:
minimum_label_spacing = maximum_possible_spacing
labels = [list(x) for x in zip(axis.label_positions, axis.labels)]
labels = sorted(labels, reverse=True)
# First pass from the top, moving colliding labels downward
for i in range(1, len(labels)):
if labels[i - 1][0] - labels[i][0] < minimum_label_spacing:
new_position = labels[i - 1][0] - minimum_label_spacing
if axis.min is not None and new_position < axis.min:
new_position = axis.min
labels[i][0] = new_position
# Second pass from the bottom, moving colliding labels upward
for i in range(len(labels) - 2, -1, -1):
if labels[i][0] - labels[i + 1][0] < minimum_label_spacing:
new_position = labels[i + 1][0] + minimum_label_spacing
if axis.max is not None and new_position > axis.max:
new_position = axis.max
labels[i][0] = new_position
# Separate positions and labels
label_positions, labels = zip(*labels)
axis.labels = labels
axis.label_positions = label_positions
def InlineLegend(chart):
"""Provide a legend for line charts by attaching labels to the right
end of each line. Supresses the regular legend.
"""
show = False
labels = []
label_positions = []
for series in chart.data:
if series.label is None:
labels.append('')
else:
labels.append(series.label)
show = True
label_positions.append(series.data[-1])
if show:
chart.right.min = chart.left.min
chart.right.max = chart.left.max
chart.right.labels = labels
chart.right.label_positions = label_positions
chart._show_legend = False # Supress the regular legend.
|