# Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2.0, # as published by the Free Software Foundation. # # This program is also distributed with certain software (including # but not limited to OpenSSL) that is licensed under separate terms, as # designated in a particular file or component or in included license # documentation. The authors of MySQL hereby grant you an additional # permission to link the program and your derivative works with the # separately licensed software that they have included with MySQL. # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See # the GNU General Public License, version 2.0, for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import cairo import cairo_utils from cairo_utils import Context, ImageSurface, Pattern from cairo_utils import VBoxNode, TextRectangle, intersect_lines, draw_arrow_head class TreeNode(VBoxNode): def __init__(self, parent, name, title, data): VBoxNode.__init__(self) self.padding = (0, 0, 0, 0) self.expand_to_fill = False self.name = name self.parent = parent self.data = data self.children = [] self.is_step_list = False if title is not None: self.content = TextRectangle(title) self.content.set_fill_color(0.83137254901960789, 0.92941176470588238, 0.99215686274509807, 1) self.items.append(self.content) self.extra_bottom_space = 0 def __repr__(self): return "<node %s>"%self.content.text def process(self): for ch in self.children: ch.process() self.calc() def render(self, c): VBoxNode.render(self, c) self.render_shadow(c) def minimal_space_required_space_for_ref_to(self, c, ch): return 0 def layout(self, c, x, y): self.calc(c) self.pos = (x, y) class TreeStepNode(TreeNode): def __init__(self, parent, name, title, data): TreeNode.__init__(self, parent, name, title, data) self.set_fill_color(0.5, 0.7, 0.5, 1) self.set_color(1, 1, 1, 1) class StepListNode(TreeNode): def __init__(self, parent, steps): TreeNode.__init__(self, parent, "steps", None, steps) def layout(self, c, x, y): self.calc(c) for ch in self.children: ch.layout(c, x, y) y += ch.size[1] + 40 def render(self, c): for ch in self.children: ch.render(c) def process(self): for step in self.data: name, value = list(step.items())[0] print("#"*20,name, value) node = TreeStepNode(self, name, name, value) self.children.append(node) class TreeRootNode(StepListNode): def __init__(self, steps): StepListNode.__init__(self, None, steps["steps"]) class TableDependency(TreeNode): def __init__(self, parent, data): title = "%s.%s" % (data["database"], data["table"]) TreeNode.__init__(self, parent, "TableDependency", title, data) # Layouter class TreeLayouter: def __init__(self, root): self.root = root self.yspacing = 30 self.xspacing = 50 def layout(self, ctx, node, x = 0, y = 0): t, l, b, r = node.padding w, h = node.size twidth = 0 theight = 0 # calc total height occupied by children if node.is_step_list: for step in node.children: step.pos = x, y if node.name == "join_optimization": print("put ", step.name, x + self.xspacing) if step.is_step_list: sw, sh = self.layout(ctx, step, x, y) else: sw, sh = self.layout_tree(ctx, step, x + step.size[0] + self.xspacing, y) step.gsize = (sw, sh) y += max(step.size[1], sh) + self.yspacing theight += sh + self.yspacing twidth = max(sw, twidth) twidth = max(w, twidth) else: twidth, theight = self.layout_tree(ctx, node, x, y) theight += h return twidth, theight def layout_tree(self, ctx, node, x, y): w, h = node.size twidth = 0 theight = h xx = x for ch in node.children: sw, sh = self.layout_tree(ctx, ch, xx, y + h + self.yspacing) xx += sw + self.xspacing twidth += sw theight = max(sh, theight) node.pos = x, y return max(twidth, w), theight + self.yspacing def adjust_child_layout(self, node, offset): for ch in node.children: cx, cy = ch.pos ch.pos = int(cx + offset), int(cy) self.adjust_child_layout(ch, offset) def get_total_size(self, ctx): def calc_all(c, node): node.calc(c) for ch in node.children: calc_all(c, ch) calc_all(ctx, self.root) return self.layout(ctx, self.root, 0, 0) def render(self, c, x, y): def calc_all(c, node): node.calc(c) for ch in node.children: calc_all(c, ch) calc_all(c, self.root) self.layout(c, self.root, x, y) self.do_render(c, self.root) self.do_render_lines(c, self.root) def do_render_lines(self, c, node): if node.is_step_list: if node.children: prev = node.children[0] node.stroke_line_from_node(c, prev) for ch in node.children[1:]: prev.stroke_line_from_node(c, ch) prev = ch else: for ch in node.children: ch.stroke_line_from_node(c, node) for ch in node.children: self.do_render_lines(c, ch) def do_render(self, c, node): node.render(c) for ch in node.children: self.do_render(c, ch) def decode_json(text): return eval(text, {"false":False, "true":True}) def process_node(parent, name, data): if type(data) is not dict or name in ["depends_on_map_bits"]: return None oper = name node = TreeNode(parent, name, oper, data) return node def tree_from_json(parent, name, json): node = process_node(parent, name, json) if not node: return None for key, value in list(json.items()): if type(value) is dict: ch = tree_from_json(node, key, value) if ch: node.children.append(ch) elif type(value) is list: if key == "steps": previous = None node.is_step_list = True for item in value: ch = tree_from_json(node, "step", item) if ch.children: # remove useless intermediate step node ch = ch.children[0] node.children.append(ch) ch.set_fill_color(0.8, 0.5, 0.5, 1) ch.previous = previous if previous: previous.next = ch previous = ch if key == "table_dependencies": interm = TreeNode(node, key, key, value) node.children.append(interm) for item in value: ch = TableDependency(interm, item) interm.children.append(ch) else: interm = TreeNode(node, key, key, {}) node.children.append(interm) for item in value: ch = tree_from_json(interm, key+" item", item) if ch: interm.children.append(ch) return node def render_json_data(json_text, background_image, png_file): padding = 50 json = decode_json(json_text) tree = TreeRootNode(json) tree.process() layout = TreeLayouter(tree) img = ImageSurface(cairo.CAIRO_FORMAT_ARGB32, 10, 100) ctx = Context(img) w, h = 500, 500 img = ImageSurface(cairo.CAIRO_FORMAT_ARGB32, w+padding*2, h+padding*2) ctx = Context(img) ctx.set_font_size(12) if background_image: bgimage = ImageSurface.from_png(background_image) else: bgimage = None if bgimage and bgimage.status() == cairo.CAIRO_STATUS_SUCCESS: ctx.save() pat = Pattern(bgimage) pat.set_extend(cairo.CAIRO_EXTEND_REPEAT) ctx.set_source(pat) ctx.paint() ctx.restore() else: ctx.set_source_rgb(1,1,1) ctx.paint() #layout.render(ctx, 0, 0) tree.layout(ctx, 0, 0) tree.render(ctx) img.write_to_png(png_file) return w+padding*2, h+padding*2 if __name__ == "__main__": import sys f = sys.argv[1] render_json_data(open(f).read(), None, f+".png")