diff options
Diffstat (limited to 'viz/draw.py')
| -rw-r--r-- | viz/draw.py | 1254 |
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 | |||
| 3 | import math | ||
| 4 | import cairo | ||
| 5 | import os | ||
| 6 | |||
| 7 | import util | ||
| 8 | import schedule | ||
| 9 | from format import * | ||
| 10 | |||
| 11 | def 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 | |||
| 20 | class 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 | |||
| 62 | class 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 | |||
| 72 | class 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 | |||
| 85 | class 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 | |||
| 115 | class 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 | |||
| 510 | class 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 | ||
| 668 | class 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 | |||
| 685 | class 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 | |||
| 921 | class 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 | |||
| 1065 | class 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) | ||
