def _transform(self, element, transform_list): rows = 0 if not transform_list: return element n_elements = len(transform_list) rows = n_elements % self.nrows cols = int(n_elements / self.nrows) if self.col_width is None: self.col_width = transform_list[0]['width'] if self.row_height is None: self.row_height = transform_list[0]['height'] for i in range(rows): element = GroupElement([element]) element.moveto(0, self.row_height) for i in range(cols): element = GroupElement([element]) element.moveto(self.col_width, 0) return element
def _generate_grid( size: ElemSize, dxy: Length, width: float = 0.5, font_size: int = 8 ) -> sc.Element: """ Adapted from svgutils.compose.Grid Fills a rectangle with horizontal and vertical grid lines, spaced every `dxy` in both directions. Parameters ---------- size : ElemSize The size of the rectangle to fill with grid lines dxy : Length The spacing of the grid lines, in units of size.unit width : float Line with of the grid lines font_size : int The size of the numbers marking each grid line. Set to zero for no labels. Returns ------- svgutils.compose.Element The grid lines and labels """ from svgutils.transform import LineElement, TextElement, GroupElement x, y = dxy, dxy lines = [] txt = [] units = size.units d_px = font_size width_px, height_px = units.to_px(size.width), units.to_px(size.height) while x <= size.width: x_px = units.to_px(x) lines.append(LineElement([(x_px, 0), (x_px, height_px)], width=width)) txt.append(TextElement(x_px+d_px/2, d_px, str(x), size=font_size)) x += dxy while y <= size.height: y_px = units.to_px(y) lines.append(LineElement([(0, y_px), (width_px, y_px)], width=width)) txt.append(TextElement(d_px/2, y_px+d_px, str(y), size=font_size)) y += dxy return sc.Element(GroupElement(txt+lines).root)
def _compose_view(bg_svgs, fg_svgs, ref=0): if fg_svgs is None: fg_svgs = [] # Merge SVGs and get roots svgs = bg_svgs + fg_svgs roots = [f.getroot() for f in svgs] # Query the size of each sizes = [] for f in svgs: viewbox = [float(v) for v in f.root.get("viewBox").split(" ")] width = int(viewbox[2]) height = int(viewbox[3]) sizes.append((width, height)) nsvgs = len(bg_svgs) sizes = np.array(sizes) # Calculate the scale to fit all widths width = sizes[ref, 0] scales = width / sizes[:, 0] heights = sizes[:, 1] * scales # Compose the views panel: total size is the width of # any element (used the first here) and the sum of heights fig = SVGFigure(width, heights[:nsvgs].sum()) yoffset = 0 for i, r in enumerate(roots): r.moveto(0, yoffset, scale=scales[i]) if i == (nsvgs - 1): yoffset = 0 else: yoffset += heights[i] # Group background and foreground panels in two groups if fg_svgs: newroots = [ GroupElement(roots[:nsvgs], {"class": "background-svg"}), GroupElement(roots[nsvgs:], {"class": "foreground-svg"}), ] else: newroots = roots fig.append(newroots) fig.root.attrib.pop("width") fig.root.attrib.pop("height") fig.root.set("preserveAspectRatio", "xMidYMid meet") with TemporaryDirectory() as tmpdirname: out_file = Path(tmpdirname) / "tmp.svg" fig.save(str(out_file)) # Post processing svg = out_file.read_text().splitlines() # Remove <?xml... line if svg[0].startswith("<?xml"): svg = svg[1:] # Add styles for the flicker animation if fg_svgs: svg.insert( 2, """\ <style type="text/css"> @keyframes flickerAnimation%s { 0%% {opacity: 1;} 100%% { opacity: 0; }} .foreground-svg { animation: 1s ease-in-out 0s alternate none infinite paused flickerAnimation%s;} .foreground-svg:hover { animation-play-state: running;} </style>""" % tuple([uuid4()] * 2), ) return svg
def _transform(self, element, transform_list): for t in transform_list: element = GroupElement([element]) element.moveto(0, t['height']) return element