From 643a002d14c2a8188b806e9884ffcca3ce68e3b8 Mon Sep 17 00:00:00 2001 From: dengzq1234 Date: Sat, 8 Mar 2025 19:43:14 +0100 Subject: [PATCH 1/7] add rotate polygon --- ete4/smartview/faces.py | 9 +++-- ete4/smartview/graphics.py | 4 +- ete4/smartview/static/js/draw.js | 64 +++++++++++++++++++++++++++++--- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/ete4/smartview/faces.py b/ete4/smartview/faces.py index 54b697574..bfb39d8b3 100644 --- a/ete4/smartview/faces.py +++ b/ete4/smartview/faces.py @@ -281,11 +281,12 @@ def draw(self, nodes, size, collapsed, zoom=(1, 1), ax_ay=(0, 0), r=1): class PolygonFace(Face): """A polygon.""" - def __init__(self, rmax=None, shape=3, style='', - position='top', column=0, anchor=None): + def __init__(self, rmax=None, shape=3, rotation=0, + style='', position='top', column=0, anchor=None): super().__init__(position, column, anchor) self.shape = shape # name of the shape or number of edges + self.rotation = rotation # rotation in degrees self.rmax = rmax # maximum "radius" in pixels self.style = style @@ -302,7 +303,7 @@ def draw(self, nodes, size, collapsed, zoom=(1, 1), ax_ay=(0, 0), r=1): # Return the graphic and its size. center = (cr / zx, cr / (r * zy)) # in tree coordinates - polygon = gr.draw_polygon(center, cr, self.shape, self.style) + polygon = gr.draw_polygon(center, cr, self.shape, self.rotation, self.style) return [polygon], Size(2*cr/zx, 2*cr/(r*zy)) # NOTE: For small r (in circular mode), that size is just approximate. @@ -329,7 +330,7 @@ def draw(self, nodes, size, collapsed, zoom=(1, 1), ax_ay=(0, 0), r=1): # Find the width and height so they are never bigger than the max. w = min(zx * dx, self.wmax) if dx > 0 else self.wmax h = min(zy * r * dy, self.hmax) if self.hmax else (zy * r * dy) - + # Keep the ratio h/w if we had hmax in addition to wmax. if self.hmax: h_over_w = self.hmax / self.wmax diff --git a/ete4/smartview/graphics.py b/ete4/smartview/graphics.py index 71aad68aa..a0acec6c8 100644 --- a/ete4/smartview/graphics.py +++ b/ete4/smartview/graphics.py @@ -69,8 +69,8 @@ def draw_arc(p1, p2, style=''): def draw_circle(center, radius=1, style=''): return ['circle', center, radius, style] -def draw_polygon(center, radius, shape=3, style=''): - return ['polygon', center, radius, shape, style] +def draw_polygon(center, radius, shape=3, rotation=0, style=''): + return ['polygon', center, radius, shape, rotation, style] def draw_box(box, style=''): return ['box', box, style] diff --git a/ete4/smartview/static/js/draw.js b/ete4/smartview/static/js/draw.js index 8ddfcabf4..cd476053d 100644 --- a/ete4/smartview/static/js/draw.js +++ b/ete4/smartview/static/js/draw.js @@ -212,8 +212,8 @@ function translate(item, shift) { return ["circle", [x + shift, y], radius, style]; } else if (item[0] === "polygon") { - const [ , [x, y], radius, shape, style] = item; - return ["polygon", [x + shift, y], radius, shape, style]; + const [ , [x, y], radius, shape, rotation, style] = item; + return ["polygon", [x + shift, y], radius, shape, rotation, style]; } else if (item[0] === "box") { const [ , box, style] = item; @@ -492,10 +492,11 @@ function create_item(item, tl, zoom, wmax) { return create_circle(center, radius, tl, zx, zy, add_ns_prefix(style)); } else if (item[0] === "polygon") { - const [ , center, radius, shape, style] = item; - + console.log("polygon", item); + const [ , center, radius, shape, rotation, style] = item; + return create_polygon(center, radius, shape, tl, zx, zy, - add_ns_prefix(style), true); + rotation, add_ns_prefix(style), true); } else if (item[0] === "box") { const [ , box, style] = item; @@ -867,7 +868,8 @@ function create_circle(center, radius, tl, zx, zy, style="") { // Create a polygon. -function create_polygon(center, r, shape, tl, zx, zy, style="", resize=false) { +function create_polygon(center, r, shape, tl, zx, zy, rotation=0, style="", resize=false) { + const n = typeof shape === "number" ? shape : {"triangle": 3, "square": 4, @@ -908,10 +910,60 @@ function create_polygon(center, r, shape, tl, zx, zy, style="", resize=false) { add_rotation(element, angle, c.x, c.y); } + // Apply custom rotation + if (rotation !== 0) { + add_rotation(element, rotation, c.x, c.y); + } + add_style(element, style); return element; } +// function create_polygon(center, r, shape, tl, zx, zy, rotation=0, style="", resize=false) { +// const n = typeof shape === "number" ? shape : +// {"triangle": 3, +// "square": 4, +// "pentagon": 5, +// "hexagon": 6, +// "heptagon": 7, +// "octogon": 8}[shape]; + +// if (n === undefined) +// throw new Error(`unknown dot shape ${shape}`); + +// const c = view.shape === "rectangular" ? // center point in screen coords +// tree2rect(center, tl, zx, zy) : +// tree2circ(center, tl, zx); + +// // When we put a polygon as a nodedot we don't need the correction, +// // but when used as a face it would look bad without it. +// const correction = resize ? Math.atan(n - 2) * 2 / Math.PI : 1; + +// const s = 2 * r * Math.tan(Math.PI / n) * correction; // side length +// let p = {x: c.x - s/2, // starting point +// y: c.y + r}; + +// const ps = [p]; // polygon points, adding a rotated (s, 0) +// for (let i = 0; i < n - 1; i++) { +// p = {x: p.x + s * Math.cos(i * 2 * Math.PI / n), +// y: p.y - s * Math.sin(i * 2 * Math.PI / n)} +// ps.push(p); +// } + +// const element = create_svg_element("polygon", { +// "points": ps.map(p => `${p.x},${p.y}`).join(" "), +// }); + +// if (view.shape === "circular") { +// const angle = 180 / Math.PI * Math.atan2(zy * tl.y + c.y, +// zx * tl.x + c.x); +// add_rotation(element, angle, c.x, c.y); +// } + +// add_style(element, style); + +// return element; +// } function create_text(box, anchor, text, fs_max, rotation, From 104a55cc3625a384c6bfbb4f7bbafc1edfd52f3d Mon Sep 17 00:00:00 2001 From: dengzq1234 Date: Sat, 8 Mar 2025 21:17:26 +0100 Subject: [PATCH 2/7] for no text header --- ete4/smartview/draw.py | 45 +++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/ete4/smartview/draw.py b/ete4/smartview/draw.py index 83958b0dc..9909a3ff7 100644 --- a/ete4/smartview/draw.py +++ b/ete4/smartview/draw.py @@ -7,7 +7,7 @@ from ..core import operations as ops from .coordinates import Size, Box, make_box, get_xs, get_ys from .layout import Label, update_style -from .faces import LegendFace, EvalTextFace, eval_as_str +from .faces import LegendFace, EvalTextFace, TextFace, eval_as_str from . import graphics as gr @@ -43,23 +43,44 @@ def draw(tree, layouts, overrides=None, labels=None, yield from drawer_obj.draw() for face in faces: + if face.position == 'header': # face must be TextFace or similar # TODO: Allow any kind of face, not only TextFace. - text = eval_as_str(face.code, tree) + if type(face) is TextFace: + text = eval_as_str(face.code, tree) - # Go to the right panel. - panel = face.column + 1 # where the header should go to - yield gr.set_panel(panel) # command to change to panel + # Go to the right panel. + panel = face.column + 1 # where the header should go to + yield gr.set_panel(panel) # command to change to panel - # Update where we keep the "maximum x" arrived to for that panel. - width = (face.fs_max/1.6) * len(text) * cos(face.rotation * pi/180) - xmax = max(face.fs_max, width) / zoom[0] - drawer_obj.xmaxs[panel] = max(drawer_obj.xmaxs.get(panel, 0), xmax) + # Update where we keep the "maximum x" arrived to for that panel. + width = (face.fs_max/1.6) * len(text) * cos(face.rotation * pi/180) + xmax = max(face.fs_max, width) / zoom[0] + drawer_obj.xmaxs[panel] = max(drawer_obj.xmaxs.get(panel, 0), xmax) + + # Draw the header and go back to the main panel. + yield gr.draw_header(text, face.fs_max, face.rotation, face.style) + yield gr.set_panel(0) # command to change to panel 0 + + else: + + # Go to the right panel. + panel = face.column + 1 # where the header should go to + yield gr.set_panel(panel) # command to change to panel + + graphics, _ = face.draw([tree], tree.size, [], zoom, (0, 0), tree.size[0]) + yield from graphics + # Draw LineFace + # dx, dy = tree.size + # zx, zy = zoom + # w = min(zx * dx, face.wmax) if dx > 0 else face.wmax + # box = make_box((0, 0), Size(w / zx, 0)) + + # yield gr.draw_line((box.x, box.y), (box.x + box.dx, box.y), face.style) + yield gr.set_panel(0) # command to change to panel 0 + - # Draw the header and go back to the main panel. - yield gr.draw_header(text, face.fs_max, face.rotation, face.style) - yield gr.set_panel(0) # command to change to panel 0 # NOTE: We don't use the face.draw() function, which would # draw inside a box. Instead we extract the data and draw by hand. elif type(face) is LegendFace: From a49a6711664844ae8ef4c6f24a6eda0e64b616c1 Mon Sep 17 00:00:00 2001 From: dengzq1234 Date: Sat, 8 Mar 2025 21:26:32 +0100 Subject: [PATCH 3/7] make node polygon work --- ete4/smartview/static/js/draw.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ete4/smartview/static/js/draw.js b/ete4/smartview/static/js/draw.js index cd476053d..80b954921 100644 --- a/ete4/smartview/static/js/draw.js +++ b/ete4/smartview/static/js/draw.js @@ -708,11 +708,12 @@ function create_dot(point, dy_max, tl, zx, zy, styles) { // Radius of the dot in pixels. const r_max = zy * dy_max * (view.shape === "circular" ? point[0] : 1); const r = Math.min(r_max, pop_style(styles, "radius") || view.node.dot.radius); + const rotation = 0; if (shape === "circle") return create_circle(point, r, tl, zx, zy, styles); else - return create_polygon(point, r, shape, tl, zx, zy, styles); + return create_polygon(point, r, shape, tl, zx, zy, rotation, styles); } From dc8f61f8d5fd559976bec74fd2c0f54acc9431d1 Mon Sep 17 00:00:00 2001 From: dengzq1234 Date: Sat, 8 Mar 2025 23:13:31 +0100 Subject: [PATCH 4/7] allow rotate --- ete4/smartview/static/js/draw.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ete4/smartview/static/js/draw.js b/ete4/smartview/static/js/draw.js index 80b954921..fc449c108 100644 --- a/ete4/smartview/static/js/draw.js +++ b/ete4/smartview/static/js/draw.js @@ -708,7 +708,7 @@ function create_dot(point, dy_max, tl, zx, zy, styles) { // Radius of the dot in pixels. const r_max = zy * dy_max * (view.shape === "circular" ? point[0] : 1); const r = Math.min(r_max, pop_style(styles, "radius") || view.node.dot.radius); - const rotation = 0; + const rotation = pop_style(styles, "rotation") || 0; if (shape === "circle") return create_circle(point, r, tl, zx, zy, styles); From a8d042bec7b0f622c615ff9a436619f590dc3b1a Mon Sep 17 00:00:00 2001 From: dengzq1234 Date: Mon, 31 Mar 2025 11:11:54 +0200 Subject: [PATCH 5/7] multi gradients in legend --- ete4/smartview/static/js/draw.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/ete4/smartview/static/js/draw.js b/ete4/smartview/static/js/draw.js index fc449c108..f2e279b7e 100644 --- a/ete4/smartview/static/js/draw.js +++ b/ete4/smartview/static/js/draw.js @@ -355,15 +355,34 @@ function legend2html(legend) { return header + lines.join("\n"); } else { // variable continuous: use value range and color range + console.log("crange", crange); const [vmin, vmax] = vrange.map(format_number); // values - const [cmin, cmax] = crange; // colors + // const [cmin, cmax] = crange; // colors + // return header + + // `${vmax} + // + // + // ${vmin}`; + + // Handle multiple color stops + let gradientStops = ""; + if (Array.isArray(crange)) { + const step = 100 / (crange.length - 1); + gradientStops = crange.map((color, i) => `${color} ${i * step}%`).join(', '); + } else { + gradientStops = `${crange[0]}, ${crange[1]}`; + } + console.log("gradientStops", gradientStops); return header + `${vmax} + background-image: linear-gradient(${gradientStops})"> ${vmin}`; + } } From 619f9cd84cdd9cc261f2ec97e43cfff3bf1a7b07 Mon Sep 17 00:00:00 2001 From: dengzq1234 Date: Mon, 19 May 2025 15:31:54 +0200 Subject: [PATCH 6/7] resume origin ete4 continuous legend --- ete4/smartview/static/js/draw.js | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/ete4/smartview/static/js/draw.js b/ete4/smartview/static/js/draw.js index 6ec8f5a9e..47fad44a3 100644 --- a/ete4/smartview/static/js/draw.js +++ b/ete4/smartview/static/js/draw.js @@ -432,31 +432,13 @@ function legend2html(legend) { return header + lines.join("\n"); } else { // variable continuous: use value range and color range - console.log("crange", crange); const [vmin, vmax] = vrange.map(format_number); // values - // const [cmin, cmax] = crange; // colors - // return header + - // `${vmax} - // - // - // ${vmin}`; - - // Handle multiple color stops - let gradientStops = ""; - if (Array.isArray(crange)) { - const step = 100 / (crange.length - 1); - gradientStops = crange.map((color, i) => `${color} ${i * step}%`).join(', '); - } else { - gradientStops = `${crange[0]}, ${crange[1]}`; - } - console.log("gradientStops", gradientStops); + return header + `${vmax} + background-image:linear-gradient(${crange.join(",")})"> ${vmin}`; } From 280bec1f0f88076eeaad460a33bab72d4a2cee55 Mon Sep 17 00:00:00 2001 From: dengzq1234 Date: Mon, 19 May 2025 15:36:26 +0200 Subject: [PATCH 7/7] clean lines --- ete4/smartview/static/js/draw.js | 47 -------------------------------- 1 file changed, 47 deletions(-) diff --git a/ete4/smartview/static/js/draw.js b/ete4/smartview/static/js/draw.js index 47fad44a3..8bd8d19d8 100644 --- a/ete4/smartview/static/js/draw.js +++ b/ete4/smartview/static/js/draw.js @@ -569,7 +569,6 @@ function create_item(item, tl, zoom, wmax) { return create_circle(center, radius, tl, zx, zy, add_ns_prefix(style)); } else if (item[0] === "polygon") { - console.log("polygon", item); const [ , center, radius, shape, rotation, style] = item; return create_polygon(center, radius, shape, tl, zx, zy, @@ -1010,52 +1009,6 @@ function create_polygon(center, r, shape, tl, zx, zy, rotation=0, style="", resi return element; } -// function create_polygon(center, r, shape, tl, zx, zy, rotation=0, style="", resize=false) { -// const n = typeof shape === "number" ? shape : -// {"triangle": 3, -// "square": 4, -// "pentagon": 5, -// "hexagon": 6, -// "heptagon": 7, -// "octogon": 8}[shape]; - -// if (n === undefined) -// throw new Error(`unknown dot shape ${shape}`); - -// const c = view.shape === "rectangular" ? // center point in screen coords -// tree2rect(center, tl, zx, zy) : -// tree2circ(center, tl, zx); - -// // When we put a polygon as a nodedot we don't need the correction, -// // but when used as a face it would look bad without it. -// const correction = resize ? Math.atan(n - 2) * 2 / Math.PI : 1; - -// const s = 2 * r * Math.tan(Math.PI / n) * correction; // side length -// let p = {x: c.x - s/2, // starting point -// y: c.y + r}; - -// const ps = [p]; // polygon points, adding a rotated (s, 0) -// for (let i = 0; i < n - 1; i++) { -// p = {x: p.x + s * Math.cos(i * 2 * Math.PI / n), -// y: p.y - s * Math.sin(i * 2 * Math.PI / n)} -// ps.push(p); -// } - -// const element = create_svg_element("polygon", { -// "points": ps.map(p => `${p.x},${p.y}`).join(" "), -// }); - -// if (view.shape === "circular") { -// const angle = 180 / Math.PI * Math.atan2(zy * tl.y + c.y, -// zx * tl.x + c.x); -// add_rotation(element, angle, c.x, c.y); -// } - -// add_style(element, style); - -// return element; -// } - function create_text(box, anchor, text, fs_max, rotation, tl, zx, zy, style="") {