|
|
@ -5,7 +5,7 @@ from dateutil.parser import parse |
|
|
|
from dateutil.relativedelta import relativedelta |
|
|
|
from dateutil.relativedelta import relativedelta |
|
|
|
|
|
|
|
|
|
|
|
from SVG import Graph |
|
|
|
from SVG import Graph |
|
|
|
from util import grouper, date_range |
|
|
|
from util import grouper, date_range, divide_timedelta_float, TimeScale |
|
|
|
|
|
|
|
|
|
|
|
__all__ = ('Schedule') |
|
|
|
__all__ = ('Schedule') |
|
|
|
|
|
|
|
|
|
|
@ -141,8 +141,8 @@ class Schedule(Graph): |
|
|
|
will probably not be discernable if both data sets are plotted on the same |
|
|
|
will probably not be discernable if both data sets are plotted on the same |
|
|
|
graph, since d1 is too granular. |
|
|
|
graph, since d1 is too granular. |
|
|
|
""" |
|
|
|
""" |
|
|
|
# note, the only reason this method is overridden is to change the |
|
|
|
# The ruby version does something different here, throwing out |
|
|
|
# docstring |
|
|
|
# any previously added data. |
|
|
|
super(Schedule, self).add_data(data) |
|
|
|
super(Schedule, self).add_data(data) |
|
|
|
|
|
|
|
|
|
|
|
# copied from Bar |
|
|
|
# copied from Bar |
|
|
@ -166,7 +166,7 @@ class Schedule(Graph): |
|
|
|
data = conf['data'] |
|
|
|
data = conf['data'] |
|
|
|
triples = grouper(3, data) |
|
|
|
triples = grouper(3, data) |
|
|
|
|
|
|
|
|
|
|
|
begin_dates, end_dates, labels = zip(*triples) |
|
|
|
labels, begin_dates, end_dates = zip(*triples) |
|
|
|
|
|
|
|
|
|
|
|
begin_dates = map(self.parse_date, begin_dates) |
|
|
|
begin_dates = map(self.parse_date, begin_dates) |
|
|
|
end_dates = map(self.parse_date, end_dates) |
|
|
|
end_dates = map(self.parse_date, end_dates) |
|
|
@ -174,7 +174,10 @@ class Schedule(Graph): |
|
|
|
# reconstruct the triples in a new order |
|
|
|
# reconstruct the triples in a new order |
|
|
|
reordered_triples = zip(begin_dates, end_dates, labels) |
|
|
|
reordered_triples = zip(begin_dates, end_dates, labels) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# because of the reordering, this will sort by begin_date |
|
|
|
|
|
|
|
# then end_date, then label. |
|
|
|
reordered_triples.sort() |
|
|
|
reordered_triples.sort() |
|
|
|
|
|
|
|
|
|
|
|
conf['data'] = reordered_triples |
|
|
|
conf['data'] = reordered_triples |
|
|
|
|
|
|
|
|
|
|
|
def parse_date(self, date_string): |
|
|
|
def parse_date(self, date_string): |
|
|
@ -202,26 +205,34 @@ class Schedule(Graph): |
|
|
|
return height / -2.0 |
|
|
|
return height / -2.0 |
|
|
|
|
|
|
|
|
|
|
|
def get_y_labels(self): |
|
|
|
def get_y_labels(self): |
|
|
|
data = self.data['data'] |
|
|
|
# ruby version uses the last data supplied |
|
|
|
|
|
|
|
last = -1 |
|
|
|
|
|
|
|
data = self.data[last]['data'] |
|
|
|
begin_dates, start_dates, labels = zip(*data) |
|
|
|
begin_dates, start_dates, labels = zip(*data) |
|
|
|
return labels |
|
|
|
return labels |
|
|
|
|
|
|
|
|
|
|
|
def draw_data(self): |
|
|
|
def draw_data(self): |
|
|
|
bar_gap = self.get_bar_gap() |
|
|
|
bar_gap = self.get_bar_gap(self.get_field_height()) |
|
|
|
|
|
|
|
|
|
|
|
subbar_height = self.field_height - bar_gap |
|
|
|
subbar_height = self.get_field_height() - bar_gap |
|
|
|
|
|
|
|
|
|
|
|
y_mod = (subbar_height / 2) + (font_size / 2) |
|
|
|
y_mod = (subbar_height / 2) + (self.font_size / 2) |
|
|
|
x_min,x_max,div = self.x_range() |
|
|
|
x_min,x_max,div = self._x_range() |
|
|
|
x_range = x_max - x_min |
|
|
|
x_range = x_max - x_min |
|
|
|
scale = (float(self.graph_width) - self.font_size*2) |
|
|
|
width = (float(self.graph_width) - self.font_size*2) |
|
|
|
scale /= x_range |
|
|
|
# time_scale |
|
|
|
|
|
|
|
#scale /= x_range |
|
|
|
|
|
|
|
scale = TimeScale(width, x_range) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ruby version uses the last data supplied |
|
|
|
|
|
|
|
last = -1 |
|
|
|
|
|
|
|
data = self.data[last]['data'] |
|
|
|
|
|
|
|
|
|
|
|
for index, (x_start, x_end, label) in enumerate(self.data['data']): |
|
|
|
for index, (x_start, x_end, label) in enumerate(data): |
|
|
|
count = index + 1 # index is 0-based, count is 1-based |
|
|
|
count = index + 1 # index is 0-based, count is 1-based |
|
|
|
y = self.graph_height - (self.field_height*count) |
|
|
|
y = self.graph_height - (self.get_field_height()*count) |
|
|
|
bar_width = (x_end-x_stant)*scale |
|
|
|
bar_width = scale*(x_end-x_start) |
|
|
|
bar_start = (x_start-x_min)*scale |
|
|
|
bar_start = scale*(x_start-x_min) |
|
|
|
|
|
|
|
|
|
|
|
rect = self._create_element('rect', { |
|
|
|
rect = self._create_element('rect', { |
|
|
|
'x': str(bar_start), |
|
|
|
'x': str(bar_start), |
|
|
@ -310,17 +321,24 @@ class Schedule(Graph): |
|
|
|
""" |
|
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
def _x_range(self): |
|
|
|
def _x_range(self): |
|
|
|
start_dates, end_dates, labels = zip(*self.data['data']) |
|
|
|
# ruby version uses teh last data supplied |
|
|
|
|
|
|
|
last = -1 |
|
|
|
|
|
|
|
data = self.data[last]['data'] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
start_dates, end_dates, labels = zip(*data) |
|
|
|
all_dates = start_dates + end_dates |
|
|
|
all_dates = start_dates + end_dates |
|
|
|
max_value = max(all_dates) |
|
|
|
max_value = max(all_dates) |
|
|
|
if not self.min_x_value is None: |
|
|
|
if not self.min_x_value is None: |
|
|
|
all_dates.append(self.min_x_value) |
|
|
|
all_dates.append(self.min_x_value) |
|
|
|
min_value = min(all_dates) |
|
|
|
min_value = min(all_dates) |
|
|
|
range = relativedelta(max_value, min_value) |
|
|
|
range = max_value - min_value |
|
|
|
right_pad = (range / 20.0) or relativedelta(days=10) |
|
|
|
right_pad = divide_timedelta_float(range, 20.0) or relativedelta(days=10) |
|
|
|
scale_range = (max_value + right_pad) - min_value |
|
|
|
scale_range = (max_value + right_pad) - min_value |
|
|
|
|
|
|
|
|
|
|
|
scale_division = self.scale_x_divisions or (scale_range / 10.0) |
|
|
|
#scale_division = self.scale_x_divisions or (scale_range / 10.0) |
|
|
|
|
|
|
|
# todo, remove timescale_x_divisions and use scale_x_divisions only |
|
|
|
|
|
|
|
# but as a time delta |
|
|
|
|
|
|
|
scale_division = divide_timedelta_float(scale_range, 10.0) |
|
|
|
|
|
|
|
|
|
|
|
# this doesn't make sense, because x is a timescale |
|
|
|
# this doesn't make sense, because x is a timescale |
|
|
|
#if self.scale_x_integers: |
|
|
|
#if self.scale_x_integers: |
|
|
@ -330,24 +348,22 @@ class Schedule(Graph): |
|
|
|
|
|
|
|
|
|
|
|
def get_x_values(self): |
|
|
|
def get_x_values(self): |
|
|
|
x_min, x_max, scale_division = self._x_range() |
|
|
|
x_min, x_max, scale_division = self._x_range() |
|
|
|
if timescale_divisions: |
|
|
|
if self.timescale_divisions: |
|
|
|
pattern = re.compile('(\d+) ?(\w+)') |
|
|
|
pattern = re.compile('(\d+) ?(\w+)') |
|
|
|
m = pattern.match(timescale_divisions) |
|
|
|
m = pattern.match(self.timescale_divisions) |
|
|
|
if not m: |
|
|
|
if not m: |
|
|
|
raise ValueError, "Invalid timescale_divisions: %s" % timescale_divisions |
|
|
|
raise ValueError, "Invalid timescale_divisions: %s" % self.timescale_divisions |
|
|
|
|
|
|
|
|
|
|
|
magnitude = int(m.groups(1)) |
|
|
|
magnitude = int(m.group(1)) |
|
|
|
units = m.groups(2) |
|
|
|
units = m.group(2) |
|
|
|
|
|
|
|
|
|
|
|
parameter = self.lookup_relativedelta_parameter(units) |
|
|
|
parameter = self.lookup_relativedelta_parameter(units) |
|
|
|
|
|
|
|
|
|
|
|
delta = relativedelta(**{parameter:magnitude}) |
|
|
|
delta = relativedelta(**{parameter:magnitude}) |
|
|
|
|
|
|
|
|
|
|
|
return daterange(x_min, x_max, delta) |
|
|
|
scale_division = delta |
|
|
|
else: |
|
|
|
|
|
|
|
# I think much of the code is assuming x is an integer (seconds) |
|
|
|
return date_range(x_min, x_max, scale_division) |
|
|
|
# The whole logic needs to be revisited for this purpose. |
|
|
|
|
|
|
|
return range(x_min, x_max, scale_division) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def lookup_relativedelta_parameter(self, unit_string): |
|
|
|
def lookup_relativedelta_parameter(self, unit_string): |
|
|
|
from util import reverse_mapping, flatten_mapping |
|
|
|
from util import reverse_mapping, flatten_mapping |
|
|
|