");
+ Generator.run();
+ }
+}
diff --git a/generator2/org.getaviz.generator/src/org/getaviz/generator/Helper.xtend b/generator2/org.getaviz.generator/src/org/getaviz/generator/Helper.xtend
new file mode 100644
index 000000000..850c81112
--- /dev/null
+++ b/generator2/org.getaviz.generator/src/org/getaviz/generator/Helper.xtend
@@ -0,0 +1,44 @@
+package org.getaviz.generator
+
+//import org.apache.commons.logging.LogFactory
+import org.apache.commons.lang3.StringUtils
+import org.getaviz.generator.city.m2m.RGBColor
+
+class Helper {
+// val log = LogFactory::getLog(class)
+// val typesMap = newHashMap('String' -> 128.0, 'byte' -> 8.0, 'short' -> 16.0, 'int' -> 32.0,
+// 'long' -> 64.0, 'float' -> 32.0, 'double' -> 64.0, 'boolean' -> 4.0, 'char' -> 16.0)
+
+ def getGradient(double value) {
+ val red = 255 * value
+ val green = 255 * (1 - value)
+ val color = new RGBColor(red, green, 0)
+ return color
+ }
+ /**
+ * Creates hash as (hopefully) unique ID for every FAMIXElement
+ *
+ * @param fqn full qualified name of FAMIXElement
+ * @return sha1 hash
+ *
+ */
+
+ def removeBrackets(String[] array) {
+ return removeBrackets(array.toString)
+ }
+
+ def removeBrackets(String string) {
+ return StringUtils::remove(StringUtils::remove(string, "["), "]")
+ }
+
+ def removeApostrophes(String string) {
+ return StringUtils::remove(StringUtils::remove(string,"'"),"")
+ }
+
+ def checkNull(String string) {
+ if(string === null) {
+ return ""
+ }
+ return string
+ }
+}
diff --git a/generator2/org.getaviz.generator/src/org/getaviz/generator/OutputFormatHelper.xtend b/generator2/org.getaviz.generator/src/org/getaviz/generator/OutputFormatHelper.xtend
new file mode 100644
index 000000000..bee7997aa
--- /dev/null
+++ b/generator2/org.getaviz.generator/src/org/getaviz/generator/OutputFormatHelper.xtend
@@ -0,0 +1,90 @@
+package org.getaviz.generator
+
+import org.getaviz.generator.SettingsConfiguration.BuildingType
+import org.getaviz.generator.city.m2m.CityLayout
+
+class OutputFormatHelper {
+ val config = SettingsConfiguration.getInstance();
+
+ def String X3DHead() '''
+
+
+
+
+
+
+
+
+
+ '''
+
+ def String settingsInfo() '''
+
+ '''
+
+ def String viewports() '''
+ «var rootEntity = CityLayout::rootRectangle»
+ «var width = rootEntity.width»
+ «var length = rootEntity.length»
+
+
+
+
+
+
+ '''
+
+ def String X3DTail() '''
+
+
+
+ '''
+
+ def String AFrameHead() '''
+
+
+
+
+ Ring
+
+
+
+
+
+
+ '''
+
+ def String AFrameTail() '''
+
+
+
+ '''
+}
diff --git a/generator2/org.getaviz.generator/src/org/getaviz/generator/SettingsConfiguration.java b/generator2/org.getaviz.generator/src/org/getaviz/generator/SettingsConfiguration.java
new file mode 100644
index 000000000..2834aaa9e
--- /dev/null
+++ b/generator2/org.getaviz.generator/src/org/getaviz/generator/SettingsConfiguration.java
@@ -0,0 +1,758 @@
+package org.getaviz.generator;
+
+import java.io.File;
+import java.awt.Color;
+
+import org.apache.commons.configuration2.PropertiesConfiguration;
+import org.apache.commons.configuration2.builder.fluent.Configurations;
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.getaviz.generator.SettingsConfiguration.Bricks.Layout;
+import org.getaviz.generator.SettingsConfiguration.Original.BuildingMetric;
+import org.getaviz.generator.SettingsConfiguration.Panels.SeparatorModes;
+
+public class SettingsConfiguration {
+ private static PropertiesConfiguration config;
+ private static SettingsConfiguration instance = null;
+
+ public static SettingsConfiguration getInstance() {
+ if (instance == null) {
+ instance = new SettingsConfiguration();
+ loadConfig("settings.properties");
+ }
+ return instance;
+ }
+
+ public static SettingsConfiguration getInstance(String path) {
+ if (instance == null) {
+ instance = new SettingsConfiguration();
+ }
+ loadConfig(path);
+ return instance;
+ }
+
+ private static void loadConfig(String path) {
+ File file = new File(path);
+ try {
+ Configurations configs = new Configurations();
+ config = configs.properties(file);
+ } catch (ConfigurationException cex) {
+ System.out.println(cex);
+ }
+ }
+
+ public void loadDefault() {
+ loadConfig("settings.properties");
+ }
+
+ public Metaphor getMetaphor() {
+ String metaphor = config.getString("metaphor", "rdr");
+ switch (metaphor) {
+ case "city":
+ return Metaphor.CITY;
+ default:
+ return Metaphor.RD;
+ }
+ }
+
+ public String getOutputPath() {
+ return config.getString("output.path", "./output/");
+ }
+
+ public String getRepositoryName() {
+ return config.getString("history.repository_name", "");
+ }
+
+ public String getRepositoryOwner() {
+ return config.getString("history.repository_owner", "");
+ }
+
+ public String getDatabaseName() {
+ return config.getString("database_name", "../databases/graph.db");
+ }
+
+ public OutputFormat getOutputFormat() {
+ switch (config.getString("output.format", "x3d")) {
+ case "aframe":
+ return OutputFormat.AFrame;
+ default:
+ return OutputFormat.X3D;
+ }
+ }
+
+ public String getBuildingTypeAsString() {
+ return config.getString("city.building_type", "original");
+ }
+
+ public BuildingType getBuildingType() {
+ String value = config.getString("city.building_type", "original");
+ switch (value) {
+ case "panels":
+ return BuildingType.CITY_PANELS;
+ case "bricks":
+ return BuildingType.CITY_BRICKS;
+ case "floor":
+ return BuildingType.CITY_FLOOR;
+ default:
+ return BuildingType.CITY_ORIGINAL;
+ }
+ }
+
+ public Schemes getScheme() {
+ String value = config.getString("city.scheme", "types");
+ switch (value) {
+ case "visibility":
+ return Schemes.VISIBILITY;
+ default:
+ return Schemes.TYPES;
+ }
+ }
+
+ public ClassElementsModes getClassElementsMode() {
+ String value = config.getString("city.class_elements_mode", "methods_and_attributes");
+ switch (value) {
+ case "methods_only":
+ return ClassElementsModes.METHODS_ONLY;
+ case "attributes_only":
+ return ClassElementsModes.ATTRIBUTES_ONLY;
+ default:
+ return ClassElementsModes.METHODS_AND_ATTRIBUTES;
+ }
+ }
+
+ public ClassElementsSortModesCoarse getClassElementsSortModeCoarse() {
+ String value = config.getString("city.class_elements_sort_mode_coarse", "methods_first");
+ switch (value) {
+ case "unsorted":
+ return ClassElementsSortModesCoarse.UNSORTED;
+ case "attributes_first":
+ return ClassElementsSortModesCoarse.ATTRIBUTES_FIRST;
+ default:
+ return ClassElementsSortModesCoarse.METHODS_FIRST;
+ }
+ }
+
+ public ClassElementsSortModesFine getClassElementsSortModeFine() {
+ String value = config.getString("city.elements_sort_mode_fine", "scheme");
+ switch (value) {
+ case "unsorted":
+ return ClassElementsSortModesFine.UNSORTED;
+ case "alphabetically":
+ return ClassElementsSortModesFine.ALPHABETICALLY;
+ case "nos":
+ return ClassElementsSortModesFine.NOS;
+ default:
+ return ClassElementsSortModesFine.SCHEME;
+ }
+ }
+
+ public boolean isClassElementsSortModeFineDirectionReversed() {
+ return config.getBoolean("city.class_elements_sort_mode_fine_direction_reversed", false);
+ }
+
+ public boolean isShowBuildingBase() {
+ return config.getBoolean("city.building_base", true);
+ }
+
+ public Layout getBrickLayout() {
+ String brickLayout = config.getString("city.brick.layout", "progressive");
+ switch (brickLayout) {
+ case "straight":
+ return Layout.STRAIGHT;
+ case "balanced":
+ return Layout.BALANCED;
+ default:
+ return Layout.PROGRESSIVE;
+ }
+ }
+
+ public double getBrickSize() {
+ return config.getDouble("city.brick.size", 1.0);
+ }
+
+ public double getBrickHorizontalMargin() {
+ return config.getDouble("city.brick.horizontal_margin", 0.5);
+ }
+
+ public double getBrickHorizontalGap() {
+ return config.getDouble("city.brick.horizontal_gap", 0.2);
+ }
+
+ public double getBrickVerticalMargin() {
+ return config.getDouble("city.brick.vertical_margin", 0.2);
+ }
+
+ public double getBrickVerticalGap() {
+ return config.getDouble("city.brick.vertical_gap", 0.2);
+ }
+
+ public boolean isShowAttributesAsCylinders() {
+ return config.getBoolean("city.show_attributes_as_cylinders", true);
+ }
+
+ public SeparatorModes getPanelSeparatorMode() {
+ String value = config.getString("city.panel_separator_mode", "separator");
+ switch (value) {
+ case "none":
+ return SeparatorModes.NONE;
+ case "gap":
+ return SeparatorModes.GAP;
+ default:
+ return SeparatorModes.SEPARATOR;
+ }
+ }
+
+ public int[] getPanelHeightTresholdNos() {
+ int[] defaultValue = { 3, 6, 12, 24, 48, 96, 144, 192, 240 };
+ String[] result = config.getStringArray("city.panel.height_treshold_nos");
+ if (result.length == 0) {
+ return defaultValue;
+ } else {
+ int[] value = new int[result.length];
+ for (int i = 0; i < result.length; i++) {
+ try {
+ value[i] = Integer.parseInt(result[i]);
+ System.out.print(value[i] + " ");
+ } catch(NumberFormatException e) {
+ return defaultValue;
+ }
+
+ }
+ return value;
+ }
+ }
+
+ public double getPanelHeightUnit() {
+ return config.getDouble("city.panel.height_unit", 0.5);
+ }
+
+ public double getPanelHorizontalMargin() {
+ return config.getDouble("city.panel.horizontal_margin", 0.5);
+ }
+
+ public double getPanelVerticalMargin() {
+ return config.getDouble("city.panel.vertical_margin", 0.25);
+ }
+
+ public double getPanelVerticalGap() {
+ return config.getDouble("city.panel.vertical_gap", 0.125);
+ }
+
+ public double getPanelSeparatorHeight() {
+ return config.getDouble("city.panel.separator_height", 0.125);
+ }
+
+ public BuildingMetric getOriginalBuildingMetric() {
+ String value = config.getString("city.original_building_metric", "none");
+ switch (value) {
+ case "nos":
+ return BuildingMetric.NOS;
+ default:
+ return BuildingMetric.NONE;
+ }
+ }
+
+ public double getWidthMin() {
+ return config.getDouble("city.width_min", 1.0);
+ }
+
+ public double getHeightMin() {
+ return config.getDouble("city.height_min", 1.0);
+ }
+
+ public double getBuildingHorizontalMargin() {
+ return config.getDouble("city.building.horizontal_margin", 3.0);
+ }
+
+ public double getBuildingHorizontalGap() {
+ return config.getDouble("city.building.horizontal_gap", 3.0);
+ }
+
+ public double getBuildingVerticalMargin() {
+ return config.getDouble("city.building.vertical_margin", 1.0);
+ }
+
+ public String getPackageColorHex() {
+ return config.getString("city.package.color_start", "#969696");
+ }
+
+ public Color getPackageColorStart() {
+ return getColor(config.getString("city.package.color_start", "#969696"));
+ }
+
+ public Color getPackageColorEnd() {
+ return getColor(config.getString("city.package.color_end", "#f0f0f0"));
+ }
+
+ public String getClassColorHex() {
+ return config.getString("city.class.color", "#353559");
+ }
+
+ public Color getClassColorStart() {
+ return getColor(config.getString("city.class.color_start", "#131615"));
+ }
+
+ public Color getClassColorEnd() {
+ return getColor(config.getString("city.class.color_end", "#00ff00"));
+ }
+
+ public Color getClassColor() {
+ return getColor(config.getString("city.class.color", "#353559"));
+ }
+
+ public Color getDynamicClassColorStart() {
+ return getColor(config.getString("city.dynamic.class.color_start", "#fa965c"));
+ }
+
+ public Color getDynamicClassColorEnd() {
+ return getColor(config.getString("city.dynamic.class.color_end", "#feb280"));
+ }
+
+ public Color getDynamicMethodColor() {
+ return getColor(config.getString("city.dynamic.method.color", "#735eb9"));
+ }
+
+ public Color getDynamicPackageColorStart() {
+ return getColor(config.getString("city.dynamic.package.color_start", "#23862c"));
+ }
+
+ public Color getDynamicPackageColorEnd() {
+ return getColor(config.getString("city.dynamic.package.color_end", "#7bcd8d"));
+ }
+
+ public Color getCityColor(String name) {
+ return getColor(getCityColorHex(name));
+ }
+
+ public String getCityColorHex(String name) {
+ String color = name.toLowerCase();
+ String defaultColor = "";
+ switch (name) {
+ case "aqua":
+ defaultColor = "#99CCFF"; break;
+ case "blue":
+ defaultColor = "#99FFCC"; break;
+ case "light_green":
+ defaultColor = "#CCFF99"; break;
+ case "dark_green":
+ defaultColor = "#99FF99"; break;
+ case "yellow":
+ defaultColor = "#FFFF99"; break;
+ case "orange":
+ defaultColor = "#FFCC99"; break;
+ case "red":
+ defaultColor = "#FF9999"; break;
+ case "pink":
+ defaultColor = "#FF99FF"; break;
+ case "violet":
+ defaultColor = "#9999FF"; break;
+ case "light_grey":
+ defaultColor = "#CCCCCC"; break;
+ case "dark_grey":
+ defaultColor = "#999999"; break;
+ case "white":
+ defaultColor = "#FFFFFF"; break;
+ case "black":
+ defaultColor = "#000000"; break;
+ }
+ return config.getString("city.color." + color, defaultColor);
+ }
+
+ public String getCityColorAsPercentage(String name) {
+ return getColorFormatted(getCityColor(name));
+ }
+
+ public double getRDDataFactor() {
+ return config.getDouble("rd.data_factor", 4.0);
+ }
+
+ public double getRDMethodFactor() {
+ return config.getDouble("rd.method_factor", 1.0);
+ }
+
+ public double getRDHeight() {
+ return config.getDouble("rd.height", 1.0);
+ }
+
+ public int getRDHeightBoost() {
+ return config.getInt("rd.height_boost", 8);
+ }
+
+ public float getRDHeightMultiplicator() {
+ return (float)config.getDouble("rd.height_multiplicator", 50.0);
+ }
+
+ public double getRDRingWidth() {
+ return config.getDouble("rd.ring_width", 2.0);
+ }
+
+ public double getRDRingWidthMD() {
+ return config.getDouble("rd.ring_width_md", 0);
+ }
+
+ public double getRDRingWidthAD() {
+ return config.getDouble("rd.ring_width_ad", 0);
+ }
+
+ public double getRDMinArea() {
+ return config.getDouble("rd.min_area", 10.0);
+ }
+
+ public double getRDNamespaceTransparency() {
+ return config.getDouble("rd.namespace_transparency", 0);
+ }
+
+ public double getRDClassTransparency() {
+ return config.getDouble("rd.class_transparency", 0);
+ }
+
+ public double getRDMethodTransparency() {
+ return config.getDouble("rd.method_transparency", 0);
+ }
+
+ public double getRDDataTransparency() {
+ return config.getDouble("rd.data_transparency", 0);
+ }
+
+ public Color getRDClassColor() {
+ return getColor(getRDClassColorHex());
+ }
+
+ public String getRDClassColorHex() {
+ return config.getString("rd.color.class", "#353559");
+ }
+
+ public String getRDClassColorAsPercentage() {
+ return getColorFormatted(getRDClassColor());
+ }
+
+ public Color getRDDataColor() {
+ return getColor(getRDDataColorHex());
+ }
+
+ public String getRDDataColorHex() {
+ return config.getString("rd.color.data", "#fffc19");
+ }
+
+ public String getRDDataColorAsPercentage() {
+ return getColorFormatted(getRDDataColor());
+ }
+
+ public Color getRDMethodColor() {
+ return getColor(getRDMethodColorHex());
+ }
+
+ public String getRDMethodColorHex() {
+ return config.getString("rd.color.method", "#1485cc");
+ }
+
+ public String getRDMethodColorAsPercentage() {
+ return getColorFormatted(getRDMethodColor());
+ }
+
+ public Color getRDNamespaceColor() {
+ return getColor(getRDNamespaceColorHex());
+ }
+
+ public String getRDNamespaceColorHex() {
+ return config.getString("rd.color.namespace", "#969696");
+ }
+
+ public String getRDNamespaceColorAsPercentage() {
+ return getColorFormatted(getRDNamespaceColor());
+ }
+
+ public boolean isMethodDisks() {
+ return config.getBoolean("rd.method_disks", false);
+ }
+
+ public boolean isDataDisks() {
+ return config.getBoolean("rd.data_disks", false);
+ }
+
+ public boolean isMethodTypeMode() {
+ return config.getBoolean("rd.method_type_mode", false);
+ }
+
+ private String getColorFormatted(Color color) {
+ double r = color.getRed() / 255.0;
+ double g = color.getGreen() / 255.0;
+ double b = color.getBlue() / 255.0;
+ return r + " " + g + " " + b;
+ }
+
+ private Color getColor(String hex) {
+ return Color.decode(hex);
+ }
+
+ public static enum OutputFormat {
+ X3D, AFrame;
+ }
+
+ /**
+ * Sets in which way the Historic Evolution
+ * of the analyzed Software should be represented,
+ * it can either be in a static or dynamic way
+ */
+
+ public static enum BuildingType{
+ CITY_ORIGINAL, CITY_PANELS, CITY_BRICKS, CITY_FLOOR;
+ }
+
+ /**
+ * Defines how the methods and attributes are sorted and colored in the city
+ * model.
+ *
+ * @see CitySettings#SET_SCHEME SET_SCHEME
+ */
+ public static enum Schemes {
+ /**
+ * The class elements are sorted and colored corresponding to there
+ * visibility modifiers.
+ *
+ * @see SortPriorities_Visibility
+ */
+ VISIBILITY,
+
+ /**
+ * The class elements are sorted and colored associated to
+ * type/functionality of the method.
+ *
+ * @see Methods.SortPriorities_Types
+ * @see Attributes.SortPriorities_Types
+ */
+ TYPES;
+ };
+
+ /**
+ * Defines which elements of a class are to show.
+ *
+ * @see CitySettings#SET_CLASS_ELEMENTS_MODE SET_CLASS_ELEMENTS_MODE
+ */
+ public static enum ClassElementsModes {
+ METHODS_ONLY, ATTRIBUTES_ONLY, METHODS_AND_ATTRIBUTES;
+ }
+
+ /**
+ * Defines which how the elements of a class are sorted.
+ *
+ * @see CitySettings#SET_CLASS_ELEMENTS_SORT_MODE_COARSE
+ * SET_CLASS_ELEMENTS_SORT_MODE_COARSE
+ */
+ public static enum ClassElementsSortModesCoarse {
+ UNSORTED, ATTRIBUTES_FIRST, METHODS_FIRST;
+ }
+
+ /**
+ * A list of types of a method with the associated priority value.
+ * Highest priority/smallest number is placed on the bottom, lowest on top.
+ *
+ * @see #SET_CLASS_ELEMENTS_SORT_MODE_FINE SET_CLASS_ELEMENTS_SORT_MODE_FINE
+ * @see SortPriorities_Visibility
+ * @see Methods.SortPriorities_Types
+ * @see Attributes.SortPriorities_Types
+ */
+ public static enum ClassElementsSortModesFine {
+ /** Class elements won't be sorted. */
+ UNSORTED,
+
+ /** Methods will be sorted according to the name. */
+ ALPHABETICALLY,
+
+ /**
+ * Methods will be sorted according to the active
+ * {@link CitySettings#SET_CLASS_ELEMENTS_SORT_MODE_FINE
+ * SET_CLASS_ELEMENTS_SORT_MODE_FINE}.
+ */
+ SCHEME,
+
+ /** Methods will be sorted according to there number of statements. */
+ NOS;
+ }
+
+ /**
+ * A list of visibility modifiers of a method with the associated priority
+ * value.
+ * Highest priority/smallest number is placed on the bottom, lowest on top.
+ *
+ * @see #SET_CLASS_ELEMENTS_SORT_MODE_FINE SET_CLASS_ELEMENTS_SORT_MODE_FINE
+ * @see ClassElementsSortModesFine
+ *
+ */
+ public static enum SortPriorities_Visibility {;
+ public static int PRIVATE = 1;
+ public static int PROTECTED = 2;
+ public static int PACKAGE = 3;
+ public static int PUBLIC = 4;
+ }
+
+ public static enum Methods {;
+
+ /**
+ * A list of types of a method with the associated priority value.
+ * Highest priority/smallest number is placed on the bottom, lowest on
+ * top.
+ *
+ * @see CitySettings#SET_CLASS_ELEMENTS_SORT_MODE_FINE
+ * SET_CLASS_ELEMENTS_SORT_MODE_FINE
+ * @see ClassElementsSortModesFine
+ * @see SortPriorities_Visibility
+ */
+ public static enum SortPriorities_Types {;
+
+ /**
+ * Method is a constructor.
+ */
+ public static int CONSTRUCTOR = 1;
+
+ /**
+ * The name of the method begins with "get".
+ */
+ public static int GETTER = 2;
+
+ /**
+ * The name of the method begins with "set".
+ */
+ public static int SETTER = 3;
+
+ /**
+ * Method has a {@code static} modifier.
+ */
+ public static int STATIC = 4;
+
+ /**
+ * Method has an {@code abstract} modifier.
+ */
+ public static int ABSTRACT = 5;
+
+ /**
+ * Every other type that isn't specified by the other constants in
+ * this field.
+ */
+ public static int LEFTOVER = 6;
+ }
+
+ }
+
+ public static enum Attributes {;
+
+ /**
+ * A list of types of a method with the associated priority value.
+ * Highest priority/smallest number is placed on the bottom, lowest on
+ * top.
+ *
+ * @see CitySettings#SET_CLASS_ELEMENTS_SORT_MODE_FINE
+ * SET_CLASS_ELEMENTS_SORT_MODE_FINE
+ * @see ClassElementsSortModesFine
+ */
+ public static enum SortPriorities_Types {;
+
+ /** Type is a primitive like {@code boolean}, {@code int}. */
+ public static int PRIMITVE = 1;
+
+ /** Type is a (Non-wrapper) class, collection, etc. */
+ public static int COMPLEX = 2;
+
+ }
+
+ }
+
+ public static enum Bricks {;
+
+ /**
+ * Defines the layout for the BuildingSegments of the city model, which
+ * represents the methods and/or attributes of a class.
+ *
+ * @see CitySettings#SET_BRICK_LAYOUT SET_BRICK_LAYOUT
+ */
+ public static enum Layout {
+
+ /**
+ * One-dimensional bricks layout, where the segments simply are
+ * placed on top of the other.
+ */
+ STRAIGHT,
+
+ /**
+ * Three-dimensional brick layout, where the base area is computed
+ * depending on the {@link CitySettings#SET_CLASS_ELEMENTS_MODE
+ * SET_CLASS_ELEMENTS_MODE}.
+ * If only methods are shown, the base area is computed by the
+ * number of attributes and vice versa.
+ * In case of methods and attributes are shown, the base area is
+ * computed by the sum of the numbers of attributes and methods
+ * inside the class.
+ *
+ * When {@link CitySettings#SET_CLASS_ELEMENTS_MODE
+ * SET_CLASS_ELEMENTS_MODE} is set to
+ * {@code METHODS_AND_ATTRIBUTES}, the {@code BALANCED} layout and
+ * {@link Layout#PROGRESSIVE PROGRESSIVE} layout are identical.
+ */
+ BALANCED,
+
+ /**
+ * Three-dimensional brick layout, where the base area is computed
+ * depending on the {@link CitySettings#SET_CLASS_ELEMENTS_MODE
+ * SET_CLASS_ELEMENTS_MODE}.
+ * If only methods are shown, the base area is computed by the
+ * number of methods and vice versa. So the aspect lies on only one
+ * type of element of a class and is visualized.
+ *
+ * When {@link CitySettings#SET_CLASS_ELEMENTS_MODE
+ * SET_CLASS_ELEMENTS_MODE} is set to
+ * {@code METHODS_AND_ATTRIBUTES}, the {@link Layout#BALANCED
+ * PROGRESSIVE} layout and {@code PROGRESSIVE} layout are identical.
+ */
+ PROGRESSIVE;
+
+ }
+ }
+
+ public enum Panels {
+ ;
+
+ /**
+ * Defines the the space between the panels.
+ * The panels can either touch each other without a gap, leave a gap
+ * between them, or fill the space with a separator of a defined color.
+ *
+ * @see CitySettings#SET_PANEL_SEPARATOR_MODE SET_PANEL_SEPARATOR_MODE
+ */
+ public static enum SeparatorModes {
+
+ /**
+ * No space between the panels and they are placed on top of each
+ * other.
+ */
+ NONE,
+
+ /**
+ * The panels have a free space between them and don't touch each
+ * other.
+ *
+ * @see Panels#PANEL_VERTICAL_GAP PANEL_VERTICAL_GAP
+ */
+ GAP,
+
+ /**
+ * Between the panels separators are placed with a fix height and
+ * color.
+ *
+ * @see Panels#SEPARATOR_HEIGHT SEPARATOR_HEIGHT
+ */
+ SEPARATOR;
+
+ }
+ }
+
+ public static enum Original {
+ ;
+ public static enum BuildingMetric {
+ NONE,
+ NOS;
+ }
+ }
+
+ public static enum Metaphor {
+ RD, CITY
+ }
+}
diff --git a/generator2/org.getaviz.generator/src/org/getaviz/generator/city/CityUtils.java b/generator2/org.getaviz.generator/src/org/getaviz/generator/city/CityUtils.java
new file mode 100644
index 000000000..7208df17c
--- /dev/null
+++ b/generator2/org.getaviz.generator/src/org/getaviz/generator/city/CityUtils.java
@@ -0,0 +1,247 @@
+package org.getaviz.generator.city;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.neo4j.graphdb.Direction;
+import org.neo4j.graphdb.Node;
+import org.neo4j.graphdb.Relationship;
+import org.getaviz.generator.SettingsConfiguration;
+import org.getaviz.generator.SettingsConfiguration.OutputFormat;
+import org.getaviz.generator.city.m2m.BuildingSegmentComparator;
+import org.getaviz.generator.city.m2m.RGBColor;
+import org.getaviz.generator.database.Labels;
+import org.getaviz.generator.database.Rels;
+
+public class CityUtils {
+
+ private static SettingsConfiguration config = SettingsConfiguration.getInstance();
+ public static String getFamixClassString(final String className) {
+ String s = className.substring(0, 5) + "." + className.substring(5, className.length());
+ if (className.endsWith("Impl"))
+ s = s.substring(0, s.length() - 4);
+ return s;
+ }
+
+ /**
+ * Creates the color gradient for the packages depending on your hierarchy
+ * level.
+ *
+ * @param start
+ * RGBColor
+ * @param end
+ * RGBColor
+ * @param maxLevel
+ * int
+ * @return color range
+ */
+ public static RGBColor[] createPackageColorGradient(final RGBColor start, final RGBColor end, final int maxLevel) {
+ int steps = maxLevel - 1;
+ if (maxLevel == 1) {
+ steps++;
+ }
+ double r_step = (end.r() - start.r()) / steps;
+ double g_step = (end.g() - start.g()) / steps;
+ double b_step = (end.b() - start.b()) / steps;
+
+ RGBColor[] colorRange = new RGBColor[maxLevel];
+ double newR, newG, newB;
+ for (int i = 0; i < maxLevel; ++i) {
+ newR = start.r() + i * r_step;
+ newG = start.g() + i * g_step;
+ newB = start.b() + i * b_step;
+
+ colorRange[i] = new RGBColor(newR, newG, newB);
+ }
+
+ return colorRange;
+ }
+
+ public static void setBuildingSegmentColor(final Node segment) {
+ String color = "";
+ Node entity = segment.getSingleRelationship(Rels.VISUALIZES, Direction.OUTGOING).getEndNode();
+ String visibility = "";
+ if(entity.hasProperty("visibility")) {
+ visibility = (String)(entity.getProperty("visibility"));
+ }
+ if (config.getOutputFormat() == OutputFormat.AFrame) {
+ switch (config.getScheme()) {
+ case VISIBILITY:
+ if (visibility.equals("public")) {
+ color = config.getCityColorHex("dark_green");
+ } else if (visibility.equals("protected")) {
+ color = config.getCityColorHex("yellow");
+ } else if (visibility.equals("private")) {
+ color = config.getCityColorHex("red");
+ } else {
+ // Package visibility or default
+ color = config.getCityColorHex("blue");
+ }
+ segment.setProperty("color", color);
+ break;
+ case TYPES:
+ if(entity.hasLabel(Labels.Field)) {
+ setAttributeColor(segment);
+ } else if(entity.hasLabel(Labels.Method)) {
+ setMethodColor(segment);
+ } else {
+ segment.setProperty("color", config.getCityColorHex("blue"));
+ }
+ default:
+ segment.setProperty("color", config.getCityColorHex("blue"));
+ }
+ } else {
+ switch (config.getScheme()) {
+ case VISIBILITY:
+ if (visibility.equals("public")) {
+ color = config.getCityColorAsPercentage("dark_green");
+ } else if (visibility.equals("protected")) {
+ color = config.getCityColorAsPercentage("yellow");
+ } else if (visibility.equals("private")) {
+ color = config.getCityColorAsPercentage("red");
+ } else {
+ // Package visibility or default
+ color = config.getCityColorAsPercentage("blue");
+ }
+ segment.setProperty("color", color);
+ break;
+ case TYPES:
+ if(entity.hasLabel(Labels.Field)) {
+ setAttributeColor(segment);
+ } else if(entity.hasLabel(Labels.Method)) {
+ setMethodColor(segment);
+ } else {
+ segment.setProperty("color", config.getCityColorAsPercentage("blue"));
+ }
+ break;
+ default:
+ segment.setProperty("color", config.getCityColorAsPercentage("blue"));
+ }
+ }
+ }
+
+ private static void setAttributeColor(final Node segment) {
+ String color = "";
+ Node entity = segment.getSingleRelationship(Rels.VISUALIZES, Direction.OUTGOING).getEndNode();
+ boolean isPrimitive = false;
+ if(entity.hasRelationship(Rels.OF_TYPE)) {
+ Node declaredType = entity.getSingleRelationship(Rels.OF_TYPE, Direction.OUTGOING).getEndNode();
+ if (declaredType.hasLabel(Labels.Primitive)) {
+ isPrimitive = true;
+ }
+ }
+ if (config.getOutputFormat() == OutputFormat.AFrame) {
+ if (isPrimitive) {
+ color = config.getCityColorHex("pink");
+ } else { // complex type
+ color = config.getCityColorHex("aqua");
+ }
+ } else {
+ if (isPrimitive) {
+ color = config.getCityColorAsPercentage("pink");
+ } else { // complex type
+ color = config.getCityColorAsPercentage("aqua");
+ }
+ }
+ segment.setProperty("color", color);
+ }
+
+ private static void setMethodColor(final Node segment) {
+ Node entity = segment.getSingleRelationship(Rels.VISUALIZES, Direction.OUTGOING).getEndNode();
+ String color = "";
+ boolean isStatic = false;
+ if(entity.hasProperty("static")) {
+ isStatic = (Boolean)entity.getProperty("static");
+ }
+ boolean isAbstract = false;
+ if(entity.hasProperty("abstract")) {
+ isAbstract = (Boolean)entity.getProperty("abstract");
+ }
+ if (config.getOutputFormat() == OutputFormat.AFrame) {
+ // if (bs.getMethodKind().equals("constructor")) {
+ if (entity.hasLabel(Labels.Constructor)) {
+ color = config.getCityColorHex("red");
+ } else if (entity.hasLabel(Labels.Getter)) {
+ color = config.getCityColorHex("light_green");
+ } else if (entity.hasLabel(Labels.Setter)) {
+ color = config.getCityColorHex("dark_green");
+ } else if (isStatic) {
+ color = config.getCityColorHex("yellow");
+ } else if (isAbstract) {
+ color = config.getCityColorHex("orange");
+ } else {
+ // Default
+ color = config.getCityColorHex("violet");
+ }
+ } else {
+ // if (bs.getMethodKind().equals("constructor")) {
+ if (entity.hasLabel(Labels.Constructor)) {
+ color = config.getCityColorAsPercentage("red");
+ } else if (entity.hasLabel(Labels.Getter)) {
+ color = config.getCityColorAsPercentage("light_green");
+ } else if (entity.hasLabel(Labels.Setter)) {
+ color = config.getCityColorAsPercentage("dark_green");
+ } else if (isStatic) {
+ color = config.getCityColorAsPercentage("yellow");
+ } else if (isAbstract) {
+ color = config.getCityColorAsPercentage("orange");
+ } else {
+ // Default
+ color = config.getCityColorAsPercentage("violet");
+ }
+ }
+ segment.setProperty("color", color);
+ }
+
+ /**
+ * Sorting the {@link BuildingSegment}s with help of
+ * {@link BuildingSegmentComparator} based on sorting settings in
+ * {@link CitySettings}.
+ *
+ * @param bsList
+ * BuildingSegments which are to be sorted.
+ *
+ */
+
+ public static void sortBuildingSegments(final List buildingSegments) {
+ final List sortedList = new ArrayList(buildingSegments.size());
+ for (Node segment : buildingSegments)
+ sortedList.add(new BuildingSegmentComparator(segment));
+ Collections.sort(sortedList);
+ buildingSegments.clear();
+ for (BuildingSegmentComparator bsc : sortedList)
+ buildingSegments.add(bsc.getSegment());
+ }
+
+ public static List getChildren(Node parent) {
+ ArrayList children = new ArrayList();
+ Iterable childrenRels = parent.getRelationships(Rels.CONTAINS, Direction.OUTGOING);
+ for (Relationship relationship : childrenRels) {
+ children.add(relationship.getEndNode());
+ }
+ return children;
+ }
+
+ public static List getMethods(Node building) {
+ ArrayList methods = new ArrayList();
+ for (Node child : getChildren(building)) {
+ Node entity = child.getSingleRelationship(Rels.VISUALIZES, Direction.OUTGOING).getEndNode();
+ if (entity.hasLabel(Labels.Method)) {
+ methods.add(child);
+ }
+ }
+ return methods;
+ }
+
+ public static List getData(Node building) {
+ ArrayList data = new ArrayList();
+ for (Node child : getChildren(building)) {
+ Node entity = child.getSingleRelationship(Rels.VISUALIZES, Direction.OUTGOING).getEndNode();
+ if (entity.hasLabel(Labels.Field)) {
+ data.add(child);
+ }
+ }
+ return data;
+ }
+}
\ No newline at end of file
diff --git a/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/BrickLayout.java b/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/BrickLayout.java
new file mode 100644
index 000000000..384c7f06c
--- /dev/null
+++ b/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/BrickLayout.java
@@ -0,0 +1,111 @@
+package org.getaviz.generator.city.m2m;
+
+import java.util.List;
+
+import org.neo4j.graphdb.Direction;
+import org.neo4j.graphdb.GraphDatabaseService;
+import org.neo4j.graphdb.Node;
+import org.neo4j.graphdb.Result;
+import org.getaviz.generator.SettingsConfiguration;
+import org.getaviz.generator.city.CityUtils;
+import org.getaviz.generator.database.Labels;
+import org.getaviz.generator.database.Rels;
+import org.getaviz.generator.database.Database;
+
+public class BrickLayout {
+ private static SettingsConfiguration config = SettingsConfiguration.getInstance();
+ private static GraphDatabaseService graph = Database.getInstance();
+
+ public static void brickLayout(Node model) {
+ Result buildings = graph.execute("MATCH (n:City:Model)-[:CONTAINS*]->(m:Building) WHERE ID(n) = " + model.getId() + " RETURN m");
+ while(buildings.hasNext()) {
+ Node building = (Node)buildings.next().get("m");
+ separateBuilding(building);
+ }
+ }
+
+ // Builds up the bricks for a specific given building/class
+ private static void separateBuilding(Node building) {
+ // Don't build up bricks, if this building isn't visualized or isn't positioned
+ // (e.g. is an inner classes)
+ if (building.getSingleRelationship(Rels.HAS, Direction.OUTGOING).getEndNode() == null) {
+ return;
+ }
+
+ // variables for brick algorithm
+ int sideCapacity, layerCapacity, brickIndexWithinSide, brickIndexWithinLayer, sideIndex, // side index -
+ // north,east,...
+ bsPosIndex_X, bsPosIndex_Y, bsPosIndex_Z;
+ double b_lowerLeftX, b_upperY, b_lowerLeftZ;
+ sideCapacity = (Integer) building.getProperty("sideCapacity");
+ List classElements = null;
+ switch (config.getClassElementsMode()) {
+ case ATTRIBUTES_ONLY:
+ classElements = CityUtils.getData(building);
+ CityUtils.sortBuildingSegments(CityUtils.getData(building));
+ break;
+ case METHODS_ONLY:
+ classElements = CityUtils.getMethods(building);
+ CityUtils.sortBuildingSegments(CityUtils.getMethods(building));
+ break;
+ default:
+ classElements = CityUtils.getChildren(building);
+ break;
+ }
+ CityUtils.sortBuildingSegments(classElements);
+ // coordinates of edges of building
+ Node position = building.getSingleRelationship(Rels.HAS, Direction.OUTGOING).getEndNode();
+ b_lowerLeftX = (Double)position.getProperty("x") - (Double)building.getProperty("width") / 2;
+ b_lowerLeftZ = (Double)position.getProperty("z") - (Double)building.getProperty("length") / 2;
+ b_upperY = (Double)position.getProperty("y") + (Double)building.getProperty("height") / 2;
+ // System.out.println("");
+ // set positions for all methods in current class
+ for (int i = 0; i < classElements.size(); ++i) {
+ if (sideCapacity <= 1) {
+ layerCapacity = 1;
+ brickIndexWithinSide = 0;
+ sideIndex = 0;
+ } else {
+ layerCapacity = (sideCapacity - 1) * 4;
+ brickIndexWithinLayer = i % layerCapacity;
+ brickIndexWithinSide = brickIndexWithinLayer % (sideCapacity - 1);
+ sideIndex = brickIndexWithinLayer / (sideCapacity - 1);
+ }
+ // System.out.println(bs.getType() + " " + bs.getValue() + " " +
+ // bs.getModifiers() + " " + bs.getNumberOfStatements());
+ // calculating position for brick
+ switch (sideIndex) {
+ case 0:
+ bsPosIndex_X = brickIndexWithinSide;
+ bsPosIndex_Z = 0;
+ break;
+ case 1:
+ bsPosIndex_X = sideCapacity - 1;
+ bsPosIndex_Z = brickIndexWithinSide;
+ break;
+ case 2:
+ bsPosIndex_X = sideCapacity - brickIndexWithinSide - 1;
+ bsPosIndex_Z = sideCapacity - 1;
+ break;
+ default:
+ bsPosIndex_X = 0;
+ bsPosIndex_Z = sideCapacity - brickIndexWithinSide - 1;
+ break;
+ }
+ bsPosIndex_Y = i / layerCapacity;
+
+ // setting position for brick
+ Node pos = graph.createNode(Labels.Position, Labels.City, Labels.Dummy);
+ classElements.get(i).createRelationshipTo(pos, Rels.HAS);
+ pos.setProperty("x", b_lowerLeftX + config.getBrickHorizontalMargin()
+ + (config.getBrickHorizontalGap() + config.getBrickSize()) * bsPosIndex_X
+ + config.getBrickSize() * 0.5);
+ pos.setProperty("y", b_upperY + config.getBrickVerticalMargin()
+ + (config.getBrickVerticalGap() + config.getBrickSize()) * bsPosIndex_Y
+ + config.getBrickSize() * 0.5);
+ pos.setProperty("z", b_lowerLeftZ + config.getBrickHorizontalMargin()
+ + (config.getBrickHorizontalGap() + config.getBrickSize()) * bsPosIndex_Z
+ + config.getBrickSize() * 0.5);
+ }
+ }
+}
diff --git a/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/BuildingSegmentComparator.java b/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/BuildingSegmentComparator.java
new file mode 100644
index 000000000..33f1e8a5e
--- /dev/null
+++ b/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/BuildingSegmentComparator.java
@@ -0,0 +1,227 @@
+package org.getaviz.generator.city.m2m;
+
+import org.neo4j.graphdb.Direction;
+import org.neo4j.graphdb.Node;
+import org.getaviz.generator.SettingsConfiguration;
+import org.getaviz.generator.SettingsConfiguration.Attributes;
+import org.getaviz.generator.SettingsConfiguration.Methods;
+import org.getaviz.generator.SettingsConfiguration.SortPriorities_Visibility;
+import org.getaviz.generator.database.Labels;
+import org.getaviz.generator.database.Rels;
+
+public class BuildingSegmentComparator implements Comparable {
+ private Node segment;
+ private Node relatedEntity;
+ // Temporary attribute to use db and famix in same transformation
+ private int coarseValue; // compares class elements (methods <-> attributes)
+ private int fineValue; // compared after coarseValue
+ private int finerValue; // compared after finevalue, if it was equal
+ private SettingsConfiguration config = SettingsConfiguration.getInstance();
+
+ public BuildingSegmentComparator(final Node segment) {
+ this.segment = segment;
+ this.relatedEntity = segment.getSingleRelationship(Rels.VISUALIZES, Direction.OUTGOING).getEndNode();
+ setCoarseValue();
+ switch (config.getClassElementsSortModeFine()) {
+ case ALPHABETICALLY: // names compared directly in compareTo-method
+ break;
+ case SCHEME:
+ switch (config.getScheme()) {
+ case VISIBILITY:
+ fineValue = getCompValue_Visibility(relatedEntity);
+ break;
+ case TYPES:
+ fineValue = getCompValue_Type(relatedEntity);
+ break;
+ }
+ break;
+ case NOS: // numberOfStatements compared directly in compareTo-method
+ finerValue = getCompValue_Type(relatedEntity); // If NOS are equal, sort for types
+ break;
+ case UNSORTED:
+ break;
+ default:
+ break;
+ }
+ }
+
+ static int getCompValue_Visibility(final String modifier) {
+ if (modifier.indexOf("private") >= 0) {
+ return SortPriorities_Visibility.PRIVATE;
+ } else if (modifier.indexOf("protected") >= 0) {
+ return SortPriorities_Visibility.PROTECTED;
+ } else if (modifier.indexOf("public") >= 0) {
+ return SortPriorities_Visibility.PUBLIC;
+ } else {
+ return SortPriorities_Visibility.PACKAGE;
+ }
+
+ }
+
+ static int getCompValue_Visibility(final Node relatedEntity) {
+ String visbility = (String) relatedEntity.getProperty("visibility");
+ if (visbility.equals("private")) {
+ return SortPriorities_Visibility.PRIVATE;
+ } else if (visbility.equals("protected")) {
+ return SortPriorities_Visibility.PROTECTED;
+ } else if (visbility.equals("public")) {
+ return SortPriorities_Visibility.PUBLIC;
+ } else {
+ return SortPriorities_Visibility.PACKAGE;
+ }
+ }
+
+ static int getCompValue_Type(final Node relatedEntity) {
+ if (relatedEntity.hasLabel(Labels.Field)) {
+ boolean isPrimitive = false;
+ if (relatedEntity.hasRelationship(Rels.OF_TYPE)) {
+ Node declaredType = relatedEntity.getSingleRelationship(Rels.OF_TYPE, Direction.OUTGOING).getEndNode();
+ if (declaredType.hasLabel(Labels.Primitive)) {
+ isPrimitive = true;
+ }
+ }
+ if (isPrimitive) {
+ return Attributes.SortPriorities_Types.PRIMITVE;
+ } else {
+ return Attributes.SortPriorities_Types.COMPLEX;
+ }
+ } else {
+ boolean isStatic = false;
+ if (relatedEntity.hasProperty("static")) {
+ isStatic = (Boolean) relatedEntity.getProperty("static");
+ }
+ boolean isAbstract = false;
+ if (relatedEntity.hasProperty("abstract")) {
+ isAbstract = (Boolean) relatedEntity.getProperty("abstract");
+ }
+ if (relatedEntity.hasLabel(Labels.Constructor)) {
+ return Methods.SortPriorities_Types.CONSTRUCTOR;
+ } else if (relatedEntity.hasLabel(Labels.Getter)) {
+ return Methods.SortPriorities_Types.GETTER;
+ } else if (relatedEntity.hasLabel(Labels.Setter)) {
+ return Methods.SortPriorities_Types.SETTER;
+ } else if (isStatic) {
+ return Methods.SortPriorities_Types.STATIC;
+ } else if (isAbstract) {
+ return Methods.SortPriorities_Types.ABSTRACT;
+ } else {
+ return Methods.SortPriorities_Types.LEFTOVER;
+ }
+ }
+ }
+
+ @Override
+ public int compareTo(final BuildingSegmentComparator comp) {
+ int result;
+ // Coarse sorting after attributes and methods if elements aren't the same type
+ // of class element
+ if (coarseValue < comp.coarseValue)
+ result = -1;
+ else if (coarseValue > comp.coarseValue)
+ result = 1;
+ else
+ result = 0;
+
+ if (result != 0)
+ return result;
+
+ // Sorting after fine sort mode between equal class elements types (e.g. method
+ // compared to method)
+ switch (config.getClassElementsSortModeFine()) {
+ case UNSORTED:
+ return 0;
+ case ALPHABETICALLY:
+ String name = (String) relatedEntity.getProperty("name");
+ result = name.compareTo((String) comp.relatedEntity.getProperty("name"));
+ break;
+ case SCHEME:
+ if (fineValue < comp.fineValue)
+ result = -1;
+ else if (fineValue > comp.fineValue)
+ result = 1;
+ else
+ return compareNOS(comp); // Largest methods are always at the bottom in SCHEME-mode
+ break;
+ case NOS:
+ result = compareNOS(comp);
+ if (result == 0)
+ if (finerValue < comp.finerValue)
+ result = -1;
+ else if (finerValue > comp.finerValue)
+ result = 1;
+ else
+ return 0;
+ break;
+ default:
+ return 0;
+ }
+
+ // Reverse order if setting has been made
+ if (config.isClassElementsSortModeFineDirectionReversed())
+ return result * -1;
+ else
+ return result;
+ }
+
+ /** Compares the number of statements inside the given methods. */
+ private int compareNOS(final BuildingSegmentComparator comp) {
+ long numberOfStatements = 0;
+ long numberOfStatementsComp = 0;
+ if (relatedEntity.hasProperty("effectiveLineCount")) {
+ numberOfStatements = (int) relatedEntity.getProperty("effectiveLineCount");
+ }
+ if (comp.relatedEntity.hasProperty("effectiveLineCount")) {
+ numberOfStatementsComp = (int) comp.relatedEntity.getProperty("effectiveLineCount");
+ }
+ if (numberOfStatements < numberOfStatementsComp)
+ return 1;
+ else if (numberOfStatements > numberOfStatementsComp)
+ return -1;
+ else
+ return 0;
+ }
+
+ public Node getSegment() {
+ return segment;
+ }
+
+ /**
+ * Called once by constructor. Sets the coarse sort value.
+ * Made an additional method for that to make it better readable.
+ */
+ private void setCoarseValue() {
+ switch (config.getClassElementsSortModeCoarse()) {
+ case ATTRIBUTES_FIRST:
+ if (relatedEntity.hasLabel(Labels.Field)) {
+ coarseValue = -1;
+ } else if (relatedEntity.hasLabel(Labels.Method)) {
+ coarseValue = 1;
+ } else {
+ coarseValue = 0;
+ }
+
+ case METHODS_FIRST:
+ if (relatedEntity.hasLabel(Labels.Field)) {
+ coarseValue = 1;
+ } else if (relatedEntity.hasLabel(Labels.Method)) {
+ coarseValue = -1;
+ } else {
+ coarseValue = 0;
+ }
+
+ break;
+ case UNSORTED:
+ coarseValue = 0;
+ break;
+ default:
+ if (relatedEntity.hasLabel(Labels.Field)) {
+ coarseValue = 1;
+ } else if (relatedEntity.hasLabel(Labels.Method)) {
+ coarseValue = -1;
+ } else {
+ coarseValue = 0;
+ }
+
+ }
+ }
+}
diff --git a/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/City2City.xtend b/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/City2City.xtend
new file mode 100644
index 000000000..bb099d367
--- /dev/null
+++ b/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/City2City.xtend
@@ -0,0 +1,574 @@
+package org.getaviz.generator.city.m2m
+
+import org.neo4j.graphdb.GraphDatabaseService
+import org.getaviz.generator.SettingsConfiguration
+import org.getaviz.generator.database.Database
+import org.getaviz.generator.database.Labels
+import org.getaviz.generator.SettingsConfiguration.BuildingType
+import org.neo4j.graphdb.Node
+import org.getaviz.generator.city.CityUtils
+import org.getaviz.generator.database.Rels
+import org.neo4j.graphdb.Direction
+import org.getaviz.generator.SettingsConfiguration.ClassElementsModes
+import org.neo4j.graphdb.Path
+import org.getaviz.generator.SettingsConfiguration.Original.BuildingMetric
+import org.getaviz.generator.SettingsConfiguration.OutputFormat
+import java.util.HashMap
+import java.util.List
+import java.util.ArrayList
+import org.getaviz.generator.SettingsConfiguration.Panels.SeparatorModes
+import org.apache.commons.logging.LogFactory
+
+class City2City {
+ var GraphDatabaseService graph
+ val config = SettingsConfiguration.instance
+ val log = LogFactory::getLog(class)
+ var RGBColor[] PCKG_colors
+ var RGBColor[] NOS_colors
+ var properties = new HashMap
+ var Node model
+
+ new () {
+ log.info("City2City started")
+ graph = Database::getInstance(config.databaseName)
+ var tx = graph.beginTx
+ try {
+ model = graph.findNode(Labels.Model, "building_type", config.buildingTypeAsString)
+ if (config.buildingType == BuildingType::CITY_BRICKS ||
+ config.buildingType == BuildingType::CITY_PANELS) {
+ val buildingSegments = graph.execute(
+ "MATCH (n:Model:City)-[:CONTAINS*]->(m:BuildingSegment) RETURN m").map[return get("m") as Node]
+ buildingSegments.forEach[setBuildingSegmentAttributes]
+ }
+
+ val result = graph.execute(
+ "MATCH p=(n:District)-[:CONTAINS*]->(m:District) WHERE NOT (m)-[:CONTAINS]->(:District) RETURN length(p) AS length ORDER BY length(p) DESC LIMIT 1")
+ val packageMaxLevel = (result.head.get("length") as Long).intValue + 1
+ PCKG_colors = createColorGradiant(new RGBColor(config.packageColorStart),
+ new RGBColor(config.packageColorEnd), packageMaxLevel)
+
+ if (config.originalBuildingMetric == BuildingMetric::NOS) {
+ val result2 = graph.execute("MATCH (n:Building) RETURN max(n.numberOfStatements) AS nos")
+ val NOS_max = result2.head.get("nos") as Integer
+ NOS_colors = createColorGradiant(new RGBColor(config.classColorStart),
+ new RGBColor(config.classColorEnd), NOS_max + 1)
+ }
+
+ val districtPaths = graph.execute("MATCH p=(n:Model:City)-[:CONTAINS*]->(m:District) RETURN p").map[return get("p") as Path]
+ val buildingNodes = graph.execute("MATCH (n:Model:City)-[:CONTAINS*]->(m:Building) RETURN m").map[return get("m") as Node]
+ districtPaths.forEach[setDistrictAttributes]
+ buildingNodes.forEach[setBuildingAttributes]
+ tx.success
+ } finally {
+ tx.close
+ }
+
+ tx = graph.beginTx
+ try {
+ val districtPaths = graph.execute("MATCH (n:Model:City)-[:CONTAINS*]->(m:District) RETURN m").map[return get("m") as Node]
+ val buildingNodes = graph.execute("MATCH (n:Model:City)-[:CONTAINS*]->(m:Building) RETURN m").map[return get("m") as Node]
+ districtPaths.forEach[
+ var width = 0.0
+ var length = 0.0
+ if(hasProperty("width")) {
+ width = getProperty("width") as Double
+ }
+ if(hasProperty("length")) {
+ length = getProperty("length") as Double
+ }
+ val double[] array = #[width, length]
+ properties.put(id, array)
+ ]
+ buildingNodes.forEach[
+ var width = 0.0
+ var length = 0.0
+ if(hasProperty("width")) {
+ width = getProperty("width") as Double
+ }
+ if(hasProperty("length")) {
+ length = getProperty("length") as Double
+ }
+ val double[] array = #[width, length]
+ properties.put(id, array)]
+ tx.success
+ } finally {
+ tx.close
+ }
+ CityLayout::cityLayout(model, properties)
+ tx = graph.beginTx
+ try {
+ val buildingNodes = graph.execute("MATCH (n:Model:City)-[:CONTAINS*]->(m:Building) RETURN m").map[return get("m") as Node]
+ switch (config.buildingType) {
+ case CITY_BRICKS:
+ BrickLayout.brickLayout(model) // Layout for buildingSegments
+ case CITY_PANELS:
+ buildingNodes.forEach[setBuildingSegmentPositions]
+ case CITY_FLOOR: {
+ buildingNodes.forEach[calculateSegments]
+ }
+ default: {
+ } // CityDebugUtils.infoEntities(cityRoot.document.entities, 0, true, true)
+ }
+ tx.success
+ } finally {
+ tx.close
+ }
+ log.info("City2City finished")
+ }
+
+ def private void setDistrictAttributes(Path districtPath) {
+ var color = ""
+ val district = districtPath.endNode
+ district.setProperty("height", config.heightMin)
+ if (config.outputFormat == OutputFormat::AFrame) {
+ color = config.packageColorHex
+ } else {
+ color = PCKG_colors.get(districtPath.length - 1).asPercentage
+ }
+ district.setProperty("color", color)
+ }
+
+ def private setBuildingAttributes(Node building) {
+ val entity = building.getSingleRelationship(Rels.VISUALIZES, Direction.OUTGOING).endNode
+ val subElements = entity.getRelationships(Rels.DECLARES, Direction.OUTGOING).map[return endNode as Node]
+ val methodCounter = subElements.filter [hasLabel(Labels.Method)].size
+ val dataCounter = subElements.filter[!entity.hasLabel(Labels.Enum) && hasLabel(Labels.Field)].size
+ switch (config.buildingType) {
+ case CITY_ORIGINAL: setBuildingAttributesOriginal(building, methodCounter, dataCounter)
+ case CITY_PANELS: setBuildingAttributesPanels(building, methodCounter, dataCounter)
+ case CITY_BRICKS: setBuildingAttributesBricks(building, methodCounter, dataCounter)
+ case CITY_FLOOR: setBuildingAttributesFloors(building, methodCounter, dataCounter)
+ }
+ }
+
+ def private setBuildingAttributesOriginal(Node building, int methodCounter, int dataCounter) {
+ var width = 0.0
+ var length = 0.0
+ var height = 0.0
+ var color = ""
+ if (dataCounter == 0) {
+ width = config.widthMin
+ length = config.widthMin
+ } else {
+ width = dataCounter
+ length = dataCounter
+ }
+ if (methodCounter == 0) {
+ height = config.heightMin
+ } else {
+ height = methodCounter
+ }
+ if (config.originalBuildingMetric == BuildingMetric::NOS) {
+ color = NOS_colors.get(building.getProperty("numberOfStatements") as Integer).asPercentage
+ } else if (config.outputFormat == OutputFormat::AFrame) {
+ color = config.classColorHex
+ } else {
+ color = new RGBColor(config.classColor).asPercentage
+ }
+ building.setProperty("width", width)
+ building.setProperty("length", length)
+ building.setProperty("height", height)
+ building.setProperty("color", color)
+ }
+
+ def private setBuildingAttributesPanels(Node building, int methodCounter, int dataCounter) {
+ var height = 0.0
+ var width = 0.0
+ var length = 0.0
+ var color = ""
+ if (config.showBuildingBase) {
+ height = config.heightMin
+ } else {
+ height = 0
+ }
+ var int areaUnit = 1
+ if (config.classElementsMode == ClassElementsModes::ATTRIBUTES_ONLY) {
+ areaUnit = methodCounter
+ } else {
+ areaUnit = dataCounter
+ }
+ if (areaUnit <= 1) {
+ width = config.widthMin + config.panelHorizontalMargin * 2
+ length = config.widthMin + config.panelHorizontalMargin * 2
+ } else {
+ width = config.widthMin * areaUnit + config.panelHorizontalMargin * 2
+ length = config.widthMin * areaUnit + config.panelHorizontalMargin * 2
+ }
+ if (config.outputFormat == OutputFormat::AFrame) {
+ color = config.classColorHex
+ } else {
+ color = new RGBColor(config.classColor).asPercentage
+ }
+ building.setProperty("height", height)
+ building.setProperty("width", width)
+ building.setProperty("length", length)
+ building.setProperty("color", color)
+ }
+
+ def setBuildingAttributesBricks(Node building, int methodCounter, int dataCounter) {
+ var height = 0.0
+ var width = 0.0
+ var length = 0.0
+ var sideCapacity = 0
+ var color = ""
+ if (config.showBuildingBase) {
+ height = config.heightMin
+ } else {
+ height = 0
+ }
+ if (config.outputFormat == OutputFormat::AFrame) {
+ color = config.classColorHex
+ } else {
+ color = new RGBColor(config.classColor).asPercentage;
+ }
+ // Setting width, height & sideCapacity
+ switch (config.brickLayout) {
+ case STRAIGHT: {
+ sideCapacity = 1;
+ }
+ case BALANCED: {
+ switch (config.classElementsMode) {
+ case ATTRIBUTES_ONLY: sideCapacity = calculateSideCapacity(methodCounter)
+ case METHODS_AND_ATTRIBUTES: sideCapacity = calculateSideCapacity(dataCounter + methodCounter)
+ default: sideCapacity = calculateSideCapacity(dataCounter)
+ }
+ }
+ case PROGRESSIVE: {
+ switch (config.classElementsMode) {
+ case METHODS_ONLY: sideCapacity = calculateSideCapacity(methodCounter)
+ case METHODS_AND_ATTRIBUTES: sideCapacity = calculateSideCapacity(dataCounter + methodCounter)
+ default: sideCapacity = calculateSideCapacity(dataCounter)
+ }
+ }
+ default: {
+ sideCapacity = 1;
+ }
+ }
+ width = config.brickSize * sideCapacity + config.brickHorizontalMargin * 2 +
+ config.brickHorizontalGap * (sideCapacity - 1)
+ length = config.brickSize * sideCapacity + config.brickHorizontalMargin * 2 +
+ config.brickHorizontalGap * (sideCapacity - 1)
+ building.setProperty("height", height)
+ building.setProperty("width", width)
+ building.setProperty("length", length)
+ building.setProperty("sideCapacity", sideCapacity)
+ building.setProperty("color", color)
+ }
+
+ def void setBuildingAttributesFloors(Node building, int methodCounter, int dataCounter) {
+ var width = 0.0
+ var length = 0.0
+ var height = 0.0
+ var color = ""
+ if (dataCounter < 2) { // pko 2016
+ width = 2 // TODO in settings datei aufnehmen
+ length = 2
+ } else {
+ width = Math.ceil(dataCounter / 4.0) + 1 // pko 2016
+ length = Math.ceil(dataCounter / 4.0) + 1 // pko 2016
+ }
+ if (methodCounter == 0) {
+ height = config.heightMin
+ } else {
+ height = methodCounter
+ }
+ if (config.outputFormat == OutputFormat::AFrame) {
+ color = config.classColorHex
+ } else {
+ color = 53 / 255.0 + " " + 53 / 255.0 + " " + 89 / 255.0 // pko 2016
+ }
+ building.setProperty("width", width)
+ building.setProperty("length", length)
+ building.setProperty("height", height)
+ building.setProperty("color", color)
+ }
+
+ def private void setBuildingSegmentAttributes(Node segment) {
+ switch (config.buildingType) {
+ case CITY_PANELS:
+ setBuildingSegmentAttributesPanels(segment)
+ case CITY_BRICKS:
+ setBuildingSegmentAttributesBricks(segment)
+ default: {
+ }
+ }
+ }
+
+ def private setBuildingSegmentAttributesPanels(Node segment) {
+ val relatedEntity = segment.getSingleRelationship(Rels.VISUALIZES, Direction.OUTGOING).endNode
+ val parent = segment.getSingleRelationship(Rels.CONTAINS, Direction.INCOMING).startNode
+ val childs = parent.getRelationships(Direction.OUTGOING, Rels.CONTAINS).map[return endNode]
+ childs.filter [
+ val entity = getSingleRelationship(Rels.VISUALIZES, Direction.OUTGOING).endNode
+ entity.hasLabel(Labels.Field) &&
+ !entity.getSingleRelationship(Rels.DECLARES, Direction.INCOMING).startNode.hasLabel(Labels.Enum)
+ ].size
+
+ var int areaUnit = 1
+ if (config.classElementsMode == ClassElementsModes::ATTRIBUTES_ONLY) {
+ areaUnit = childs.filter [
+ val entity = getSingleRelationship(Rels.VISUALIZES, Direction.OUTGOING).endNode
+ entity.hasLabel(Labels.Method)
+ ].size
+ } else {
+ areaUnit = childs.filter [
+ val entity = getSingleRelationship(Rels.VISUALIZES, Direction.OUTGOING).endNode
+ entity.hasLabel(Labels.Field) &&
+ !entity.getSingleRelationship(Rels.DECLARES, Direction.INCOMING).startNode.hasLabel(Labels.Enum)
+ ].size
+ }
+ var width = 0.0
+ var length = 0.0
+ if (areaUnit <= 1) {
+ width = config.widthMin
+ length = config.widthMin
+ } else {
+ width = config.widthMin * areaUnit
+ length = config.widthMin * areaUnit
+ }
+ var index = 0
+ var effectiveLineCount = 0
+ if(relatedEntity.hasProperty("effectiveLineCount")) {
+ effectiveLineCount = (relatedEntity.getProperty("effectiveLineCount") as Long).intValue
+ }
+ while (index < config.panelHeightTresholdNos.size &&
+ effectiveLineCount >= config.panelHeightTresholdNos.get(index)) {
+ index = index + 1
+ }
+ segment.setProperty("width", width)
+ segment.setProperty("length", length)
+ segment.setProperty("height", config.panelHeightUnit * (index + 1))
+ CityUtils.setBuildingSegmentColor(segment);
+ }
+
+ def private setBuildingSegmentAttributesBricks(Node segment) {
+ segment.setProperty("width", config.brickSize)
+ segment.setProperty("height", config.brickSize)
+ segment.setProperty("length", config.brickSize)
+ CityUtils.setBuildingSegmentColor(segment);
+ }
+
+ def private void setBuildingSegmentPositions(Node building) {
+ // Sorting elements
+ var List classElements = new ArrayList
+ switch (config.classElementsMode) {
+ case ATTRIBUTES_ONLY:
+ classElements += CityUtils.getData(building)
+ case METHODS_AND_ATTRIBUTES: {
+ classElements += CityUtils.getData(building)
+ classElements += CityUtils.getMethods(building)
+ }
+ default:
+ classElements += CityUtils.getMethods(building)
+ }
+ CityUtils.sortBuildingSegments(classElements)
+
+ // upper bound of the panel below the actual panel inside the loop
+ val position = building.getSingleRelationship(Rels.HAS, Direction.OUTGOING).endNode
+ var lowerBsPosY = position.getProperty("y") as Double + building.getProperty("height") as Double / 2 + config.panelVerticalMargin
+
+ // Correcting the initial gap on top of building depending on SeparatorMode
+ if (config.panelSeparatorMode == SeparatorModes::GAP || config.panelSeparatorMode == SeparatorModes::SEPARATOR)
+ lowerBsPosY = lowerBsPosY - config.panelVerticalGap
+ // System.out.println("")
+ // Looping through methods of building
+ for (var i = 0; i < classElements.size(); i++) {
+ val segment = classElements.get(i)
+ val height = segment.getProperty("height") as Double
+ val width = segment.getProperty("width") as Double
+ // System.out.println(bs.getType() + " " + bs.getValue() + " " + bs.getModifiers() + " " + bs.getNumberOfStatements());
+// val bsPos = cityFactory.createPosition
+ val pos = graph.createNode(Labels.City, Labels.Position)
+ val x = position.getProperty("x") as Double
+ var double y
+ val z = position.getProperty("z") as Double
+ pos.setProperty("x", x)
+ pos.setProperty("z", z)
+ switch (config.panelSeparatorMode) {
+ case NONE: { // place segments on top of each other
+ y = lowerBsPosY + height / 2
+ lowerBsPosY = y + height / 2
+ }
+ case GAP: { // Leave a free space between segments
+ y = lowerBsPosY + config.panelVerticalGap + height / 2
+ lowerBsPosY = y + height / 2
+ }
+ case SEPARATOR: { // Placing additional separators
+ y = lowerBsPosY + height / 2
+
+ // Placing a separator on top of the current method if it is not last method
+ if (i < classElements.size() - 1) {
+ val sepPos = graph.createNode(Labels.City, Labels.Position)
+ val sepY = y + height / 2 + config.panelSeparatorHeight / 2
+ sepPos.setProperty("x", x)
+ sepPos.setProperty("y", sepY)
+ sepPos.setProperty("z", z)
+
+ // Deciding which shape the separator has to have
+ val nextElementType = classElements.get(i + 1).getSingleRelationship(Rels.VISUALIZES, Direction.OUTGOING).endNode
+ val segmentType = segment.getSingleRelationship(Rels.VISUALIZES, Direction.OUTGOING).endNode
+ val panelSeparator = graph.createNode(Labels.City, Labels.PanelSeparator)
+ panelSeparator.createRelationshipTo(sepPos, Rels.HAS)
+ segment.createRelationshipTo(panelSeparator, Rels.HAS)
+ if ((segmentType.hasLabel(Labels.Method) && nextElementType.hasLabel(Labels.Method)) ||
+ !config.showAttributesAsCylinders) {
+ panelSeparator.addLabel(Labels.Box)
+ panelSeparator.setProperty("width", width)
+ panelSeparator.setProperty("length", segment.getProperty("length"))
+ } else {
+ panelSeparator.addLabel(Labels.Cylinder)
+ panelSeparator.setProperty("radius", width / 2)
+ }
+
+ lowerBsPosY = x + config.panelSeparatorHeight / 2
+ }
+ }
+ }
+ pos.setProperty("y", y);
+ segment.createRelationshipTo(pos, Rels.HAS)
+ }
+ }
+
+ def calculateSegments(Node building) {
+ building.calculateFloors
+ building.calculateChimneys
+ }
+
+ def void calculateFloors(Node building) {
+ val position = building.getSingleRelationship(Rels.HAS, Direction.OUTGOING).endNode
+ val bHeight = building.getProperty("height") as Double
+ val bWidth = building.getProperty("width") as Double
+ val bLength = building.getProperty("length") as Double
+ val bPosX = position.getProperty("x") as Double
+ val bPosY = position.getProperty("y") as Double
+ val bPosZ = position.getProperty("z") as Double
+ val floors = building.getRelationships(Rels.CONTAINS, Direction.OUTGOING).map[return endNode].filter[(hasLabel(Labels.Floor))]
+ val floorNumber = floors.length
+ var floorCounter = 0
+ for (floor : floors) {
+ floorCounter++
+ floor.setProperty("height", bHeight / ( floorNumber + 2 ) * 0.80)
+ floor.setProperty("width", bWidth * 1.1)
+ floor.setProperty("length", bLength * 1.1)
+ var color = 20 / 255.0 + " " + 133 / 255.0 + " " + 204 / 255.0
+ if (config.outputFormat == OutputFormat::AFrame) {
+ color = "#1485CC"
+ }
+ floor.setProperty("color", color)
+ val floorPosition = graph.createNode(Labels.City, Labels.Position)
+ floorPosition.setProperty("x", bPosX)
+ floorPosition.setProperty("y", (bPosY - ( bHeight / 2) ) + bHeight / ( floorNumber + 2 ) * floorCounter)
+ floorPosition.setProperty("z", bPosZ)
+ floor.createRelationshipTo(floorPosition, Rels.HAS)
+ }
+ }
+
+ def void calculateChimneys(Node building) {
+ val position = building.getSingleRelationship(Rels.HAS, Direction.OUTGOING).endNode
+ val bHeight = building.getProperty("height") as Double
+ val bWidth = building.getProperty("width") as Double
+ val bPosX = position.getProperty("x") as Double
+ val bPosY = position.getProperty("y") as Double
+ val bPosZ = position.getProperty("z") as Double
+ val chimneys = building.getRelationships(Rels.CONTAINS, Direction.OUTGOING).map[return endNode].filter[(hasLabel(Labels.Chimney))]
+
+ // val chimneyNumber = chimneys.length
+ var courner1 = newArrayList()
+ var courner2 = newArrayList()
+ var courner3 = newArrayList()
+ var courner4 = newArrayList()
+
+ var chimneyCounter = 0
+ for (chimney : chimneys) {
+ chimney.setProperty("height", 1.0)
+ chimney.setProperty("width", 0.5)
+ chimney.setProperty("length", 0.5)
+ var color = 255 / 255.0 + " " + 252 / 255.0 + " " + 25 / 255.0
+ if (config.outputFormat == OutputFormat::AFrame) {
+ color = "#FFFC19"
+ }
+ chimney.setProperty("color", color)
+ val chimneyPosition = graph.createNode(Labels.City, Labels.Position)
+ chimney.createRelationshipTo(chimneyPosition, Rels.HAS)
+
+ if (chimneyCounter % 4 == 0) {
+ courner1.add(chimneyPosition)
+ }
+ if (chimneyCounter % 4 == 1) {
+ courner2.add(chimneyPosition)
+ }
+ if (chimneyCounter % 4 == 2) {
+ courner3.add(chimneyPosition)
+ }
+ if (chimneyCounter % 4 == 3) {
+ courner4.add(chimneyPosition)
+ }
+ chimneyCounter++
+ }
+ chimneyCounter = 0
+ for (chimneyPosition : courner1) {
+ chimneyPosition.setProperty("x", (bPosX - ( bWidth / 2) ) + 0.5 + (1 * chimneyCounter))
+ chimneyPosition.setProperty("y", (bPosY + ( bHeight / 2) ) + 0.5)
+ chimneyPosition.setProperty("z", (bPosZ - ( bWidth / 2) ) + 0.5)
+ chimneyCounter++
+ }
+ chimneyCounter = 0
+ for (chimneyPosition : courner2) {
+ chimneyPosition.setProperty("x", (bPosX + ( bWidth / 2) ) - 0.5)
+ chimneyPosition.setProperty("y", (bPosY + ( bHeight / 2) ) + 0.5)
+ chimneyPosition.setProperty("z", (bPosZ - ( bWidth / 2) ) + 0.5 + (1 * chimneyCounter))
+ chimneyCounter++
+ }
+ chimneyCounter = 0
+ for (chimneyPosition : courner3) {
+ chimneyPosition.setProperty("x", (bPosX + ( bWidth / 2) ) - 0.5 - (1 * chimneyCounter))
+ chimneyPosition.setProperty("y", (bPosY + ( bHeight / 2) ) + 0.5)
+ chimneyPosition.setProperty("z", (bPosZ + ( bWidth / 2) ) - 0.5)
+ chimneyCounter++
+ }
+ chimneyCounter = 0
+ for (chimneyPosition : courner4) {
+ chimneyPosition.setProperty("x", (bPosX - ( bWidth / 2) ) + 0.5)
+ chimneyPosition.setProperty("y", (bPosY + ( bHeight / 2) ) + 0.5)
+ chimneyPosition.setProperty("z", (bPosZ + ( bWidth / 2) ) - 0.5 - (1 * chimneyCounter))
+ chimneyCounter++
+ }
+ }
+
+ def private RGBColor[] createColorGradiant(RGBColor start, RGBColor end, int maxLevel) {
+ var steps = maxLevel - 1
+ if (maxLevel == 1) {
+ steps++
+ }
+ val r_step = (end.r - start.r) / steps
+ val g_step = (end.g - start.g) / steps
+ val b_step = (end.b - start.b) / steps
+
+ val colorRange = newArrayOfSize(maxLevel)
+ for (i : 0 ..< maxLevel) {
+ val newR = start.r + i * r_step
+ val newG = start.g + i * g_step
+ val newB = start.b + i * b_step
+ colorRange.set(i, new RGBColor(newR, newG, newB))
+ }
+ return colorRange
+ }
+
+ // Calculates side capacity for progressive/balanced bricks layout
+ def private int calculateSideCapacity(double value) {
+ var sc = 0 // side capacity
+ var lc = 0 // layer capacity
+ var nolMin = 0 // number of layers
+ var bcMin = 0 // building capacity min
+ var bcMax = 0 // building capacity max
+ do {
+ sc++
+ lc = sc * 4
+ nolMin = sc * 2
+ bcMin = lc * nolMin
+ bcMax = bcMin - 1
+ } while (bcMax < value)
+
+ return sc;
+ }
+}
\ No newline at end of file
diff --git a/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/CityKDTree.java b/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/CityKDTree.java
new file mode 100644
index 000000000..d06074c25
--- /dev/null
+++ b/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/CityKDTree.java
@@ -0,0 +1,36 @@
+package org.getaviz.generator.city.m2m;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.getaviz.generator.city.m2m.Rectangle;
+
+public class CityKDTree{
+ public CityKDTree() {
+ super();
+ this.root = new CityKDTreeNode();
+ }
+
+ public CityKDTree(CityKDTreeNode root) {
+ super();
+ this.root = root;
+ }
+
+ public CityKDTree(Rectangle rectangle) {
+ super();
+ this.root = new CityKDTreeNode(rectangle);
+ }
+
+ private CityKDTreeNode root;
+
+ public List getFittingNodes(Rectangle r){
+ List fittingNodes = new ArrayList();
+ this.root.isEmptyLeaf(r, fittingNodes);
+ return fittingNodes;
+ }
+ public CityKDTreeNode getRoot() {
+ return root;
+ }
+ public void setRoot(CityKDTreeNode root) {
+ this.root = root;
+ }
+}
\ No newline at end of file
diff --git a/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/CityKDTreeNode.java b/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/CityKDTreeNode.java
new file mode 100644
index 000000000..505af45e8
--- /dev/null
+++ b/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/CityKDTreeNode.java
@@ -0,0 +1,76 @@
+package org.getaviz.generator.city.m2m;
+
+import java.util.List;
+import org.getaviz.generator.city.m2m.Rectangle;
+/**
+ * This class is specifically designed for a KD-Tree used in SVIS-Generato,
+ * following the example of Richard Wettel's CodeCity-Visualization Tool
+ *
+ * @see
+ */
+public class CityKDTreeNode {
+ public CityKDTreeNode() {
+ super();
+ this.leftChild = null;
+ this.rightChild = null;
+ this.rectangle = new Rectangle();
+ this.occupied = false;
+ }
+
+ public CityKDTreeNode(Rectangle rectangle) {
+ super();
+ this.leftChild = null;
+ this.rightChild = null;
+ this.rectangle = rectangle;
+ this.occupied = false;
+ }
+
+ public CityKDTreeNode(CityKDTreeNode leftChild, CityKDTreeNode rightChild, Rectangle rectangle) {
+ super();
+ this.leftChild = leftChild;
+ this.rightChild = rightChild;
+ this.rectangle = rectangle;
+ this.occupied = false;
+ }
+
+ private CityKDTreeNode leftChild;
+ private CityKDTreeNode rightChild;
+ private Rectangle rectangle;
+ private boolean occupied;
+
+ public void isEmptyLeaf(Rectangle r, List list){
+ if(this.rectangle.getWidth() >= r.getWidth() && this.rectangle.getLength() >= r.getLength() && this.occupied == false){
+ list.add(this);
+ }
+ if(this.leftChild != null){
+ this.leftChild.isEmptyLeaf(r, list);
+ }
+ if(this.rightChild != null){
+ this.rightChild.isEmptyLeaf(r, list);
+ }
+ }
+ public CityKDTreeNode getLeftChild() {
+ return leftChild;
+ }
+ public void setLeftChild(CityKDTreeNode leftChild) {
+ this.leftChild = leftChild;
+ }
+ public CityKDTreeNode getRightChild() {
+ return rightChild;
+ }
+ public void setRightChild(CityKDTreeNode rightChild) {
+ this.rightChild = rightChild;
+ }
+ public Rectangle getRectangle() {
+ return rectangle;
+ }
+ public void setRectangle(Rectangle rectangle) {
+ this.rectangle = rectangle;
+ }
+ public boolean isOccupied() {
+ return occupied;
+ }
+ public void setOccupied(boolean occupied) {
+ this.occupied = occupied;
+ }
+}
\ No newline at end of file
diff --git a/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/CityLayout.java b/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/CityLayout.java
new file mode 100644
index 000000000..bb3a51a88
--- /dev/null
+++ b/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/CityLayout.java
@@ -0,0 +1,445 @@
+package org.getaviz.generator.city.m2m;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.neo4j.graphdb.Direction;
+import org.neo4j.graphdb.GraphDatabaseService;
+import org.neo4j.graphdb.Node;
+import org.neo4j.graphdb.Relationship;
+import org.neo4j.graphdb.Transaction;
+import org.getaviz.generator.SettingsConfiguration;
+import org.getaviz.generator.city.m2m.Rectangle;
+import org.getaviz.generator.database.Labels;
+import org.getaviz.generator.database.Rels;
+import org.getaviz.generator.database.Database;
+
+public class CityLayout {
+ private static boolean DEBUG = false;
+ private static boolean DEBUG_Part2 = false;
+ private static String info = "[INFOstream] ";
+ public static Rectangle rootRectangle;
+ private static SettingsConfiguration config = SettingsConfiguration.getInstance();
+ private static GraphDatabaseService graph = Database.getInstance();
+ private static Transaction tx;
+ //This is to hold actual values of width (index 0) and length (index 1) of database object before transaction can save them
+ private static Map properties;
+
+ public static void cityLayout(Node model, Map testMap) {
+ properties = testMap;
+ tx = graph.beginTx();
+ try {
+ arrangeChildrenRoot(model);
+ tx.success();
+ } finally {
+ tx.close();
+ }
+
+ tx = graph.beginTx();
+ try {
+ adjustPositions(getChildren(model), 0, 0, 0);
+ tx.success();
+ } finally {
+ tx.close();
+ }
+ }
+
+ /* functions for Document */
+
+ private static void arrangeChildrenRoot(Node model) {
+ // get maxArea (worst case) for root of KDTree
+ Rectangle docRectangle = calculateMaxAreaRoot(model);
+ CityKDTree ptree = new CityKDTree(docRectangle);
+ Rectangle covrec = new Rectangle();
+ List elements = sortChildrenAsRectangles(getChildren(model));
+
+ // algorithm
+ for (Rectangle el : elements) {
+ List pnodes = ptree.getFittingNodes(el);
+ Map preservers = new LinkedHashMap(); // LinkedHashMap
+ // necessary, so
+ // elements are
+ // ordered by
+ // inserting-order
+ Map expanders = new LinkedHashMap();
+ CityKDTreeNode targetNode = new CityKDTreeNode();
+ CityKDTreeNode fitNode = new CityKDTreeNode();
+
+ // check all empty leaves: either they extend COVREC (->expanders) or it doesn't
+ // change (->preservers)
+ for (CityKDTreeNode pnode : pnodes) {
+ sortEmptyLeaf(pnode, el, covrec, preservers, expanders);
+ }
+
+ // choose best-fitting pnode
+ if (preservers.isEmpty() != true) {
+ targetNode = bestFitIsPreserver(preservers.entrySet());
+ } else {
+ targetNode = bestFitIsExpander(expanders.entrySet());
+ }
+
+ // modify targetNode if necessary
+ if (targetNode.getRectangle().getWidth() == el.getWidth()
+ && targetNode.getRectangle().getLength() == el.getLength()) { // this if could probably be skipped,
+ // trimmingNode() always returns
+ // fittingNode
+ fitNode = targetNode;
+ } else {
+ fitNode = trimmingNode(targetNode, el);
+ }
+
+ // set fitNode as occupied
+ fitNode.setOccupied(true);
+
+ // give Entity it's Position
+ setNewPositionFromNode(el, fitNode);
+
+ // if fitNode expands covrec, update covrec
+ if (fitNode.getRectangle().getBottomRightX() > covrec.getBottomRightX()
+ || fitNode.getRectangle().getBottomRightY() > covrec.getBottomRightY()) {
+ updateCovrec(fitNode, covrec);
+ }
+ }
+
+ rootRectangle = covrec; // used to adjust viewpoint in x3d
+ }
+
+ private static Rectangle calculateMaxAreaRoot(Node model) {
+ double sum_width = 0;
+ double sum_length = 0;
+ for (Node child : getChildren(model)) {
+ Node entity = child.getSingleRelationship(Rels.VISUALIZES, Direction.OUTGOING).getEndNode();
+// Map properties = null;
+ if (entity.hasLabel(Labels.Package)) {
+ arrangeChildren(child);
+ }
+ sum_width += properties.get(child.getId())[0] + config.getBuildingHorizontalGap();
+ sum_length += properties.get(child.getId())[1] + config.getBuildingHorizontalGap();
+ }
+ return new Rectangle(0, 0, sum_width, sum_length, 1);
+ }
+
+ /* functions for Entity */
+
+ private static void arrangeChildren(Node entity) {
+ // get maxArea (worst case) for root of KDTree
+ Rectangle entityRec = calculateMaxArea(entity);
+ CityKDTree ptree = new CityKDTree(entityRec);
+ Rectangle covrec = new Rectangle();
+ List elements = sortChildrenAsRectangles(getChildren(entity));
+
+ // start algorithm
+ for (Rectangle el : elements) {
+ List pnodes = ptree.getFittingNodes(el);
+ Map preservers = new LinkedHashMap(); // LinkedHashMap
+ // necessary, so
+ // elements are
+ // ordered by
+ // inserting-order
+ Map expanders = new LinkedHashMap();
+ CityKDTreeNode targetNode = new CityKDTreeNode();
+ CityKDTreeNode fitNode = new CityKDTreeNode();
+
+ // check all empty leaves: either they extend COVREC (->expanders) or it doesn't
+ // change (->preservers)
+ for (CityKDTreeNode pnode : pnodes) {
+ sortEmptyLeaf(pnode, el, covrec, preservers, expanders);
+ }
+
+ // choose best-fitting pnode
+ if (preservers.isEmpty() != true) {
+ targetNode = bestFitIsPreserver(preservers.entrySet());
+ } else {
+ targetNode = bestFitIsExpander(expanders.entrySet());
+ }
+
+ // modify targetNode if necessary
+ if (targetNode.getRectangle().getWidth() == el.getWidth()
+ && targetNode.getRectangle().getLength() == el.getLength()) { // this if could be skipped,
+ // trimmingNode() always returns
+ // fittingNode
+ fitNode = targetNode;
+ } else {
+ fitNode = trimmingNode(targetNode, el);
+ }
+
+ // set fitNode as occupied
+ fitNode.setOccupied(true);
+
+ // give Entity it's Position
+ setNewPositionFromNode(el, fitNode);
+
+ // if fitNode expands covrec, update covrec
+ if (fitNode.getRectangle().getBottomRightX() > covrec.getBottomRightX()
+ || fitNode.getRectangle().getBottomRightY() > covrec.getBottomRightY()) {
+ updateCovrec(fitNode, covrec);
+ }
+ }
+ double width = covrec.getBottomRightX()
+ + (config.getBuildingHorizontalMargin() - config.getBuildingHorizontalGap() / 2) * 2;
+ double length = covrec.getBottomRightY()
+ + (config.getBuildingHorizontalMargin() - config.getBuildingHorizontalGap() / 2) * 2;
+ double[] array = {width,length};
+ properties.put(entity.getId(), array);
+ }
+
+ private static Rectangle calculateMaxArea(Node entity) {
+ double sum_width = 0;
+ double sum_length = 0;
+ for (Node child : getChildren(entity)) {
+ Node element = child.getSingleRelationship(Rels.VISUALIZES, Direction.OUTGOING).getEndNode();
+ if (element.hasLabel(Labels.Package)) {
+ arrangeChildren(child);
+ }
+ sum_width += properties.get(child.getId())[0] + config.getBuildingHorizontalGap();
+ sum_length += properties.get(child.getId())[1] + config.getBuildingHorizontalGap();
+ }
+ return new Rectangle(0, 0, sum_width, sum_length, 1);
+ }
+
+ /* functions for algorithm */
+ private static List sortChildrenAsRectangles(List children) {
+ List elements = new ArrayList();
+ // copy all child-elements into a List (for easier sort) with links
+ // to former entities
+ for (Node child : children) {
+ double width = properties.get(child.getId())[0];
+ double length = properties.get(child.getId())[1];
+
+ Rectangle rectangle = new Rectangle(0, 0, width + config.getBuildingHorizontalGap(),
+ length + config.getBuildingHorizontalGap(), 1);
+ rectangle.setNodeLink(child);
+ elements.add(rectangle);
+ }
+ // sort elements by size in descending order
+ Collections.sort(elements);
+ Collections.reverse(elements);
+ return elements;
+ }
+
+ private static void sortEmptyLeaf(CityKDTreeNode pnode, Rectangle el, Rectangle covrec,
+ Map preservers, Map expanders) {
+ // either element fits in current bounds (->preservers) or it doesn't
+ // (->expanders)
+ double nodeUpperLeftX = pnode.getRectangle().getUpperLeftX();
+ double nodeUpperLeftY = pnode.getRectangle().getUpperLeftY();
+ double nodeNewBottomRightX = nodeUpperLeftX + el.getWidth(); // expected BottomRightCorner, if el was insert
+ // into pnode
+ double nodeNewBottomRightY = nodeUpperLeftY + el.getLength(); // this new corner-point is compared with covrec
+
+ if (nodeNewBottomRightX <= covrec.getBottomRightX() && nodeNewBottomRightY <= covrec.getBottomRightY()) {
+ double waste = pnode.getRectangle().getArea() - el.getArea();
+ preservers.put(pnode, waste);
+ if (DEBUG_Part2) {
+ System.out.println("\t\t" + info + "Node is preserver. waste=" + waste);
+ }
+ } else {
+ double ratio = ((nodeNewBottomRightX > covrec.getBottomRightX() ? nodeNewBottomRightX
+ : covrec.getBottomRightX())
+ / (nodeNewBottomRightY > covrec.getBottomRightY() ? nodeNewBottomRightY
+ : covrec.getBottomRightY()));
+ expanders.put(pnode, ratio);
+ if (DEBUG_Part2) {
+ System.out.println(
+ "\t\t" + info + "Node is expander. ratio=" + ratio + " distance=" + Math.abs(ratio - 1));
+ }
+ }
+ }
+
+ private static CityKDTreeNode bestFitIsPreserver(Set> entrySet) {
+ // determines which entry in Set has the lowest value of all
+ double lowestValue = -1;
+ CityKDTreeNode targetNode = new CityKDTreeNode();
+ for (Map.Entry entry : entrySet) {
+ if (entry.getValue() < lowestValue || lowestValue == -1) {
+ lowestValue = entry.getValue();
+ targetNode = entry.getKey();
+ }
+ }
+ if (DEBUG_Part2) {
+ System.out.println("\t\t" + info + "chosen Node is preserver: " + lowestValue);
+ System.out.println("\t\t" + info + "Node Rec[(" + targetNode.getRectangle().getUpperLeftX() + "|"
+ + targetNode.getRectangle().getUpperLeftY() + "), (" + targetNode.getRectangle().getBottomRightX()
+ + "|" + targetNode.getRectangle().getBottomRightY() + ")]");
+ }
+ return targetNode;
+ }
+
+ private static CityKDTreeNode bestFitIsExpander(Set> entrySet) {
+ double closestTo = 1;
+ double lowestDistance = -1;
+ CityKDTreeNode targetNode = new CityKDTreeNode();
+ for (Map.Entry entry : entrySet) {
+ double distance = Math.abs(entry.getValue() - closestTo);
+ if (distance < lowestDistance || lowestDistance == -1) {
+ lowestDistance = distance;
+ targetNode = entry.getKey();
+ }
+ }
+ if (DEBUG_Part2) {
+ System.out.println("\t\t" + info + "chosen Node is expander: " + lowestDistance);
+ System.out.println("\t\t" + info + "Node Rec[(" + targetNode.getRectangle().getUpperLeftX() + "|"
+ + targetNode.getRectangle().getUpperLeftY() + "), (" + targetNode.getRectangle().getBottomRightX()
+ + "|" + targetNode.getRectangle().getBottomRightY() + ")]");
+ }
+ return targetNode;
+ }
+
+ private static CityKDTreeNode trimmingNode(CityKDTreeNode node, Rectangle r) {
+ if (DEBUG) {
+ System.out.println("\t\t" + info + "trimmingNode()-arrival.");
+ }
+ double nodeUpperLeftX = node.getRectangle().getUpperLeftX();
+ double nodeUpperLeftY = node.getRectangle().getUpperLeftY();
+ double nodeBottomRightX = node.getRectangle().getBottomRightX();
+ double nodeBottomRightY = node.getRectangle().getBottomRightY();
+
+ // first split: horizontal cut, if necessary
+ // Round to 3 digits to prevent infinity loop, because e.g. 12.34000000007 is
+ // declared equal to 12.34
+ if (Math.round(node.getRectangle().getLength() * 1000d) != Math.round(r.getLength() * 1000d)) {
+ // new child-nodes
+ node.setLeftChild(new CityKDTreeNode(
+ new Rectangle(nodeUpperLeftX, nodeUpperLeftY, nodeBottomRightX, (nodeUpperLeftY + r.getLength()))));
+ node.setRightChild(new CityKDTreeNode(new Rectangle(nodeUpperLeftX, (nodeUpperLeftY + r.getLength()),
+ nodeBottomRightX, nodeBottomRightY)));
+ // set node as occupied (only leaves can contain elements)
+ node.setOccupied(true);
+
+ if (DEBUG_Part2) {
+ System.out.println("\t\t\t" + info + "horizontal");
+ System.out.println("\t\t\t" + info + "targetNode Rec[(" + nodeUpperLeftX + "|" + nodeUpperLeftY + "), ("
+ + nodeBottomRightX + "|" + nodeBottomRightY + ")]");
+ System.out.println(
+ "\t\t\t" + info + "LeftChild Rec[(" + node.getLeftChild().getRectangle().getUpperLeftX() + "|"
+ + node.getLeftChild().getRectangle().getUpperLeftY() + "), ("
+ + node.getLeftChild().getRectangle().getBottomRightX() + "|"
+ + node.getLeftChild().getRectangle().getBottomRightY() + ")]");
+ System.out.println(
+ "\t\t\t" + info + "RightChild Rec[(" + node.getRightChild().getRectangle().getUpperLeftX() + "|"
+ + node.getRightChild().getRectangle().getUpperLeftY() + "), ("
+ + node.getRightChild().getRectangle().getBottomRightX() + "|"
+ + node.getRightChild().getRectangle().getBottomRightY() + ")]");
+ }
+
+ return trimmingNode(node.getLeftChild(), r);
+ // second split: vertical cut, if necessary
+ // Round to 3 digits, because e.g. 12.34000000007 is declared equal to 12.34
+ } else if (Math.round(node.getRectangle().getWidth() * 1000d) != Math.round(r.getWidth() * 1000d)) {
+ // new child-nodes
+ node.setLeftChild(new CityKDTreeNode(
+ new Rectangle(nodeUpperLeftX, nodeUpperLeftY, (nodeUpperLeftX + r.getWidth()), nodeBottomRightY)));
+ node.setRightChild(new CityKDTreeNode(new Rectangle((nodeUpperLeftX + r.getWidth()), nodeUpperLeftY,
+ nodeBottomRightX, nodeBottomRightY)));
+ // set node as occupied (only leaves can contain elements)
+ node.setOccupied(true);
+
+ if (DEBUG_Part2) {
+ System.out.println("\t\t\t" + info + "vertical");
+ System.out.println("\t\t\t" + info + "targetNode Rec[(" + nodeUpperLeftX + "|" + nodeUpperLeftY + "), ("
+ + nodeBottomRightX + "|" + nodeBottomRightY + ")]");
+ System.out.println(
+ "\t\t\t" + info + "LeftChild Rec[(" + node.getLeftChild().getRectangle().getUpperLeftX() + "|"
+ + node.getLeftChild().getRectangle().getUpperLeftY() + "), ("
+ + node.getLeftChild().getRectangle().getBottomRightX() + "|"
+ + node.getLeftChild().getRectangle().getBottomRightY() + ")]");
+ System.out
+ .println("\t\t\t" + info + "LeftChild center(" + node.getLeftChild().getRectangle().getCenterX()
+ + "|" + node.getLeftChild().getRectangle().getCenterY() + ")");
+ System.out.println(
+ "\t\t\t" + info + "RightChild Rec[(" + node.getRightChild().getRectangle().getUpperLeftX() + "|"
+ + node.getRightChild().getRectangle().getUpperLeftY() + "), ("
+ + node.getRightChild().getRectangle().getBottomRightX() + "|"
+ + node.getRightChild().getRectangle().getBottomRightY() + ")]");
+ }
+ if (DEBUG) {
+ System.out.println("\t\t" + info + "trimmingNode()-exit.");
+ }
+ return node.getLeftChild();
+ } else {
+ if (DEBUG) {
+ System.out.println("\t\t" + info + "trimmingNode()-exit.");
+ }
+ return node;
+ }
+ }
+ private static void setNewPositionFromNode(Rectangle el, CityKDTreeNode fitNode) {
+ Node node = el.getNodeLink();
+ // mapping 2D rectangle on 3D building
+ double x = fitNode.getRectangle().getCenterX() - config.getBuildingHorizontalGap() / 2;
+ double y = ((Double) (node.getProperty("height")) / 2);
+ double z = fitNode.getRectangle().getCenterY() - config.getBuildingHorizontalGap() / 2;
+ Node position = graph.createNode(Labels.Position, Labels.City);
+ position.setProperty("name", "position");
+ position.setProperty("x", x);
+ position.setProperty("y", y);
+ position.setProperty("z", z);
+ node.createRelationshipTo(position, Rels.HAS);
+ }
+
+ private static void updateCovrec(CityKDTreeNode fitNode, Rectangle covrec) {
+ double newX = (fitNode.getRectangle().getBottomRightX() > covrec.getBottomRightX()
+ ? fitNode.getRectangle().getBottomRightX()
+ : covrec.getBottomRightX());
+ double newY = (fitNode.getRectangle().getBottomRightY() > covrec.getBottomRightY()
+ ? fitNode.getRectangle().getBottomRightY()
+ : covrec.getBottomRightY());
+ covrec.changeRectangle(0, 0, newX, newY);
+ if (DEBUG) {
+ System.out.println(
+ "\t\t" + info + "CovRec [checkVALUES]: [(" + covrec.getUpperLeftX() + "|" + covrec.getUpperLeftY()
+ + "), (" + covrec.getBottomRightX() + "|" + covrec.getBottomRightY() + ")]");
+ }
+ }
+
+ private static void adjustPositions(List children, double parentX, double parentY,
+ double parentZ) {
+ for (Node child : children) {
+ Node entity = child.getSingleRelationship(Rels.VISUALIZES, Direction.OUTGOING).getEndNode();
+ Node position = null;
+ Iterable tmp = child.getRelationships(Rels.HAS, Direction.OUTGOING);
+ for (Relationship element : tmp) {
+ Node node = element.getEndNode();
+ if (node.hasLabel(Labels.Position)) {
+ position = node;
+ }
+ }
+ double centerX = (Double) (position.getProperty("x"));
+ double centerZ = (Double) (position.getProperty("z"));
+ double centerY = (Double) (position.getProperty("y"));
+ double setX = centerX + parentX + config.getBuildingHorizontalMargin();
+ double setZ = centerZ + parentZ + config.getBuildingHorizontalMargin();
+ double setY = centerY + parentY + config.getBuildingVerticalMargin();
+ double width = properties.get(child.getId())[0];
+ double length = properties.get(child.getId())[1];
+ child.setProperty("width", width);
+ child.setProperty("length", length);
+ position.setProperty("x", setX);
+ position.setProperty("y", setY);
+ position.setProperty("z", setZ);
+ double height = (Double) (child.getProperty("height"));
+ if (entity.hasLabel(Labels.Package)) {
+ double newUpperLeftX = setX - width / 2;
+ double newUpperLeftZ = setZ - length / 2;
+ double newUpperLeftY = setY - height / 2;
+ adjustPositions(getChildren(child), newUpperLeftX, newUpperLeftY, newUpperLeftZ);
+ }
+ }
+ }
+
+ private static List getChildren(Node entity) {
+ List children = new ArrayList();
+ for(Relationship relationship : entity.getRelationships(Rels.CONTAINS, Direction.OUTGOING)) {
+ Node child = relationship.getEndNode();
+ if(child.hasLabel(Labels.District) || child.hasLabel(Labels.Building)) {
+ children.add(child);
+ }
+ }
+ return children;
+ }
+}
diff --git a/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/RGBColor.java b/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/RGBColor.java
new file mode 100644
index 000000000..3ad525753
--- /dev/null
+++ b/generator2/org.getaviz.generator/src/org/getaviz/generator/city/m2m/RGBColor.java
@@ -0,0 +1,75 @@
+package org.getaviz.generator.city.m2m;
+
+import java.awt.Color;
+
+public class RGBColor {
+ private final double lowerBound =0;
+ private final double upperBound =255;
+
+ public RGBColor() {
+ super();
+ this.r =0;
+ this.g =0;
+ this.b =0;
+ }
+
+ public RGBColor(double r, double g, double b) {
+ super();
+ this.r = checkRanges(r);
+ this.g = checkRanges(g);
+ this.b = checkRanges(b);
+ }
+
+ public RGBColor(Color color) {
+ super();
+ this.r = color.getRed();
+ this.g = color.getGreen();
+ this.b = color.getBlue();
+ }
+
+ private double r;
+ private double g;
+ private double b;
+
+ private double checkRanges(double value){
+ if(value>upperBound){
+ return upperBound;
+ }else if(value {
+ @Accessors(PUBLIC_GETTER) var double width
+ @Accessors(PUBLIC_GETTER) var double length
+ var double area
+ @Accessors(PUBLIC_GETTER, PUBLIC_SETTER) var Node nodeLink
+ var double upperLeftX
+ var double upperLeftY
+ var double bottomRightX
+ var double bottomRightY
+ var double centerX
+ var double centerY
+
+
+ new() {
+ super()
+ changeRectangle(0, 0, 0, 0)
+ }
+
+ new(double x1, double y1, double x2, double y2) {
+ super();
+ changeRectangle(x1, y1, x2, y2);
+ }
+
+ /**
+ * Constructs a new Rectangle
+ *
+ * @param pX x coordinate
+ * @param pY y coordinate
+ * @param width width of the new Rectangle
+ * @param length length of the new Rectangle
+ * @param pointPosition determines how P(x|y) is interpreted:
+ * 0: P(x|y) is CenterPoint
+ * 1: P(x|y) is UpperLeft
+ * 2: P(x|y) is UpperRight
+ * 3: P(x|y) is BottomRight
+ * 4: P(x|y) is BottomLeft
+ */
+ new(double pX, double pY, double width, double length, int pointPosition) {
+ super();
+ changeRectangle(pX, pY, width, length, pointPosition);
+ }
+
+ /**
+ * Uses two corner points to change values of the rectangle
+ * @param x1 corner1 of Rectangle
+ * @param y1 corner1 of Rectangle
+ * @param x2 corner2 of Rectangle
+ * @param y2 corner2 of Rectangle
+ */
+ def void changeRectangle(double x1, double y1, double x2, double y2){
+ setCornerPoints(x1, y1, x2, y2)
+ update()
+ }
+ /**
+ *