Skip to content
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

problems rendering an SVG? #1866

Closed
jonathan-taylor opened this issue Jun 6, 2012 · 33 comments · Fixed by #4250
Closed

problems rendering an SVG? #1866

jonathan-taylor opened this issue Jun 6, 2012 · 33 comments · Fixed by #4250
Milestone

Comments

@jonathan-taylor
Copy link
Contributor

In a local branch, I seem to have run into a possible rendering problem with the notebook. The branch is:

https://github.com/jonathan-taylor/ipython/tree/Rdevice

The last cell of docs/notebooks/examples/rmagic_extension.ipynb is mangled in my browser but when I extract the svg from the .ipynb file and view it with chrome it looks fine.

I used the following script to extract the svg data from the notebook:

import simplejson
r = simplejson.load(open('docs/examples/notebooks/rmagic_extension.ipynb'))
svgcell = r.values()[1][0]['cells'][-2]
svgdata = svgcell['outputs'][0]
raw_svg = '\n'.join(svgdata['svg'] )
file('last_plot.svg','w').write(raw_svg)
@fperez
Copy link
Member

fperez commented Jun 6, 2012

@takluyver, since you're poking into the R magic machinery, perhaps you have some ideas? I'd also like to ping @stefanv, who also saw some SVG weirdness with the octave magic, in case this is similar. Unfortunately right now I'm busy finishing a report that's due so I can't look at this code quite right now.

@takluyver
Copy link
Member

No immediate ideas, but I'll try to remember to look at it.

@stefanv
Copy link
Contributor

stefanv commented Jun 7, 2012

I can reproduce the problem, but it's not the same one I had with the Octave backend. I can't see anything obvious causing it.

@stefanv
Copy link
Contributor

stefanv commented Jun 7, 2012

It turns out that SVGs embedded the way IPython currently does all live inside the same DOM. This means that when any single SVG modifies styles, those changes are propagated to the rest. One solution is to encode SVGs the same way PNGs and JPEGs are, as base64 encoded data chunks, and add them to an <img> tag.

I've followed this approach here, and it now renders correctly:

https://github.com/stefanv/ipython/tree/svg_as_image_blob

The above changes made me realize that there's special casing and duplicate code for png/jpeg, so we may want to look at cleaning that up as well eventually.

@takluyver
Copy link
Member

Would that mean they're also saved in the ipynb file as base64 encoded data? It seems a bit ugly to turn XML into unreadable base64 strings, although I guess SVG from plotting isn't going to be very readable anyway.

@stefanv
Copy link
Contributor

stefanv commented Jun 7, 2012

Yes, unfortunately.

@jonathan-taylor
Copy link
Contributor Author

Let me see if I understand: with multiple SVG files in the document, when one changes some of its xml representation (say, its axis labels), the browser propagates these changes to the next SVG plot?

@fperez
Copy link
Member

fperez commented Jun 8, 2012

@jonathan-taylor, yes apparently at least some style elements do persist that way. Unfortunately as I pointed out to @stefanv, I'm not sure encoding is a good idea: it seems to kill the scriptability support of SVG, and that would immediately rule out things like interactive d3.js figures. That sounds like throwing away the baby with the bathwater.

We should try to find a solution to this that doesn't preclude things like interactive d3.js svg objects. @minrk, you know a lot about graphics stuff, any thoughts on this front?

@satra
Copy link
Contributor

satra commented Aug 25, 2012

before i open a new issue, is the following related:

i'm trying to display some svg and possibly missing something obvious. the command executes without error but nothing is displayed.

from IPython.core.display import display_svg
display_svg(url='http://en.wikipedia.org/wiki/File:Correlation_examples2.svg')

sys info:

{'commit_hash': '5308c36',
 'commit_source': 'installation',
 'default_encoding': 'UTF-8',
 'ipython_path': '/software/virtualenvs.EPD/7.2/devpype/lib/python2.7/site-packages/IPython',
 'ipython_version': '0.14.dev',
 'os_name': 'posix',
 'platform': 'Darwin-12.0.0-x86_64-i386-64bit',
 'sys_executable': '/software/virtualenvs.EPD/7.2/devpype/bin/python',
 'sys_platform': 'darwin',
 'sys_version': '2.7.2 |CUSTOM| (default, Sep  7 2011, 16:31:15) \n[GCC 4.0.1 (Apple Inc. build 5493)]'}
chrome-dev

@lgautier
Copy link
Contributor

I reached this page when investigating a plotting issue with the ipython notebook and rpy2: it turns out to be the same problem.

Shouldn't SVG rendering the last possible rendering option by default, until the problem is resolved ? (that is iPython would try first to display PNG, JPEG, or other possible alternatives).

@Carreau
Copy link
Member

Carreau commented May 29, 2013

This would be a shame to deactivate svg if they are resolution independent. we can try to wrap them in a IFrame.

In the meantime you can locally monkey patch notebook from js console/custom.js:

IPython.OutputArea.display_order has the following display order by default ['javascript','html','latex','svg','png','jpeg','text']; you can mess with it if you wish.

Just set it to another value and new output will follow the new preference.

@lgautier
Copy link
Contributor

Right now we have resolution-independent figures, only garbled as soon as there is more than one figure in a notebook. ;-)

I'd trade those for PNGs with a reasonable resolution any day.

@stefanv
Copy link
Contributor

stefanv commented May 29, 2013

@lgautier Yes, multiple figures in a notebook is such specialized use, isn't it :)

@m3nu
Copy link

m3nu commented Sep 7, 2013

I'm seeing the issue as well. The dev-version of igraph renders SVG and the second plot has messed-up labels.

@pablooliveira
Copy link
Contributor

This annoys me so I have looked a bit into this issue.
Yet I'm not sure this is a priority right now given http://mail.scipy.org/pipermail/ipython-dev/2012-June/009754.html

A simple test reproducing the problem is

<html>
<head>
</head>
<body>

    <!-- First inline SVG with inline style block -->
    <svg width="10cm" height="5cm" viewBox="0 0 1000 500">
    <defs>
    <style><![CDATA[

        rect {
            fill: red;
            stroke: blue;
            stroke-width: 3
        }
        ]]></style>
    </defs>
    <rect x="200" y="100" width="600" height="300"/>
    </svg>

    <!-- Second SVG -->
    <svg width="10cm" height="5cm" viewBox="0 0 1000 500">
    <rect x="200" y="100" width="600" height="300"/>
    </svg>

</body>
</html>

Both rectangles are red because the style of the first svg leaks into the second svg.
This is correct according to the w3c specification at http://www.w3.org/TR/SVG/styling.html#Scope
which reads:

Stand-alone SVG content textually included in an XML document
There is a single parse tree, using multiple namespaces; one or more subtrees are in the SVG namespace. Style sheets defined anywhere within the XML document (in style elements or style attributes, or in external style sheets linked with the style sheet processing instruction) apply across the entire document, including those parts of it in the SVG namespace. To get different styling for the SVG part, use the ‘style’ attribute, or put an ‘id’ on the ‘svg’ element and use contextual CSS selectors, or use XSL selectors.

So what can we do about it ? I have explored the following solutions:

  1. Wrap the svg into an img or object tag. The inconvenient, as mentioned by @fperez, is that we loose the nice xml representation of the svg.

  2. Wrap the svg in an iframe as mentioned by @Carreau. The inconvenient is that we have to serve the svg data from another url.

  3. One could imagine injecting the style using javascript after the css is computed for the other elements.
    Yet from what I've looked, it seems it's not possible to change the inline style attribute using javascript.

  4. One could follow the w3c recommendation and add an unique id selector per svg figure in front of every inline style rule. This would provide a unique namespace for each svg figure. The problem is that this requires parsing the css inline block which seems hard and error prone.

  5. Resetting the css adding a set of default css rules before each style block is very hard because of css inheritance rules.

  6. The ideal solution would be to use the <style scoped > html 5 extension. (http://www.w3.org/TR/html- markup/style.html#style.attrs.scoped). The scoped attribute restricts the css scope to be local.

I think approach 6 is probably the more elegant one. The problem is that scoped is planned but not yet included into SVG2 specification (http://www.w3.org/TR/SVG2/styling.html#Scope). Also html scoped is
still very experimental and not supported in major browsers.

I have successfully experimented the following approach:

Using this the above code would become:

<html>
    <head>
        <script src="http://code.jquery.com/jquery-1.9.1.js"></script>
        <script src="jquery.scoped.js"></script>
    </head>

    <body>

    <div>
        <style scoped >
            rect {
                fill: red;
                stroke: blue;
                stroke-width: 3
            }
        </style>

        <svg width="10cm" height="5cm" viewBox="0 0 1000 500">
        <defs>
        </defs>
        <rect x="200" y="100" width="600" height="300"/>
        </svg>
    </div>

        <svg width="10cm" height="5cm" viewBox="0 0 1000 500">
        <rect x="200" y="100" width="600" height="300"/>
        </svg>

    <script>
        $.scoped();
    </script>

</body>
</html>

which renders correctly.

Another maybe more pragmatical solution would be to embed svg into an iframe or object for now, and use inline svg when browsers are ready.

What do you think ?
Is there any interest in fixing this issue now or should we wait for the svg support to mature ?

Thanks,

Pablo

@stefanv
Copy link
Contributor

stefanv commented Sep 18, 2013

When you write "not supported in major browsers", are you saying that that the scoped style fix won't help at the moment?

@pablooliveira
Copy link
Contributor

See this table http://caniuse.com/style-scoped for the full details. In a nutshell, current versions of Chrome and IE do not support scoped style.

But this jquery plugin https://github.com/thingsinjars/jQuery-Scoped-CSS-plugin plugin enables scoped style on current browser (while disabling itself on browsers with scoped support).

@stefanv
Copy link
Contributor

stefanv commented Sep 19, 2013

The jQuery plugin then sounds like a viable approach to me.

@Carreau
Copy link
Member

Carreau commented Sep 19, 2013

This is an expérimental feature that can be enabled in chrome://flags

Envoyé de mon iPhone

Le 19 sept. 2013 à 11:02, Stefan van der Walt notifications@github.com a écrit :

The jQuery plugin then sounds like a viable approach to me.


Reply to this email directly or view it on GitHub.

pablooliveira added a commit to pablooliveira/ipython that referenced this issue Sep 19, 2013
When multiple SVG figures are inlined in the same dom, they
share the same css namespace. This patch, scopes each SVG's style
CSS rules so they do not leak into other figures.

Should help fixing ipython#1866
@pablooliveira
Copy link
Contributor

Actually, I realized #4236 does not fix the initial issue (rendering https://github.com/jonathan-taylor/ipython/tree/Rdevice). My reduced test case is too simple and possibly exhibits a different problem. Something else is going on with the original example, but I have no clue for the moment.

EDIT:

It seems the initial issue in https://github.com/jonathan-taylor/ipython/tree/Rdevice is not due to style leaks, but to xlink:href reused identifiers.

The SVG spec allows to reuse elements defined inside a <defs> section by inserting a <use> element (http://www.w3.org/TR/SVG/struct.html#UseElement).

The use element references the origin element thanks to a xlink:href attribute.
For example:

 <defs>
    <rect id="MyRect" width="60" height="10"/>
  </defs>
  ...
  <use x="20" y="10" xlink:href="#MyRect" />

The problem in the rmagic_extension.ipynb notebook is that different SVG figures are redefining the same ids.
Therefore the xlink:href has multiple targets.

Making the ids and xlink:href unique (for example prepending a unique hash) solves the issue.

Nevertheless this is getting hairy, maybe wrapping the svg in an img/iframe/object is the best option for now ?

@ellisonbg
Copy link
Member

The basic message here is that SVG is just like any other HTML - ids have to be global on the page. I consider it to be a bug in other's libraries if they are writing SVG like this. It would be the same if pandas was writing HTML tables that couldn't be on the page with other pandas HTML tables. I would tell them to 1) use classes instead of ids or 2) use ids that are always unique on a page. I am -1 on the iframe fix.

@minrk
Copy link
Member

minrk commented Sep 23, 2013

I think the argument is that most of the time, SVGs are treated like images - they don't all exist on the same page, but are sourced from separate URLs, avoiding any conflict. it's a bit unusual that we inline SVGs rather than treating them individually as separate documents, which any normal webpage would do.

@pablooliveira
Copy link
Contributor

Yes, @minrk is right: most of the time SVGs are treated as images so the programs producing SVGs do not need to produce unique ids. Even Inkscape may produce SVGs with colliding ids.

This means that, right now, SVG support in IPython notebook is broken in most non-trivial usages.
While one could debate that it's upstream responsibility to generate unique ids [*], I do not think that is a pragmatic solution in the mean time.

[*] I'm not sure it's upstream responsibility to guarantee a clean namespace (btw, non trivial problem, would you use UUID ?) but the application that inlines multiple contents.

@ellisonbg
Copy link
Member

I agree that is the traditional model of SVG. But in the modern web, where the Notebook lives, SVG in understodd to be "just part of the DOM". Libraries such as d3 fully embrace this way of thinking about SVG. I know that IPython can't completely ignore the old way of think about SVGs, I am just not ready to throw all SVG in iframes, as it seriously retricts the more modern usages cases by sandboxing SVG away from the rest of the code on the page.

@takluyver
Copy link
Member

I thought we were discussing using the 'scoped' option for styles along
with a JQuery plugin for browser compatibility, not iframes.

@pablooliveira
Copy link
Contributor

@ellisonbg: I agree, but to encourage the new Web SVG we should encourage adoption of SVG by providing solid support.

We could by default inline SVGs and provide a compatibility mode for old SVGs.
For example with

SVG(url="xxx", scoped=True). 

What do you think?

@stefanv
Copy link
Contributor

stefanv commented Sep 25, 2013

Another option could be to parse the SVGs and replace IDs with some image
specific identifier + the old ID.

@ellisonbg
Copy link
Member

+1

Sent from my iPhone

On Sep 24, 2013, at 6:32 PM, Stefan van der Walt notifications@github.com wrote:

Another option could be to parse the SVGs and replace IDs with some image
specific identifier + the old ID.

Reply to this email directly or view it on GitHub.

@pablooliveira
Copy link
Contributor

@stefanv:

Another option could be to parse the SVGs and replace IDs with some image specific identifier + the old ID.

At first I though this was the way to go (see ~11 comments above :) ), but now I think it's a bad idea because:

  1. It has the same downside that the iframe, it breaks interaction from other elements in the page such as global CSS or Javascript. That is because the CSS ID rules do not match anymore, and the Javascript cannot
    access the elements anymore, because the IDs have been dynamically rewritten.
    At least with the iframe solution we can embed d3.js code or CSS rules in the
    iframe and they still work on the SVG.

  2. The implementation is much more error prone and costly than the iframe: it requires parsing the SVG and there are many tricky cases for IDs.

@Carreau
Copy link
Member

Carreau commented Sep 25, 2013

Agreed that this is suboptimal to search and replace ids.

I think this would be easier to "fix" once we have the plug-able mechanism for arbitrary mimetype.
It would be easier to dispatch to another handler based on some metadata.

We should also try to think that as a language agnostic issue.

@pablooliveira
Copy link
Contributor

I updated #4250 to disable SVG iframe wrapping by default.
But it can be enabled by the user by calling the code.display.SVG constructor with scoped=True.

I agree with @Carreau that using a special mimetype for this would be nice, whenever support for that comes.

In the meantime, the proposed patch adds a class descriptor to the svg named "ipython-scoped".
It fixes the SVG issues without being very intruding in my opinion.

@Carreau

We should also try to think that as a language agnostic issue.

Could you please clarify this point?

@Carreau
Copy link
Member

Carreau commented Sep 25, 2013

We should also try to think that as a language agnostic issue.
Could you please clarify this point?

The notebook format as well as the message specs try to make as little assumption as possible from the fact that
the kernel is IPython. In particular the biggest use of SVG is made by the Julia kernel and their plotting library that by default send SVG. They probably can't (or don't want) to modify the SVG on the fly to add classes (especially if thoses classes start with IPython.

So knowing that usually you can send from the kernel (<- did not write python) either (data) or (data,metadata). Could you think of a mechanism that allow to decide wether svg should be scoped/iframed/embeded/whatever.

You can add more question to that like :
- Is this mechanism also easy applicable to nbconvert (and the different format it can output) ?
- Does this modify data (if it does then it is probably bad as other application might try to use data and crash on them.) ?
- Can I fallback gracefully if my application does not support metadata in ipynb file ?

@ellisonbg
Copy link
Member

Let discuss this in the lab meeting tomorrow...

On Wed, Sep 25, 2013 at 9:06 AM, Matthias Bussonnier <
notifications@github.com> wrote:

We should also try to think that as a language agnostic issue.
Could you please clarify this point?

The notebook format as well as the message specs try to make as little
assumption as possible from the fact that
the kernel is IPython. In particular the biggest use of SVG is made by the
Julia kernel and their plotting library that by default send SVG. They
probably can't (or don't want) to modify the SVG on the fly to add classes
(especially if thoses classes start with IPython.

So knowing that usually you can send from the kernel (<- did not write
python) either (data) or (data,metadata). Could you think of a mechanism
that allow to decide wether svg should be scoped/iframed/embeded/whatever.

You can add more question to that like :

  • Is this mechanism also easy applicable to nbconvert (and the different
    format it can output) ?
  • Does this modify data (if it does then it is probably bad as other
    application might try to use data and crash on them.) ?
  • Can I fallback gracefully if my application does not support metadata in
    ipynb file ?


Reply to this email directly or view it on GitHubhttps://github.com//issues/1866#issuecomment-25100849
.

Brian E. Granger
Cal Poly State University, San Luis Obispo
bgranger@calpoly.edu and ellisonbg@gmail.com

pablooliveira added a commit to pablooliveira/ipython that referenced this issue Oct 29, 2013
When multiple inline SVGs are included in a single document,
they share the same parse tree. Therefore, style collisions and
use id collisions can occur and upset the rendering.

This patch wraps each SVG inside an individual iframe, ensuring
that SVG's declarations do not collide.

(The SVG representation is kept as XML and not converted to a binary
format, so I do not think this approach precludes the use of d3.js)

Tested on:
* Chrome Version 29.0.1547.57 Debian 7.1 (217859)
* Mozilla/5.0 (X11; Linux x86_64; rv:17.0) Gecko/20130806 Firefox/17.0 Iceweasel/17.0.8

Closes ipython#1866
@minrk minrk added this to the 2.0 milestone Mar 26, 2014
mattvonrocketstein pushed a commit to mattvonrocketstein/ipython that referenced this issue Nov 3, 2014
When multiple inline SVGs are included in a single document,
they share the same parse tree. Therefore, style collisions and
use id collisions can occur and upset the rendering.

This patch wraps each SVG inside an individual iframe, ensuring
that SVG's declarations do not collide.

(The SVG representation is kept as XML and not converted to a binary
format, so I do not think this approach precludes the use of d3.js)

Tested on:
* Chrome Version 29.0.1547.57 Debian 7.1 (217859)
* Mozilla/5.0 (X11; Linux x86_64; rv:17.0) Gecko/20130806 Firefox/17.0 Iceweasel/17.0.8

Closes ipython#1866
betteridiot added a commit to betteridiot/seqlogo that referenced this issue Aug 14, 2019
This biggest issue here is that the vanilla notebooks treated SVGs as images, such that the IDs used in their generation were not treated as unique. Therefore, when painted onto the DOM, since SVGs could have non-unique IDs, they would collide with each other. This isn't really seen in jupyterlab because of how it does rendering, but this issues was originally seen in ipython/ipython#1866. I fixed this by quickly parsing the SVG data and inserting the original hash of the data into the unique ids. Kind of a pain, but it fixes this problem
DentonGentry added a commit to ProjectDrawdown/solutions that referenced this issue Feb 8, 2020
Work around ipython/ipython#1866 in
Jupyter Notebook by prepending a unique slug to the id fields within
the SVG.

This also requires updating any filter, marker-start, and marker-end nodes
which have a 'url(#id)' reference to refer to the beslugged version.

If ever you see an issue where the SVG on the Model tab is fine but the
rest are broken in some way, suspect a cross-SVG ID referencing issue.
The Model tab is the first one rendered and will "win" any such
conflict.

JupyterLab renders SVGs within an iframe and is not impacted. Only
Notebook (and Voila) really need to have the IDs uniquified. As it
doesn't hurt JupyterLab to do this, we do so unconditionally.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.