今日もプログラミング

IT技術とかプログラミングのこととか特にJavaを中心に書いていきます

POIでコネクタを描いてみる

POIでコネクタは描けるのか?

SIerExcelが大好きなので、設計書もExcelで作ることが多い(いいか悪いかは置いといて…)。

でも手で全部書くのは面倒なので、Javaのソースから自動生成できると便利だなー、とか思うことがある。

設計書には、オートシェイプとかコネクタを使って図も入れたい。

だけど、POIでコネクタは描けるんだろうか?

 

一応それらしいメソッドはあったが…

Javadocを覗くと、XSSFDrawingクラスにcreateConnectorというメソッドがあった。

これっぽいんだけど…、こいつの引数はXSSFClientAnchorなんだよね。

つまり、座標を指定するんだけど、接続先の図形は指定しなくていいの?

とりあえず、コードを書いて実行してみる。

    try (Workbook workbook = new XSSFWorkbook()) {
        Sheet sheet = workbook.createSheet();
        XSSFDrawing drawing = (XSSFDrawing)sheet.createDrawingPatriarch();

        XSSFClientAnchor anchor1 = drawing.createAnchor(0, 0, 400000, 100000, 1, 1, 1, 1);
        XSSFTextBox textBox1 = drawing.createTextbox(anchor1);
        textBox1.setLineWidth(1);
        textBox1.setLineStyleColor(255, 0, 0);
        textBox1.setFillColor(255, 255, 0);

        XSSFClientAnchor anchor2 = drawing.createAnchor(0, 0, 400000, 100000, 2, 3, 2, 3);
        XSSFTextBox textBox2 = drawing.createTextbox(anchor2);
        textBox2.setLineWidth(1);
        textBox2.setLineStyleColor(255, 0, 0);
        textBox2.setFillColor(255, 255, 0);

        XSSFClientAnchor anchor3 = drawing.createAnchor(200000, 100000, 200000, 0, 1, 1, 2, 3);
        XSSFConnector connector = drawing.createConnector(anchor3);
        connector.setLineWidth(1);
        connector.setLineStyleColor(128, 0, 0);

        File file = new File("connector.xlsx");
        try (OutputStream out = new FileOutputStream(file)) {
            workbook.write(out);
        }
    }

一応それらしいのはできたんだけど…

f:id:hito4_t:20150515175906p:plain

テキストボックスを動かしてみると、線が付いてこない。

f:id:hito4_t:20150515180416p:plain

これは「コネクタ」じゃなくて、ただの「線」だ。

 

コネクタXMLを見てみる

とは言え、XSSFConnectorにそれらしいメソッドは見当たらない。

ただ、XMLレベルでいじれるメソッド(getCTConnector)はあるので、これを使えば何とかなるかも。

という訳で、コネクタが保存されたxlsxファイルのXML構造を調べてみる。

上で作成したxlsxファイルの他、ちゃんとコネクタの接点をつなげたxlsxファイルを用意して、拡張子をzipに変えて展開、xl/drawings/drawing1.xmlを見比べてみる。

すると、後者だけにある怪しい要素が見つかった。

<xdr:wsdr>
    <xdr:twoCellAnchor>
        <xdr:cxnSp>
            <xdr:nvCxnSpPr>
                <xdr:cNvCxnSpPr>
                    <a:stCxn id="2" idx="2"/>
                    <a:endCxn id="3" idx="0"/>
                </xdr:cNvCxnSpPr>
            ...
    

おそらく、idは図形を特定するID、idxは図形における接点の場所(たぶん上が0で下が2)だろう。

 

IDの謎

で、以下のようなコードを追加したんだけど…、うまくいかない…。

    connector.getCTConnector().getNvCxnSpPr().getCNvCxnSpPr().addNewStCxn().setId(textBox1.getCTShape().getNvSpPr().getCNvPr().getId());
    connector.getCTConnector().getNvCxnSpPr().getCNvCxnSpPr().getStCxn().setIdx(2);
    connector.getCTConnector().getNvCxnSpPr().getCNvCxnSpPr().addNewEndCxn().setId(textBox2.getCTShape().getNvSpPr().getCNvPr().getId());
    connector.getCTConnector().getNvCxnSpPr().getCNvCxnSpPr().getEndCxn().setIdx(0);

いろいろ調べてみると、どうもIDがおかしいっぽい。

正しいxlsxの方は、

テキストボックス1 = 2
テキストボックス2 = 3
コネクタ = 4

なんだけど、POIの方は

テキストボックス1 = 1
テキストボックス2 = 2
コネクタ = 1

になっている。

IDが重複しているのがおかしいし、正しいxlsxでは1が欠番になっている。

以下のようにしたら、どうやらうまくいったようだ。

    textBox1.getCTShape().getNvSpPr().getCNvPr().setId(2);
    textBox2.getCTShape().getNvSpPr().getCNvPr().setId(3);
    connector.getCTConnector().getNvCxnSpPr().getCNvPr().setId(4);
    connector.getCTConnector().getNvCxnSpPr().getCNvCxnSpPr().addNewStCxn().setId(textBox1.getCTShape().getNvSpPr().getCNvPr().getId());
    connector.getCTConnector().getNvCxnSpPr().getCNvCxnSpPr().getStCxn().setIdx(2);
    connector.getCTConnector().getNvCxnSpPr().getCNvCxnSpPr().addNewEndCxn().setId(textBox2.getCTShape().getNvSpPr().getCNvPr().getId());
    connector.getCTConnector().getNvCxnSpPr().getCNvCxnSpPr().getEndCxn().setIdx(0);

ちゃんとつながっている!

f:id:hito4_t:20150515190742p:plain

とは言え、また今回も謎が残ってしまった…。

 

OS Windows 7
Office 2010
Java 8u31
Apache POI 3.11