001/*
002 * @author Francesco.Kriegel@gmx.de
003 */
004package conexp.fx.core.exporter;
005
006/*
007 * #%L
008 * Concept Explorer FX
009 * %%
010 * Copyright (C) 2010 - 2023 Francesco Kriegel
011 * %%
012 * This program is free software: you can redistribute it and/or modify
013 * it under the terms of the GNU General Public License as
014 * published by the Free Software Foundation, either version 3 of the
015 * License, or (at your option) any later version.
016 * 
017 * This program is distributed in the hope that it will be useful,
018 * but WITHOUT ANY WARRANTY; without even the implied warranty of
019 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
020 * GNU General Public License for more details.
021 * 
022 * You should have received a copy of the GNU General Public
023 * License along with this program.  If not, see
024 * <http://www.gnu.org/licenses/gpl-3.0.html>.
025 * #L%
026 */
027
028import java.io.BufferedWriter;
029import java.io.File;
030import java.io.FileWriter;
031import java.io.IOException;
032import java.util.Map;
033
034import conexp.fx.core.context.Concept;
035import conexp.fx.core.context.MatrixContext;
036import conexp.fx.core.layout.AdditiveConceptLayout;
037
038public class TeXExporter<G, M> {
039
040  public enum ScaleEnum {
041    Fit,
042    FitWidth,
043    FitHeight,
044    FitRatio;
045
046    public TeXExporter.ScaleOption toOption(final int w, final int h) {
047      switch (this) {
048      case Fit:
049        return new TeXExporter.FitScale(w, h);
050      case FitWidth:
051        return new TeXExporter.FitWidthScale(w);
052      case FitHeight:
053        return new TeXExporter.FitHeightScale(h);
054      case FitRatio:
055        return new TeXExporter.FitRatioScale(w, h);
056      }
057      return null;
058    }
059  }
060
061  public static abstract class ScaleOption {
062
063    protected TeXExporter.ScaleEnum scale;
064    protected double                widthInMillimeter, heightInMillimeter;
065
066    public abstract double widthFactor(double dWidth, double dHeight);
067
068    public abstract double heightFactor(double dWidth, double dHeight);
069  }
070
071  public static final class FitScale extends TeXExporter.ScaleOption {
072
073    public FitScale(final int widthInMillimeter, final int heightInMillimeter) {
074      super();
075      this.scale = TeXExporter.ScaleEnum.Fit;
076      this.widthInMillimeter = widthInMillimeter;
077      this.heightInMillimeter = heightInMillimeter;
078    }
079
080    @Override
081    public final double widthFactor(final double dWidth, final double dHeight) {
082      return widthInMillimeter / dWidth;
083    }
084
085    @Override
086    public final double heightFactor(final double dWidth, final double dHeight) {
087      return heightInMillimeter / dHeight;
088    }
089  }
090
091  public static final class FitRatioScale extends TeXExporter.ScaleOption {
092
093    public FitRatioScale(final int widthInMillimeter, final int heightInMillimeter) {
094      super();
095      this.scale = TeXExporter.ScaleEnum.FitRatio;
096      this.widthInMillimeter = widthInMillimeter;
097      this.heightInMillimeter = heightInMillimeter;
098    }
099
100    @Override
101    public final double widthFactor(final double dWidth, final double dHeight) {
102      final double w = widthInMillimeter / dWidth;
103      final double h = heightInMillimeter / dHeight;
104      return Math.min(w, h);
105    }
106
107    @Override
108    public final double heightFactor(final double dWidth, final double dHeight) {
109      return widthFactor(dWidth, dHeight);
110    }
111  }
112
113  public static final class FitHeightScale extends TeXExporter.ScaleOption {
114
115    public FitHeightScale(final int heightInMillimeter) {
116      super();
117      this.scale = TeXExporter.ScaleEnum.FitHeight;
118      this.heightInMillimeter = heightInMillimeter;
119    }
120
121    @Override
122    public final double widthFactor(final double dWidth, final double dHeight) {
123      return heightFactor(dWidth, dHeight);
124    }
125
126    @Override
127    public final double heightFactor(final double dWidth, final double dHeight) {
128      return heightInMillimeter / dHeight;
129    }
130  }
131
132  public static final class FitWidthScale extends TeXExporter.ScaleOption {
133
134    public FitWidthScale(final int widthInMillimeter) {
135      super();
136      this.scale = TeXExporter.ScaleEnum.FitWidth;
137      this.widthInMillimeter = widthInMillimeter;
138    }
139
140    @Override
141    public final double widthFactor(final double dWidth, final double dHeight) {
142      return widthInMillimeter / dWidth;
143    }
144
145    @Override
146    public final double heightFactor(final double dWidth, final double dHeight) {
147      return widthFactor(dWidth, dHeight);
148    }
149  }
150
151  public static final class TeXOptions {
152
153    public File                    file              = null;
154    public boolean                 arrows            = false;
155    public boolean                 labels            = true;
156    public boolean                 standAlone        = false;
157    public ContextTeXPackage       contextTeXPackage = ContextTeXPackage.Tabular;
158    public DiagramTeXPackage       diagramTeXPackage = DiagramTeXPackage.ConExpFX;
159    public TeXExporter.ScaleOption scale             = null;
160
161    public TeXOptions(
162        File file,
163        boolean arrows,
164        boolean labels,
165        boolean standAlone,
166        ContextTeXPackage contextTeXPackage,
167        DiagramTeXPackage diagramTeXPackage,
168        TeXExporter.ScaleOption scale) {
169      super();
170      this.file = file;
171      this.arrows = arrows;
172      this.labels = labels;
173      this.standAlone = standAlone;
174      this.contextTeXPackage = contextTeXPackage;
175      this.diagramTeXPackage = diagramTeXPackage;
176      this.scale = scale;
177    }
178  }
179
180  public enum ContextTeXPackage {
181    None,
182    Ganter,
183    Tabular
184  }
185
186  public enum DiagramTeXPackage {
187    None,
188    Ganter,
189    ConExpFX
190  }
191
192  private final MatrixContext<G, M>         formalContext;
193  private final Map<Integer, Integer>       objectPermutation;
194  private final Map<Integer, Integer>       attributePermutation;
195  private final AdditiveConceptLayout<G, M> conceptLayout;
196  private final TeXExporter.TeXOptions      teXOptions;
197  private final StringBuffer                buffer = new StringBuffer();
198
199  public TeXExporter(
200      final MatrixContext<G, M> formalContext,
201      final Map<Integer, Integer> objectPermutation,
202      final Map<Integer, Integer> attributePermutation,
203      final AdditiveConceptLayout<G, M> conceptLayout,
204      final TeXExporter.TeXOptions teXOptions) {
205    this.formalContext = formalContext;
206    this.objectPermutation = objectPermutation;
207    this.attributePermutation = attributePermutation;
208    this.conceptLayout = conceptLayout;
209    this.teXOptions = teXOptions;;
210  }
211
212  public final void export() throws IOException {
213    final BufferedWriter writer = createFile();
214    newLine();
215    if (teXOptions.standAlone) {
216      appendHeader();
217      newLine();
218      newLine();
219    }
220    switch (teXOptions.contextTeXPackage) {
221    case None:
222      break;
223    case Ganter:
224      appendGanterContext();
225      break;
226    case Tabular:
227      appendTabularContext();
228      break;
229    }
230    newLine();
231    newLine();
232    switch (teXOptions.diagramTeXPackage) {
233    case None:
234      break;
235    case Ganter:
236      appendGanterDiagram();
237      break;
238    case ConExpFX:
239      appendConExpFXDiagram();
240      break;
241    }
242    newLine();
243    newLine();
244    if (teXOptions.standAlone) {
245      appendFooter();
246      newLine();
247      newLine();
248    }
249    writer.append(buffer);
250    writer.close();
251  }
252
253  private final BufferedWriter createFile() throws IOException {
254    if (!teXOptions.file.exists()) {
255      if (!teXOptions.file.getParentFile().exists())
256        teXOptions.file.mkdirs();
257      teXOptions.file.createNewFile();
258    }
259    return new BufferedWriter(new FileWriter(teXOptions.file));
260  }
261
262  private final void append(final String string) {
263    buffer.append(string);
264  }
265
266  private final void newLine() {
267    append("\r\n");
268  }
269
270  private final void appendHeader() {
271    // TODO Auto-generated method stub
272
273  }
274
275  private final void appendFooter() {
276    // TODO Auto-generated method stub
277
278  }
279
280  private final void appendGanterContext() {
281    append("\\begin{cxt}\r\n\\cxtName{" + teXOptions.file.getName() + "}\r\n");
282    final int js = formalContext.colHeads().size();
283    final int is = formalContext.rowHeads().size();
284    for (int j = 0; j < js; j++) {
285      final int jj =
286          attributePermutation == null || !attributePermutation.containsKey(j) ? j : attributePermutation.get(j);
287      final M m = formalContext.colHeads().get(jj);
288      append("\\atr{" + (teXOptions.labels ? m : "") + "}\r\n");
289    }
290    for (int i = 0; i < is; i++) {
291      final int ii = objectPermutation == null || !objectPermutation.containsKey(i) ? i : objectPermutation.get(i);
292      final G g = formalContext.rowHeads().get(ii);
293      String incidences = "";
294      for (int j = 0; j < js; j++) {
295        final int jj =
296            attributePermutation == null || !attributePermutation.containsKey(j) ? j : attributePermutation.get(j);
297        final M m = formalContext.colHeads().get(jj);
298        if (formalContext.contains(g, m)) {
299          incidences += "X";
300        } else if (teXOptions.arrows) {
301          final boolean isDownArrow = formalContext.DownArrows.contains(g, m);
302          final boolean isUpArrow = formalContext.UpArrows.contains(g, m);
303          if (isDownArrow) {
304            if (isUpArrow) {
305              incidences += "b";
306            } else {
307              incidences += "d";
308            }
309          } else {
310            if (isUpArrow) {
311              incidences += "u";
312            } else {
313              incidences += ".";
314            }
315          }
316        } else {
317          incidences += ".";
318        }
319      }
320      append("\\obj{" + incidences + "}{" + (teXOptions.labels ? g : "") + "}\r\n");
321    }
322    append("\\end{cxt}\r\n");
323  }
324
325  private final void appendTabularContext() {
326    final int is = formalContext.rowHeads().size();
327    final int js = formalContext.colHeads().size();
328    append("{\\setlength{\\tabcolsep}{1pt}");
329    newLine();
330    if (js == 0)
331      append("\\begin{tabular}{r@{\\hspace{3pt}}|}");
332    else if (js == 1)
333      append("\\begin{tabular}{r@{\\hspace{3pt}}| c|}");
334    else
335      append("\\begin{tabular}{r@{\\hspace{3pt}}| *{" + (js - 1) + "}{c} c|}");
336    newLine();
337    append("\\multicolumn{1}{c}{}");
338    newLine();
339    for (int j = 0; j < js; j++) {
340      final int jj =
341          attributePermutation == null || !attributePermutation.containsKey(j) ? j : attributePermutation.get(j);
342      final M m = formalContext.colHeads().get(jj);
343      append("&\\multicolumn{1}{c}{\\begin{turn}{90}" + (teXOptions.labels ? m : "") + "\\end{turn}}");
344      newLine();
345    }
346    append("\\\\ \\cline{2-" + (js + 1) + "}");
347    newLine();
348    for (int i = 0; i < is; i++) {
349      final int ii = objectPermutation == null || !objectPermutation.containsKey(i) ? i : objectPermutation.get(i);
350      final G g = formalContext.rowHeads().get(ii);
351      append((teXOptions.labels ? g : "") + "");
352      for (int j = 0; j < js; j++) {
353        final int jj =
354            attributePermutation == null || !attributePermutation.containsKey(j) ? j : attributePermutation.get(j);
355        final M m = formalContext.colHeads().get(jj);
356        if (formalContext.contains(g, m)) {
357          append("& $\\times$ ");
358        } else if (teXOptions.arrows) {
359          final boolean isDownArrow = formalContext.DownArrows.contains(g, m);
360          final boolean isUpArrow = formalContext.UpArrows.contains(g, m);
361          if (isDownArrow) {
362            if (isUpArrow) {
363              append("& $\\Doppelpfeil$ ");
364            } else {
365              append("& $\\Runterpfeil$ ");
366            }
367          } else {
368            if (isUpArrow) {
369              append("& $\\Hochpfeil$ ");
370            } else {
371              append("& $\\cdot$ ");
372            }
373          }
374        } else {
375          append("& $\\cdot$ ");
376        }
377      }
378      if (i == is - 1)
379        append("\\\\ \\cline{2-" + (js + 1) + "}");
380      else
381        append("\\\\");
382      newLine();
383    }
384    append("\\end{tabular}}");
385    newLine();
386  }
387
388  private final void appendGanterDiagram() {
389    final double width = conceptLayout.getCurrentBoundingBox(false, false).getWidth();
390    final double minX = conceptLayout.getCurrentBoundingBox(false, false).getMinX();
391    final double height = conceptLayout.getCurrentBoundingBox(false, false).getHeight();
392    final double w = teXOptions.scale.widthFactor(width, height);
393    final double h = teXOptions.scale.heightFactor(width, height);
394    final double unit = (teXOptions.scale.scale == TeXExporter.ScaleEnum.FitHeight ? h
395        : (teXOptions.scale.scale == TeXExporter.ScaleEnum.FitWidth ? w : Math.min(w, h)));
396    append("\\begin{diagram}{" + width + "}{" + height + "}\r\n");
397    append("\\unitlength " + unit + "mm\r\n");
398    append("\\CircleSize{1}\r\n");
399    append("\\NodeThickness{1pt}\r\n");
400    append("\\EdgeThickness{1pt}\r\n");
401    for (int i = 0; i < conceptLayout.lattice.rowHeads().size(); i++) {
402      final Concept<G, M> concept = conceptLayout.lattice.rowHeads().get(i);
403      final double x = conceptLayout.getPosition(concept).getValue().getX();
404      final double y = conceptLayout.getPosition(concept).getValue().getY();
405      append("\\Node{" + i + "}{" + (x - minX) + "}{" + (height - y) + "}\r\n");
406    }
407    for (int i = 0; i < conceptLayout.lattice.rowHeads().size(); i++) {
408      for (int j = 0; j < conceptLayout.lattice.rowHeads().size(); j++) {
409        if (conceptLayout.lattice._contains(i, j))
410          append("\\Edge{" + i + "}{" + j + "}\r\n");
411      }
412    }
413    if (teXOptions.labels)
414      for (int i = 0; i < conceptLayout.lattice.rowHeads().size(); i++) {
415        final Concept<G, M> concept = conceptLayout.lattice.rowHeads().get(i);
416        final String objLabels = conceptLayout.lattice
417            .objectLabels(concept)
418            .toString()
419            .substring(1, conceptLayout.lattice.objectLabels(concept).toString().length() - 1)
420            .trim();
421        final String attLabels = conceptLayout.lattice
422            .attributeLabels(concept)
423            .toString()
424            .substring(1, conceptLayout.lattice.attributeLabels(concept).toString().length() - 1)
425            .trim();
426        if (!objLabels.isEmpty())
427          append("\\centerObjbox{" + i + "}{0}{1}{" + objLabels + "}\r\n");
428        if (!attLabels.isEmpty())
429          append("\\centerAttbox{" + i + "}{0}{1}{" + attLabels + "}\r\n");
430      }
431    append("\\end{diagram}\r\n");
432  }
433
434  private final void appendConExpFXDiagram() {
435    final double width = conceptLayout.getCurrentBoundingBox(false, false).getWidth();
436    final double height = conceptLayout.getCurrentBoundingBox(false, false).getHeight();
437    final double minX = conceptLayout.getCurrentBoundingBox(false, false).getMinX();
438    final double w = teXOptions.scale.widthFactor(width, height);
439    final double h = teXOptions.scale.heightFactor(width, height);
440    append("\\begin{conceptdiagram}[x=" + w + "mm, y=" + h + "mm]\r\n");
441    append("\\setlength{\\nodesize}{3mm}\r\n");
442    append("\\setlength{\\edgewidth}{0.4pt}\r\n");
443    append("\\setlength{\\labeldistance}{1pt}\r\n");
444    for (int i = 0; i < conceptLayout.lattice.rowHeads().size(); i++) {
445      final Concept<G, M> concept = conceptLayout.lattice.rowHeads().get(i);
446      final double x = conceptLayout.getPosition(concept).getValue().getX();
447      final double y = conceptLayout.getPosition(concept).getValue().getY();
448      append("\\conceptnode{" + i + "}{" + (x - minX) + "}{" + (height - y) + "}\r\n");
449    }
450    for (int i = 0; i < conceptLayout.lattice.rowHeads().size(); i++) {
451      for (int j = 0; j < conceptLayout.lattice.rowHeads().size(); j++) {
452        if (conceptLayout.lattice._contains(i, j))
453          append("\\conceptedge{" + i + "}{" + j + "}\r\n");
454      }
455    }
456    if (teXOptions.labels)
457      for (int i = 0; i < conceptLayout.lattice.rowHeads().size(); i++) {
458        final Concept<G, M> concept = conceptLayout.lattice.rowHeads().get(i);
459        final String objLabels = conceptLayout.lattice
460            .objectLabels(concept)
461            .toString()
462            .substring(1, conceptLayout.lattice.objectLabels(concept).toString().length() - 1)
463            .trim();
464        final String attLabels = conceptLayout.lattice
465            .attributeLabels(concept)
466            .toString()
467            .substring(1, conceptLayout.lattice.attributeLabels(concept).toString().length() - 1)
468            .trim();
469        if (!attLabels.isEmpty())
470          append("\\attributelabel{" + i + "}{" + attLabels + "}\r\n");
471        if (!objLabels.isEmpty())
472          append("\\objectlabel{" + i + "}{" + objLabels + "}\r\n");
473      }
474    append("\\end{conceptdiagram}\r\n");
475  }
476}