-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding to SVG output #3086
Comments
If someone were to do this themselves, parsing the SVG as XML or whatever and updating using upstream libraries, what would it look like? |
Good question. I guess it might involve either knowing the structure of the SVG, or doing what I have previously done and embedding both SVGs in another SVG. The problem with this is that it doesn't automatically work in a notebook. So e.g.: from IPython.display import SVG
svg = tree.draw_svg(**params)
legend = "".join([ # as before
f'<g transform="translate(40, {20 + 15*p.id})" class="node p{p.id}"><rect width="6" height="6" class="sym" /><text x="10" y="7">{p.name} (id={p.id})</text></g>'
for p in model.populations
])
## New stuff (untested)
# Somehow extract the height and width from the current SVG
h, w = some_function_to_get_h_w(svg)
# make an SVG tag: I always have to look up the syntax for this
svg_start = f'<svg baseProfile="full" height="{h}" version="1.1" width="{w}" xmlns="http://www.w3.org/2000/svg">'
display(SVG(svg_start + svg + legend + "</svg>")) |
Right, but using a proper html/svg/xml library what does it look like? My issue here is that you need to know a lot about SVG anyway to usefully tweak these things, so why don't we figure out how to get people using more powerful upstream tools for this rather than rolling our own? |
Well, I was hoping that this might open the way to allow this sort of functionality for people who don't know about SVG. In particular, you can embed one SVG within another, so my_legend = some_library.generate_svg_legend(**params)
tree.draw_svg().embed(my_legend) Otherwise, as you say, you would need to use a specific SVG library (I've never really used any, so the below is a rough guess). This certainly would require you to know how SVG works. svg = tree.draw_svg()
obj = some_svg_lib.parse(svg)
my_legend = some_library.generate_svg_legend(**params)
obj.add_to_root(my_legend) # or maybe you would embed both the `svg` and `my_legend` components inside another svg object?
display(SVG(obj.as_string)) |
I mean, this isn't super important, but it would mean that (a) making nice docs is easier (especially as we wouldn't need document how to import a specific external SVG library, which might become obsolete etc) and (b) I suspect that it would make it quite a lot easier to produce readable one-off plots in notebooks, e.g. like the sc2ts notebooks we are trying to make public. It's a tiny bit like saying "should we have a |
Sure, sounds good, just checking. Would probably be easier to use the minidom parser than to be faddling around with event driven stuff, though. |
Ah neat, I didn't know about that. But the html.parser approach means you don't need to parse the entire SVG first (you just want to grab the very first from xml.etree.ElementTree import XMLPullParser
class SVGString(str):
...
def embed(self, svg_to_embed):
"""
Embed additional svg markup at the start of the svg string (immediately after the initial
<svg ...> tag). This could include standard tags, or even a nested <svg> component.
"""
parser = XMLPullParser(['start'])
start = 0
while start < len(self):
end = self.find('>', start) # feed up to potential end-of-tag into parser
if end == -1:
break
parser.feed(self[start:(end + 1)])
start = end + 1
parser.flush()
for event, elem in parser.read_events():
if elem.tag == "svg" or elem.tag.endswith("}svg"):
return tskit.drawing.SVGString(self[:start] + svg_to_embed + self[start:])
raise ValueError("No starting <svg> tag found in SVGstring") I'll also check with Ben if he thinks the idea of adding a method to the tskit SVGstring class is the best approach here. Another possibility would be to add (yet another) parameter to the class Tree:
...
def draw_svg(
...
output = draw.drawing.tostring()
if extra_svg is not None
first_tag = get_first_tag(output)
output = first_tag + extra_svg + output[len(first_tag):]
... |
I don't see anything wrong with adding another parameter here to |
There are a number of use-cases for adding material to tskit tree drawings. In particular I'm thinking about legends, but this would also be useful for annotations for tutorials etc. I am unsure if adding the extra code to the tskit drawing module would be worth the maintenance burden, but it seems pretty nice to be able to do something like the following in a notebook:
This would also allow multiple SVGs trees to be shown side-by-side, which can be helpful
Note that this isn't done by changing the SVG drawing routine (which is hard, and already fairly overloaded with functionality), but can be implemented by adding a method to the
tskit.drawing.SVGstring
class that adds arbitrary text after the<svg>
tag. The code below does so relatively efficiently using Python's built-in html parser:Is this too much to add into the tskit code base? If so, we can simply close this issue.
The text was updated successfully, but these errors were encountered: