Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
d324f6e
testclass file push to check access rights
Sep 30, 2025
e556c10
main method work around to debug cause tests in eclipse do not run
Sep 30, 2025
4ca36bd
ComputeFormula first Unit Tests for only root and one feature
Sep 30, 2025
c4cb5ab
formatting, cleanup and a TODO
Sep 30, 2025
b4893a0
gradle spotless and license
Sep 30, 2025
fe41b3d
ComputeFormula:Draft: feature cardinality to cardinality group
KlaSur Oct 1, 2025
5db8f4b
Unit Test for cardinality group
Oct 2, 2025
218d260
feat:translate int and floatt
Oct 2, 2025
a406dc6
fix:translate int and float
Oct 2, 2025
cec5e10
ComputeFormula: Added Visitor for implication chain and cardinality g…
Oct 2, 2025
6665d8f
ComputeFromula:Draft: extension to child feature of cardinality feature
KlaSur Oct 6, 2025
aaf3fca
fix: add missing parentheses for getType() calls
Oct 6, 2025
f4b5ceb
fix: changed featureLiteral to featureFormula
Oct 6, 2025
6803b3c
added simple translation for cardinality features and unit tests acco…
Oct 7, 2025
36eeb65
fix: changed Literal to IFormula
Oct 7, 2025
28780d1
fix within cloneTree(). Missing initialization of an array
Oct 7, 2025
803ed23
feat: Test float, int, boolean for Formula translations
Oct 7, 2025
3491d99
fix: removed empty lines
Oct 7, 2025
aa3a234
remove: comments and lines
Oct 8, 2025
780c006
fix: cast float and changed assertqueals to asserttrue in the test
Oct 8, 2025
bfa7943
feat: attribute aggregates in constraints will be replaced with corre…
Oct 8, 2025
fbeef5f
added recursive tree traversal in ComputeFormula to build cardinality…
Oct 8, 2025
7fdcbee
feat: starting to add support for tikz export
Oct 8, 2025
275c30a
Additional unit test for recursive compute formula
Oct 8, 2025
3ab3cfa
fix of root having a group and cleanup of unit tests
Oct 8, 2025
b81f5a6
finished cardinality translation and additional unit test
Oct 9, 2025
1b2244a
Merge pull request #1 from Malenq/feat/attribute_aggregates
jonasha96 Oct 9, 2025
3604026
Fixes line endings
Oct 9, 2025
660aad7
feat: added test
Oct 9, 2025
3a241d5
changes: removed unused mehtods
Oct 9, 2025
d6875c0
changes: added authors
Oct 9, 2025
d422cf6
feat: added working serializer
Oct 9, 2025
05e4123
pseudo-merge ComputeFormula
Oct 9, 2025
7d1f0df
Merge pull request #2 from Malenq/feature-cardinalityToBool
Malenq Oct 9, 2025
b9e9941
change: placeholder for replace in the process
Oct 9, 2025
4a5a14a
change: small changes - start legend
Oct 9, 2025
3ebee6e
feat: constraints for tikz export
Oct 10, 2025
61ef640
feat: added authors and the legend print with the matrixhelper
Oct 10, 2025
1ffcf20
feat: added matrixhelper for cleaner code
Oct 10, 2025
b3856d3
feat: new test for matrixhelper
Oct 10, 2025
1bdbb08
feat: merge constraints
Oct 10, 2025
98f1dc5
fix: small changes
Oct 10, 2025
5b7d9e3
fix: impl working test
Oct 10, 2025
706c248
feat: added testfile
Oct 10, 2025
72d7854
feat: added tree visitor and comments in the class
Oct 10, 2025
647506e
fix: impl tree visitor and added comments to the class
Oct 10, 2025
0378608
fix: legend output
Oct 13, 2025
125b2db
feat: added attributes to the tikz format
Oct 13, 2025
a6971af
remove: removed unused method
Oct 13, 2025
dd4818a
merged attribute aggregate and translation of cardinality features to…
Oct 13, 2025
9f665bf
Added unit tests for attribute aggregates in compute formula as well …
Oct 14, 2025
bf958c7
better error handling in unit test
Oct 14, 2025
9d2cfcc
feat: added feature cardinalities
Oct 14, 2025
cc81084
Merge branch 'main' into translate_int_float
jonasha96 Oct 15, 2025
b25520f
Merge pull request #3 from Malenq/translate_int_float
jonasha96 Oct 15, 2025
dbe1ac8
feat: added class descirption
Oct 15, 2025
f57f27b
feat: added new attribute test
Oct 15, 2025
9213392
update test file
Oct 15, 2025
50fa621
feat: added attribute helper and comments in the classes.
Oct 15, 2025
7d90e28
feat: simple formula visitor can use numeric features
Oct 15, 2025
01b282e
feat: added new style and new test file
Oct 16, 2025
4808591
fix: correct feature model and added cardinality
Oct 16, 2025
2e0f345
fix: removed value
Oct 16, 2025
3afcdb8
feat: added cardinality and fixed nodes
Oct 16, 2025
8c7ae29
change: renamed classes for a better split
Oct 16, 2025
496c173
refactor: numeric features (integer, float) can be used
Oct 16, 2025
870d1aa
refactor: runtime exception for unsupported feature types
Oct 16, 2025
13750d0
feat: dynamic angle size calculation for groups in latex
Oct 17, 2025
80a90f2
Merge remote-tracking branch 'teamprojekt/translate_int_float' into T…
Oct 17, 2025
3beaa1a
Merge remote-tracking branch 'refs/remotes/teamprojekt/Tikz_Export' i…
Oct 17, 2025
2509783
fix: added correct user function for the attributes and remove underl…
Oct 17, 2025
c4f775d
fix: changed test to running version
Oct 17, 2025
dd13f83
fix: ignore constrains if they are empty
Oct 17, 2025
69a0c74
refactor: added documentation for Features
Oct 17, 2025
b72aa02
fix: filter color with the string and not with the object
Oct 17, 2025
e9477ac
Merge pull request #4 from Malenq/Tikz_Export
jonasha96 Oct 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/java/de/featjar/feature/model/FeatureTree.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ protected FeatureTree(FeatureTree otherFeatureTree) {
feature = otherFeatureTree.feature;
parentGroupID = otherFeatureTree.parentGroupID;
cardinality = otherFeatureTree.cardinality.clone();
childrenGroups = new ArrayList<>(otherFeatureTree.childrenGroups.size());
otherFeatureTree.childrenGroups.stream().map(Group::clone).forEach(childrenGroups::add);
attributeValues = otherFeatureTree.cloneAttributes();
}
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/de/featjar/feature/model/Features.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package de.featjar.feature.model;

import de.featjar.formula.structure.Expressions;
import de.featjar.formula.structure.IFormula;
import de.featjar.formula.structure.predicate.NotEquals;
import de.featjar.formula.structure.term.value.Constant;
import de.featjar.formula.structure.term.value.Variable;

/**
* Defines useful methods to wrap a bool or numeric feature into a IFormula:
* bool: {@link de.featjar.formula.structure.predicate.Literal}
* int: {@link NotEquals 0}
* float: {@link NotEquals 0}
*
* Numeric features are therefore selected, if there value is not 0.
*
* @author Jonas Hanke
*/
public class Features {

public static IFormula createFeatureFormel(IFeature feature) {
return createFeatureFormel(feature, feature.getName().orElse(""));
}

public static IFormula createFeatureFormel(IFeature feature, String featureName) {
if (feature.getType().equals(Boolean.class)) {
return Expressions.literal(featureName);
} else if (feature.getType().equals(Integer.class)) {
return new NotEquals(new Variable(featureName, feature.getType()), new Constant(0));
} else if(feature.getType().equals(Float.class)) {
return new NotEquals(new Variable(featureName, feature.getType()), new Constant(0.0f));
} else {
throw new UnsupportedOperationException("Unsupported feature type: " + feature.getType());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package de.featjar.feature.model.io.tikz;

import de.featjar.base.data.Problem;
import de.featjar.base.data.Result;
import de.featjar.base.io.format.IFormat;
import de.featjar.feature.model.IFeatureModel;
import de.featjar.feature.model.IFeatureTree;
import de.featjar.feature.model.io.tikz.format.TikzHeadFormat;
import de.featjar.feature.model.io.tikz.format.TikzMainFormat;
import de.featjar.feature.model.io.tikz.helper.TikzAttributeHelper;

import java.util.ArrayList;
import java.util.List;

/**
* This class is moved from FeatureIDE to FeatJAR. The former class was written by Simon Wenk and Yang Liu.
* We did some changes, moved in different classes, and we rewrote some code and added new functions.
*
* @author Felix Behme
* @author Lara Merza
* @author Jonas Hanke
*/
public class TikzGraphicalFeatureModelFormat implements IFormat<IFeatureModel> {

public static String LINE_SEPERATOR = System.lineSeparator();

private TikzAttributeHelper.FilterType filterType = TikzAttributeHelper.FilterType.WITH_OUT; // default
private List<String> filterValues = new ArrayList<>(); // default

public TikzGraphicalFeatureModelFormat() {} // default

public TikzGraphicalFeatureModelFormat(TikzAttributeHelper.FilterType filterType, List<String> filterValues) {
this.filterType = filterType;
this.filterValues = filterValues;
}

@Override
public Result<String> serialize(IFeatureModel featureModel) {
StringBuilder stringBuilder = new StringBuilder();
List<Problem> problemList = new ArrayList<>();

stringBuilder.append("\\documentclass[border=5pt]{standalone}").append(LINE_SEPERATOR);
TikzHeadFormat.header(stringBuilder, problemList, false);

stringBuilder
.append("\\begin{document}").append(LINE_SEPERATOR)
.append(" %---The Feature Diagram-----------------------------------------------------").append(LINE_SEPERATOR);
for (IFeatureTree featureTree : featureModel.getRoots()) {
new TikzMainFormat(featureModel, featureTree, stringBuilder, filterType, filterValues).printForest();
}
stringBuilder
.append(LINE_SEPERATOR)
.append("\t%---------------------------------------------------------------------------").append(LINE_SEPERATOR)
.append("\\end{document}");

return Result.of(stringBuilder.toString(), problemList);
}

public void setFilterType(TikzAttributeHelper.FilterType filterType) {
this.filterType = filterType;
}

public void setFilterValues(List<String> filterValues) {
this.filterValues = filterValues;
}

@Override
public boolean supportsWrite() {
return true;
}

@Override
public String getFileExtension() {
return ".tex";
}

@Override
public String getName() {
return "LaTeX-Document with TikZ";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package de.featjar.feature.model.io.tikz.color;

/**
* Color in tikz for later implemntation (colors doesnt exists in features in the moment)
*
* @author Felix Behme
* @author Lara Merza
* @author Jonas Hanke
*/
public enum TikzFeatureColor {

RED("redColor"),
ORANGE("orangeColor"),
YELLOW("yellowColor"),
DARK_GREEN("darkGreenColor"),
LIGHT_GREEN("lightGreenColor"),
CYAN("cyanColor"),
LIGHT_GRAY("lightGrayColor"),
BLUE("blueColor"),
MAGENTA("magentaColor"),
PINK("pinkColor"),
NO_COLOR("");

public static String color(String tikzFeatureColor) {
for (TikzFeatureColor tikzFeatureColors : values()) {
if (tikzFeatureColors.getColor().equalsIgnoreCase(tikzFeatureColor)) {
return tikzFeatureColors.getColor();
}
}
return NO_COLOR.getColor();
}

final String color;

TikzFeatureColor(String color) {
this.color = color;
}

public String getColor() {
return color;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package de.featjar.feature.model.io.tikz.format;

import de.featjar.base.data.Problem;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
* This class prints the header of a LaTex document containing the Tikz definitions.
*
* @author Felix Behme
* @author Lara Merza
* @author Jonas Hanke
*/
public class TikzHeadFormat {

public static void header(StringBuilder stringBuilder, List<Problem> problemList, boolean hasVerticalLayout) {
String replacement = String.format( //
" parent anchor = %s," + System.lineSeparator() //
+ " child anchor = %s," + System.lineSeparator() //
+ "%s" //
+ " l sep = 2em," + System.lineSeparator() //
+ " s sep = 1em," //
+ "%s", //
hasVerticalLayout ? "east" : "south", //
hasVerticalLayout ? "west" : "north", //
hasVerticalLayout ? " grow' = east," + System.lineSeparator() : "", //
hasVerticalLayout ? " tier/.pgfmath=level()," : "");

InputStream inputStream = TikzHeadFormat.class.getClassLoader().getResourceAsStream("head.tex");

if (inputStream == null) {
problemList.add(new Problem("InputStream in header is null / not found"));
return;
}

try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
String line;
while (((line = bufferedReader.readLine()) != null)) {
if (line.contains("{replaceWithVerticalSetting}")) {
line = replacement;
}
stringBuilder.append(line).append("\n");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package de.featjar.feature.model.io.tikz.format;

import de.featjar.base.tree.Trees;
import de.featjar.feature.model.IConstraint;
import de.featjar.feature.model.IFeatureModel;
import de.featjar.feature.model.IFeatureTree;
import de.featjar.feature.model.io.tikz.TikzGraphicalFeatureModelFormat;
import de.featjar.feature.model.io.tikz.helper.TikzAttributeHelper;
import de.featjar.feature.model.io.tikz.helper.TikzMatrixHelper;
import de.featjar.feature.model.io.tikz.helper.TikzMatrixType;
import de.featjar.feature.model.io.tikz.helper.PrintVisitor;
import de.featjar.formula.io.textual.ExpressionSerializer;
import de.featjar.formula.io.textual.LaTexSymbols;

import java.util.List;

/**
* This class generates the Tikz representation of a {@link IFeatureModel} including all constraints ({@link IConstraint}).
*
* @author Felix Behme
* @author Lara Merza
* @author Jonas Hanke
*/
public class TikzMainFormat {

private final IFeatureModel featureModel;
private final IFeatureTree featureTree;
private final StringBuilder stringBuilder;
private final TikzAttributeHelper.FilterType filterType;
private final List<String> filterValues;

public TikzMainFormat(IFeatureModel featureModel , IFeatureTree featureTree, StringBuilder stringBuilder, TikzAttributeHelper.FilterType filterType, List<String> filterValues) {
this.featureModel = featureModel;
this.featureTree = featureTree;
this.stringBuilder = stringBuilder;
this.filterType = filterType;
this.filterValues = filterValues;
}

/**
* Build the complete tree of the FeatureModel.
*/
public void printForest() {
stringBuilder
.append("\\begin{forest}").append(TikzGraphicalFeatureModelFormat.LINE_SEPERATOR)
.append("\tfeatureDiagram").append(TikzGraphicalFeatureModelFormat.LINE_SEPERATOR).append("\t");

PrintVisitor printVisitor = new PrintVisitor(filterType, filterValues);
Trees.traverse(featureTree, printVisitor);
stringBuilder.append(printVisitor.getResult().get());

postProcessing();
stringBuilder.append("\t").append(TikzGraphicalFeatureModelFormat.LINE_SEPERATOR);
if (!featureTree.getFeature().isHidden()) {
printLegend();
}
if(!featureModel.getConstraints().isEmpty()) {
printConstraints();
}
stringBuilder.append("\\end{forest}").append(TikzGraphicalFeatureModelFormat.LINE_SEPERATOR);
}

/**
* Processes a String to make special symbols LaTeX compatible.
*/
private void postProcessing() {
stringBuilder.replace(0, stringBuilder.length(), stringBuilder.toString().replace("_", "\\_"));
}

private void printLegend() {
TikzMatrixHelper tikzMatrixHelper = new TikzMatrixHelper(TikzMatrixType.LEGEND);

if (stringBuilder.indexOf(",abstract") != -1 && stringBuilder.indexOf(",concrete") != -1) {
tikzMatrixHelper.writeNode("[abstract,label=right:Abstract Feature] {}");
tikzMatrixHelper.writeNode("[concrete,label=right:Concrete Feature] {}");
} else if (stringBuilder.indexOf(",abstract") != -1) {
tikzMatrixHelper.writeNode("[abstract,label=right:Feature] {}");
} else if (stringBuilder.indexOf(",concrete") != -1) {
tikzMatrixHelper.writeNode("[concrete,label=right:Feature] {}");
}

if (stringBuilder.indexOf(",mandatory") != -1) {
tikzMatrixHelper.writeNode("[mandatory,label=right:Mandatory] {}");
}

if (stringBuilder.indexOf(",optional") != -1) {
tikzMatrixHelper.writeNode("[optional,label=right:Optional] {}");
}

if (stringBuilder.indexOf(",or") != -1) {
tikzMatrixHelper
.writeFillDraw("(0.1,0) - +(-0,-0.2) - +(0.2,-0.2)- +(0.1,0)")
.writeDraw("(0.1,0) -- +(-0.2, -0.4)")
.writeDraw("(0.1,0) -- +(0.2,-0.4)")
.writeFill("(0,-0.2) arc (240:300:0.2)")
.writeNode("[label=right:Or Group] {}");
}

if (stringBuilder.indexOf(",alternative") != -1) {
tikzMatrixHelper
.writeDraw("(0.1,0) -- +(-0.2, -0.4)")
.writeDraw("(0.1,0) -- +(0.2,-0.4)")
.writeDraw("(0,-0.2) arc (240:300:0.2)")
.writeNode("[label=right:Alternative Group] {}");
}

stringBuilder.append(tikzMatrixHelper.build());
}

private void printConstraints() {
ExpressionSerializer expressionSerializer = new ExpressionSerializer();
expressionSerializer.setEnquoteAlways(true);
expressionSerializer.setSymbols(LaTexSymbols.INSTANCE);

stringBuilder.append(" \\matrix [below=1mm of current bounding box] {").append(TikzGraphicalFeatureModelFormat.LINE_SEPERATOR);
for (IConstraint constraint : featureModel.getConstraints()) {
String text = constraint.getFormula().traverse(expressionSerializer).get();
text = text.replaceAll("\"([\\w\" ]+)\"", " \\\\text\\{$1\\} "); // wrap all words in \text{} // replace with $2
text = text.replaceAll("\\s+", " "); // remove unnecessary whitespace characters
stringBuilder.append(" \\node {\\(").append(text).append("\\)}; \\\\").append(TikzGraphicalFeatureModelFormat.LINE_SEPERATOR);

expressionSerializer.reset();
}
stringBuilder.append(" };").append(TikzGraphicalFeatureModelFormat.LINE_SEPERATOR);
}
}
Loading