summaryrefslogtreecommitdiffstats
path: root/viz/draw.py
diff options
context:
space:
mode:
Diffstat (limited to 'viz/draw.py')
-rw-r--r--viz/draw.py1254
1 files changed, 1254 insertions, 0 deletions
diff --git a/viz/draw.py b/viz/draw.py
new file mode 100644
index 0000000..c3ab756
--- /dev/null
+++ b/viz/draw.py
@@ -0,0 +1,1254 @@
1#!/usr/bin/python
2
3import math
4import cairo
5import os
6
7import util
8import schedule
9from format import *
10
11def snap(pos):
12 """Takes in an x- or y-coordinate ``pos'' and snaps it to the pixel grid.
13 This is necessary because integer coordinates in Cairo actually denote
14 the spaces between pixels, not the pixels themselves, so if we draw a
15 line of width 1 on integer coordinates, it will come out blurry unless we shift it,
16 since the line will get distributed over two pixels. We actually apply this to all
17 coordinates to make sure everything is aligned."""
18 return pos - 0.5
19
20class Surface(object):
21 def __init__(self, fname='temp', ctx=None):
22 self.virt_x = 0
23 self.virt_y = 0
24 self.surface = None
25 self.width = 0
26 self.height = 0
27 self.fname = fname
28 self.ctx = ctx
29
30 def renew(self, width, height):
31 raise NotImplementedError
32
33 def change_ctx(self, ctx):
34 self.ctx = ctx
35
36 def get_fname(self):
37 return self.fname
38
39 def write_out(self, fname):
40 raise NotImplementedError
41
42 def pan(self, x, y, width, height):
43 """A surface might actually represent just a ``window'' into
44 what we are drawing on. For instance, if we are scrolling through
45 a graph, then the surface represents the area in the GUI window,
46 not the entire graph (visible or not). So this method basically
47 moves the ``window's'' upper-left corner to (x, y), and resizes
48 the dimensions to (width, height)."""
49 self.virt_x = x
50 self.virt_y = y
51 self.width = width
52 self.height = height
53
54 def get_real_coor(self, x, y):
55 """Translates the coordinates (x, y)
56 in the ``theoretical'' plane to the true (x, y) coordinates on this surface
57 that we should draw to. Note that these might actually be outside the
58 bounds of the surface,
59 if we want something outside the surface's ``window''."""
60 return (x - self.virt_x, y - self.virt_y)
61
62class SVGSurface(Surface):
63 def renew(self, width, height):
64 iwidth = int(math.ceil(width))
65 iheight = int(math.ceil(height))
66 self.surface = cairo.SVGSurface(self.fname, iwidth, iheight)
67 self.ctx = cairo.Context(self.surface)
68
69 def write_out(self, fname):
70 os.execl('cp', self.fname, fname)
71
72class ImageSurface(Surface):
73 def renew(self, width, height):
74 iwidth = int(math.ceil(width))
75 iheight = int(math.ceil(height))
76 self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, iwidth, iheight)
77 self.ctx = cairo.Context(self.surface)
78
79 def write_out(self, fname):
80 if self.surface is None:
81 raise ValueError('Don\'t own surface, can\'t write to to file')
82
83 self.surface.write_to_png(fname)
84
85class Pattern(object):
86 DEF_STRIPE_SIZE = 10
87
88 def __init__(self, color_list, stripe_size=DEF_STRIPE_SIZE):
89 self.color_list = color_list
90 self.stripe_size = stripe_size
91
92 def render_on_canvas(self, canvas, x, y, width, height, fade=False):
93 if len(self.color_list) == 1:
94 if fade:
95 canvas.fill_rect_fade(x, y, width, height, (1.0, 1.0, 1.0), \
96 self.color_list[0])
97 else:
98 canvas.fill_rect(x, y, width, height, self.color_list[0])
99
100 else:
101 n = 0
102 bottom = y + height
103 while y < bottom:
104 linear = cairo.LinearGradient(x, y, x + width, math.min(y + self.stripe_size, bottom))
105 i = n % len(self.color_list)
106 if fade:
107 canvas.fill_rect_fade(x, y, width, min(self.stripe_size, bottom - y), (1.0, 1.0, 1.0), \
108 self.color_list[i])
109 else:
110 canvas.fill_rect(x, y, width, min(self.stripe_size, bottom - y), self.color_list[i])
111
112 y += self.stripe_size
113 n += 1
114
115class Canvas(object):
116 """This is a basic class that stores and draws on a Cairo surface,
117 using various primitives related to drawing a real-time graph (up-arrows,
118 down-arrows, bars, ...).
119
120 This is the lowest-level representation (aside perhaps from the Cairo
121 surface itself) of a real-time graph. It allows the user to draw
122 primitives at certain locations, but for the most part does not know
123 anything about real-time scheduling, just how to draw the basic parts
124 that make up a schedule graph. For that, see Graph or its descendants."""
125
126 BOTTOM_LAYER = 0
127 MIDDLE_LAYER = 1
128 TOP_LAYER = 2
129
130 LAYERS = (BOTTOM_LAYER, MIDDLE_LAYER, TOP_LAYER)
131
132 SQRT3 = math.sqrt(3.0)
133
134 def __init__(self, width, height, item_clist, bar_plist, surface):
135 """Creates a new Canvas of dimensions (width, height). The
136 parameters ``item_plist'' and ``bar_plist'' each specify a list
137 of patterns to choose from when drawing the items on the y-axis
138 or filling in bars, respectively."""
139
140 self.surface = surface
141
142 self.width = int(math.ceil(width))
143 self.height = int(math.ceil(height))
144 self.item_clist = item_clist
145 self.bar_plist = bar_plist
146
147 self.selectable_regions = {}
148
149 self.scale = 1.0
150
151 # clears the canvas.
152 def clear(self):
153 raise NotImplementedError
154
155 def scaled(self, *coors):
156 return [coor * self.scale for coor in coors]
157
158 def draw_rect(self, x, y, width, height, color, thickness):
159 """Draws a rectangle somewhere (border only)."""
160 raise NotImplementedError
161
162 def fill_rect(self, x, y, width, height, color):
163 """Draws a filled rectangle somewhere. ``color'' is a 3-tuple."""
164 raise NotImplementedError
165
166 def fill_rect_fade(self, x, y, width, height, lcolor, rcolor):
167 """Draws a rectangle somewhere, filled in with the fade."""
168 raise NotImplementedError
169
170 def draw_line(self, p0, p1, color, thickness):
171 """Draws a line from p0 to p1 with a certain color and thickness."""
172 raise NotImplementedError
173
174 def draw_polyline(self, coor_list, color, thickness):
175 """Draws a polyline, where coor_list = [(x_0, y_0), (x_1, y_1), ... (x_m, y_m)]
176 specifies a polyline from (x_0, y_0) to (x_1, y_1), etc."""
177 raise NotImplementedError
178
179 def fill_polyline(self, coor_list, color, thickness):
180 """Draws a polyline (probably a polygon) and fills it."""
181 raise NotImplementedError
182
183 def draw_label(self, text, x, y, fopts=GraphFormat.DEF_FOPTS_LABEL, halign=AlignMode.LEFT, valign=AlignMode.BOTTOM):
184 """Draws text at a position with a certain alignment."""
185 raise NotImplementedError
186
187 def draw_label_with_sscripts(self, text, supscript, subscript, x, y, \
188 textfopts=GraphFormat.DEF_FOPTS_LABEL,
189 sscriptfopts=GraphFormat.DEF_FOPTS_LABEL_SSCRIPT, \
190 halign=AlignMode.LEFT, valign=AlignMode.BOTTOM):
191 """Draws text at a position with a certain alignment, along with optionally a superscript and
192 subscript (which are None if either is not used.)"""
193 raise NotImplementedError
194
195 def draw_y_axis(self, x, y, height):
196 """Draws the y-axis, starting from the bottom at the point x, y."""
197 self.surface.ctx.set_source_rgb(0.0, 0.0, 0.0)
198
199 self.draw_line((x, y), (x, y - height), (0.0, 0.0, 0.0), GraphFormat.AXIS_THICKNESS)
200
201 def draw_y_axis_labels(self, x, y, height, item_list, item_size, fopts=None):
202 """Draws the item labels on the y-axis. ``item_list'' is the list
203 of strings to print, while item_size gives the vertical amount of
204 space that each item shall take up, in pixels."""
205 if fopts is None:
206 fopts = GraphFormat.DEF_FOPTS_ITEM
207
208 x -= GraphFormat.Y_AXIS_ITEM_GAP
209 y -= height - item_size / 2.0
210
211 orig_color = fopts.color
212 for ctr, item in enumerate(item_list):
213 fopts.color = self.get_item_color(ctr)
214 self.draw_label(item, x, y, fopts, AlignMode.RIGHT, AlignMode.CENTER)
215 y += item_size
216
217 fopts.color = orig_color
218
219 def draw_x_axis(self, x, y, start_tick, end_tick, maj_sep, min_per_maj):
220 """Draws the x-axis, including all the major and minor ticks (but not the labels).
221 ``num_maj'' gives the number of major ticks, ``maj_sep'' the number of pixels between
222 major ticks, and ``min_per_maj'' the number of minor ticks between two major ticks
223 (including the first major tick)"""
224 self.draw_line((x, y), (x + GraphFormat.X_AXIS_MEASURE_OFS, y),
225 (0.0, 0.0, 0.0), GraphFormat.AXIS_THICKNESS)
226 x += GraphFormat.X_AXIS_MEASURE_OFS + start_tick * maj_sep
227
228 for i in range(start_tick, end_tick + 1):
229 self.draw_line((x, y), (x, y + GraphFormat.MAJ_TICK_SIZE),
230 (0.0, 0.0, 0.0), GraphFormat.AXIS_THICKNESS)
231
232 if (i < end_tick):
233 for j in range(0, min_per_maj):
234 self.draw_line((x, y), (x + maj_sep / min_per_maj, y),
235 (0.0, 0.0, 0.0), GraphFormat.AXIS_THICKNESS)
236
237 x += 1.0 * maj_sep / min_per_maj
238 if j < min_per_maj - 1:
239 self.draw_line((x, y), (x, y + GraphFormat.MIN_TICK_SIZE),
240 (0.0, 0.0, 0.0), GraphFormat.AXIS_THICKNESS)
241
242 def draw_x_axis_labels(self, x, y, start_tick, end_tick, maj_sep, min_per_maj, start=0, incr=1, show_min=False, \
243 majfopts=GraphFormat.DEF_FOPTS_MAJ, minfopts=GraphFormat.DEF_FOPTS_MIN):
244 """Draws the labels for the x-axis. (x, y) should give the origin.
245 how far down you want the text. ``incr'' gives the increment per major
246 tick. ``start'' gives the value of the first tick. ``show_min'' specifies
247 whether to draw labels at minor ticks."""
248
249 x += GraphFormat.X_AXIS_MEASURE_OFS + start_tick * maj_sep
250 y += GraphFormat.X_AXIS_LABEL_GAP + GraphFormat.MAJ_TICK_SIZE
251
252 minincr = incr / (min_per_maj * 1.0)
253
254 cur = start * 1.0
255
256 for i in range(start_tick, end_tick + 1):
257 text = util.format_float(cur, 2)
258 self.draw_label(text, x, y, majfopts, AlignMode.CENTER, AlignMode.TOP)
259
260 if (i < end_tick):
261 if show_min:
262 for j in range(0, min_per_maj):
263 x += 1.0 * maj_sep / min_per_maj
264 cur += minincr
265 text = util.format_float(cur, 2)
266
267 if j < min_per_maj - 1:
268 self.draw_label(text, x, y, minfopts, AlignMode.CENTER, AlignMode.TOP)
269 else:
270 x += maj_sep
271 cur += incr
272
273 def draw_grid(self, x, y, height, start_tick, end_tick, start_item, end_item, maj_sep, item_size, \
274 min_per_maj=None, show_min=False):
275 """Draws a grid dividing along the item boundaries and the major ticks.
276 (x, y) gives the origin. ``show_min'' specifies whether to draw vertical grid lines at minor ticks.
277 ``start_tick'' and ``end_tick'' give the major ticks to start and end at for drawing vertical lines.
278 ``start_item'' and ``end_item'' give the item boundaries to start and end drawing horizontal lines."""
279 if start_tick > end_tick or start_item > end_item:
280 raise ValueError('start must be less than end')
281
282 line_width = (end_tick - start_tick) * maj_sep
283 line_height = (end_item - start_item) * item_size
284
285 origin = (x, y)
286
287 # draw horizontal lines first
288 x = origin[0] + GraphFormat.X_AXIS_MEASURE_OFS + start_tick * maj_sep
289 y = origin[1] - height + start_item * item_size
290 for i in range(start_item, end_item + 1):
291 self.draw_line((x, y), (x + line_width, y), GraphFormat.GRID_COLOR, GraphFormat.GRID_THICKNESS)
292 y += item_size
293
294 x = origin[0] + GraphFormat.X_AXIS_MEASURE_OFS + start_tick * maj_sep
295 y = origin[1] - height + start_item * item_size
296
297 if show_min:
298 for i in range(0, (end_tick - start_tick) * min_per_maj + 1):
299 self.draw_line((x, y), (x, y + line_height), GraphFormat.GRID_COLOR, GraphFormat.GRID_THICKNESS)
300 x += maj_sep * 1.0 / min_per_maj
301 else:
302 for i in range(start_tick, end_tick + 1):
303 self.draw_line((x, y), (x, y + line_height), GraphFormat.GRID_COLOR, GraphFormat.GRID_THICKNESS)
304 x += maj_sep
305
306 def draw_bar(self, x, y, width, height, n, selected):
307 """Draws a bar with a certain set of dimensions, using pattern ``n'' from the
308 bar pattern list."""
309
310 color, thickness = {False : (GraphFormat.BORDER_COLOR, GraphFormat.BORDER_THICKNESS),
311 True : (GraphFormat.HIGHLIGHT_COLOR, GraphFormat.BORDER_THICKNESS * 2.0)}[selected]
312
313 # use a pattern to be pretty
314 self.get_bar_pattern(n).render_on_canvas(self, x, y, width, height, True)
315 self.draw_rect(x, y, width, height, color, thickness)
316
317 def add_sel_bar(self, x, y, width, height, event):
318 self.add_sel_region(SelectableRegion(x, y, width, height, event))
319
320 def draw_mini_bar(self, x, y, width, height, n, selected):
321 """Like the above, except it draws a miniature version. This is usually used for
322 secondary purposes (i.e. to show jobs that _should_ have been running at a certain time).
323
324 Of course we don't enforce the fact that this is mini, since the user can pass in width
325 and height (but the mini bars do look slightly different: namely the borders are a different
326 color)"""
327
328 color, thickness = {False : (GraphFormat.LITE_BORDER_COLOR, GraphFormat.BORDER_THICKNESS),
329 True : (GraphFormat.HIGHLIGHT_COLOR, GraphFormat.BORDER_THICKNESS * 1.5)}[selected]
330
331 self.get_bar_pattern(n).render_on_canvas(self, x, y, width, height, True)
332 self.draw_rect(x, y, width, height, color, thickness)
333
334 def add_sel_mini_bar(self, x, y, width, height, event):
335 self.add_sel_region(SelectableRegion(x, y, width, height, event))
336
337 def draw_completion_marker(self, x, y, height, selected):
338 """Draws the symbol that represents a job completion, using a certain height."""
339
340 color = {False : GraphFormat.BORDER_COLOR, True : GraphFormat.HIGHLIGHT_COLOR}[selected]
341 self.draw_line((x - height * GraphFormat.TEE_FACTOR / 2.0, y),
342 (x + height * GraphFormat.TEE_FACTOR / 2.0, y),
343 color, GraphFormat.BORDER_THICKNESS)
344 self.draw_line((x, y), (x, y + height), color, GraphFormat.BORDER_THICKNESS)
345
346 def add_sel_completion_marker(self, x, y, height, event):
347 self.add_sel_region(SelectableRegion(x - height * GraphFormat.TEE_FACTOR / 2.0, y,
348 height * GraphFormat.TEE_FACTOR, height, event))
349
350 def draw_release_arrow_big(self, x, y, height, selected):
351 """Draws a release arrow of a certain height: (x, y) should give the top
352 (northernmost point) of the arrow. The height includes the arrowhead."""
353 big_arrowhead_height = GraphFormat.BIG_ARROWHEAD_FACTOR * height
354
355 color = {False : GraphFormat.BORDER_COLOR, True : GraphFormat.HIGHLIGHT_COLOR}[selected]
356 colors = [(1.0, 1.0, 1.0), color]
357 draw_funcs = [self.__class__.fill_polyline, self.__class__.draw_polyline]
358 for i in range(0, 2):
359 color = colors[i]
360 draw_func = draw_funcs[i]
361
362 draw_func(self, [(x, y), (x - big_arrowhead_height / Canvas.SQRT3, y + big_arrowhead_height), \
363 (x + big_arrowhead_height / Canvas.SQRT3, y + big_arrowhead_height), (x, y)], \
364 color, GraphFormat.BORDER_THICKNESS)
365
366 self.draw_line((x, y + big_arrowhead_height), (x, y + height), color, GraphFormat.BORDER_THICKNESS)
367
368 def add_sel_release_arrow_big(self, x, y, height, event):
369 self.add_sel_arrow_big(x, y, height, event)
370
371 def draw_deadline_arrow_big(self, x, y, height, selected):
372 """Draws a release arrow: x, y should give the top (northernmost
373 point) of the arrow. The height includes the arrowhead."""
374 big_arrowhead_height = GraphFormat.BIG_ARROWHEAD_FACTOR * height
375
376 color = {False : GraphFormat.BORDER_COLOR, True : GraphFormat.HIGHLIGHT_COLOR}[selected]
377 colors = [(1.0, 1.0, 1.0), color]
378 draw_funcs = [self.__class__.fill_polyline, self.__class__.draw_polyline]
379 for i in range(0, 2):
380 color = colors[i]
381 draw_func = draw_funcs[i]
382
383 draw_func(self, [(x, y + height), (x - big_arrowhead_height / Canvas.SQRT3, \
384 y + height - big_arrowhead_height), \
385 (x + big_arrowhead_height / Canvas.SQRT3, \
386 y + height - big_arrowhead_height), \
387 (x, y + height)], color, GraphFormat.BORDER_THICKNESS)
388
389 self.draw_line((x, y), (x, y + height - big_arrowhead_height),
390 color, GraphFormat.BORDER_THICKNESS)
391
392 def add_sel_deadline_arrow_big(self, x, y, height, event):
393 self.add_sel_arrow_big(x, y, height, event)
394
395 def add_sel_arrow_big(self, x, y, height, event):
396 big_arrowhead_height = GraphFormat.BIG_ARROWHEAD_FACTOR * height
397
398 self.add_sel_region(SelectableRegion(x - big_arrowhead_height / Canvas.SQRT3,
399 y, 2.0 * big_arrowhead_height / Canvas.SQRT3, height, event))
400
401 def draw_release_arrow_small(self, x, y, height, selected):
402 """Draws a small release arrow (most likely coming off the x-axis, although
403 this method doesn't enforce this): x, y should give the top of the arrow"""
404 small_arrowhead_height = GraphFormat.SMALL_ARROWHEAD_FACTOR * height
405
406 color = {False : GraphFormat.BORDER_COLOR, True : GraphFormat.HIGHLIGHT_COLOR}[selected]
407
408 self.draw_line((x, y), (x - small_arrowhead_height, y + small_arrowhead_height), \
409 color, GraphFormat.BORDER_THICKNESS)
410 self.draw_line((x, y), (x + small_arrowhead_height, y + small_arrowhead_height), \
411 color, GraphFormat.BORDER_THICKNESS)
412 self.draw_line((x, y), (x, y + height), color, GraphFormat.BORDER_THICKNESS)
413
414 def add_sel_release_arrow_small(self, x, y, height, event):
415 self.add_sel_arrow_small(x, y, height, event)
416
417 def draw_deadline_arrow_small(self, x, y, height, selected):
418 """Draws a small deadline arrow (most likely coming off the x-axis, although
419 this method doesn't enforce this): x, y should give the top of the arrow"""
420 small_arrowhead_height = GraphFormat.SMALL_ARROWHEAD_FACTOR * height
421
422 color = {False : GraphFormat.BORDER_COLOR, True : GraphFormat.HIGHLIGHT_COLOR}[selected]
423
424 self.draw_line((x, y), (x, y + height), color, GraphFormat.BORDER_THICKNESS)
425 self.draw_line((x - small_arrowhead_height, y + height - small_arrowhead_height), \
426 (x, y + height), color, GraphFormat.BORDER_THICKNESS)
427 self.draw_line((x + small_arrowhead_height, y + height - small_arrowhead_height), \
428 (x, y + height), color, GraphFormat.BORDER_THICKNESS)
429
430 def add_sel_deadline_arrow_small(self, x, y, height, event):
431 self.add_sel_arrow_small(x, y, height, event)
432
433 def add_sel_arrow_small(self, x, y, height, event):
434 small_arrowhead_height = GraphFormat.SMALL_ARROWHEAD_FACTOR * height
435
436 self.add_sel_region(SelectableRegion(x - small_arrowhead_height, y,
437 small_arrowhead_height * 2.0, height, event))
438
439 def draw_suspend_triangle(self, x, y, height, selected):
440 """Draws the triangle that marks a suspension. (x, y) gives the topmost (northernmost) point
441 of the symbol."""
442
443 color = {False : GraphFormat.BORDER_COLOR, True : GraphFormat.HIGHLIGHT_COLOR}[selected]
444 colors = [(0.0, 0.0, 0.0), color]
445
446 draw_funcs = [self.__class__.fill_polyline, self.__class__.draw_polyline]
447 for i in range(0, 2):
448 color = colors[i]
449 draw_func = draw_funcs[i]
450 draw_func(self, [(x, y), (x + height / 2.0, y + height / 2.0), (x, y + height), (x, y)], \
451 color, GraphFormat.BORDER_THICKNESS)
452
453 def add_sel_suspend_triangle(self, x, y, height, event):
454 self.add_sel_region(SelectableRegion(x, y, height / 2.0, height, event))
455
456 def draw_resume_triangle(self, x, y, height, selected):
457 """Draws the triangle that marks a resumption. (x, y) gives the topmost (northernmost) point
458 of the symbol."""
459
460 color = {False : GraphFormat.BORDER_COLOR, True : GraphFormat.HIGHLIGHT_COLOR}[selected]
461 colors = [(1.0, 1.0, 1.0), color]
462
463 draw_funcs = [self.__class__.fill_polyline, self.__class__.draw_polyline]
464 for i in range(0, 2):
465 color = colors[i]
466 draw_func = draw_funcs[i]
467 draw_func(self, [(x, y), (x - height / 2.0, y + height / 2.0), (x, y + height), (x, y)], \
468 color, GraphFormat.BORDER_THICKNESS)
469
470 def add_sel_resume_triangle(self, x, y, height, event):
471 self.add_sel_region(SelectableRegion(x - height / 2.0, y, height / 2.0, height, event))
472
473 def clear_selectable_regions(self):
474 self.selectable_regions = {}
475
476 def add_sel_region(self, region):
477 self.selectable_regions[region.get_event()] = region
478
479 def get_selected_regions(self, real_x, real_y):
480 x = real_x + self.surface.virt_x
481 y = real_y + self.surface.virt_y
482
483 selected = {}
484 for event in self.selectable_regions:
485 region = self.selectable_regions[event]
486 if region.contains(x, y):
487 selected[event] = region
488
489 return selected
490
491 def whiteout(self):
492 """Overwrites the surface completely white, but technically doesn't delete anything"""
493 self.fill_rect(self.surface.virt_x, self.surface.virt_y, self.surface.width,
494 self.surface.height, (1.0, 1.0, 1.0))
495
496 def get_item_color(self, n):
497 """Gets the nth color in the item color list, which are the colors used to draw the items
498 on the y-axis. Note that there are conceptually infinitely
499 many patterns because the patterns repeat -- that is, we just mod out by the size of the pattern
500 list when indexing."""
501 return self.item_clist[n % len(self.item_clist)]
502
503 def get_bar_pattern(self, n):
504 """Gets the nth pattern in the bar pattern list, which is a list of surfaces that are used to
505 fill in the bars. Note that there are conceptually infinitely
506 many patterns because the patterns repeat -- that is, we just mod out by the size of the pattern
507 list when indexing."""
508 return self.bar_plist[n % len(self.bar_plist)]
509
510class CairoCanvas(Canvas):
511 """This is a basic class that stores and draws on a Cairo surface,
512 using various primitives related to drawing a real-time graph (up-arrows,
513 down-arrows, bars, ...).
514
515 This is the lowest-level non-abstract representation
516 (aside perhaps from the Cairo surface itself) of a real-time graph.
517 It allows the user to draw primitives at certain locations, but for
518 the most part does not know anything about real-time scheduling,
519 just how to draw the basic parts that make up a schedule graph.
520 For that, see Graph or its descendants."""
521
522 #def __init__(self, fname, width, height, item_clist, bar_plist, surface):
523 # """Creates a new Canvas of dimensions (width, height). The
524 # parameters ``item_plist'' and ``bar_plist'' each specify a list
525 # of patterns to choose from when drawing the items on the y-axis
526 # or filling in bars, respectively."""
527
528 # super(CairoCanvas, self).__init__(fname, width, height, item_clist, bar_plist, surface)
529
530 #def clear(self):
531 # self.surface = self.SurfaceType(self.width, self.height, self.fname)
532 # self.whiteout()
533
534 def get_surface(self):
535 """Gets the Surface that we are drawing on in its current state."""
536 return self.surface
537
538 def _rect_common(self, x, y, width, height, color, thickness):
539 x, y, width, height = self.scaled(x, y, width, height)
540 x, y = self.surface.get_real_coor(x, y)
541 self.surface.ctx.rectangle(snap(x), snap(y), width, height)
542 self.surface.ctx.set_line_width(thickness * self.scale)
543 self.surface.ctx.set_source_rgb(color[0], color[1], color[2])
544
545 def draw_rect(self, x, y, width, height, color, thickness):
546 self._rect_common(x, y, width, height, color, thickness)
547 self.surface.ctx.stroke()
548
549 def fill_rect(self, x, y, width, height, color):
550 self._rect_common(x, y, width, height, color, 1)
551 self.surface.ctx.fill()
552
553 def fill_rect_fade(self, x, y, width, height, lcolor, rcolor):
554 """Draws a rectangle somewhere, filled in with the fade."""
555 x, y, width, height = self.scaled(x, y, width, height)
556 x, y = self.surface.get_real_coor(x, y)
557
558 linear = cairo.LinearGradient(snap(x), snap(y), \
559 snap(x + width), snap(y + height))
560 linear.add_color_stop_rgb(0.0, lcolor[0], lcolor[1], lcolor[2])
561 linear.add_color_stop_rgb(1.0, rcolor[0], rcolor[1], rcolor[2])
562 self.surface.ctx.set_source(linear)
563 self.surface.ctx.rectangle(snap(x), snap(y), width, height)
564 self.surface.ctx.fill()
565
566 def draw_line(self, p0, p1, color, thickness):
567 """Draws a line from p0 to p1 with a certain color and thickness."""
568 p0 = self.scaled(p0[0], p0[1])
569 p0 = self.surface.get_real_coor(p0[0], p0[1])
570 p1 = self.scaled(p1[0], p1[1])
571 p1 = self.surface.get_real_coor(p1[0], p1[1])
572 self.surface.ctx.move_to(p0[0], p0[1])
573 self.surface.ctx.line_to(p1[0], p1[1])
574 self.surface.ctx.set_source_rgb(color[0], color[1], color[2])
575 self.surface.ctx.set_line_width(thickness * self.scale)
576 self.surface.ctx.stroke()
577
578 def _polyline_common(self, coor_list, color, thickness):
579 real_coor_list = [self.surface.get_real_coor(coor[0], coor[1]) for coor in coor_list]
580 self.surface.ctx.move_to(real_coor_list[0][0], real_coor_list[0][1])
581 for coor in real_coor_list[1:]:
582 self.surface.ctx.line_to(coor[0], coor[1])
583
584 self.surface.ctx.set_line_width(thickness)
585 self.surface.ctx.set_source_rgb(color[0], color[1], color[2])
586
587 def draw_polyline(self, coor_list, color, thickness):
588 self._polyline_common(coor_list, color, thickness)
589 self.surface.ctx.stroke()
590
591 def fill_polyline(self, coor_list, color, thickness):
592 self._polyline_common(coor_list, color, thickness)
593 self.surface.ctx.fill()
594
595 def _draw_label_common(self, text, x, y, fopts, x_bearing_factor, \
596 f_descent_factor, width_factor, f_height_factor):
597 """Helper function for drawing a label with some alignment. Instead of taking in an alignment,
598 it takes in the scale factor for the font extent parameters, which give the raw data of how much to adjust
599 the x and y parameters. Only should be used internally."""
600 x, y = self.scaled(x, y)
601 x, y = self.surface.get_real_coor(x, y)
602
603 self.surface.ctx.set_source_rgb(0.0, 0.0, 0.0)
604
605 self.surface.ctx.select_font_face(fopts.name, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
606 self.surface.ctx.set_font_size(fopts.size)
607
608 fe = self.surface.ctx.font_extents()
609 f_ascent, f_descent, f_height = fe[:3]
610
611 te = self.surface.ctx.text_extents(text)
612 x_bearing, y_bearing, width, height = te[:4]
613
614 actual_x = x - x_bearing * x_bearing_factor - width * width_factor
615 actual_y = y - f_descent * f_descent_factor + f_height * f_height_factor
616
617 self.surface.ctx.set_source_rgb(fopts.color[0], fopts.color[1], fopts.color[2])
618
619 self.surface.ctx.move_to(snap(actual_x), snap(actual_y))
620
621 self.surface.ctx.show_text(text)
622
623 def draw_label(self, text, x, y, fopts=GraphFormat.DEF_FOPTS_LABEL, halign=AlignMode.LEFT, valign=AlignMode.BOTTOM):
624 """Draws a label with the given parameters, with the given horizontal and vertical justification. One can override
625 the color from ``fopts'' by passing something in to ``pattern'', which overrides the color with an arbitrary
626 pattern."""
627 x_bearing_factor, f_descent_factor, width_factor, f_height_factor = 0.0, 0.0, 0.0, 0.0
628 halign_factors = {AlignMode.LEFT : (0.0, 0.0), AlignMode.CENTER : (1.0, 0.5), AlignMode.RIGHT : (1.0, 1.0)}
629 if halign not in halign_factors:
630 raise ValueError('Invalid alignment value')
631 x_bearing_factor, width_factor = halign_factors[halign]
632
633 valign_factors = {AlignMode.BOTTOM : (0.0, 0.0), AlignMode.CENTER : (1.0, 0.5), AlignMode.TOP : (1.0, 1.0)}
634 if valign not in valign_factors:
635 raise ValueError('Invalid alignment value')
636 f_descent_factor, f_height_factor = valign_factors[valign]
637
638 self._draw_label_common(text, x, y, fopts, x_bearing_factor, \
639 f_descent_factor, width_factor, f_height_factor)
640
641 def draw_label_with_sscripts(self, text, supscript, subscript, x, y, \
642 textfopts=GraphFormat.DEF_FOPTS_LABEL, sscriptfopts=GraphFormat.DEF_FOPTS_LABEL_SSCRIPT, \
643 halign=AlignMode.LEFT, valign=AlignMode.BOTTOM):
644 """Draws a label, but also optionally allows a superscript and subscript to be rendered."""
645 self.draw_label(text, x, y, textfopts, halign, valign)
646
647 self.surface.ctx.set_source_rgb(0.0, 0.0, 0.0)
648 self.surface.ctx.select_font_face(textfopts.name, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
649 self.surface.ctx.set_font_size(textfopts.size)
650 te = self.surface.ctx.text_extents(text)
651 fe = self.surface.ctx.font_extents()
652 if supscript is not None:
653 f_height = fe[2]
654 x_advance = te[4]
655 xtmp = x + x_advance
656 ytmp = y
657 ytmp = y - f_height / 4.0
658 self.draw_label(supscript, xtmp, ytmp, sscriptfopts, halign, valign)
659 if subscript is not None:
660 f_height = fe[2]
661 x_advance = te[4]
662 xtmp = x + x_advance
663 ytmp = y
664 ytmp = y + f_height / 4.0
665 self.draw_label(subscript, xtmp, ytmp, sscriptfopts, halign, valign)
666
667# represents a selectable region of the graph
668class SelectableRegion(object):
669 def __init__(self, x, y, width, height, event):
670 self.x = x
671 self.y = y
672 self.width = width
673 self.height = height
674 self.event = event
675
676 def get_dimensions(self):
677 return (self.x, self.y, self.width, self.height)
678
679 def get_event(self):
680 return self.event
681
682 def contains(self, x, y):
683 return self.x <= x <= self.x + self.width and self.y <= y <= self.y + self.height
684
685class Graph(object):
686 DEF_BAR_PLIST = [Pattern([(0.0, 0.9, 0.9)]), Pattern([(0.9, 0.3, 0.0)]), Pattern([(0.9, 0.7, 0.0)]),
687 Pattern([(0.0, 0.0, 0.8)]), Pattern([(0.0, 0.2, 0.9)]), Pattern([(0.0, 0.6, 0.6)]),
688 Pattern([(0.75, 0.75, 0.75)])]
689 DEF_ITEM_CLIST = [(0.3, 0.0, 0.0), (0.0, 0.3, 0.0), (0.0, 0.0, 0.3), (0.3, 0.3, 0.0), (0.0, 0.3, 0.3),
690 (0.3, 0.0, 0.3)]
691
692 def __init__(self, CanvasType, surface, start_time, end_time, y_item_list, attrs=GraphFormat(),
693 item_clist=DEF_ITEM_CLIST, bar_plist=DEF_BAR_PLIST):
694 if start_time > end_time:
695 raise ValueError("Litmus is not a time machine")
696
697 self.attrs = attrs
698 self.start_time = start_time
699 self.end_time = end_time
700 self.y_item_list = y_item_list
701 self.num_maj = int(math.ceil((self.end_time - self.start_time) * 1.0 / self.attrs.time_per_maj)) + 1
702
703 width = self.num_maj * self.attrs.maj_sep + GraphFormat.X_AXIS_MEASURE_OFS + GraphFormat.WIDTH_PAD
704 height = (len(self.y_item_list) + 1) * self.attrs.y_item_size + GraphFormat.HEIGHT_PAD
705
706 # We need to stretch the width in order to fit the y-axis labels. To do this we need
707 # the extents information, but we haven't set up a surface yet, so we just use a
708 # temporary one.
709 extra_width = 0.0
710 dummy_surface = surface.__class__()
711 dummy_surface.renew(10, 10)
712
713 dummy_surface.ctx.select_font_face(self.attrs.item_fopts.name, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
714 dummy_surface.ctx.set_font_size(self.attrs.item_fopts.size)
715 for item in self.y_item_list:
716 dummy_surface.ctx.set_source_rgb(0.0, 0.0, 0.0)
717 te = dummy_surface.ctx.text_extents(item)
718 cur_width = te[2]
719 if cur_width > extra_width:
720 extra_width = cur_width
721
722 width += extra_width
723 self.origin = (extra_width + GraphFormat.WIDTH_PAD / 2.0, height - GraphFormat.HEIGHT_PAD / 2.0)
724
725 self.width = width
726 self.height = height
727
728 #if surface.ctx is None:
729 # surface.renew(width, height)
730
731 self.canvas = CanvasType(width, height, item_clist, bar_plist, surface)
732
733 def get_selected_regions(self, real_x, real_y):
734 return self.canvas.get_selected_regions(real_x, real_y)
735
736 def get_width(self):
737 return self.width
738
739 def get_height(self):
740 return self.height
741
742 def get_attrs(self):
743 return self.attrs
744
745 def update_view(self, x, y, width, height, ctx):
746 """Proxy into the surface's pan."""
747 self.canvas.surface.pan(x, y, width, height)
748 self.canvas.surface.change_ctx(ctx)
749
750 def _get_time_xpos(self, time):
751 """get x so that x is at instant ``time'' on the graph"""
752 return self.origin[0] + GraphFormat.X_AXIS_MEASURE_OFS + 1.0 * (time - self.start_time) / self.attrs.time_per_maj * self.attrs.maj_sep
753
754 def _get_item_ypos(self, item_no):
755 """get y so that y is where the top of a bar would be in item #n's area"""
756 return self.origin[1] - self._get_y_axis_height() + self.attrs.y_item_size * (item_no + 0.5 - GraphFormat.BAR_SIZE_FACTOR / 2.0)
757
758 def _get_bar_width(self, start_time, end_time):
759 return 1.0 * (end_time - start_time) / self.attrs.time_per_maj * self.attrs.maj_sep
760
761 def _get_bar_height(self):
762 return self.attrs.y_item_size * GraphFormat.BAR_SIZE_FACTOR
763
764 def _get_mini_bar_height(self):
765 return self.attrs.y_item_size * GraphFormat.MINI_BAR_SIZE_FACTOR
766
767 def _get_mini_bar_ofs(self):
768 return self.attrs.y_item_size * (GraphFormat.MINI_BAR_SIZE_FACTOR + GraphFormat.BAR_MINI_BAR_GAP_FACTOR)
769
770 def _get_y_axis_height(self):
771 return (len(self.y_item_list) + 1) * self.attrs.y_item_size
772
773 def _get_bottom_tick(self, time):
774 return int(math.floor((time - self.start_time) / self.attrs.time_per_maj))
775
776 def _get_top_tick(self, time):
777 return int(math.ceil((time - self.start_time) / self.attrs.time_per_maj))
778
779 def get_surface(self):
780 """Gets the underlying surface."""
781 return self.canvas.get_surface()
782
783 def xcoor_to_time(self, x):
784 #x = self.origin[0] + GraphFormat.X_AXIS_MEASURE_OFS + (time - self.start) / self.attrs.time_per_maj * self.attrs.maj_sep
785 return (x - self.origin[0] - GraphFormat.X_AXIS_MEASURE_OFS) / self.attrs.maj_sep \
786 * self.attrs.time_per_maj + self.start_time
787
788 def ycoor_to_item_no(self, y):
789 return int((y - self.origin[1] + self._get_y_axis_height()) // self.attrs.y_item_size)
790
791 def get_offset_params(self):
792 start_time = self.xcoor_to_time(self.canvas.surface.virt_x)
793 end_time = self.xcoor_to_time(self.canvas.surface.virt_x + self.canvas.surface.width)
794
795 start_item = self.ycoor_to_item_no(self.canvas.surface.virt_y)
796 end_item = 1 + self.ycoor_to_item_no(self.canvas.surface.virt_y + self.canvas.surface.height)
797
798 return (start_time, end_time, start_item, end_item)
799
800 def draw_skeleton(self, start_time, end_time, start_item, end_item):
801 self.draw_grid_at_time(start_time, end_time, start_item, end_item)
802 self.draw_x_axis_with_labels_at_time(start_time, end_time)
803 self.draw_y_axis_with_labels()
804
805 def render_surface(self, sched, list_type):
806 raise NotImplementedError
807
808 def render_all(self, schedule):
809 raise NotImplementedError
810
811 def render_events(self, event_list):
812 for layer in Canvas.LAYERS:
813 prev_events = {}
814 for event in event_list:
815 event.render(self, layer, prev_events)
816
817 def draw_axes(self, x_axis_label, y_axis_label):
818 """Draws and labels the axes according to the parameters that we were initialized
819 with."""
820 self.draw_grid_at_time(self.start_time, self.end_time, 0, len(self.attrs.y_item_list) - 1)
821
822 self.canvas.draw_x_axis(self.origin[0], self.origin[1], self.num_maj, self.attrs.maj_sep, self.attrs.min_per_maj)
823 self.canvas.draw_y_axis(self.origin[0], self.origin[1], self._get_y_axis_height())
824 self.canvas.draw_x_axis_labels(self.origin[0], self.origin[1], 0, self.num_maj - 1,\
825 self.attrs.maj_sep, self.attrs.min_per_maj, self.start_time, \
826 self.attrs.time_per_maj, self.attrs.show_min, self.attrs.majfopts, self.attrs.minfopts)
827 self.canvas.draw_y_axis_labels(self.origin[0], self.origin[1], self._get_y_axis_height(), self.y_item_list, \
828 self.attrs.y_item_size, self.attrs.item_fopts)
829
830 def draw_grid_at_time(self, start_time, end_time, start_item, end_item):
831 """Draws the grid, but only in a certain time and item range."""
832 start_tick = max(0, self._get_bottom_tick(start_time))
833 end_tick = min(self.num_maj - 1, self._get_top_tick(end_time))
834
835 start_item = max(0, start_item)
836 end_item = min(len(self.y_item_list), end_item)
837
838 self.canvas.draw_grid(self.origin[0], self.origin[1], self._get_y_axis_height(),
839 start_tick, end_tick, start_item, end_item, self.attrs.maj_sep, self.attrs.y_item_size, \
840 self.attrs.min_per_maj, True)
841
842 def draw_x_axis_with_labels_at_time(self, start_time, end_time):
843 start_tick = max(0, self._get_bottom_tick(start_time))
844 end_tick = min(self.num_maj - 1, self._get_top_tick(end_time))
845
846 self.canvas.draw_x_axis(self.origin[0], self.origin[1], start_tick, end_tick, \
847 self.attrs.maj_sep, self.attrs.min_per_maj)
848 self.canvas.draw_x_axis_labels(self.origin[0], self.origin[1], start_tick, \
849 end_tick, self.attrs.maj_sep, self.attrs.min_per_maj,
850 self.start_time + start_tick * self.attrs.time_per_maj,
851 self.attrs.time_per_maj, False)
852
853 def draw_y_axis_with_labels(self):
854 self.canvas.draw_y_axis(self.origin[0], self.origin[1], self._get_y_axis_height())
855 self.canvas.draw_y_axis_labels(self.origin[0], self.origin[1], self._get_y_axis_height(), \
856 self.y_item_list, self.attrs.y_item_size)
857
858 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False):
859 """Draws a suspension symbol for a certain task at an instant in time."""
860 raise NotImplementedError
861
862 def add_sel_suspend_triangle_at_time(self, time, task_no, cpu_no, event):
863 """Same as above, except instead of drawing adds a selectable region at
864 a certain time."""
865 raise NotImplementedError
866
867 def draw_resume_triangle_at_time(self, time, task_no, cpu_no, selected=False):
868 """Draws a resumption symbol for a certain task at an instant in time."""
869 raise NotImplementedError
870
871 def add_sel_resume_triangle_at_time(self, time, task_no, cpu_no, event):
872 """Same as above, except instead of drawing adds a selectable region at
873 a certain time."""
874 raise NotImplementedError
875
876 def draw_completion_marker_at_time(self, time, task_no, cpu_no, selected=False):
877 """Draws a completion marker for a certain task at an instant in time."""
878 raise NotImplementedError
879
880 def add_sel_completion_marker_at_time(self, time, task_no, cpu_no, event):
881 """Same as above, except instead of drawing adds a selectable region at
882 a certain time."""
883 raise NotImplementedError
884
885 def draw_release_arrow_at_time(self, time, task_no, job_no, selected=False):
886 """Draws a release arrow at a certain time for some task and job"""
887 raise NotImplementedError
888
889 def add_sel_release_arrow_at_time(self, time, task_no, event):
890 """Same as above, except instead of drawing adds a selectable region at
891 a certain time."""
892 raise NotImplementedError
893
894 def draw_deadline_arrow_at_time(self, time, task_no, job_no, selected=False):
895 """Draws a deadline arrow at a certain time for some task and job"""
896 raise NotImplementedError
897
898 def add_sel_deadline_arrow_at_time(self, time, task_no, event):
899 """Same as above, except instead of drawing adds a selectable region at
900 a certain time."""
901 raise NotImplementedError
902
903 def draw_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None):
904 """Draws a bar over a certain time period for some task, optionally labelling it."""
905 raise NotImplementedError
906
907 def add_sel_bar_at_time(self, start_time, end_time, task_no, cpu_no, event):
908 """Same as above, except instead of drawing adds a selectable region at
909 a certain time."""
910 raise NotImplementedError
911
912 def draw_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None):
913 """Draws a mini bar over a certain time period for some task, optionally labelling it."""
914 raise NotImplementedError
915
916 def add_sel_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, event):
917 """Same as above, except instead of drawing adds a selectable region at
918 a certain time."""
919 raise NotImplementedError
920
921class TaskGraph(Graph):
922 def render_surface(self, sched):
923 self.canvas.whiteout()
924 self.canvas.clear_selectable_regions()
925
926 start_time, end_time, start_item, end_item = self.get_offset_params()
927
928 self.draw_skeleton(start_time, end_time, start_item, end_item)
929
930 for layer in Canvas.LAYERS:
931 prev_events = {}
932 for event in sched.get_time_slot_array().iter_over_period(
933 start_time, end_time, start_item, end_item,
934 schedule.TimeSlotArray.TASK_LIST, schedule.EVENT_LIST):
935 event.render(self, layer, prev_events)
936
937 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False):
938 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR
939 x = self._get_time_xpos(time)
940 y = self._get_item_ypos(task_no) + self._get_bar_height() / 2.0 - height / 2.0
941 self.canvas.draw_suspend_triangle(x, y, height, selected)
942
943 def add_sel_suspend_triangle_at_time(self, time, task_no, cpu_no, event):
944 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR
945 x = self._get_time_xpos(time)
946 y = self._get_item_ypos(task_no) + self._get_bar_height() / 2.0 - height / 2.0
947
948 self.canvas.add_sel_suspend_triangle(x, y, height, event)
949
950 def draw_resume_triangle_at_time(self, time, task_no, cpu_no, selected=False):
951 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR
952 x = self._get_time_xpos(time)
953 y = self._get_item_ypos(task_no) + self._get_bar_height() / 2.0 - height / 2.0
954
955 self.canvas.draw_resume_triangle(x, y, height, selected)
956
957 def add_sel_resume_triangle_at_time(self, time, task_no, cpu_no, event):
958 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR
959 x = self._get_time_xpos(time)
960 y = self._get_item_ypos(task_no) + self._get_bar_height() / 2.0 - height / 2.0
961
962 self.canvas.add_sel_resume_triangle(x, y, height, event)
963
964 def draw_completion_marker_at_time(self, time, task_no, cpu_no, selected=False):
965 height = self._get_bar_height() * GraphFormat.COMPLETION_MARKER_FACTOR
966 x = self._get_time_xpos(time)
967 y = self._get_item_ypos(task_no) + self._get_bar_height() - height
968
969 self.canvas.draw_completion_marker(x, y, height, selected)
970
971 def add_sel_completion_marker_at_time(self, time, task_no, cpu_no, event):
972 height = self._get_bar_height() * GraphFormat.COMPLETION_MARKER_FACTOR
973
974 x = self._get_time_xpos(time)
975 y = self._get_item_ypos(task_no) + self._get_bar_height() - height
976
977 self.canvas.add_sel_completion_marker(x, y, height, event)
978
979 def draw_release_arrow_at_time(self, time, task_no, job_no=None, selected=False):
980 height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR
981
982 x = self._get_time_xpos(time)
983 y = self._get_item_ypos(task_no) + self._get_bar_height() - height
984
985 self.canvas.draw_release_arrow_big(x, y, height, selected)
986
987 def add_sel_release_arrow_at_time(self, time, task_no, event):
988 height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR
989
990 x = self._get_time_xpos(time)
991 y = self._get_item_ypos(task_no) + self._get_bar_height() - height
992
993 self.canvas.add_sel_release_arrow_big(x, y, height, event)
994
995 def draw_deadline_arrow_at_time(self, time, task_no, job_no=None, selected=False):
996 height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR
997
998 x = self._get_time_xpos(time)
999 y = self._get_item_ypos(task_no)
1000
1001 self.canvas.draw_deadline_arrow_big(x, y, height, selected)
1002
1003 def add_sel_deadline_arrow_at_time(self, time, task_no, event):
1004 height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR
1005
1006 x = self._get_time_xpos(time)
1007 y = self._get_item_ypos(task_no)
1008
1009 self.canvas.add_sel_deadline_arrow_big(x, y, height, event)
1010
1011 def draw_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, selected=False):
1012 if start_time > end_time:
1013 raise ValueError("Litmus is not a time machine")
1014
1015 x = self._get_time_xpos(start_time)
1016 y = self._get_item_ypos(task_no)
1017 width = self._get_bar_width(start_time, end_time)
1018 height = self._get_bar_height()
1019
1020 self.canvas.draw_bar(x, y, width, height, cpu_no, selected)
1021
1022 # if a job number is specified, we want to draw a superscript and subscript for the task and job number, respectively
1023 if job_no is not None:
1024 x += GraphFormat.BAR_LABEL_OFS
1025 y += self.attrs.y_item_size * GraphFormat.BAR_SIZE_FACTOR / 2.0
1026 self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \
1027 GraphFormat.DEF_FOPTS_BAR, GraphFormat.DEF_FOPTS_BAR_SSCRIPT, AlignMode.LEFT, AlignMode.CENTER)
1028
1029 def add_sel_bar_at_time(self, start_time, end_time, task_no, cpu_no, event):
1030 if start_time > end_time:
1031 raise ValueError("Litmus is not a time machine")
1032
1033 x = self._get_time_xpos(start_time)
1034 y = self._get_item_ypos(task_no)
1035 width = self._get_bar_width(start_time, end_time)
1036 height = self._get_bar_height()
1037
1038 self.canvas.add_sel_bar(x, y, width, height, event)
1039
1040 def draw_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, selected=False):
1041 if start_time > end_time:
1042 raise ValueError("Litmus is not a time machine")
1043
1044 x = self._get_time_xpos(start_time)
1045 y = self._get_item_ypos(task_no) - self._get_mini_bar_ofs()
1046 width = self._get_bar_width(start_time, end_time)
1047 height = self._get_mini_bar_height()
1048
1049 self.canvas.draw_mini_bar(x, y, width, height, cpu_no, selected)
1050
1051 if job_no is not None:
1052 x += GraphFormat.MINI_BAR_LABEL_OFS
1053 y += self.attrs.y_item_size * GraphFormat.MINI_BAR_SIZE_FACTOR / 2.0
1054 self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \
1055 GraphFormat.DEF_FOPTS_MINI_BAR, GraphFormat.DEF_FOPTS_MINI_BAR_SSCRIPT, AlignMode.LEFT, AlignMode.CENTER)
1056
1057 def add_sel_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, event):
1058 x = self._get_time_xpos(start_time)
1059 y = self._get_item_ypos(task_no) - self._get_mini_bar_ofs()
1060 width = self._get_bar_width(start_time, end_time)
1061 height = self._get_mini_bar_height()
1062
1063 self.canvas.add_sel_mini_bar(x, y, width, height, event)
1064
1065class CpuGraph(Graph):
1066 def render_surface(self, sched):
1067 self.canvas.whiteout()
1068 self.canvas.clear_selectable_regions()
1069
1070 start_time, end_time, start_item, end_item = self.get_offset_params()
1071
1072 self.draw_skeleton(start_time, end_time, start_item, end_item)
1073
1074 event_list = dict(schedule.EVENT_LIST)
1075
1076 del event_list[schedule.ReleaseEvent]
1077 del event_list[schedule.DeadlineEvent]
1078
1079 for layer in Canvas.LAYERS:
1080 prev_events = {}
1081 for event in sched.get_time_slot_array().iter_over_period(
1082 start_time, end_time, start_item, end_item,
1083 schedule.TimeSlotArray.CPU_LIST, schedule.EVENT_LIST):
1084 event.render(self, layer, prev_events)
1085
1086 if end_item >= len(self.y_item_list):
1087 # we are far down enough that we should render the releases and deadlines
1088 for layer in Canvas.LAYERS:
1089 prev_events = {}
1090 for event in sched.get_time_slot_array().iter_over_period(
1091 start_time, end_time, start_item, end_item,
1092 schedule.TimeSlotArray.CPU_LIST,
1093 (schedule.ReleaseEvent, schedule.DeadlineEvent)):
1094 event.render(self, layer, prev_events)
1095
1096 def render(self, schedule, start_time=None, end_time=None):
1097 if end_time < start_time:
1098 raise ValueError('start must be less than end')
1099
1100 if start_time is None:
1101 start_time = self.start
1102 if end_time is None:
1103 end_time = self.end
1104 start_slot = self.get_time_slot(start_time)
1105 end_slot = min(len(self.time_slots), self.get_time_slot(end_time) + 1)
1106
1107 for layer in Canvas.LAYERS:
1108 prev_events = {}
1109 for i in range(start_slot, end_slot):
1110 for event in self.time_slots[i]:
1111 event.render(graph, layer, prev_events)
1112
1113 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False):
1114 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR
1115 x = self._get_time_xpos(time)
1116 y = self._get_item_ypos(cpu_no) + self._get_bar_height() / 2.0 - height / 2.0
1117 self.canvas.draw_suspend_triangle(x, y, height, selected)
1118
1119 def add_sel_suspend_triangle_at_time(self, time, task_no, cpu_no, event):
1120 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR
1121 x = self._get_time_xpos(time)
1122 y = self._get_item_ypos(cpu_no) + self._get_bar_height() / 2.0 - height / 2.0
1123
1124 self.canvas.add_sel_suspend_triangle(x, y, height, event)
1125
1126 def draw_resume_triangle_at_time(self, time, task_no, cpu_no, selected=False):
1127 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR
1128 x = self._get_time_xpos(time)
1129 y = self._get_item_ypos(cpu_no) + self._get_bar_height() / 2.0 - height / 2.0
1130
1131 self.canvas.draw_resume_triangle(x, y, height, selected)
1132
1133 def add_sel_resume_triangle_at_time(self, time, task_no, cpu_no, event):
1134 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR
1135 x = self._get_time_xpos(time)
1136 y = self._get_item_ypos(cpu_no) + self._get_bar_height() / 2.0 - height / 2.0
1137
1138 self.canvas.add_sel_suspend_triangle(x, y, height, event)
1139
1140 def draw_completion_marker_at_time(self, time, task_no, cpu_no, selected=False):
1141 height = self._get_bar_height() * GraphFormat.COMPLETION_MARKER_FACTOR
1142 x = self._get_time_xpos(time)
1143 y = self._get_item_ypos(cpu_no) + self._get_bar_height() - height
1144
1145 self.canvas.draw_completion_marker(x, y, height, selected)
1146
1147 def add_sel_completion_marker_at_time(self, time, task_no, cpu_no, event):
1148 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR
1149 x = self._get_time_xpos(time)
1150 y = self._get_item_ypos(cpu_no) + self._get_bar_height() / 2.0 - height / 2.0
1151
1152 self.canvas.add_sel_completion_marker(x, y, height, event)
1153
1154 def draw_release_arrow_at_time(self, time, task_no, job_no=None, selected=False):
1155 if job_no is None and task_no is not None:
1156 raise ValueError("Must specify a job number along with the task number")
1157
1158 height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR
1159
1160 x = self._get_time_xpos(time)
1161 y = self.origin[1] - height
1162
1163 self.canvas.draw_release_arrow_small(x, y, height, selected)
1164
1165 if task_no is not None:
1166 y -= GraphFormat.ARROW_LABEL_OFS
1167 self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \
1168 GraphFormat.DEF_FOPTS_ARROW, GraphFormat.DEF_FOPTS_ARROW_SSCRIPT, \
1169 AlignMode.CENTER, AlignMode.BOTTOM)
1170
1171 def add_sel_release_arrow_at_time(self, time, task_no, event):
1172 height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR
1173
1174 x = self._get_time_xpos(time)
1175 y = self.origin[1] - height
1176
1177 self.canvas.add_sel_release_arrow_small(x, y, height, event)
1178
1179 def draw_deadline_arrow_at_time(self, time, task_no, job_no=None, selected=False):
1180 if job_no is None and task_no is not None:
1181 raise ValueError("Must specify a job number along with the task number")
1182
1183 height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR
1184
1185 x = self._get_time_xpos(time)
1186 y = self.origin[1] - height
1187
1188 self.canvas.draw_deadline_arrow_small(x, y, height, selected)
1189
1190 if task_no is not None:
1191 y -= GraphFormat.ARROW_LABEL_OFS
1192 self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \
1193 GraphFormat.DEF_FOPTS_ARROW, GraphFormat.DEF_FOPTS_ARROW_SSCRIPT, \
1194 AlignMode.CENTER, AlignMode.BOTTOM)
1195
1196 def add_sel_deadline_arrow_at_time(self, time, task_no, event):
1197 height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR
1198
1199 x = self._get_time_xpos(time)
1200 y = self.origin[1] - height
1201
1202 self.canvas.add_sel_deadline_arrow_small(x, y, height, event)
1203
1204 def draw_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, selected=False):
1205 if start_time > end_time:
1206 raise ValueError("Litmus is not a time machine")
1207
1208 x = self._get_time_xpos(start_time)
1209 y = self._get_item_ypos(cpu_no)
1210 width = self._get_bar_width(start_time, end_time)
1211 height = self._get_bar_height()
1212
1213 self.canvas.draw_bar(x, y, width, height, task_no, selected)
1214
1215 # if a job number is specified, we want to draw a superscript and subscript for the task and job number, respectively
1216 if job_no is not None:
1217 x += GraphFormat.BAR_LABEL_OFS
1218 y += self.attrs.y_item_size * GraphFormat.BAR_SIZE_FACTOR / 2.0
1219 self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \
1220 GraphFormat.DEF_FOPTS_BAR, GraphFormat.DEF_FOPTS_BAR_SSCRIPT, \
1221 AlignMode.LEFT, AlignMode.CENTER)
1222
1223 def add_sel_bar_at_time(self, start_time, end_time, task_no, cpu_no, event):
1224 x = self._get_time_xpos(start_time)
1225 y = self._get_item_ypos(cpu_no)
1226 width = self._get_bar_width(start_time, end_time)
1227 height = self._get_bar_height()
1228
1229 self.canvas.add_sel_region(SelectableRegion(x, y, width, height, event))
1230
1231 def draw_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, selected=False):
1232 if start_time > end_time:
1233 raise ValueError("Litmus is not a time machine")
1234
1235 x = self._get_time_xpos(start_time)
1236 y = self._get_item_ypos(cpu_no) - self._get_mini_bar_ofs()
1237 width = self._get_bar_width(start_time, end_time)
1238 height = self._get_mini_bar_height()
1239
1240 self.canvas.draw_mini_bar(x, y, width, height, cpu_no, selected)
1241
1242 if job_no is not None:
1243 x += GraphFormat.MINI_BAR_LABEL_OFS
1244 y += self.attrs.y_item_size * GraphFormat.MINI_BAR_SIZE_FACTOR / 2.0
1245 self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \
1246 GraphFormat.DEF_FOPTS_MINI_BAR, GraphFormat.DEF_FOPTS_MINI_BAR_SSCRIPT, AlignMode.LEFT, AlignMode.CENTER)
1247
1248 def add_sel_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, event):
1249 x = self._get_time_xpos(start_time)
1250 y = self._get_item_ypos(cpu_no) - self._get_mini_bar_ofs()
1251 width = self._get_bar_width(start_time, end_time)
1252 height = self._get_mini_bar_height()
1253
1254 self.canvas.add_sel_mini_bar(x, y, width, height, cpu_no, selected)