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}