使JSVGScrollPane中的JSVGCanvas与SVGDocument大小匹配

时间:2012-05-31 18:13:27

标签: java swing svg batik

我在图表应用程序中显示SVG绘图时遇到问题。画布采用其父组件的大小,而不是它包含的文档。大于此大小的文档未完全呈现。

案例

我有一个绘图,例如,621x621像素。

<?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>

它没有viewBox,因为它是动态创建的。当我创建文档时,我没有大小。

我在JSVGCanvas内有JSVGScrollPane,滚动窗格的父级比文档小,可以说是500x500像素。

预期行为

启动应用程序时,可以看到适合500x500的图形部分。底层文档更大,因此有滚动条允许我向左和向下滚动121px。

当您增加框架的大小时,越来越多的图形变得可见。当您将其增加到大于620x620px时,滚动条可能会完全消失。

如果再次使面板变小,则再次出现滚动条。

遇到的行为

启动应用程序时,可以看到适合500x500的图形部分。没有滚动条可见。

当您将面板放大时,画布会增加(背景颜色),但绘图不会扩展到初始500x500px之外的部分。

当你将面板缩小时,让我们说400x400,滚动条会出现,但只允许我滚动100px。所以画布仍然是最初的500x500而不是所需的621x621。

示例

可以使用Batik存储库中的ScrollExample和我上面添加的SVG文件重现遇到的行为。该示例使用固定大小500x500px。

如果要在画布和滚动窗格中添加背景颜色,则整个框架为蓝色。因此,虽然画布确实增加了尺寸,但它不会绘制超出其初始尺寸的任何东西。

canvas.setBackground(Color.blue);
scroller.setBackground(Color.red);

当我重新调整面板大小时,我可以重置文档。这使得它重绘了绘图的可见部分,但滚动仍然没有按预期工作,因为画布仍然只是可见部分的大小而不是文档的大小。你可以通过将这个监听器添加到框架(或我想的任何其他面板)来测试这一点。

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
    }

    // ...
};

我已经读过我应该有一个viewBox,但我的文档经常被重新创建(基于一个modelchange-event),当我创建文档时,我不知道如何获取viewBox。 SVGDocument.getRootElement().getBBox();通常会给我null。当没有给出viewBox时,应该在Batik代码中出现回退,所以我希望这是可选的。此外,当我在某处播放时,如果我更改文档,它应该保持平移。

我希望我能清楚地解决问题。当我期望他们提供开箱即用的我想要的行为或者我在这里遗漏了什么时,我是否期望过多的JSVG课程?有人可以指导我寻求解决方案吗?

1 个答案:

答案 0 :(得分:1)

我想我可能找到了解决方案。我没有搞乱viewBox,而是需要设置文档的高度和宽度。设置大小后,不需要viewBox,滚动条按预期工作。如果我设置了一个视图框,画布会不断缩放图像以使其适合。

现在,为了更新尺寸,每当我更改可能影响尺寸的dom时,我都会使用它。

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() + "");
}

我在UpdateManager线程的,在dom修改后我使用JSVGCanvas.setSVGDocument()。当我尝试通过UpdateManager执行此操作时,它仅更新了第一个更改。对于updatequeue的正确初始化,可能有一个解决方法,但由于我更改了很多文档,我每次都可以重新开始。

我留下了一个小问题。每当我设置新文档时,都会重置滚动位置。我尝试在操作之前进行转换并在之后重新应用它,但这似乎不起作用。

修改+ : 我设法通过替换文档的根节点而不是替换整个文档来解决这个小问题。画布使用文档状态ALWAYS_DYNAMIC进行设置。立即应用文档中的更改,包括根节点的新大小(如上所述设置)。但由于文档未完全替换,因此保留了滚动状态。

画布只有在完成渲染文档后才能修改dom。我使用SVGLoad-listener来检测它。

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;
    }

    // ...
}