def plot(self, svg: Drawing) -> None: recolor: Optional[str] = None if isinstance(self.color, list): linear_gradient: LinearGradient = svg.linearGradient( self.map_((0, self.max_y)), self.map_((0, 1)), gradientUnits="userSpaceOnUse", ) for index, color in enumerate(self.color): linear_gradient.add_stop_color( index / (len(self.color) - 1), color.hex ) gradient: LinearGradient = svg.defs.add(linear_gradient) recolor = gradient.get_funciri() if isinstance(self.color, Color): recolor = self.color.hex svg.add( svg.rect( insert=(0, 0), size=("100%", "100%"), rx=None, ry=None, fill=self.background_color.hex, ) ) self.draw_grid(svg) last_text_y = 0 for xs, ys, color, title in self.data: if recolor: color = recolor assert len(xs) == len(ys) xs_second: list[float] = [ (x - self.min_x).total_seconds() for x in xs ] points = [] for index, x in enumerate(xs_second): y = ys[index] mapped: np.ndarray = map_array( np.array((x, y)), np.array((self.min_x_second, self.max_y)), np.array((self.max_x_second, self.min_y)), self.canvas.workspace[0], self.canvas.workspace[1], ) points.append(mapped) previous_point: Optional[np.ndarray] = None for point in points: if previous_point is not None: line: Line = svg.line( (previous_point[0], previous_point[1]), (point[0], point[1]), stroke=self.background_color.hex, stroke_width=6, ) svg.add(line) line: Line = svg.line( (previous_point[0], previous_point[1]), (point[0], point[1]), stroke=color, stroke_width=2, ) svg.add(line) previous_point = point for point in points: svg.add( svg.circle( (point[0], point[1]), 5.5, fill=self.background_color.hex, ) ) svg.add(svg.circle((point[0], point[1]), 3.5, fill=color)) title: str text_y = max(last_text_y + 20, point[1] + 6) self.text(svg, (point[0] + 15, text_y), title.upper(), color) last_text_y = text_y with Path(svg.filename).open("w+") as output_file: svg.write(output_file)
def disvg(paths=None, colors=None, filename=os_path.join(getcwd(), 'disvg_output.svg'), stroke_widths=None, nodes=None, node_colors=None, node_radii=None, openinbrowser=True, timestamp=False, margin_size=0.1, mindim=600, dimensions=None, viewbox=None, text=None, text_path=None, font_size=None, attributes=None, svg_attributes=None, svgwrite_debug=False): """Takes in a list of paths and creates an SVG file containing said paths. REQUIRED INPUTS: :param paths - a list of paths OPTIONAL INPUT: :param colors - specifies the path stroke color. By default all paths will be black (#000000). This paramater can be input in a few ways 1) a list of strings that will be input into the path elements stroke attribute (so anything that is understood by the svg viewer). 2) a string of single character colors -- e.g. setting colors='rrr' is equivalent to setting colors=['red', 'red', 'red'] (see the 'color_dict' dictionary above for a list of possibilities). 3) a list of rgb 3-tuples -- e.g. colors = [(255, 0, 0), ...]. :param filename - the desired location/filename of the SVG file created (by default the SVG will be stored in the current working directory and named 'disvg_output.svg'). :param stroke_widths - a list of stroke_widths to use for paths (default is 0.5% of the SVG's width or length) :param nodes - a list of points to draw as filled-in circles :param node_colors - a list of colors to use for the nodes (by default nodes will be red) :param node_radii - a list of radii to use for the nodes (by default nodes will be radius will be 1 percent of the svg's width/length) :param text - string or list of strings to be displayed :param text_path - if text is a list, then this should be a list of path (or path segments of the same length. Note: the path must be long enough to display the text or the text will be cropped by the svg viewer. :param font_size - a single float of list of floats. :param openinbrowser - Set to True to automatically open the created SVG in the user's default web browser. :param timestamp - if True, then the a timestamp will be appended to the output SVG's filename. This will fix issues with rapidly opening multiple SVGs in your browser. :param margin_size - The min margin (empty area framing the collection of paths) size used for creating the canvas and background of the SVG. :param mindim - The minimum dimension (height or width) of the output SVG (default is 600). :param dimensions - The display dimensions of the output SVG. Using this will override the mindim parameter. :param viewbox - This specifies what rectangular patch of R^2 will be viewable through the outputSVG. It should be input in the form (min_x, min_y, width, height). This is different from the display dimension of the svg, which can be set through mindim or dimensions. :param attributes - a list of dictionaries of attributes for the input paths. Note: This will override any other conflicting settings. :param svg_attributes - a dictionary of attributes for output svg. :param svgwrite_debug - This parameter turns on/off `svgwrite`'s debugging mode. By default svgwrite_debug=False. This increases speed and also prevents `svgwrite` from raising of an error when not all `svg_attributes` key-value pairs are understood. NOTES: * The `svg_attributes` parameter will override any other conflicting settings. * Any `extra` parameters that `svgwrite.Drawing()` accepts can be controlled by passing them in through `svg_attributes`. * The unit of length here is assumed to be pixels in all variables. * If this function is used multiple times in quick succession to display multiple SVGs (all using the default filename), the svgviewer/browser will likely fail to load some of the SVGs in time. To fix this, use the timestamp attribute, or give the files unique names, or use a pause command (e.g. time.sleep(1)) between uses. """ _default_relative_node_radius = 5e-3 _default_relative_stroke_width = 1e-3 _default_path_color = '#000000' # black _default_node_color = '#ff0000' # red _default_font_size = 12 if isinstance(filename, str): # append directory to filename (if not included) if os_path.dirname(filename) == '': filename = os_path.join(getcwd(), filename) # append time stamp to filename if timestamp: fbname, fext = os_path.splitext(filename) dirname = os_path.dirname(filename) tstamp = str(time()).replace('.', '') stfilename = os_path.split(fbname)[1] + '_' + tstamp + fext filename = os_path.join(dirname, stfilename) # check paths and colors are set if isinstance(paths, Path) or is_path_segment(paths): paths = [paths] if paths: if not colors: colors = [_default_path_color] * len(paths) else: assert len(colors) == len(paths) if isinstance(colors, str): colors = str2colorlist(colors, default_color=_default_path_color) elif isinstance(colors, list): for idx, c in enumerate(colors): if is3tuple(c): colors[idx] = "rgb" + str(c) # check nodes and nodes_colors are set (node_radii are set later) if nodes: if not node_colors: node_colors = [_default_node_color] * len(nodes) else: assert len(node_colors) == len(nodes) if isinstance(node_colors, str): node_colors = str2colorlist(node_colors, default_color=_default_node_color) elif isinstance(node_colors, list): for idx, c in enumerate(node_colors): if is3tuple(c): node_colors[idx] = "rgb" + str(c) # set up the viewBox and display dimensions of the output SVG # along the way, set stroke_widths and node_radii if not provided assert paths or nodes stuff2bound = [] if viewbox: szx, szy = viewbox[2:4] else: if paths: stuff2bound += paths if nodes: stuff2bound += nodes if text_path: stuff2bound += text_path xmin, xmax, ymin, ymax = big_bounding_box(stuff2bound) dx = xmax - xmin dy = ymax - ymin if dx == 0: dx = 1 if dy == 0: dy = 1 # determine stroke_widths to use (if not provided) and max_stroke_width if paths: if not stroke_widths: sw = max(dx, dy) * _default_relative_stroke_width stroke_widths = [sw]*len(paths) max_stroke_width = sw else: assert len(paths) == len(stroke_widths) max_stroke_width = max(stroke_widths) else: max_stroke_width = 0 # determine node_radii to use (if not provided) and max_node_diameter if nodes: if not node_radii: r = max(dx, dy) * _default_relative_node_radius node_radii = [r]*len(nodes) max_node_diameter = 2*r else: assert len(nodes) == len(node_radii) max_node_diameter = 2*max(node_radii) else: max_node_diameter = 0 extra_space_for_style = max(max_stroke_width, max_node_diameter) xmin -= margin_size*dx + extra_space_for_style/2 ymin -= margin_size*dy + extra_space_for_style/2 dx += 2*margin_size*dx + extra_space_for_style dy += 2*margin_size*dy + extra_space_for_style viewbox = "%s %s %s %s" % (xmin, ymin, dx, dy) if dimensions: szx, szy = dimensions else: if dx > dy: szx = str(mindim) + 'px' szy = str(int(ceil(mindim * dy / dx))) + 'px' else: szx = str(int(ceil(mindim * dx / dy))) + 'px' szy = str(mindim) + 'px' # Create an SVG file if svg_attributes is not None: szx = svg_attributes.get("width", szx) szy = svg_attributes.get("height", szy) debug = svg_attributes.get("debug", svgwrite_debug) dwg = Drawing(filename=filename, size=(szx, szy), debug=debug, **svg_attributes) else: dwg = Drawing(filename=filename, size=(szx, szy), debug=svgwrite_debug, viewBox=viewbox) # add paths if paths: for i, p in enumerate(paths): if isinstance(p, Path): ps = p.d() elif is_path_segment(p): ps = Path(p).d() else: # assume this path, p, was input as a Path d-string ps = p if attributes: good_attribs = {'d': ps} for key in attributes[i]: val = attributes[i][key] if key != 'd': try: dwg.path(ps, **{key: val}) good_attribs.update({key: val}) except Exception as e: warn(str(e)) dwg.add(dwg.path(**good_attribs)) else: dwg.add(dwg.path(ps, stroke=colors[i], stroke_width=str(stroke_widths[i]), fill='none')) # add nodes (filled in circles) if nodes: for i_pt, pt in enumerate([(z.real, z.imag) for z in nodes]): dwg.add(dwg.circle(pt, node_radii[i_pt], fill=node_colors[i_pt])) # add texts if text: assert isinstance(text, str) or (isinstance(text, list) and isinstance(text_path, list) and len(text_path) == len(text)) if isinstance(text, str): text = [text] if not font_size: font_size = [_default_font_size] if not text_path: pos = complex(xmin + margin_size*dx, ymin + margin_size*dy) text_path = [Line(pos, pos + 1).d()] else: if font_size: if isinstance(font_size, list): assert len(font_size) == len(text) else: font_size = [font_size] * len(text) else: font_size = [_default_font_size] * len(text) for idx, s in enumerate(text): p = text_path[idx] if isinstance(p, Path): ps = p.d() elif is_path_segment(p): ps = Path(p).d() else: # assume this path, p, was input as a Path d-string ps = p # paragraph = dwg.add(dwg.g(font_size=font_size[idx])) # paragraph.add(dwg.textPath(ps, s)) pathid = 'tp' + str(idx) dwg.defs.add(dwg.path(d=ps, id=pathid)) txter = dwg.add(dwg.text('', font_size=font_size[idx])) txter.add(txt.TextPath('#'+pathid, s)) # save svg if isinstance(filename, str): if not os_path.exists(os_path.dirname(filename)): makedirs(os_path.dirname(filename)) dwg.save() else: dwg.write(filename, pretty=False) if isinstance(filename, str): # re-open the svg, make the xml pretty, and save it again xmlstring = md_xml_parse(filename).toprettyxml() with open(filename, 'w') as f: f.write(xmlstring) # try to open in web browser if openinbrowser: try: open_in_browser(filename) except: print("Failed to open output SVG in browser. SVG saved to:") print(filename)
def create_svg(file, surface, controls_sequence = None, sn = None): min_x, max_x = None, None min_y, max_y = None, None for polygon in surface.polygons: for vertex in polygon.vertices: if min_x is None: min_x, max_x = vertex[0], vertex[0] min_y, max_y = vertex[1], vertex[1] else: min_x, max_x = min(vertex[0], min_x), max(vertex[0], max_x) min_y, max_y = min(vertex[1], min_y), max(vertex[1], max_y) a = (min_x, max_x, min_y, max_y) b = (max_x - min_x, max_y - min_y, 10.0) surface_view = Drawing(size = (max_x - min_x, max_y - min_y)) def correct_vertex(vertex, surface_bounds, canvas_bounds): x_coordinate, y_coordinate = vertex left_bound, right_bound, bottom_bound, top_bound = surface_bounds canvas_width, canvas_height, canvas_padding = canvas_bounds x_coordinate_scaling = \ (canvas_width - 2.0 * canvas_padding) \ / (right_bound - left_bound) y_coordinate_scaling = \ (canvas_height - 2.0 * canvas_padding) \ / (top_bound - bottom_bound) x_coordinate = \ x_coordinate_scaling * (x_coordinate - left_bound) \ + canvas_padding y_coordinate = \ y_coordinate_scaling * (y_coordinate - bottom_bound) \ + canvas_padding y_coordinate = canvas_height - y_coordinate return x_coordinate, y_coordinate for polygon in surface.polygons: vertices = \ [correct_vertex((x_coordinate, y_coordinate), a, b) \ for x_coordinate, y_coordinate, z_coordinate \ in polygon.vertices] if surface.maximal_impossibility - surface.minimal_impossibility > 0.0: relative_impossibility = \ (polygon.impossibility - surface.minimal_impossibility) \ / (surface.maximal_impossibility - surface.minimal_impossibility) else: relative_impossibility = 0.0 fill_color_red_component = round(255.0 * relative_impossibility) fill_color_green_component = round(255.0 - 255.0 * relative_impossibility) fill_color_blue_component = 0 fill_color = \ rgb( fill_color_red_component, fill_color_green_component, fill_color_blue_component ) polygon_view = \ Polygon( vertices, stroke_width = 1, stroke = rgb(0, 0, 0), fill = fill_color ) surface_view.add(polygon_view) if sn is not None: for polygon_index, polygon in enumerate(surface.polygons): polygon_index_view = \ Text( str(polygon_index), insert = \ correct_vertex( (polygon.center[0], polygon.center[1]), a, b ) ) surface_view.add(polygon_index_view) # if sn is not None: # from svgwrite.text import Text # for state in sn: # polygon = state.polygon # polygon_number = sn[state] # polygon_center = polygon.center[0], polygon.center[1] # q,w = correct_vertex(polygon_center, a, b) # polygon_center_view = \ # Text( # str(polygon_number), # insert = (q+2,w+2), # style = "font-size: 50%; font-color: #808080" # ) # surface_view.add(polygon_center_view) if controls_sequence is not None: last_state = None last_polygon_center = None smoother = Smoother(surface = surface, smoothing_depth = 1) for state in controls_sequence: polygon = state.polygons_sequence[-1] polygon_center = polygon.center[0], polygon.center[1] polygon_center_view = \ Circle( correct_vertex(polygon_center, a, b), 2, stroke_width = 0, fill = rgb(0, 0, 0), ) surface_view.add(polygon_center_view) if last_state is not None: smoother.push_transfer( last_state.get_transfer(state) ) first_transfer_point, second_transfer_point = \ smoother.transfers_points_sequence[0] first_trek_view = \ Line( correct_vertex(last_polygon_center, a, b), correct_vertex( (first_transfer_point.coordinates[0], first_transfer_point.coordinates[1]), a, b ), stroke_width = 1, stroke = rgb(0, 0, 0), ) first_trek_end_view = \ Circle( correct_vertex( (first_transfer_point.coordinates[0], first_transfer_point.coordinates[1]), a, b ), 2, stroke_width = 0, fill = rgb(0, 0, 0), ) second_trek_view = \ Line( correct_vertex( (second_transfer_point.coordinates[0], second_transfer_point.coordinates[1]), a, b ), correct_vertex(polygon_center, a, b), stroke_width = 1, stroke = rgb(0, 0, 0), ) second_trek_start_view = \ Circle( correct_vertex( (second_transfer_point.coordinates[0], second_transfer_point.coordinates[1]), a, b ), 2, stroke_width = 0, fill = rgb(0, 0, 0), ) surface_view.add(first_trek_view) surface_view.add(first_trek_end_view) surface_view.add(second_trek_view) surface_view.add(second_trek_start_view) last_state = state last_polygon_center = polygon_center surface_view.write(file)
def visualize(self, trajectory, output_file): drawing = Drawing() # Определение параметров образов состояний аппарата machine_view_length, machine_view_width = self.__machine_view_size coordinates_scaling = machine_view_length / self.__machine_length machine_diameter = \ ((machine_view_length * 2.0) ** 2.0 + machine_view_width ** 2.0) \ ** 0.5 machine_radius = machine_diameter / 2.0 # Создание последовательности записываемых состояний аппарата def generate_states_sequence(): spawn_time = 0.0 for trajectory_time, state in trajectory: if trajectory_time >= spawn_time: spawn_time += self.__time_interval yield state states_sequence = generate_states_sequence() # Запись последовательности состояний аппарата is_view_box_initialized = False view_box_minimal_x, view_box_minimal_y = 0.0, 0.0 view_box_maximal_x, view_box_maximal_y = 0.0, 0.0 for state in states_sequence: # Создание образа состояния аппарата state_view_angle = - state.coordinates[2] / math.pi * 180.0 state_view_center = \ state.coordinates[0] * coordinates_scaling, \ - state.coordinates[1] * coordinates_scaling state_view_position = \ state_view_center[0], \ state_view_center[1] - machine_view_width / 2.0 state_view = \ Rect( insert = state_view_position, size = self.__machine_view_size, fill = rgb(255, 255, 255), stroke = rgb(0, 0, 0), stroke_width = 1 ) state_view.rotate( state_view_angle, center = state_view_center ) # Добавление образа состояния аппарата к образу траектории drawing.add(state_view) if is_view_box_initialized: view_box_minimal_x, view_box_minimal_y = \ min(state_view_center[0], view_box_minimal_x), \ min(state_view_center[1], view_box_minimal_y) view_box_maximal_x, view_box_maximal_y = \ max(state_view_center[0], view_box_maximal_x), \ max(state_view_center[1], view_box_maximal_y) else: is_view_box_initialized = True view_box_minimal_x, view_box_minimal_y = \ state_view_center[0], \ state_view_center[1] view_box_maximal_x, view_box_maximal_y = \ state_view_center[0], \ state_view_center[1] # Настройка отображения образа траектории drawing.viewbox( minx = view_box_minimal_x - machine_radius, miny = view_box_minimal_y - machine_radius, width = view_box_maximal_x - view_box_minimal_x + machine_diameter, height = view_box_maximal_y - view_box_minimal_y + machine_diameter ) # Запись образа траектории в файл try: drawing.write(output_file) except: raise Exception() #!!!!! Генерировать хорошие исключения
def generate(self): # Concrete execution self.graph = CFGRepresentation() self.line(self.graph, 2, 2, "s", ["0", "1", "...", "t", "..."]) self.line(self.graph, 2, 8, "s", ["0", "1", "...", "t"]) self.draw("concrete_execution", np.array((7 * 4 + 4, 10))) # Symbolic execution self.graph = CFGRepresentation(12.5) def se(x, y, vstep, hstep, level, i): point = v(x, y) self.add( Node(point, name="s", index=i, is_terminal=False, is_feasible=True)) point_ = v(x, y) point_1 = v(x + hstep, y - vstep) self.add(Arrow(point_, point_1, is_feasible=True)) point_2 = v(x, y) point_3 = v(x + hstep, y + vstep) self.add(Arrow(point_2, point_3, is_feasible=True)) point_4 = v(x, y) point_5 = v(x + hstep, y) self.add(Arrow(point_4, point_5, is_feasible=True)) self.add(Ellipsis(v(x + hstep, y), v(0, 1))) if level == 2: return next = ",l" if i == "0" else (",m" if i == "0,0" else ",n") se(x + hstep, y - vstep, vstep / 2.0, hstep, level + 1, i + ",0") se(x + hstep, y + vstep, vstep / 2.0, hstep, level + 1, i + next) se(3, 20, 10, 9, 0, "0") self.draw("symbolic_execution", np.array((9 * 3 + 6, 40))) # Simple program self.graph = CFGRepresentation() x, y = 2, 4 self.add( Text( svgwrite.text.Text("CFG", np.array((2.5, 2.5)) + np.array( (x, y)) * 5))) self.line(self.graph, x, y + 4, "s", ["0", "1"], 5, True, True, True) x += 12 self.add( Text( svgwrite.text.Text( "symbolic", np.array((2.5, 2.5)) + np.array((x, y - 2)) * 5))) self.add( Text( svgwrite.text.Text("execution tree", np.array((2.5, 2.5)) + np.array( (x, y)) * 5))) self.line(self.graph, x, y + 4, "s", ["0", "1"], 5, True, True) x += 12 text_wrap = svgwrite.text.Text( "", np.array((2.5, 2.5)) + np.array((x, y)) * 5) text_wrap.add(svgwrite.text.TSpan("P", font_style="italic")) text_wrap.add( svgwrite.text.TSpan("0", font_size="65%", baseline_shift="sub")) self.add(Text(text_wrap)) self.line(self.graph, x, y + 4, "s", ["0", "1"], 5, True, True) self.draw("simple", np.array((x + 2, 18))) # Branch program c = CFG() s = Drawing() x, y = 2, 6 c.add_vertex(Vertex("0", np.array((x + 4, y)))) c.add_vertex(Vertex("1", np.array((x, y + 4)), is_terminal=True)) c.add_vertex(Vertex("2", np.array((x + 8, y + 4)), is_terminal=True)) c.add_edges([("0", "1"), ("0", "2")]) c.draw_cfg(s, title=(Text( svgwrite.text.Text( "CFG", np.array((2.5, 2.5)) + np.array((6, 2)) * 5)))) x += 10 c.draw_paths(s, np.array((x, y))) with open(os.path.join(self.directory_name, "branch_paths.svg"), "w+") as output_file: s.write(output_file) # Branch program self.graph = CFGRepresentation() x, y = 6, 2 v0, v1, v2 = v(x, y), v(x - 4, y + 4), v(x + 4, y + 4) self.add(Node(v0, index="0")) self.add(Node(v1, index="1", is_terminal=True)) self.add(Node(v2, index="2", is_terminal=True)) self.add(Arrow(v0, v1)) self.add(Arrow(v0, v2)) angle = math.pi / 2 self.add(Loop(v1, angle)) angle1 = math.pi / 2 self.add(Loop(v2, angle1)) x += 10 v0, v1 = v(x, y), v(x, y + 5) self.add(Node(v0, index="0")) self.add(Node(v1, index="1", is_terminal=True)) self.add(Arrow(v0, v1)) x += 5 v0, v1 = v(x, y), v(x, y + 5) self.add(Node(v0, index="0")) self.add(Node(v1, index="2", is_terminal=True)) self.add(Arrow(v0, v1, is_feasible=True)) x += 10 v0, v1, v2 = v(x, y), v(x - 4, y + 4), v(x + 4, y + 4) self.add(Node(v0, index="0")) self.add(Node(v1, index="1", is_terminal=True)) self.add(Node(v2, index="2", is_terminal=True)) self.add(Arrow(v0, v1)) self.add(Arrow(v0, v2)) self.draw("branch2") # Cycle program self.graph = CFGRepresentation() x, y = 2, 2 v0, v1 = v(x, y), v(x, y + 5) self.add(Node(v0, index="0")) self.add(Node(v1, index="1", is_terminal=True)) self.add(Arrow(v0, v1)) angle2 = math.pi / 2 self.add(Loop(v1, angle2)) self.add(Loop(v0, 0)) x += 10 v0, v1 = v(x, y), v(x, y + 5) self.add(Node(v0, index="0")) self.add(Node(v1, index="1", is_terminal=True)) self.add(Arrow(v0, v1)) x += 5 v0, v1, v2 = v(x, y), v(x, y + 5), v(x, y + 10) self.add(Node(v0, index="0")) self.add(Node(v1, index="0")) self.add(Node(v2, index="1", is_terminal=True)) self.add(Arrow(v0, v1)) self.add(Arrow(v1, v2)) x += 5 v0, v1, v2, v3 = v(x, y), v(x, y + 5), v(x, y + 10), v(x, y + 15) self.add(Node(v0, index="0")) self.add(Node(v1, index="0")) self.add(Node(v2, index="0")) self.add(Node(v3, index="1", is_terminal=True)) self.add(Arrow(v0, v1)) self.add(Arrow(v1, v2)) self.add(Arrow(v2, v3)) x += 10 v0, v1, v2 = v(x, y), v(x - 4, y + 4), v(x + 4, y + 4) for i in range(3): self.add(Node(v0, index="0")) self.add(Node(v1, index="1", is_terminal=True)) self.add(Arrow(v0, v1)) self.add(Arrow(v0, v2)) v0 = v0 + v(4, 4) v1 = v1 + v(4, 4) v2 = v2 + v(4, 4) self.draw("cycle") # Control flow dependence g = CFG() g.add_vertices([("0", 6, 2), ("1", 2, 6), ("2", 2, 11), ("3", 10, 6), ("4", 10, 11), ("5", 6, 15)]) g.add_edges([("0", "1"), ("1", "2"), ("0", "3"), ("3", "4"), ("2", "5"), ("4", "5")]) s = Drawing() g.draw_cfg(s) with open(os.path.join(self.directory_name, "implicit.svg"), "w+") as output_file: s.write(output_file) # Sequence comparison self.graph = CFGRepresentation() x, y = 2, 2 v0, v1, v2 = v(x, y), v(x + 4, y + 4), v(x, y + 8) for i in range(4): id_ = str(i * 2) self.add(Node(v0, index=id_)) id_1 = str(i * 2 + 1) self.add(Node(v1, index=id_1)) self.add(Arrow(v0, v1)) self.add(Arrow(v0, v2)) self.add(Arrow(v1, v2)) v0 = v0 + v(0, 8) v1 = v1 + v(0, 8) v2 = v2 + v(0, 8) self.add(Node(v0, index="8", is_terminal=True)) self.draw("classic_cfg", (v0 + v(2 + 4, 2))) self.graph = CFGRepresentation() x, y = 2, 2 for i in range(16): k = "0" * (6 - len(bin(i))) + bin(i)[2:] chain = ["0"] if k[3] == "1": chain.append("1") chain.append("2") if k[2] == "1": chain.append("3") chain.append("4") if k[1] == "1": chain.append("5") chain.append("6") if k[0] == "1": chain.append("7") chain.append("8") self.graph.add_chain(v(x, y), chain, is_terminated=True) x += 5 self.draw("classic_paths") self.graph = CFGRepresentation() x, y = 2, 40 + 2 - 2.5 def dr(index, x, y, step, count, f): point = v(x, y) id_ = str(index) t = index == 8 self.add(Node(point, index=id_, is_terminal=t, is_feasible=f)) if index == 8: return if index in [1, 3, 5]: count += 1 if index % 2 == 0: point_ = v(x, y) point_1 = v(x + 7, y - step) f1 = count != 3 self.add(Arrow(point_, point_1, is_feasible=f1)) dr(index + 2, x + 7, y - step, step / 2.0, count, f=count != 3) point_2 = v(x, y) point_3 = v(x + 7, y + step) f2 = index < 6 or count == 3 self.add(Arrow(point_2, point_3, is_feasible=f2)) dr(index + 1, x + 7, y + step, step / 2.0, count, f=index < 6 or count == 3) else: point_4 = v(x, y) point_5 = v(x + 7, y) f3 = index < 6 or count == 3 self.add(Arrow(point_4, point_5, is_feasible=f3)) dr(index + 1, x + 7, y, step, count, f=index < 6 or count == 3) dr(0, x, y, 20, 0, True) self.draw("classic_symbolic_tree", np.array((60, 80 + 4 - 5))) # Sequence comparison 2 self.graph = CFGRepresentation() x, y = 2, 2 v0, v1, v2 = v(x, y), v(x + 4, y + 4), v(x + 4, y + 9) v3 = v(x, y + 4 * 9) for i in range(4): id_2 = str(i * 2) self.add(Node(v0, index=id_2)) id_3 = str(i * 2 + 1) self.add(Node(v1, index=id_3)) self.add(Arrow(v0, v1)) self.add(Arrow(v0, v3)) self.add(Arrow(v1, v2)) v0 = v0 + v(4, 9) v1 = v1 + v(4, 9) v2 = v2 + v(4, 9) self.add(Node(v0, index="8")) self.add(Arrow(v0, v3)) self.add(Node(v3, index="9", is_terminal=True)) self.draw("cascade_cfg") with open("result.html", "w+") as debug_file: debug_file.write("".join( map(lambda x: f"<img src=\"image/{x}.svg\">", self.ids)))
class SvgRenderer(StreamRenderer): """ Draws the board like an SVG image (best representation for web) """ __rend_name__ = 'svg' DEFAULT_CELL_SIZE_IN_PIXELS = 15 BOLD_EVERY = 5 GRID_STROKE_WIDTH = 1 GRID_BOLD_STROKE_WIDTH = 2 @property def clues_font_size(self): """The size of the descriptions text""" return self.cell_size * 0.6 def __init__(self, board=None, stream=stdout, size=DEFAULT_CELL_SIZE_IN_PIXELS): super(SvgRenderer, self).__init__(board, stream) # decrease startup time when do not need this renderer from svgwrite import Drawing self.cell_size = size self.color_symbols = dict() self.drawing = Drawing(size=(self.full_width + self.cell_size, self.full_height + self.cell_size)) self._add_definitions() def _color_id_by_name(self, color): color_id = self.board.color_id_by_name(color) if color_id: return color_id if is_list_like(color): return from_two_powers( self.board.color_id_by_name(single_color) for single_color in color) return None def _add_symbol(self, id_, color, *parts, **kwargs): drawing = self.drawing symbol = drawing.symbol(id_=id_, **kwargs) for part in parts: symbol.add(part) if color is not None: # SPACE is already an ID if color not in (SPACE, SPACE_COLORED): if self.is_colored: color = self._color_id_by_name(color) self.color_symbols[color] = id_ drawing.defs.add(symbol) def _add_definitions(self): drawing = self.drawing # dynamic style rules drawing.defs.add( drawing.style( 'g.grid-lines line {stroke-width: %i} ' 'g.grid-lines line.bold {stroke-width: %i} ' 'g.header-clues text, g.side-clues text {font-size: %f} ' % ( self.GRID_STROKE_WIDTH, self.GRID_BOLD_STROKE_WIDTH, self.clues_font_size, ))) self._add_colors_def() self._add_symbol('check', None, drawing.circle(r=40, stroke_width=10, center=(50, 50)), drawing.polyline(stroke_width=12, points=[(35, 35), (35, 55), (75, 55)], transform='rotate(-45 50 50)'), stroke='green', fill='none') self.check_icon_size = 100 def _add_colors_def(self): drawing = self.drawing white_color = Color.white().name cell_size = self.cell_size rect_size = (cell_size, cell_size) upper_triangle_points = ((0, 0), (0, cell_size), (cell_size, 0)) lower_triangle_points = ((0, cell_size), (cell_size, 0), (cell_size, cell_size)) # three_colored_flag_rect_size = (cell_size / 3, cell_size) # three_colored_flag_insert_points = [(0, 0), (cell_size / 3, 0), (2 * cell_size / 3, 0)] three_color_triangle_size = round(cell_size * ((1 / 2)**0.5), 2) three_color_triangle_coord = round( cell_size - three_color_triangle_size, 2) three_colors_upper_points = [(0, 0), (0, three_color_triangle_size), (three_color_triangle_size, 0)] three_colors_lower_points = [ (cell_size, three_color_triangle_coord), (three_color_triangle_coord, cell_size), (cell_size, cell_size), ] # rendering should be predictable colors = [] if self.is_colored: for color_name in sorted(self.board.color_map): colors.append((color_name, self._color_from_name(color_name))) space_color = SPACE_COLORED else: colors.append((BOX, 'black')) space_color = SPACE for color_name, fill_color in colors: if color_name != white_color: self._add_symbol( 'color-%s' % color_name, color_name, drawing.rect( size=rect_size, fill=fill_color, )) if self.is_colored: for (color_name, fill_color), (color_name2, fill_color2) in combinations(colors, 2): LOG.info('Transient symbol: %s, %s + %s, %s', color_name, fill_color, color_name2, fill_color2) color_tuple = (color_name, color_name2) self._add_symbol( 'x2-%s' % '-'.join(map(str, color_tuple)), color_tuple, drawing.polygon( points=upper_triangle_points, fill=fill_color, ), drawing.polygon( points=lower_triangle_points, fill=fill_color2, ), ) for (color_name, fill_color), (color_name2, fill_color2), (color_name3, fill_color3) in combinations( colors, 3): LOG.info('Transient symbol: %s, %s + %s, %s + %s, %s', color_name, fill_color, color_name2, fill_color2, color_name3, fill_color3) color_tuple = (color_name, color_name2, color_name3) self._add_symbol( 'x3-%s' % '-'.join(map(str, color_tuple)), color_tuple, # drawing.rect( # insert=three_colored_flag_insert_points[0], # size=three_colored_flag_rect_size, # fill=fill_color, # ), # drawing.rect( # insert=three_colored_flag_insert_points[1], # size=three_colored_flag_rect_size, # fill=fill_color2, # ), # drawing.rect( # insert=three_colored_flag_insert_points[2], # size=three_colored_flag_rect_size, # fill=fill_color3, # ), drawing.rect( size=rect_size, fill=fill_color, ), drawing.polygon( points=three_colors_upper_points, fill=fill_color2, ), drawing.polygon( points=three_colors_lower_points, fill=fill_color3, ), ) # it's a little circle self._add_symbol('space', space_color, drawing.circle(r=cell_size / 10)) @property def pixel_side_width(self): """Horizontal clues side width in pixels""" return self.side_width * self.cell_size @property def pixel_header_height(self): """Vertical clues header height in pixels""" return self.header_height * self.cell_size @property def pixel_board_width(self): """The width of the main area in pixels""" return self.board.width * self.cell_size @property def pixel_board_height(self): """The height of the main area in pixels""" return self.board.height * self.cell_size @property def full_width(self): """Full width of the SVG board representation""" return self.pixel_side_width + self.pixel_board_width @property def full_height(self): """Full height of the SVG board representation""" return self.pixel_header_height + self.pixel_board_height def _color_from_name(self, color_name): return self.board.rgb_for_color_name(color_name) def block_svg(self, value, is_column, clue_number, block_number): """ Return the SVG element for the clue number (colored case included) """ # left to right, bottom to top block_number = -block_number shift = (0.85, -0.3) if is_column else (-0.3, 0.75) i, j = (clue_number, block_number) if is_column else (block_number, clue_number) if isinstance(value, (list, tuple)): # colored board value, color_id = value[:2] else: color_id = None block_color = None if color_id is not None: id_ = self.color_symbols[color_id] if is_column: color_box = (i, j - 1) else: color_box = (i - 1, j) # drawing.g(class_=id_) insert_point = (self.pixel_side_width + (color_box[0] * self.cell_size), self.pixel_header_height + (color_box[1] * self.cell_size)) block_color = (id_, insert_point) extra = dict() if color_id == Color.black().id_: extra['fill'] = 'white' if value == BlottedBlock: text_value = ClueCell.BLOTTED_SYMBOL else: text_value = str(value) block_text = self.drawing.text( text_value, insert=( self.pixel_side_width + (i + shift[0]) * self.cell_size, self.pixel_header_height + (j + shift[1]) * self.cell_size, ), **extra) return block_color, block_text def draw_header(self): drawing = self.drawing drawing.add( drawing.rect(size=(self.pixel_side_width, self.pixel_header_height), class_='nonogram-thumbnail')) drawing.add( drawing.rect(insert=(self.pixel_side_width, 0), size=(self.pixel_board_width, self.pixel_header_height), class_='nonogram-header')) header_group = drawing.g(class_='header-clues') for i, col_desc in enumerate(self.board.columns_descriptions): if self.board.column_solution_rate(i) == 1: x_pos = self.pixel_side_width + (i * self.cell_size) header_group.add( drawing.rect(insert=(x_pos, 0), size=(self.cell_size, self.pixel_header_height), class_='solved')) for j, desc_item in enumerate(reversed(col_desc)): color, text = self.block_svg(desc_item, True, i, j) # color first, text next (to write on color) if color: id_, insert_point = color icon = drawing.use( href='#' + id_, insert=insert_point, ) header_group.add(icon) header_group.add(text) drawing.add(header_group) def draw_side(self): drawing = self.drawing drawing.add( drawing.rect(insert=(0, self.pixel_header_height), size=(self.pixel_side_width, self.pixel_board_height), class_='nonogram-side')) side_group = drawing.g(class_='side-clues') for j, row_desc in enumerate(self.board.rows_descriptions): if self.board.row_solution_rate(j) == 1: y_pos = self.pixel_header_height + (j * self.cell_size) side_group.add( drawing.rect(insert=(0, y_pos), size=(self.pixel_side_width, self.cell_size), class_='solved')) for i, desc_item in enumerate(reversed(row_desc)): color, text = self.block_svg(desc_item, False, j, i) # color first, text next (to write on color) if color: id_, insert_point = color icon = drawing.use( href='#' + id_, insert=insert_point, ) side_group.add(icon) side_group.add(text) drawing.add(side_group) if self.board.is_solved_full: self._insert_solved_symbol() def _insert_solved_symbol(self): drawing = self.drawing check_icon_size = self.check_icon_size left_padding = (self.pixel_side_width - check_icon_size) / 2 top_padding = (self.pixel_header_height - check_icon_size) / 2 left_padding = max(left_padding, 0) top_padding = max(top_padding, 0) drawing.add(drawing.use('#check', insert=(left_padding, top_padding))) @classmethod def _color_code(cls, cell): if is_color_cell(cell): single_colors = two_powers(cell) if len(single_colors) > 3: # allow two and three colors # multiple colors return UNKNOWN return cell def draw_grid(self, cells=None): if cells is None: cells = self.board.cells drawing = self.drawing drawing.add( drawing.rect(insert=(self.pixel_side_width, self.pixel_header_height), size=(self.pixel_board_width, self.pixel_board_height), class_='nonogram-grid')) cell_groups = dict() for cell_value, id_ in iteritems(self.color_symbols): cell_groups[cell_value] = drawing.g(class_=id_) space_cell = SPACE_COLORED if self.is_colored else SPACE for j, row in enumerate(cells): for i, cell in enumerate(row): cell = self._color_code(cell) if cell == UNKNOWN: continue if cell == space_cell: insert_point = (self.pixel_side_width + (i + 0.5) * self.cell_size, self.pixel_header_height + (j + 0.5) * self.cell_size) else: # for boxes colored and black insert_point = (self.pixel_side_width + (i * self.cell_size), self.pixel_header_height + (j * self.cell_size)) id_ = self.color_symbols[cell] icon = drawing.use(href='#' + id_, insert=insert_point) cell_groups[cell].add(icon) # to get predictable order for cell_value, group in sorted(iteritems(cell_groups), key=lambda x: x[0]): drawing.add(group) # write grid on top of the colors self._insert_grid_lines() def _insert_grid_lines(self): drawing = self.drawing grid_lines = drawing.g(class_='grid-lines') for line in self._get_grid_lines(): grid_lines.add(line) drawing.add(grid_lines) def _get_grid_lines(self): drawing = self.drawing # draw horizontal lines for i in range(self.board.height + 1): extra = dict() if i % self.BOLD_EVERY == 0 or i == self.board.height: extra['class'] = 'bold' y_pos = self.pixel_header_height + (i * self.cell_size) yield drawing.line(start=(0, y_pos), end=(self.full_width, y_pos), **extra) # draw vertical lines for i in range(self.board.width + 1): extra = dict() if i % self.BOLD_EVERY == 0 or i == self.board.width: extra['class'] = 'bold' x_pos = self.pixel_side_width + (i * self.cell_size) yield drawing.line(start=(x_pos, 0), end=(x_pos, self.full_height), **extra) def render(self): self.drawing.write(self.stream) # self._print(self.drawing.tostring()) def draw(self, cells=None): self.drawing.elements = [] self.drawing.add(self.drawing.defs) super(SvgRenderer, self).draw(cells=cells)