Reputation: 3209
I am having a problem with displaying my SVG drawing in an diagramming application. The canvas takes the size of its parent component, instead of the document it contains. Documents that are bigger than this size are not completely rendered.
Case
I have a drawing that is, for example, 621x621 pixels.
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" contentScriptType="text/ecmascript" zoomAndPan="magnify" contentStyleType="text/css" preserveAspectRatio="xMaxYMax slice" version="1.0">
<rect x="50" y="50" fill="white" width="20" height="20" stroke="black" stroke-width="1"/>
<rect x="600" y="600" fill="blue" width="20" height="20" stroke="black" stroke-width="1"/>
</svg>
It has no viewBox because it is created dynamically. When I'm creating the document I don't have the size.
I have a JSVGCanvas
inside a JSVGScrollPane
, the parent of the scrollpane is smaller then the document, lets say its 500x500 pixels.
Expected behaviour
When you launch the application the part of the drawing that fits in the 500x500 is visible. The underlying document is bigger so there are scrollbars that allow me to scroll 121px to the left and to below.
When you increase the size of the frame, more and more of the drawing becomes visible. When you increase it to be greater than 620x620px the scrollbars can disappear completely.
If you make the panel smaller then the document again, the scrollbars reappear.
Encountered behaviour
When you launch the application the part of the drawing that fits in the 500x500 is visible. No scrollbars are visible.
When you make the panel bigger, the canvas is increased (its background color) but the drawing is not extended to the part that's outside the initial 500x500px.
When you make the panel smaller, lets say to 400x400, scrollbars do appear, but only allow me to scroll 100px. So the canvas still is the initial 500x500 instead of the desired 621x621.
Example
The encountered behaviour can be reproduced with the ScrollExample
on the Batik repository and the SVG-file i've added above. The example uses a fixed size of 500x500px.
If you'd add background colors to the canvas and scrollpane, the entire frame is blue. So while the canvas does increase in size, it does not draw anything beyond its initial size.
canvas.setBackground(Color.blue);
scroller.setBackground(Color.red);
I could reset the document when I've re-sized the panel. This makes it redraw the visible part of the drawing, but the scrolling is still not working as expected, since the canvas is still just the size of the visible portion instead of the size of the document. You can test this by adding this listener to the frame (or any other panel I suppose).
class ResizeListener implements ComponentListener {
JSVGCanvas canvas;
URI url;
public ResizeListener(JSVGCanvas c, URI u) {
canvas = c;
url = u;
}
@Override
public void componentResized(ComponentEvent arg0) {
canvas.setURI(url.toString());
// Calling invalidate on canvas or scroller does not work
}
// ...
};
I have read that I should have a viewBox, but my document is recreated often (based on a modelchange-event) and when I'm creating the document, i don't know how to get the viewBox. SVGDocument.getRootElement().getBBox();
usually gives me null
. There are supposed to be fallbacks in the Batik code when no viewBox is given, so I hope this is optional. Also when I'm panned somewhere, it should keep this panning if I change the document.
I hope i'm making my problem clear. Am I expecting too much of the JSVG-classes when I expect them to provide my desired behaviour out of the box or am I missing something here? Could someone please guide me towards a solution?
Upvotes: 2
Views: 1961
Reputation: 3209
I think I might have found a solution. Instead of messing with viewBox I need to set the height and width of the document. With the size set, no viewBox is necessary and the scrollbars work as expected. If I do set a viewbox, the canvas keeps scaling the image to make it fit.
Now, for updating the size I use this whenever I change the dom that could have influence on the size.
static BridgeContext ctx = new BridgeContext(new UserAgentAdapter());
static GVTBuilder builder = new GVTBuilder();
private static void calculateSize(SVGDocument doc) {
GraphicsNode gvtRoot = builder.build(ctx, doc);
Rectangle2D rect = gvtRoot.getSensitiveBounds();
doc.getRootElement().setAttributeNS(null,
SVGConstants.SVG_WIDTH_ATTRIBUTE, rect.getMaxX() + "");
doc.getRootElement().setAttributeNS(null,
SVGConstants.SVG_HEIGHT_ATTRIBUTE, rect.getMaxY() + "");
}
I do this outside of the UpdateManager
thread and after the dom modification I use JSVGCanvas.setSVGDocument()
. When I tried to do this via the UpdateManager
it only updated the first change. There is probably a fix for this with the right initialization of the updatequeue, but since I change a lot of the document, I could just as well start fresh every time.
I am left with one minor issue. Whenever i set a new document, the scroll position is reset. I tried getting the transformation before the manipulation and reapply it afterwards, but this doesn't seem to work.
Edit+: I managed to fix this minor issue by replacing the root node of the document rather than replacing the entire document. The canvas is set with the documentstate ALWAYS_DYNAMIC. The changes in the document are applied immediately, including the new size of the root node (set as described above). But because the document is not entirely replaced the scroll state is kept.
The canvas is only ready for modification of the dom after it has finished rendering a document. I used the SVGLoad-listener to detect this.
class Canvas extends JSVGCanvas implements SVGLoadEventDispatcherListener {
public Canvas() {
super();
setDocumentState(ALWAYS_DYNAMIC);
addSVGLoadEventDispatcherListener(this);
}
/**
* Indicatates whether the canvas has finished its first render, the
* canvas is now ready for modification of the dom
*/
private boolean isReadyForModification = false;
/**
* Renew the document by replacing the root node with the one of the new
* document
*
* @param doc The new document
*/
public void renewDocument(final SVGDocument doc) {
if (isReadyForModification) {
getUpdateManager().getUpdateRunnableQueue().invokeLater(
new Runnable() {
@Override
public void run() {
// Get the root tags of the documents
Node oldRoot = getSVGDocument().getFirstChild();
Node newRoot = doc.getFirstChild();
// Make the new node suitable for the old
// document
newRoot = getSVGDocument().importNode(newRoot,
true);
// Replace the nodes
getSVGDocument().replaceChild(newRoot, oldRoot);
}
});
} else {
setSVGDocument(doc);
}
}
@Override
public void svgLoadEventDispatchCompleted(SVGLoadEventDispatcherEvent e) {
isReadyForModification = true;
}
// ...
}
Upvotes: 1