001package conexp.fx.core.exporter;
002
003/*
004 * #%L
005 * Concept Explorer FX
006 * %%
007 * Copyright (C) 2010 - 2023 Francesco Kriegel
008 * %%
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as
011 * published by the Free Software Foundation, either version 3 of the
012 * License, or (at your option) any later version.
013 * 
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 * 
019 * You should have received a copy of the GNU General Public
020 * License along with this program.  If not, see
021 * <http://www.gnu.org/licenses/gpl-3.0.html>.
022 * #L%
023 */
024
025import java.io.File;
026import java.io.FileOutputStream;
027import java.io.IOException;
028import java.util.Map;
029
030import javax.xml.transform.Result;
031import javax.xml.transform.Source;
032import javax.xml.transform.Transformer;
033import javax.xml.transform.TransformerException;
034import javax.xml.transform.TransformerFactory;
035import javax.xml.transform.TransformerFactoryConfigurationError;
036import javax.xml.transform.dom.DOMSource;
037import javax.xml.transform.stream.StreamResult;
038
039import org.apache.batik.dom.svg.SVGDOMImplementation;
040import org.w3c.dom.DOMImplementation;
041import org.w3c.dom.Document;
042import org.w3c.dom.Element;
043
044import conexp.fx.core.context.Concept;
045import conexp.fx.core.context.MatrixContext;
046import conexp.fx.core.layout.AdditiveConceptLayout;
047
048public class SVGExporter<G, M> {
049
050  public static <G, M> void export(
051      MatrixContext<G, M> context,
052      Map<Integer, Integer> domainPermutation,
053      Map<Integer, Integer> codomainPermutation,
054      AdditiveConceptLayout<G, M> layout,
055      boolean exportArrows,
056      boolean exportLabels,
057      File file) {
058    try {
059      if (!file.exists()) {
060        if (!file.getParentFile().exists())
061          file.mkdirs();
062        file.createNewFile();
063      }
064      Document doc = toSVGDocument(
065          file.getName(),
066          context,
067          domainPermutation,
068          codomainPermutation,
069          layout,
070          exportArrows,
071          exportLabels);
072      Transformer transformer = TransformerFactory.newInstance().newTransformer();
073      Result output = new StreamResult(new FileOutputStream(file));
074      Source input = new DOMSource(doc);
075      transformer.transform(input, output);
076    } catch (TransformerFactoryConfigurationError | TransformerException | IOException e) {
077      System.err.println("Unable to create or write SVGDocument to file " + file);
078      e.printStackTrace();
079    }
080  }
081
082  private static <G, M> Document toSVGDocument(
083      String name,
084      MatrixContext<G, M> formalContext,
085      Map<Integer, Integer> domainPermutation,
086      Map<Integer, Integer> codomainPermutation,
087      AdditiveConceptLayout<G, M> layout,
088      boolean exportArrows,
089      boolean exportLabels) {
090    final double width = 100d * layout.getCurrentBoundingBox(false, false).getWidth();
091    final double minX = 100d * layout.getCurrentBoundingBox(false, false).getMinX();
092    final double height = 100d * layout.getCurrentBoundingBox(false, false).getHeight();
093    // final double unit = Math.min(160 / width, 230 / height);
094    final int border = 100;
095    final int circleSize = 16;
096    final int textOffset = 12;
097    DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
098    String svgNS = SVGDOMImplementation.SVG_NAMESPACE_URI;
099    Document doc = impl.createDocument(svgNS, "svg", null);
100    Element svgRoot = doc.getDocumentElement();
101    svgRoot.setAttributeNS(null, "width", String.valueOf((int) width + border));
102    svgRoot.setAttributeNS(null, "height", String.valueOf((int) height + border));
103    for (int i = 0; i < layout.lattice.rowHeads().size(); i++) {
104      for (int j = 0; j < layout.lattice.rowHeads().size(); j++) {
105        if (layout.lattice._contains(i, j)) {
106          final int x1 = (int) (100d * layout.getPosition(layout.lattice.rowHeads().get(i)).getValue().getX()
107              - minX + (border / 2));
108          final int y1 = (int) (100d * layout.getPosition(layout.lattice.rowHeads().get(i)).getValue().getY()
109              + (border / 2));
110          final int x2 = (int) (100d * layout.getPosition(layout.lattice.rowHeads().get(j)).getValue().getX()
111              - minX + (border / 2));
112          final int y2 = (int) (100d * layout.getPosition(layout.lattice.rowHeads().get(j)).getValue().getY()
113              + (border / 2));
114          Element line = doc.createElementNS(svgNS, "line");
115          line.setAttributeNS(null, "x1", String.valueOf(x1));
116          line.setAttributeNS(null, "y1", String.valueOf(y1));
117          line.setAttributeNS(null, "x2", String.valueOf(x2));
118          line.setAttributeNS(null, "y2", String.valueOf(y2));
119          line.setAttributeNS(null, "stroke-width", "4");
120          line.setAttributeNS(null, "stroke", "grey");
121          svgRoot.appendChild(line);
122        }
123      }
124    }
125    for (int i = 0; i < layout.lattice.rowHeads().size(); i++) {
126      Concept<G, M> conceptNode = layout.lattice.rowHeads().get(i);
127      final double x = 100d * layout.getPosition(conceptNode).getValue().getX();
128      final double y = 100d * layout.getPosition(conceptNode).getValue().getY();
129      Element circle = doc.createElementNS(svgNS, "circle");
130      circle.setAttributeNS(null, "cx", String.valueOf((int) (x - minX) + (border / 2)));
131      circle.setAttributeNS(null, "cy", String.valueOf((int) y + (border / 2)));
132      circle.setAttributeNS(null, "r", String.valueOf(circleSize));
133      svgRoot.appendChild(circle);
134    }
135    if (exportLabels)
136      for (int i = 0; i < layout.lattice.rowHeads().size(); i++) {
137        Concept<G, M> conceptNode = layout.lattice.rowHeads().get(i);
138        String objLabels = layout.lattice.objectLabels(conceptNode).toString().substring(
139            1,
140            layout.lattice.objectLabels(conceptNode).toString().length() - 1);
141        String attLabels = layout.lattice.attributeLabels(conceptNode).toString().substring(
142            1,
143            layout.lattice.attributeLabels(conceptNode).toString().length() - 1);
144        final int x =
145            (int) (100d * layout.getPosition(layout.lattice.rowHeads().get(i)).getValue().getX() - minX);
146        final int y = (int) (100d * layout.getPosition(layout.lattice.rowHeads().get(i)).getValue().getY());
147        final int ox = x + (border / 2);
148        final int oy = y + (circleSize + textOffset) + (border / 2) + 10;
149        final int ax = x + (border / 2);
150        final int ay = y - (circleSize + textOffset) + (border / 2);
151        Element objText = doc.createElementNS(svgNS, "text");
152        objText.setAttributeNS(null, "x", String.valueOf(ox));
153        objText.setAttributeNS(null, "y", String.valueOf(oy));
154        objText.setAttributeNS(null, "text-anchor", "middle");
155        objText.setTextContent(objLabels);
156        svgRoot.appendChild(objText);
157        Element attText = doc.createElementNS(svgNS, "text");
158        attText.setAttributeNS(null, "x", String.valueOf(ax));
159        attText.setAttributeNS(null, "y", String.valueOf(ay));
160        attText.setAttributeNS(null, "text-anchor", "middle");
161        attText.setTextContent(attLabels);
162        svgRoot.appendChild(attText);
163      }
164    return doc;
165  }
166}