changeset 9555:ef5754ba5573

Implemented legend aggregation based on type of themes. Added theme-editor style configuration for aggregated legend entries. Only configured themes get aggregated.
author gernotbelger
date Tue, 23 Oct 2018 16:26:48 +0200
parents 33ce8eba9806
children 9b8e8fc1f408
files artifacts/src/main/java/org/dive4elements/river/artifacts/context/RiverContext.java artifacts/src/main/java/org/dive4elements/river/artifacts/context/RiverContextFactory.java artifacts/src/main/java/org/dive4elements/river/exports/AbstractChartGenerator.java artifacts/src/main/java/org/dive4elements/river/exports/AnnotationRenderer.java artifacts/src/main/java/org/dive4elements/river/exports/ChartGenerator2.java artifacts/src/main/java/org/dive4elements/river/exports/CrossSectionGenerator.java artifacts/src/main/java/org/dive4elements/river/exports/DiagramGenerator.java artifacts/src/main/java/org/dive4elements/river/exports/DischargeGenerator.java artifacts/src/main/java/org/dive4elements/river/exports/LegendAggregator.java artifacts/src/main/java/org/dive4elements/river/exports/LegendProcessor.java artifacts/src/main/java/org/dive4elements/river/exports/TimeseriesChartGenerator.java artifacts/src/main/java/org/dive4elements/river/exports/XYChartGenerator.java artifacts/src/main/java/org/dive4elements/river/jfree/AxisDataset.java artifacts/src/main/java/org/dive4elements/river/jfree/StripedAreaDataset.java artifacts/src/main/java/org/dive4elements/river/jfree/Style.java artifacts/src/main/java/org/dive4elements/river/jfree/StyledAreaSeriesCollection.java artifacts/src/main/java/org/dive4elements/river/jfree/StyledSeries.java artifacts/src/main/java/org/dive4elements/river/jfree/StyledTimeSeries.java artifacts/src/main/java/org/dive4elements/river/jfree/StyledXYDataset.java artifacts/src/main/java/org/dive4elements/river/jfree/StyledXYSeries.java artifacts/src/main/java/org/dive4elements/river/jfree/XYStyle.java artifacts/src/main/java/org/dive4elements/river/themes/DefaultTheme.java artifacts/src/main/java/org/dive4elements/river/themes/Theme.java artifacts/src/main/java/org/dive4elements/river/themes/ThemeDocument.java artifacts/src/main/java/org/dive4elements/river/themes/ThemeFactory.java artifacts/src/main/resources/messages.properties artifacts/src/main/resources/messages_de.properties
diffstat 27 files changed, 1027 insertions(+), 1300 deletions(-) [+]
line wrap: on
line diff
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/context/RiverContext.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/context/RiverContext.java	Tue Oct 23 16:26:48 2018 +0200
@@ -49,6 +49,9 @@
         "flys.export.facetfilter";
 
     /** The key that is used to store the map of themes in the context. */
+    public static final String LEGEND = "flys.legend.map";
+    
+    /** The key that is used to store the map of themes in the context. */
     public static final String THEMES =
         "flys.themes.map";
 
@@ -67,7 +70,6 @@
     /** Key to store the configured modules in the context. */
     public static final String MODULES = "flys.modules";
 
-
     /**
      * The default constructor.
      */
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/context/RiverContextFactory.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/context/RiverContextFactory.java	Tue Oct 23 16:26:48 2018 +0200
@@ -9,8 +9,8 @@
 package org.dive4elements.river.artifacts.context;
 
 import java.io.File;
-
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -18,39 +18,28 @@
 import javax.xml.xpath.XPathConstants;
 
 import org.apache.log4j.Logger;
-
 import org.dive4elements.artifactdatabase.state.State;
 import org.dive4elements.artifactdatabase.state.StateEngine;
-
 import org.dive4elements.artifactdatabase.transition.Transition;
 import org.dive4elements.artifactdatabase.transition.TransitionEngine;
-
 import org.dive4elements.artifacts.ArtifactContextFactory;
+import org.dive4elements.artifacts.ContextInjector;
 import org.dive4elements.artifacts.GlobalContext;
-
-import org.dive4elements.artifacts.ContextInjector;
-
 import org.dive4elements.artifacts.common.utils.Config;
 import org.dive4elements.artifacts.common.utils.ElementConverter;
 import org.dive4elements.artifacts.common.utils.XMLUtils;
-
 import org.dive4elements.river.artifacts.model.Module;
 import org.dive4elements.river.artifacts.model.RiverFactory;
 import org.dive4elements.river.artifacts.model.ZoomScale;
-
 import org.dive4elements.river.artifacts.states.StateFactory;
-
 import org.dive4elements.river.artifacts.transitions.TransitionFactory;
-
 import org.dive4elements.river.exports.GeneratorLookup;
 import org.dive4elements.river.exports.OutGenerator;
-
 import org.dive4elements.river.model.River;
 import org.dive4elements.river.themes.Theme;
 import org.dive4elements.river.themes.ThemeFactory;
 import org.dive4elements.river.themes.ThemeGroup;
 import org.dive4elements.river.themes.ThemeMapping;
-
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
@@ -68,69 +57,60 @@
     private static Logger log = Logger.getLogger(RiverContextFactory.class);
 
     /** The XPath to the artifacts configured in the configuration. */
-    public static final String XPATH_ARTIFACTS =
-        "/artifact-database/artifacts/artifact";
+    private static final String XPATH_ARTIFACTS = "/artifact-database/artifacts/artifact";
 
     /** The XPath to the name of the artifact. */
-    public static final String XPATH_ARTIFACT_NAME = "/artifact/@name";
+    private static final String XPATH_ARTIFACT_NAME = "/artifact/@name";
 
     /** The XPath to the xlink ref in an artifact configuration. */
-    public static final String XPATH_XLINK = "xlink:href";
+    private static final String XPATH_XLINK = "xlink:href";
 
     /** The XPath to the transitions configured in the artifact config. */
-    public static final String XPATH_TRANSITIONS =
-        "/artifact/states/transition";
+    private static final String XPATH_TRANSITIONS = "/artifact/states/transition";
 
     /** The XPath to the states configured in the artifact config. */
-    public static final String XPATH_STATES =
-        "/artifact/states/state";
-
-    public static final String XPATH_OUTPUT_GENERATORS =
-        "/artifact-database/output-generators//output-generator";
-
-    public static final String XPATH_THEME_CONFIG =
-        "/artifact-database/flys/themes/configuration/text()";
-
-    public static final String XPATH_THEMES =
-        "theme";
+    private static final String XPATH_STATES = "/artifact/states/state";
 
-    public static final String XPATH_THEME_GROUPS =
-        "/themes/themegroup";
-
-    public static final String XPATH_THEME_MAPPINGS =
-        "/themes/mappings/mapping";
+    private static final String XPATH_OUTPUT_GENERATORS = "/artifact-database/output-generators//output-generator";
 
-    public static final String XPATH_RIVER_WMS =
-        "/artifact-database/floodmap/river";
+    private static final String XPATH_THEME_CONFIG = "/artifact-database/flys/themes/configuration/text()";
 
-    public static final String XPATH_MODULES =
-        "/artifact-database/modules/module";
+    private static final String XPATH_THEMES = "theme";
 
-    private static final String XPATH_ZOOM_SCALES =
-        "/artifact-database/options/zoom-scales/zoom-scale";
+    private static final String XPATH_LEGEND_GROUP = "/themes/legendgroup";
 
-    private static final String XPATH_DGM_PATH =
-        "/artifact-database/options/dgm-path/text()";
+    private static final String XPATH_THEME_GROUPS = "/themes/themegroup";
+
+    private static final String XPATH_THEME_MAPPINGS = "/themes/mappings/mapping";
+
+    private static final String XPATH_RIVER_WMS = "/artifact-database/floodmap/river";
+
+    private static final String XPATH_MODULES = "/artifact-database/modules/module";
+
+    private static final String XPATH_ZOOM_SCALES = "/artifact-database/options/zoom-scales/zoom-scale";
+
+    private static final String XPATH_DGM_PATH = "/artifact-database/options/dgm-path/text()";
 
     private static GlobalContext GLOBAL_CONTEXT_INSTANCE;
 
-
     /**
      * Creates a new D4EArtifactContext object and initialize all
      * components required by the application.
      *
-     * @param config The artifact server configuration.
+     * @param config
+     *            The artifact server configuration.
      * @return a D4EArtifactContext.
      */
     @Override
-    public GlobalContext createArtifactContext(Document config) {
-        RiverContext context = new RiverContext(config);
+    public GlobalContext createArtifactContext(final Document config) {
+        final RiverContext context = new RiverContext(config);
 
         configureTransitions(config, context);
         configureStates(config, context);
         configureOutGenerators(config, context);
         configureThemes(config, context);
         configureThemesMappings(config, context);
+        configureLegend(config, context);
         configureFloodmapWMS(config, context);
         configureModules(config, context);
         configureZoomScales(config, context);
@@ -147,25 +127,17 @@
         return GLOBAL_CONTEXT_INSTANCE;
     }
 
-
-    private void configureDGMPath(Document config, RiverContext context) {
-        String dgmPath = (String) XMLUtils.xpath(
-            config,
-            XPATH_DGM_PATH,
-            XPathConstants.STRING);
+    private void configureDGMPath(final Document config, final RiverContext context) {
+        final String dgmPath = (String) XMLUtils.xpath(config, XPATH_DGM_PATH, XPathConstants.STRING);
 
         context.put("dgm-path", dgmPath);
     }
 
-
-    protected void configureZoomScales(Document config, RiverContext context) {
-        NodeList list = (NodeList)XMLUtils.xpath(
-            config,
-            XPATH_ZOOM_SCALES,
-            XPathConstants.NODESET);
-        ZoomScale scale = new ZoomScale();
+    private void configureZoomScales(final Document config, final RiverContext context) {
+        final NodeList list = (NodeList) XMLUtils.xpath(config, XPATH_ZOOM_SCALES, XPathConstants.NODESET);
+        final ZoomScale scale = new ZoomScale();
         for (int i = 0; i < list.getLength(); i++) {
-            Element element = (Element)list.item(i);
+            final Element element = (Element) list.item(i);
             String river = "default";
             double range = 0d;
             double radius = 10d;
@@ -174,72 +146,63 @@
             }
             if (!element.hasAttribute("range")) {
                 continue;
-            }
-            else {
-                String r = element.getAttribute("range");
+            } else {
+                final String r = element.getAttribute("range");
                 try {
                     range = Double.parseDouble(r);
                 }
-                catch (NumberFormatException nfe) {
+                catch (final NumberFormatException nfe) {
                     continue;
                 }
             }
             if (!element.hasAttribute("radius")) {
                 continue;
-            }
-            else {
-                String r = element.getAttribute("radius");
+            } else {
+                final String r = element.getAttribute("radius");
                 try {
                     radius = Double.parseDouble(r);
                 }
-                catch (NumberFormatException nfe) {
+                catch (final NumberFormatException nfe) {
                     continue;
                 }
             }
             scale.addRange(river, range, radius);
-       }
-       context.put("zoomscale", scale);
+        }
+        context.put("zoomscale", scale);
     }
 
-
     /**
      * This method initializes the transition configuration.
      *
-     * @param config the config document.
-     * @param context the RiverContext.
+     * @param config
+     *            the config document.
+     * @param context
+     *            the RiverContext.
      */
-    protected void configureTransitions(
-        Document config,
-        RiverContext context
-    ) {
-        TransitionEngine engine = new TransitionEngine();
+    private void configureTransitions(final Document config, final RiverContext context) {
+        final TransitionEngine engine = new TransitionEngine();
 
-        List<Document> artifacts = getArtifactConfigurations(config);
+        final List<Document> artifacts = getArtifactConfigurations(config);
         log.info("Found " + artifacts.size() + " artifacts in the config.");
 
-        for (Document doc: artifacts) {
+        for (final Document doc : artifacts) {
 
-            String artName = (String) XMLUtils.xpath(
-                doc, XPATH_ARTIFACT_NAME, XPathConstants.STRING);
+            final String artName = (String) XMLUtils.xpath(doc, XPATH_ARTIFACT_NAME, XPathConstants.STRING);
 
-            NodeList list = (NodeList) XMLUtils.xpath(
-                doc, XPATH_TRANSITIONS, XPathConstants.NODESET);
+            final NodeList list = (NodeList) XMLUtils.xpath(doc, XPATH_TRANSITIONS, XPathConstants.NODESET);
 
             if (list == null) {
-                log.warn("The artifact " + artName +
-                    " has no transitions configured.");
+                log.warn("The artifact " + artName + " has no transitions configured.");
                 continue;
             }
 
-            int trans = list.getLength();
+            final int trans = list.getLength();
 
-            log.info(
-                "Artifact '" + artName + "' has " + trans + " transitions.");
+            log.info("Artifact '" + artName + "' has " + trans + " transitions.");
 
             for (int i = 0; i < trans; i++) {
-                Transition t = TransitionFactory.createTransition(
-                    list.item(i));
-                String     s = t.getFrom();
+                final Transition t = TransitionFactory.createTransition(list.item(i));
+                final String s = t.getFrom();
                 engine.addTransition(s, t);
             }
         }
@@ -247,87 +210,82 @@
         context.put(RiverContext.TRANSITION_ENGINE_KEY, engine);
     }
 
-
     /**
      * This method returns all artifact documents defined in
-     * <code>config</code>. <br>NOTE: The artifact configurations need to be
+     * <code>config</code>. <br>
+     * NOTE: The artifact configurations need to be
      * stored in own files referenced by an xlink.
      *
-     * @param config The global configuration.
+     * @param config
+     *            The global configuration.
      *
      * @return an array of Artifact configurations.
      */
-    protected List<Document> getArtifactConfigurations(Document config) {
-        NodeList artifacts = (NodeList) XMLUtils.xpath(
-            config, XPATH_ARTIFACTS, XPathConstants.NODESET);
+    private List<Document> getArtifactConfigurations(final Document config) {
+        final NodeList artifacts = (NodeList) XMLUtils.xpath(config, XPATH_ARTIFACTS, XPathConstants.NODESET);
 
-        int count = artifacts.getLength();
+        final int count = artifacts.getLength();
 
-        ArrayList<Document> docs = new ArrayList<Document>(count);
+        final ArrayList<Document> docs = new ArrayList<>(count);
 
         for (int i = 0; i < count; i++) {
-            Element tmp = (Element) artifacts.item(i);
+            final Element tmp = (Element) artifacts.item(i);
 
             String xlink = tmp.getAttribute(XPATH_XLINK);
-            xlink        = Config.replaceConfigDir(xlink);
+            xlink = Config.replaceConfigDir(xlink);
 
             if (!xlink.isEmpty()) {
-                File file = new File(xlink);
+                final File file = new File(xlink);
                 if (!file.isFile() || !file.canRead()) {
-                    log.warn("Artifact configuration '"
-                        + file + "' not found.");
+                    log.warn("Artifact configuration '" + file + "' not found.");
                 } else {
-                    Document doc = XMLUtils.parseDocument(file);
+                    final Document doc = XMLUtils.parseDocument(file);
                     if (doc != null) {
                         docs.add(doc);
                     }
                 }
                 continue;
             }
-            Document doc = XMLUtils.newDocument();
-            Node copy = doc.adoptNode(tmp.cloneNode(true));
+            final Document doc = XMLUtils.newDocument();
+            final Node copy = doc.adoptNode(tmp.cloneNode(true));
             doc.appendChild(copy);
             docs.add(doc);
         }
         return docs;
     }
 
-
     /**
      * This method initializes the transition configuration.
      *
-     * @param config the config document.
-     * @param context the RiverContext.
+     * @param config
+     *            the config document.
+     * @param context
+     *            the RiverContext.
      */
-    protected void configureStates(Document config, RiverContext context) {
-        StateEngine engine = new StateEngine();
+    private void configureStates(final Document config, final RiverContext context) {
+        final StateEngine engine = new StateEngine();
 
-        List<Document> artifacts = getArtifactConfigurations(config);
+        final List<Document> artifacts = getArtifactConfigurations(config);
         log.info("Found " + artifacts.size() + " artifacts in the config.");
 
-        for (Document doc: artifacts) {
-            List<State> states = new ArrayList<State>();
+        for (final Document doc : artifacts) {
+            final List<State> states = new ArrayList<>();
 
-            String artName = (String) XMLUtils.xpath(
-                doc, XPATH_ARTIFACT_NAME, XPathConstants.STRING);
+            final String artName = (String) XMLUtils.xpath(doc, XPATH_ARTIFACT_NAME, XPathConstants.STRING);
 
-            NodeList stateList = (NodeList) XMLUtils.xpath(
-                doc, XPATH_STATES, XPathConstants.NODESET);
+            final NodeList stateList = (NodeList) XMLUtils.xpath(doc, XPATH_STATES, XPathConstants.NODESET);
 
             if (stateList == null) {
-                log.warn("The artifact " + artName +
-                    " has no states configured.");
+                log.warn("The artifact " + artName + " has no states configured.");
                 continue;
             }
 
-            int count = stateList.getLength();
+            final int count = stateList.getLength();
 
-            log.info(
-                "Artifact '" + artName + "' has " + count + " states.");
+            log.info("Artifact '" + artName + "' has " + count + " states.");
 
             for (int i = 0; i < count; i++) {
-                states.add(StateFactory.createState(
-                    stateList.item(i)));
+                states.add(StateFactory.createState(stateList.item(i)));
             }
 
             engine.addStates(artName, states);
@@ -336,23 +294,18 @@
         context.put(RiverContext.STATE_ENGINE_KEY, engine);
     }
 
-
     /**
      * This method intializes the provided output generators.
      *
-     * @param config the config document.
-     * @param context the RiverContext.
+     * @param config
+     *            the config document.
+     * @param context
+     *            the RiverContext.
      */
-    protected void configureOutGenerators(
-        Document config,
-        RiverContext context
-    ) {
-        NodeList outGenerators = (NodeList) XMLUtils.xpath(
-            config,
-            XPATH_OUTPUT_GENERATORS,
-            XPathConstants.NODESET);
+    private void configureOutGenerators(final Document config, final RiverContext context) {
+        final NodeList outGenerators = (NodeList) XMLUtils.xpath(config, XPATH_OUTPUT_GENERATORS, XPathConstants.NODESET);
 
-        int num = outGenerators == null ? 0 : outGenerators.getLength();
+        final int num = outGenerators == null ? 0 : outGenerators.getLength();
 
         if (num == 0) {
             log.warn("No output generators configured in this application.");
@@ -361,17 +314,17 @@
 
         log.info("Found " + num + " configured output generators.");
 
-        GeneratorLookup generators = new GeneratorLookup();
+        final GeneratorLookup generators = new GeneratorLookup();
 
         int idx = 0;
 
         for (int i = 0; i < num; i++) {
-            Element item = (Element)outGenerators.item(i);
+            final Element item = (Element) outGenerators.item(i);
 
-            String names      = item.getAttribute("names").trim();
-            String clazz      = item.getAttribute("class").trim();
-            String converter  = item.getAttribute("converter").trim();
-            String injectors  = item.getAttribute("injectors").trim();
+            final String names = item.getAttribute("names").trim();
+            final String clazz = item.getAttribute("class").trim();
+            final String converter = item.getAttribute("converter").trim();
+            final String injectors = item.getAttribute("injectors").trim();
 
             if (names.isEmpty() || clazz.isEmpty()) {
                 continue;
@@ -380,9 +333,9 @@
             Class<OutGenerator> generatorClass = null;
 
             try {
-                generatorClass = (Class<OutGenerator>)Class.forName(clazz);
+                generatorClass = (Class<OutGenerator>) Class.forName(clazz);
             }
-            catch (ClassNotFoundException cnfe) {
+            catch (final ClassNotFoundException cnfe) {
                 log.error(cnfe, cnfe);
                 continue;
             }
@@ -391,18 +344,16 @@
 
             if (!converter.isEmpty()) {
                 try {
-                    ElementConverter ec =
-                        (ElementConverter)Class.forName(converter)
-                            .newInstance();
+                    final ElementConverter ec = (ElementConverter) Class.forName(converter).newInstance();
                     cfg = ec.convert(item);
                 }
-                catch (ClassNotFoundException cnfe) {
+                catch (final ClassNotFoundException cnfe) {
                     log.error(cnfe, cnfe);
                 }
-                catch (InstantiationException ie) {
+                catch (final InstantiationException ie) {
                     log.error(ie);
                 }
-                catch (IllegalAccessException iae) {
+                catch (final IllegalAccessException iae) {
                     log.error(iae);
                 }
             }
@@ -410,28 +361,26 @@
             List<ContextInjector> cis = null;
 
             if (!injectors.isEmpty()) {
-                cis = new ArrayList<ContextInjector>();
-                for (String injector: injectors.split("[\\s,]+")) {
+                cis = new ArrayList<>();
+                for (final String injector : injectors.split("[\\s,]+")) {
                     try {
-                        ContextInjector ci = (ContextInjector)Class
-                            .forName(injector)
-                            .newInstance();
+                        final ContextInjector ci = (ContextInjector) Class.forName(injector).newInstance();
                         ci.setup(item);
                         cis.add(ci);
                     }
-                    catch (ClassNotFoundException cnfe) {
+                    catch (final ClassNotFoundException cnfe) {
                         log.error(cnfe, cnfe);
                     }
-                    catch (InstantiationException ie) {
+                    catch (final InstantiationException ie) {
                         log.error(ie);
                     }
-                    catch (IllegalAccessException iae) {
+                    catch (final IllegalAccessException iae) {
                         log.error(iae);
                     }
                 }
             }
 
-            for (String key: names.split("[\\s,]+")) {
+            for (String key : names.split("[\\s,]+")) {
                 if (!(key = key.trim()).isEmpty()) {
                     generators.putGenerator(key, generatorClass, cfg, cis);
                     idx++;
@@ -444,23 +393,23 @@
         context.put(RiverContext.FACETFILTER_KEY, generators);
     }
 
-
     /**
      * This methods reads the configured themes and puts them into the
      * RiverContext.
      *
-     * @param config The global configuration.
-     * @param context The RiverContext.
+     * @param config
+     *            The global configuration.
+     * @param context
+     *            The RiverContext.
      */
-    protected void configureThemes(Document config, RiverContext context) {
+    private void configureThemes(final Document config, final RiverContext context) {
         log.debug("RiverContextFactory.configureThemes");
 
-        Document cfg = getThemeConfig(config);
+        final Document cfg = getThemeConfig(config);
 
-        NodeList themeGroups = (NodeList) XMLUtils.xpath(
-            cfg, XPATH_THEME_GROUPS, XPathConstants.NODESET);
+        final NodeList themeGroups = (NodeList) XMLUtils.xpath(cfg, XPATH_THEME_GROUPS, XPathConstants.NODESET);
 
-        int groupNum = themeGroups != null ? themeGroups.getLength() : 0;
+        final int groupNum = themeGroups != null ? themeGroups.getLength() : 0;
 
         if (groupNum == 0) {
             log.warn("There are no theme groups configured!");
@@ -468,105 +417,126 @@
 
         log.info("Found " + groupNum + " theme groups in configuration");
 
-        List<ThemeGroup> groups = new ArrayList<ThemeGroup>();
+        final List<ThemeGroup> groups = new ArrayList<>();
 
         for (int g = 0; g < groupNum; g++) {
-            Element themeGroup = (Element) themeGroups.item(g);
-            NodeList themes = (NodeList) XMLUtils.xpath(
-                themeGroup, XPATH_THEMES, XPathConstants.NODESET);
+            final Element themeGroup = (Element) themeGroups.item(g);
 
-            int num = themes != null ? themes.getLength() : 0;
+            final Map<String, Theme> theThemes = readThemes(cfg, themeGroup);
 
-            if (num == 0) {
+            if (theThemes.size() == 0) {
                 log.warn("There are no themes configured!");
                 return;
             }
 
-            log.info("Theme group has " + num + " themes.");
-
-            Map<String, Theme> theThemes = new HashMap<String, Theme>();
-
-            for (int i = 0; i < num; i++) {
-                Node theme = themes.item(i);
-
-                Theme theTheme = ThemeFactory.createTheme(cfg, theme);
-
-                if (theme != null) {
-                    theThemes.put(theTheme.getName(), theTheme);
-                }
-            }
-            String gName = themeGroup.getAttribute("name");
+            final String gName = themeGroup.getAttribute("name");
             groups.add(new ThemeGroup(gName, theThemes));
 
-            log.info(
-                "Initialized " + theThemes.size() + "/" + num + " themes " +
-                "of theme-group '" + gName + "'");
+            log.info("Initialized " + theThemes.size() + "/" + theThemes.size() + " themes " + "of theme-group '" + gName + "'");
         }
         context.put(RiverContext.THEMES, groups);
     }
 
     /**
+     * This methods reads the configured themes and puts them into the
+     * RiverContext.
+     *
+     * @param config
+     *            The global configuration.
+     * @param context
+     *            The RiverContext.
+     */
+    private void configureLegend(final Document config, final RiverContext context) {
+        log.debug("RiverContextFactory.configureLegend");
+
+        final Map<String, Theme> legendGroup = readLegend(config);
+        context.put(RiverContext.LEGEND, legendGroup);
+    }
+
+    private Map<String, Theme> readLegend(final Document config) {
+
+        final Document cfg = getThemeConfig(config);
+
+        final Node legendGroup = (Node) XMLUtils.xpath(cfg, XPATH_LEGEND_GROUP, XPathConstants.NODE);
+        if (legendGroup == null) {
+            log.warn("There is no legend group configured");
+            return Collections.emptyMap();
+        }
+
+        return readThemes(cfg, legendGroup);
+    }
+
+    private Map<String, Theme> readThemes(final Document cfg, final Node themeGroup) {
+        final NodeList themes = (NodeList) XMLUtils.xpath(themeGroup, XPATH_THEMES, XPathConstants.NODESET);
+        if (themes == null)
+            return Collections.emptyMap();
+
+        final int num = themes.getLength();
+        log.info("Theme group has " + num + " themes.");
+        final Map<String, Theme> theThemes = new HashMap<>();
+
+        for (int i = 0; i < num; i++) {
+            final Node theme = themes.item(i);
+
+            final Theme theTheme = ThemeFactory.createTheme(cfg, theme);
+            theThemes.put(theTheme.getName(), theTheme);
+        }
+
+        return theThemes;
+    }
+
+    /**
      * This method is used to retrieve the theme configuration document.
      *
-     * @param config The global configuration.
+     * @param config
+     *            The global configuration.
      *
      * @return the theme configuration.
      */
-    protected Document getThemeConfig(Document config) {
-        String themeConfig = (String) XMLUtils.xpath(
-            config,
-            XPATH_THEME_CONFIG,
-            XPathConstants.STRING);
+    private Document getThemeConfig(final Document config) {
+        String themeConfig = (String) XMLUtils.xpath(config, XPATH_THEME_CONFIG, XPathConstants.STRING);
 
         themeConfig = Config.replaceConfigDir(themeConfig);
 
         log.debug("Parse theme cfg: " + themeConfig);
 
-        return XMLUtils.parseDocument(
-            new File(themeConfig), true, XMLUtils.CONF_RESOLVER);
+        return XMLUtils.parseDocument(new File(themeConfig), true, XMLUtils.CONF_RESOLVER);
     }
 
-
-    protected void configureThemesMappings(
-        Document cfg,
-        RiverContext context
-    ) {
+    private void configureThemesMappings(final Document cfg, final RiverContext context) {
         log.debug("RiverContextFactory.configureThemesMappings");
 
-        Document config = getThemeConfig(cfg);
+        final Document config = getThemeConfig(cfg);
 
-        NodeList mappings = (NodeList) XMLUtils.xpath(
-            config, XPATH_THEME_MAPPINGS, XPathConstants.NODESET);
+        final NodeList mappings = (NodeList) XMLUtils.xpath(config, XPATH_THEME_MAPPINGS, XPathConstants.NODESET);
 
-        int num = mappings != null ? mappings.getLength() : 0;
+        final int num = mappings != null ? mappings.getLength() : 0;
 
         if (num == 0) {
             log.warn("No theme <--> facet mappins found!");
             return;
         }
 
-        Map<String, List<ThemeMapping>> mapping =
-            new HashMap<String, List<ThemeMapping>>();
+        final Map<String, List<ThemeMapping>> mapping = new HashMap<>();
 
         for (int i = 0; i < num; i++) {
-            Element node = (Element)mappings.item(i);
+            final Element node = (Element) mappings.item(i);
 
-            String from              = node.getAttribute("from");
-            String to                = node.getAttribute("to");
-            String pattern           = node.getAttribute("pattern");
-            String masterAttrPattern = node.getAttribute("masterAttr");
-            String outputPattern     = node.getAttribute("output");
+            final String from = node.getAttribute("from");
+            final String to = node.getAttribute("to");
+            final String pattern = node.getAttribute("pattern");
+            final String masterAttrPattern = node.getAttribute("masterAttr");
+            final String outputPattern = node.getAttribute("output");
 
             if (from.length() > 0 && to.length() > 0) {
                 List<ThemeMapping> tm = mapping.get(from);
 
                 if (tm == null) {
-                    tm = new ArrayList<ThemeMapping>();
+                    tm = new ArrayList<>();
                     mapping.put(from, tm);
                 }
 
-                tm.add(new ThemeMapping(
-                    from, to, pattern, masterAttrPattern, outputPattern));
+                tm.add(new ThemeMapping(from, to, pattern, masterAttrPattern, outputPattern));
             }
         }
 
@@ -575,26 +545,25 @@
         context.put(RiverContext.THEME_MAPPING, mapping);
     }
 
-
     /**
      * Reads configured floodmap river WMSs from floodmap.xml and
      * loads them into the given RiverContext.
+     *
      * @param cfg
      * @param context
      */
-    protected void configureFloodmapWMS(Document cfg, RiverContext context) {
-        Map<String, String> riverWMS = new HashMap<String, String>();
+    private void configureFloodmapWMS(final Document cfg, final RiverContext context) {
+        final Map<String, String> riverWMS = new HashMap<>();
 
-        NodeList rivers = (NodeList) XMLUtils.xpath(
-            cfg, XPATH_RIVER_WMS, XPathConstants.NODESET);
+        final NodeList rivers = (NodeList) XMLUtils.xpath(cfg, XPATH_RIVER_WMS, XPathConstants.NODESET);
 
-        int num = rivers != null ? rivers.getLength() : 0;
+        final int num = rivers != null ? rivers.getLength() : 0;
 
         for (int i = 0; i < num; i++) {
-            Element e = (Element) rivers.item(i);
+            final Element e = (Element) rivers.item(i);
 
-            String river = e.getAttribute("name");
-            String url   = XMLUtils.xpathString(e, "river-wms/@url", null);
+            final String river = e.getAttribute("name");
+            final String url = XMLUtils.xpathString(e, "river-wms/@url", null);
 
             if (river != null && url != null) {
                 riverWMS.put(river, url);
@@ -606,16 +575,16 @@
         context.put(RiverContext.RIVER_WMS, riverWMS);
     }
 
-
     /**
      * This method initializes the modules configuration.
      *
-     * @param config the config document.
-     * @param context the RiverContext.
+     * @param config
+     *            the config document.
+     * @param context
+     *            the RiverContext.
      */
-    protected void configureModules(Document cfg, RiverContext context) {
-        NodeList modulenodes = (NodeList) XMLUtils.xpath(
-            cfg, XPATH_MODULES, XPathConstants.NODESET);
+    private void configureModules(final Document cfg, final RiverContext context) {
+        final NodeList modulenodes = (NodeList) XMLUtils.xpath(cfg, XPATH_MODULES, XPathConstants.NODESET);
 
         final int num = modulenodes != null ? modulenodes.getLength() : 0;
 
@@ -627,24 +596,23 @@
             final String attrselected = e.getAttribute("selected");
             final boolean selected = Boolean.parseBoolean(attrselected);
             final String group = e.getAttribute("group");
-            
+
             log.debug("Loaded module " + modulename);
-            
+
             final NodeList children = e.getChildNodes();
             final List<String> rivers = new ArrayList<>(children.getLength());
             for (int j = 0; j < children.getLength(); j++) {
                 if (children.item(j).getNodeType() != Node.ELEMENT_NODE) {
                     continue;
                 }
-                
-                final Element ce = (Element)children.item(j);
+
+                final Element ce = (Element) children.item(j);
                 if (ce.hasAttribute("uuid")) {
                     rivers.add(ce.getAttribute("uuid"));
-                }
-                else if (ce.hasAttribute("name")) {
+                } else if (ce.hasAttribute("name")) {
                     final List<River> allRivers = RiverFactory.getRivers();
                     final String name = ce.getAttribute("name");
-                    for (final River r: allRivers) {
+                    for (final River r : allRivers) {
                         if (name.equals(r.getName())) {
                             rivers.add(r.getModelUuid());
                             break;
@@ -656,5 +624,4 @@
         }
         context.put(RiverContext.MODULES, modules);
     }
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/exports/AbstractChartGenerator.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/AbstractChartGenerator.java	Tue Oct 23 16:26:48 2018 +0200
@@ -58,7 +58,6 @@
 import org.dive4elements.river.themes.ThemeDocument;
 import org.jfree.chart.JFreeChart;
 import org.jfree.chart.LegendItem;
-import org.jfree.chart.LegendItemCollection;
 import org.jfree.chart.axis.NumberAxis;
 import org.jfree.chart.plot.XYPlot;
 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
@@ -96,6 +95,8 @@
 
     private static final String DEFAULT_CHART_FORMAT = "png";
 
+    private static final int DEFAULT_LEGEND_AGGREGATION_THRESHOLD = 10;
+
     private static final String XPATH_CHART_EXPORT = "/art:action/art:attributes/art:export/@art:value";
 
     private static final String XPATH_CHART_SIZE = "/art:action/art:attributes/art:size";
@@ -224,15 +225,6 @@
     protected abstract String getDefaultChartTitle(CallContext context);
 
     /**
-     * This method is used to create new AxisDataset instances which may differ
-     * in concrete subclasses.
-     *
-     * @param idx
-     *            The index of an axis.
-     */
-    protected abstract AxisDataset createAxisDataset(int idx);
-
-    /**
      * Combines the ranges of the X axis at index <i>idx</i>.
      *
      * @param bounds
@@ -340,7 +332,7 @@
         final LegendSection legendSection = new LegendSection();
         legendSection.setVisibility(isLegendVisible());
         legendSection.setFontSize(getLegendFontSize());
-        legendSection.setAggregationThreshold(10);
+        legendSection.setAggregationThreshold(DEFAULT_LEGEND_AGGREGATION_THRESHOLD);
         return legendSection;
     }
 
@@ -686,46 +678,25 @@
         return fontSize;
     }
 
-    /**
-     * Creates a new LegendItem with <i>name</i> and font provided by
-     * <i>createLegendLabelFont()</i>.
-     *
-     * @param theme
-     *            The theme of the chart line.
-     * @param name
-     *            The displayed name of the item.
-     *
-     * @return a new LegendItem instance.
-     */
-    protected final LegendItem createLegendItem(final ThemeDocument theme, final String name) {
-        // OPTIMIZE Pass font, parsed Theme items.
-
-        Color color = theme.parseLineColorField();
-        if (color == null)
-            color = Color.BLACK;
-
-        final LegendItem legendItem = new LegendItem(name, color);
-        legendItem.setLabelFont(createLegendLabelFont());
-        return legendItem;
+    protected final LegendAggregator createLegendBuilder() {
+        return new LegendAggregator(getLegendAggregationThreshold(), createLegendLabelFont());
     }
 
-    /**
-     * Create new legend entries, dependent on settings.
-     *
-     * @param plot
-     *            The plot for which to modify the legend.
-     */
-    protected final void aggregateLegendEntries(final XYPlot plot) {
+    private int getLegendAggregationThreshold() {
 
         final ChartSettings chartSettings = getChartSettings();
         if (chartSettings == null)
-            return;
+            return DEFAULT_LEGEND_AGGREGATION_THRESHOLD;
 
-        final Integer threshold = chartSettings.getLegendSection().getAggregationThreshold();
+        final LegendSection legendSection = chartSettings.getLegendSection();
+        if (legendSection == null)
+            return DEFAULT_LEGEND_AGGREGATION_THRESHOLD;
 
-        final int aggrThreshold = threshold != null ? threshold.intValue() : 0;
+        final Integer threshold = legendSection.getAggregationThreshold();
+        if (threshold == null)
+            return DEFAULT_LEGEND_AGGREGATION_THRESHOLD;
 
-        LegendProcessor.aggregateLegendEntries(plot, aggrThreshold);
+        return threshold;
     }
 
     /**
@@ -818,7 +789,7 @@
      * @param plot
      *            plot to add datasets to.
      */
-    protected void addDatasets(final XYPlot plot) {
+    protected void addDatasets(final XYPlot plot, final LegendAggregator legendBuilder) {
         log.debug("addDatasets()");
 
         // AxisDatasets are sorted, but some might be empty.
@@ -846,7 +817,7 @@
                         plot.setDataset(datasetIndex, dataset);
                         plot.mapDatasetToRangeAxis(datasetIndex, axisIndex);
 
-                        applyThemes(plot, dataset, datasetIndex);
+                        applyThemes(plot, legendBuilder, dataset, datasetIndex);
 
                         datasetIndex++;
                     }
@@ -855,7 +826,6 @@
                     }
                 }
 
-                axisDataset.setPlotAxisIndex(axisIndex);
                 axisIndex++;
             }
         }
@@ -875,14 +845,12 @@
      *            true if the series describes an area and shall be rendered
      *            as such.
      */
-    private void applyThemes(final XYPlot plot, final XYDataset dataset, final int datasetIndex) {
-
-        final Font legendFont = createLegendLabelFont();
+    private void applyThemes(final XYPlot plot, final LegendAggregator legendBuilder, final XYDataset dataset, final int datasetIndex) {
 
         if (dataset instanceof StyledXYDataset)
-            ((StyledXYDataset) dataset).applyTheme(this.context.getMeta(), plot, datasetIndex, legendFont);
+            ((StyledXYDataset) dataset).applyTheme(this.context.getMeta(), plot, legendBuilder, datasetIndex);
         else
-            applyLineTheme(plot, dataset, datasetIndex, legendFont);
+            applyLineTheme(plot, legendBuilder, dataset, datasetIndex);
     }
 
     /**
@@ -933,12 +901,9 @@
      *            The index of the renderer / dataset.
      * @param legendFont2
      */
-    private void applyLineTheme(final XYPlot plot, final XYDataset dataset, final int idx, final Font legendFont) {
+    private void applyLineTheme(final XYPlot plot, final LegendAggregator legendBuilder, final XYDataset dataset, final int idx) {
         log.debug("Apply LineTheme for dataset at index: " + idx);
 
-        final LegendItemCollection lic = new LegendItemCollection();
-        final LegendItemCollection anno = plot.getFixedLegendItems();
-
         final XYLineAndShapeRenderer renderer = createRenderer(plot, idx);
 
         for (int s = 0, num = dataset.getSeriesCount(); s < num; s++) {
@@ -952,29 +917,24 @@
             // special case: if there is just one single item, we need to enable
             // points for this series, otherwise we would not see anything in
             // the chart area.
-            if (series.getItemCount() == 1) {
-                renderer.setSeriesShapesVisible(s, true);
-            }
+            // if (series.getItemCount() == 1) {
+            // renderer.setSeriesShapesVisible(s, true);
+            // }
 
             LegendItem legendItem = renderer.getLegendItem(idx, s);
             if (legendItem.getLabel().endsWith(" ")) {
                 legendItem = null;
             }
 
+            final String themeType = series instanceof StyledSeries ? ((StyledSeries) series).getThemeType() : null;
+
             if (legendItem != null) {
-                legendItem.setLabelFont(legendFont);
-                lic.add(legendItem);
+                legendBuilder.addLegendItem(themeType, legendItem);
             } else {
                 log.warn("Could not get LegentItem for renderer: " + idx + ", series-idx " + s);
             }
         }
 
-        if (anno != null) {
-            lic.addAll(anno);
-        }
-
-        plot.setFixedLegendItems(lic);
-
         plot.setRenderer(idx, renderer);
     }
 
@@ -1077,7 +1037,7 @@
         AxisDataset axisDataset = this.datasets.get(idx);
 
         if (axisDataset == null) {
-            axisDataset = createAxisDataset(idx);
+            axisDataset = new AxisDataset();
             this.datasets.put(idx, axisDataset);
         }
 
@@ -1321,12 +1281,12 @@
         return true;
     }
 
-    protected final void addAnnotationsToRenderer(final XYPlot plot) {
+    protected final void addAnnotationsToRenderer(final XYPlot plot, final LegendAggregator legendBuilder) {
 
-        final AnnotationRenderer annotationRenderer = new AnnotationRenderer(getChartSettings(), this.datasets, DEFAULT_FONT_NAME);
-        annotationRenderer.addAnnotationsToRenderer(plot, this.annotations);
+        final AnnotationRenderer annotationRenderer = new AnnotationRenderer(this.datasets);
+        annotationRenderer.addAnnotationsToRenderer(plot, legendBuilder, this.annotations);
 
-        doAddFurtherAnnotations(plot, this.annotations);
+        doAddFurtherAnnotations(plot, legendBuilder, this.annotations);
     }
 
     /**
@@ -1334,7 +1294,7 @@
      *
      * Does nothing by default.
      */
-    protected void doAddFurtherAnnotations(final XYPlot plot, final List<RiverAnnotation> annotations) {
+    protected void doAddFurtherAnnotations(final XYPlot plot, final LegendAggregator legendBuilder, final List<RiverAnnotation> annotations) {
 
     }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/exports/AnnotationRenderer.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/AnnotationRenderer.java	Tue Oct 23 16:26:48 2018 +0200
@@ -11,7 +11,6 @@
 
 import java.awt.BasicStroke;
 import java.awt.Color;
-import java.awt.Font;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -26,7 +25,6 @@
 import org.dive4elements.river.themes.TextStyle;
 import org.dive4elements.river.themes.ThemeDocument;
 import org.jfree.chart.LegendItem;
-import org.jfree.chart.LegendItemCollection;
 import org.jfree.chart.annotations.XYLineAnnotation;
 import org.jfree.chart.annotations.XYTextAnnotation;
 import org.jfree.chart.plot.XYPlot;
@@ -42,16 +40,10 @@
 
     private static float ANNOTATIONS_AXIS_OFFSET = 0.02f;
 
-    private final ChartSettings settings;
-
     private final Map<Integer, AxisDataset> datasets;
 
-    private final String fontName;
-
-    public AnnotationRenderer(final ChartSettings settings, final Map<Integer, AxisDataset> datasets, final String fontName) {
-        this.settings = settings;
+    public AnnotationRenderer(final Map<Integer, AxisDataset> datasets) {
         this.datasets = datasets;
-        this.fontName = fontName;
     }
 
     /**
@@ -66,7 +58,7 @@
      * @param datasets
      *            Map of axis index and datasets
      */
-    public final void addAnnotationsToRenderer(final XYPlot plot, final List<RiverAnnotation> annotations) {
+    public final void addAnnotationsToRenderer(final XYPlot plot, final LegendAggregator legendBuilder, final List<RiverAnnotation> annotations) {
         if (annotations == null || annotations.isEmpty()) {
             log.debug("addAnnotationsToRenderer: no annotations.");
             return;
@@ -88,10 +80,6 @@
                 textStyle = theme.parseComplexTextStyle();
                 lineStyle = theme.parseComplexLineStyle();
                 if (fa.getLabel() != null) {
-                    // Legend handling, maybe misplaced?
-                    final LegendItemCollection lic = new LegendItemCollection();
-                    LegendItemCollection old = plot.getFixedLegendItems();
-
                     Color color = theme.parseLineColorField();
                     if (color == null) {
                         color = Color.BLACK;
@@ -104,22 +92,12 @@
 
                     final LegendItem newItem = new LegendItem(fa.getLabel(), color);
 
-                    final LegendSection ls = this.settings != null ? this.settings.getLegendSection() : null;
-
-                    final Integer size = ls != null ? ls.getFontSize() : null;
-
-                    newItem.setLabelFont(new Font(this.fontName, Font.PLAIN, size));
-
                     newItem.setLabelPaint(textColor);
 
-                    lic.add(newItem);
-                    // (Re-)Add prior legend entries.
-                    if (old != null) {
-                        old.addAll(lic);
-                    } else {
-                        old = lic;
-                    }
-                    plot.setFixedLegendItems(old);
+                    // REMARK: connection to facet is lost at this point
+                    final String themeType = null;
+
+                    legendBuilder.addLegendItem(themeType, newItem);
                 }
             }
 
@@ -231,7 +209,8 @@
         renderer.addAnnotation(lineAnnotation, org.jfree.ui.Layer.FOREGROUND);
     }
 
-    public final void addYAnnotationsToRenderer(final XYPlot plot, final SortedMap<Integer, RiverAnnotation> yAnnotations) {
+    public final void addYAnnotationsToRenderer(final XYPlot plot, final LegendAggregator legendBuilder,
+            final SortedMap<Integer, RiverAnnotation> yAnnotations) {
         final List<RiverAnnotation> annotations = new ArrayList<>();
 
         for (final Map.Entry<Integer, RiverAnnotation> entry : yAnnotations.entrySet()) {
@@ -249,7 +228,7 @@
             }
         }
 
-        addAnnotationsToRenderer(plot, annotations);
+        addAnnotationsToRenderer(plot, legendBuilder, annotations);
     }
 
     /**
--- a/artifacts/src/main/java/org/dive4elements/river/exports/ChartGenerator2.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/ChartGenerator2.java	Tue Oct 23 16:26:48 2018 +0200
@@ -298,8 +298,8 @@
         return axisNameToAxis.size();
     }
     
-    protected final void addYAnnotationsToRenderer(final XYPlot plot) {
-        final AnnotationRenderer annotationRenderer = new AnnotationRenderer(getChartSettings(), getDatasets(), DEFAULT_FONT_NAME);
-        annotationRenderer.addYAnnotationsToRenderer(plot, this.yAnnotations);
+    protected final void addYAnnotationsToRenderer(final XYPlot plot, LegendAggregator legendBuilder) {
+        final AnnotationRenderer annotationRenderer = new AnnotationRenderer(getDatasets());
+        annotationRenderer.addYAnnotationsToRenderer(plot, legendBuilder, this.yAnnotations);
     }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/exports/CrossSectionGenerator.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/CrossSectionGenerator.java	Tue Oct 23 16:26:48 2018 +0200
@@ -33,13 +33,12 @@
 import org.dive4elements.river.themes.ThemeDocument;
 import org.dive4elements.river.utils.Formatter;
 import org.dive4elements.river.utils.RiverUtils;
-import org.jfree.chart.LegendItemCollection;
+import org.jfree.chart.LegendItem;
 import org.jfree.chart.annotations.XYBoxAnnotation;
 import org.jfree.chart.annotations.XYTextAnnotation;
 import org.jfree.chart.plot.XYPlot;
 import org.jfree.data.xy.XYSeries;
 
-
 /**
  * An OutGenerator that generates cross section graphs.
  */
@@ -165,7 +164,7 @@
     }
 
     @Override
-    protected void doAddFurtherAnnotations(XYPlot plot, List<RiverAnnotation> annotations) {
+    protected void doAddFurtherAnnotations(XYPlot plot, LegendAggregator legendBuilder, List<RiverAnnotation> annotations) {
 
         // Paints for the boxes/lines.
         Stroke basicStroke = new BasicStroke(1.0f);
@@ -189,17 +188,14 @@
             if (theme != null) {
                 textStyle = theme.parseComplexTextStyle();
                 if (fa.getLabel() != null) {
-                    LegendItemCollection lic = new LegendItemCollection();
-                    LegendItemCollection old = plot.getFixedLegendItems();
-                    lic.add(createLegendItem(theme, fa.getLabel()));
-                    // (Re-)Add prior legend entries.
-                    if (old != null) {
-                        old.addAll(lic);
-                    }
-                    else {
-                        old = lic;
-                    }
-                    plot.setFixedLegendItems(old);
+                    
+                    Color color = theme.parseLineColorField();
+                    if (color == null)
+                        color = Color.BLACK;
+
+                    final String themeType = null;
+                    
+                    legendBuilder.addLegendItem(themeType, new LegendItem(fa.getLabel(), color));
                 }
             }
 
@@ -282,17 +278,20 @@
         
         CallContext context = getContext();
 
+        final String facetDescription = artifactFacet.getFacetDescription();
+        final String facetName = artifactFacet.getFacetName();
+        
         if (name.equals(CROSS_SECTION)) {
-            doCrossSectionOut(
+            doCrossSectionOut(facetName,
                     artifactFacet.getData(context),
-                    artifactFacet.getFacetDescription(),
+                    facetDescription,
                     attr,
                     visible);
         }
         else if (name.endsWith(CROSS_SECTION_WATER_LINE)) {
-            doCrossSectionWaterLineOut(
+            doCrossSectionWaterLineOut(facetName,
                     artifactFacet.getData(context),
-                    artifactFacet.getFacetDescription(),
+                    facetDescription,
                     attr,
                     visible);
         }
@@ -304,7 +303,7 @@
         }
         else if (name.equals(HYK)) {
             doHyk(artifactFacet.getData(context),
-                    artifactFacet.getFacetDescription(),
+                    facetDescription,
                     attr,
                     visible);
         }
@@ -312,9 +311,9 @@
             VegetationZonesCrossSectionProcessor.generateSeries(this, artifactFacet, context, attr, visible);
         }
         else if (FacetTypes.IS.MANUALLINE(name)) {
-            doCrossSectionWaterLineOut(
+            doCrossSectionWaterLineOut(facetName,
                     artifactFacet.getData(context),
-                    artifactFacet.getFacetDescription(),
+                    facetDescription,
                     attr,
                     visible);
         }
@@ -345,6 +344,7 @@
      * @param theme Theme for the data series.
      */
     protected void doCrossSectionWaterLineOut(
+            String facetName,
             Object   o,
             String   seriesName,
             ThemeDocument theme,
@@ -354,7 +354,7 @@
 
         Lines.LineData lines = (Lines.LineData) o;
         // DO NOT SORT DATA! This destroys the gaps indicated by NaNs.
-        StyledXYSeries series = new StyledXYSeries(seriesName, false, theme);
+        StyledXYSeries series = new StyledXYSeries(facetName, seriesName, false, theme);
 
         CallContext context = getContext();
         
@@ -433,7 +433,8 @@
      * @param seriesName name of the data (line) to display in legend.
      * @param theme Theme for the data series.
      */
-    protected void doCrossSectionOut(
+    private void doCrossSectionOut(
+            String facetName,
             Object   o,
             String   seriesName,
             ThemeDocument theme,
@@ -441,7 +442,7 @@
             ) {
         log.debug("CrossSectionGenerator.doCrossSectionOut");
 
-        XYSeries series = new StyledXYSeries(seriesName, theme);
+        XYSeries series = new StyledXYSeries(facetName, seriesName, theme);
 
         StyledSeriesBuilder.addPoints(series, (double [][]) o, false);
 
--- a/artifacts/src/main/java/org/dive4elements/river/exports/DiagramGenerator.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/DiagramGenerator.java	Tue Oct 23 16:26:48 2018 +0200
@@ -170,8 +170,9 @@
         adjustPlot(plot);
 
         //debugAxis(plot);
+        final LegendAggregator legendBuilder = createLegendBuilder();
 
-        addDatasets(plot);
+        addDatasets(plot, legendBuilder);
 
         //debugDatasets(plot);
 
@@ -199,13 +200,13 @@
         //debugAxis(plot);
 
         // These have to go after the autozoom.
-        addAnnotationsToRenderer(plot);
-        addYAnnotationsToRenderer(plot);
+        addAnnotationsToRenderer(plot, legendBuilder);
+        addYAnnotationsToRenderer(plot, legendBuilder);
 
         // Add a logo (maybe).
         addLogo(plot);
 
-        aggregateLegendEntries(plot);
+        legendBuilder.apply( context, plot );
 
         /* allow extenders to do some work */
         final Collection<ChartExtender> extenders = this.diagramAttributes.getExtenders();
@@ -383,14 +384,6 @@
         return ((XYSeriesCollection) dataset).getSeries(idx);
     }
 
-
-    @Override
-    protected AxisDataset createAxisDataset(int idx) {
-        log.debug("Create new AxisDataset for index: " + idx);
-        return new AxisDataset(idx);
-    }
-
-
     /**
      * Put debug output about datasets.
      */
@@ -961,50 +954,11 @@
         ((NumberAxis) rangeAxis).setNumberFormatOverride(nf);
     }
 
-
-    /**
-     * Create a hash from a legenditem.
-     * This hash can then be used to merge legend items labels.
-     * @return hash for given legenditem to identify mergeables.
-     */
-    public static String legendItemHash(LegendItem li) {
-        // TODO Do proper implementation.
-        // Ensure that only mergable sets are created.
-        // getFillPaint()
-        // getFillPaintTransformer()
-        // getLabel()
-        // getLine()
-        // getLinePaint()
-        // getLineStroke()
-        // getOutlinePaint()
-        // getOutlineStroke()
-        // Shape getShape()
-        // String getToolTipText()
-        // String getURLText()
-        // boolean isLineVisible()
-        // boolean isShapeFilled()
-        // boolean isShapeOutlineVisible()
-        // boolean isShapeVisible()
-        String hash = li.getLinePaint().toString();
-        String label = li.getLabel();
-        if (label.startsWith("W (") || label.startsWith("W(")) {
-            hash += "-W-";
-        }
-        else if (label.startsWith("Q(") || label.startsWith("Q (")) {
-            hash += "-Q-";
-        }
-
-        // WQ.java holds example of using regex Matcher/Pattern.
-
-        return hash;
-    }
-
     /** True if x axis has been inverted. */
     public boolean isInverted() {
         return inverted;
     }
 
-
     /** Set to true if x axis should be inverted.
      * This can not be set to false afterwards. */
     public void setInverted(boolean value) {
--- a/artifacts/src/main/java/org/dive4elements/river/exports/DischargeGenerator.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/DischargeGenerator.java	Tue Oct 23 16:26:48 2018 +0200
@@ -10,7 +10,6 @@
 import java.awt.Font;
 
 import org.apache.log4j.Logger;
-import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.exports.injector.InjectorConstants;
 import org.dive4elements.river.jfree.AxisDataset;
 import org.dive4elements.river.jfree.DoubleBounds;
@@ -36,8 +35,8 @@
     }
 
     @Override
-    public void addDatasets(XYPlot plot) {
-        super.addDatasets(plot);
+    public void addDatasets(XYPlot plot, final LegendAggregator legendBuilder) {
+        super.addDatasets(plot, legendBuilder);
 
         Object pnp = getContext().getContextValue(PNP);
         if (!(pnp instanceof Number)) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/LegendAggregator.java	Tue Oct 23 16:26:48 2018 +0200
@@ -0,0 +1,228 @@
+/** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde
+ * Software engineering by
+ *  Björnsen Beratende Ingenieure GmbH
+ *  Dr. Schumacher Ingenieurbüro für Wasser und Umwelt
+ *
+ * This file is Free Software under the GNU AGPL (>=v3)
+ * and comes with ABSOLUTELY NO WARRANTY! Check out the
+ * documentation coming with Dive4Elements River for details.
+ */
+package org.dive4elements.river.exports;
+
+import java.awt.Font;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.dive4elements.artifacts.CallContext;
+import org.dive4elements.river.artifacts.context.RiverContext;
+import org.dive4elements.river.artifacts.resources.Resources;
+import org.dive4elements.river.jfree.StyledSeries;
+import org.dive4elements.river.jfree.StyledXYDataset;
+import org.dive4elements.river.themes.Theme;
+import org.dive4elements.river.themes.ThemeDocument;
+import org.dive4elements.river.themes.ThemeFactory;
+import org.jfree.chart.LegendItem;
+import org.jfree.chart.LegendItemCollection;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.data.general.Dataset;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.w3c.dom.Document;
+
+/**
+ * Builds and adds legend entries to the plot.
+ *
+ * @author Gernot Belger
+ */
+public class LegendAggregator {
+
+    private static final String NULL_THEME_TYPE = ""; //$NON-NLS-1$
+
+    private static final String I10N_MERGED = "legend.aggregator.merged"; //$NON-NLS-1$
+
+    private final List<Map.Entry<LegendItem, String>> legendItems = new ArrayList<>();
+
+    private final Map<String, List<LegendItem>> itemsPerThemeType = new HashMap<>();
+
+    private final Font labelFont;
+
+    private final int aggregationThreshold;
+
+    public LegendAggregator(final int aggregationThreshold, final Font labelFont) {
+        this.aggregationThreshold = aggregationThreshold;
+        this.labelFont = labelFont;
+    }
+
+    public void addLegendItem(final String themeType, final LegendItem item) {
+
+        item.setLabelFont(this.labelFont);
+
+        this.legendItems.add(new SimpleEntry<>(item, themeType));
+
+        final List<LegendItem> perThemeType = getItemsPerThemeType(themeType);
+        perThemeType.add(item);
+    }
+
+    private List<LegendItem> getItemsPerThemeType(final String themeType) {
+
+        final String key = themeType == null ? NULL_THEME_TYPE : themeType;
+
+        if (!this.itemsPerThemeType.containsKey(key))
+            this.itemsPerThemeType.put(key, new ArrayList<LegendItem>());
+
+        return this.itemsPerThemeType.get(key);
+    }
+
+    /**
+     * Apply the gathered items to the plot. Use only once.
+     */
+    public void apply(final CallContext context, final XYPlot plot) {
+
+        final LegendItemCollection aggregatedItems = new LegendItemCollection();
+
+        /* marker set for types that are already aggregated */
+        final Set<String> aggregatedTypes = new HashSet<>();
+
+        for (final Entry<LegendItem, String> entry : this.legendItems) {
+
+            final LegendItem item = entry.getKey();
+            final String themeType = entry.getValue();
+
+            /* ignore already aggregated items */
+            if (aggregatedTypes.contains(themeType))
+                continue;
+
+            /* aggregate known types if count over threshold */
+            final Theme legendTheme = getLegendTheme(context, themeType);
+
+            if (legendTheme != null && getItemsPerThemeType(themeType).size() > this.aggregationThreshold) {
+
+                final String labelDescription = Resources.getMsg(context.getMeta(), legendTheme.getDescription());
+                final String labelMerged = Resources.getMsg(context.getMeta(), I10N_MERGED);
+                final String label = String.format("%s (%s)", labelDescription, labelMerged);
+
+                final List<LegendItem> items = findDistinctItems(getItemsPerThemeType(themeType));
+                /* add items for each distinct shape, only the last one gets the label */
+                for (final Iterator<LegendItem> iterator = items.iterator(); iterator.hasNext();) {
+
+                    final LegendItem legendItem = iterator.next();
+                    final String itemLabel = iterator.hasNext() ? "," : label;
+
+                    /* create and add aggregated item(s) */
+                    final LegendItem aggregatedItem = createAggregatedItem(legendItem, legendTheme, itemLabel);
+                    aggregatedItems.add(aggregatedItem);
+                }
+
+                /* mark as handles */
+                aggregatedTypes.add(themeType);
+
+            } else {
+                /* simply add normal items */
+                aggregatedItems.add(item);
+            }
+        }
+
+        plot.setFixedLegendItems(aggregatedItems);
+
+        this.itemsPerThemeType.clear();
+        this.legendItems.clear();
+    }
+
+    /**
+     * Extract distinct items, curently only those that are different regarding their shape
+     */
+    private List<LegendItem> findDistinctItems(final List<LegendItem> items) {
+
+        // HACKY: we hash by unique shape, because we know that the used shapes are cashed in a static cache.
+        final Map<Shape, LegendItem> shapeMap = new IdentityHashMap<>();
+
+        for (final LegendItem item : items) {
+
+            final Shape shape = item.isShapeVisible() ? item.getShape() : null;
+            if (!shapeMap.containsKey(shape))
+                shapeMap.put(shape, item);
+        }
+
+        return new ArrayList<>(shapeMap.values());
+    }
+
+    private LegendItem createAggregatedItem(final LegendItem item, final Theme legendTheme, final String label) {
+        /* clone properties from current item */
+        final String description = item.getDescription();
+        final String tooltipText = item.getToolTipText();
+        final String urlText = item.getURLText();
+        final boolean shapeVisible = item.isShapeVisible();
+        final Shape shape = item.getShape();
+        final boolean shapeFilled = item.isShapeFilled();
+        final Paint fillPaint = item.getFillPaint();
+        final boolean shapeOutlineVisible = item.isShapeOutlineVisible();
+        final Paint outlinePaint = item.getOutlinePaint();
+        final Stroke outlineStroke = item.getOutlineStroke();
+        final boolean lineVisible = item.isLineVisible();
+        final Shape line = item.getLine();
+        final Stroke lineStroke = item.getLineStroke();
+        final Paint linePaint = item.getLinePaint();
+        final LegendItem aggregatedItem = new LegendItem(label, description, tooltipText, urlText, shapeVisible, shape, shapeFilled, fillPaint,
+                shapeOutlineVisible, outlinePaint, outlineStroke, lineVisible, line, lineStroke, linePaint);
+
+        aggregatedItem.setDataset(item.getDataset());
+        aggregatedItem.setDatasetIndex(item.getDatasetIndex());
+        aggregatedItem.setFillPaintTransformer(item.getFillPaintTransformer());
+        aggregatedItem.setLabelFont(item.getLabelFont());
+        aggregatedItem.setLabelPaint(item.getLabelPaint());
+        aggregatedItem.setSeriesIndex(item.getSeriesIndex());
+
+        /* let styled dataset apply specific theme configuration */
+        applyThemeToLegend(aggregatedItem, legendTheme);
+
+        return aggregatedItem;
+    }
+
+    private void applyThemeToLegend(final LegendItem item, final Theme legendTheme) {
+
+        final Dataset dataset = item.getDataset();
+        if (dataset == null)
+            return;
+
+        final Document xml = legendTheme.toXML();
+        final ThemeDocument themeDocument = new ThemeDocument(xml);
+
+        if (dataset instanceof StyledXYDataset) {
+            final StyledXYDataset styledDataset = (StyledXYDataset) dataset;
+            styledDataset.applyAggregatedLegendTheme(item, themeDocument);
+            return;
+        }
+
+        if (dataset instanceof XYSeriesCollection) {
+
+            final int seriesIndex = item.getSeriesIndex();
+
+            final XYSeriesCollection seriesCollection = (XYSeriesCollection) dataset;
+            if (seriesIndex >= 0 && seriesIndex < seriesCollection.getSeriesCount()) {
+                final XYSeries series = seriesCollection.getSeries(seriesIndex);
+
+                if (series instanceof StyledSeries) {
+                    ((StyledSeries) series).applyAggregatedLegendTheme(item, themeDocument);
+                }
+            }
+        }
+    }
+
+    private Theme getLegendTheme(final CallContext context, final String themeType) {
+
+        final RiverContext flysContext = context instanceof RiverContext ? (RiverContext) context : (RiverContext) context.globalContext();
+
+        return ThemeFactory.getLegendTheme(flysContext, themeType);
+    }
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/exports/LegendProcessor.java	Mon Oct 22 18:26:05 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
- * Software engineering by Intevation GmbH
- *
- * This file is Free Software under the GNU AGPL (>=v3)
- * and comes with ABSOLUTELY NO WARRANTY! Check out the
- * documentation coming with Dive4Elements River for details.
- */
-
-package org.dive4elements.river.exports;
-
-import java.awt.geom.Line2D;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-
-import org.jfree.chart.LegendItem;
-import org.jfree.chart.LegendItemCollection;
-import org.jfree.chart.plot.XYPlot;
-
-/** Class to process Plots legends. */
-public abstract class LegendProcessor {
-
-    /** (Empty) shape for aggregated Legend Items. */
-    private static final Line2D.Double SPACE = new Line2D.Double(0, 0, 0, 0);
-
-    /** Prevent instantiations. */
-    private LegendProcessor() {
-    }
-
-    /**
-     * Create a hash from a legenditem.
-     * This hash can then be used to merge legend items labels.
-     *
-     * @return hash for given legenditem to identify mergeables.
-     */
-    protected static String legendItemHash(final LegendItem li) {
-        // TODO Do proper implementation.
-        // Ensure that only mergable sets are created.
-        // getFillPaint()
-        // getFillPaintTransformer()
-        // getLabel()
-        // getLine()
-        // getLinePaint()
-        // getLineStroke()
-        // getOutminePaint()
-        // getOutlineStroke()
-        // Shape getShape()
-        // String getToolTipText()
-        // String getURLText()
-        // boolean isLineVisible()
-        // boolean isShapeFilled()
-        // boolean isShapeOutlineVisible()
-        // boolean isShapeVisible()
-        final String hash = li.getLinePaint().toString();
-        // XXX: DEAD CODE // String label = li.getLabel();
-        /*
-         * if (label.startsWith("W (") || label.startsWith("W(")) {
-         * hash += "-W-";
-         * }
-         * else if (label.startsWith("Q(") || label.startsWith("Q (")) {
-         * hash += "-Q-";
-         * }
-         */
-
-        // WQ.java holds example of using regex Matcher/Pattern.
-
-        return hash;
-    }
-
-    /**
-     * Create new legend entries, dependent on settings.
-     *
-     * @param plot
-     *            The plot for which to modify the legend.
-     * @param threshold
-     *            How many items are needed for aggregation to
-     *            be triggered?
-     */
-    public static void aggregateLegendEntries(final XYPlot plot, final int threshold) {
-        final LegendItemCollection old = plot.getLegendItems();
-        // Find "similar" entries if aggregation is enabled.
-
-        int maxListSize = 0;
-        final int AGGR_THRESHOLD = threshold;
-
-        if (AGGR_THRESHOLD > old.getItemCount() || AGGR_THRESHOLD <= 0) {
-            return;
-        }
-
-        final HashMap<String, List<LegendItem>> entries = new LinkedHashMap<>();
-        // for (Iterator<LegendItem> i = old.iterator(); i.hasNext();) {
-
-        final Iterator<LegendItem> iterator = old.iterator();
-        while (iterator.hasNext()) {
-            final LegendItem item = iterator.next();
-            final String hash = legendItemHash(item);
-            List<LegendItem> itemList = entries.get(hash);
-            if (itemList == null) {
-                itemList = new ArrayList<>();
-                entries.put(hash, itemList);
-            }
-            itemList.add(item);
-
-            if (itemList.size() > maxListSize) {
-                maxListSize = itemList.size();
-            }
-        }
-
-        if (maxListSize < AGGR_THRESHOLD) {
-            // No need to do anything.
-            return;
-        }
-
-        // Run over collected entries, merge their names and create new
-        // entry if needed.
-        final LegendItemCollection newLegend = new LegendItemCollection();
-        for (final List<LegendItem> itemList : entries.values()) {
-            if (itemList.size() >= AGGR_THRESHOLD) {
-                // Now do merging.
-                // XXX: DEAD CODE // LegendItem item = itemList.get(0);
-                // Unfortunately we cannot clone and just setDescription,
-                // as this method was added in JFreeChart 1.0.14
-                // (we are at .13).
-
-                // Remove the shapes of all but the first items,
-                // to prevent "overfill" of legenditemblock.
-                for (int i = 0, I = itemList.size(); i < I; i++) {
-                    if (i != 0) {
-                        final LegendItem litem = itemList.get(i);
-
-                        // Make shape and line really small.
-                        final LegendItem merged = new LegendItem("," + litem.getLabel(), litem.getDescription(), litem.getToolTipText(), litem.getURLText(),
-                                false, SPACE, false, litem.getFillPaint(), false, litem.getOutlinePaint(), litem.getOutlineStroke(), false, SPACE,
-                                litem.getLineStroke(), litem.getLinePaint());
-                        newLegend.add(merged);
-                    } else {
-                        newLegend.add(itemList.get(i));
-                    }
-                }
-            } else {
-                // Do not merge entries.
-                for (final LegendItem li : itemList) {
-                    newLegend.add(li);
-                }
-            }
-        }
-
-        plot.setFixedLegendItems(newLegend);
-    }
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifacts/src/main/java/org/dive4elements/river/exports/TimeseriesChartGenerator.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/TimeseriesChartGenerator.java	Tue Oct 23 16:26:48 2018 +0200
@@ -17,7 +17,6 @@
 import org.dive4elements.river.jfree.RiverAnnotation;
 import org.dive4elements.river.jfree.StyledTimeSeries;
 import org.dive4elements.river.jfree.TimeBounds;
-import org.dive4elements.river.jfree.AxisDataset;
 import org.dive4elements.river.themes.ThemeDocument;
 
 import java.awt.Color;
@@ -27,7 +26,6 @@
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -36,15 +34,12 @@
 import org.apache.log4j.Logger;
 import org.jfree.chart.ChartFactory;
 import org.jfree.chart.JFreeChart;
-import org.jfree.chart.LegendItem;
-import org.jfree.chart.LegendItemCollection;
 import org.jfree.chart.annotations.XYAnnotation;
 import org.jfree.chart.annotations.XYImageAnnotation;
 import org.jfree.chart.annotations.XYTextAnnotation;
 import org.jfree.chart.axis.ValueAxis;
 import org.jfree.chart.plot.Marker;
 import org.jfree.chart.plot.XYPlot;
-import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
 import org.jfree.data.Range;
 import org.jfree.data.general.Series;
 import org.jfree.data.time.FixedMillisecond;
@@ -84,10 +79,10 @@
     public TimeseriesChartGenerator() {
         super();
 
-        xBounds = new HashMap<Integer, Bounds>();
-        yBounds = new HashMap<Integer, Bounds>();
-        domainMarker = new ArrayList<Marker>();
-        valueMarker = new ArrayList<Marker>();
+        xBounds = new HashMap<>();
+        yBounds = new HashMap<>();
+        domainMarker = new ArrayList<>();
+        valueMarker = new ArrayList<>();
     }
 
     @Override
@@ -111,7 +106,11 @@
         plot.setBackgroundPaint(Color.WHITE);
 
         adjustPlot(plot);
-        addDatasets(plot);
+        
+        final LegendAggregator legendBuilder = createLegendBuilder();
+        
+        addDatasets(plot, legendBuilder);
+
         adjustAxes(plot);
         addDomainAxisMarker(plot);
         addValueAxisMarker(plot);
@@ -119,14 +118,15 @@
 
         consumeAxisSettings(plot);
 
-        addAnnotationsToRenderer(plot);
+        addAnnotationsToRenderer(plot, legendBuilder);
         
+        legendBuilder.apply(context, plot);
+
         addLogo(plot);
-        aggregateLegendEntries(plot);
+
         return chart;
     }
 
-
     /**
      * Return left most data points x value (on first axis).
      * Shortcut, especially to be overridden in (LS) charts where
@@ -321,19 +321,6 @@
         return ((TimeSeriesCollection) dataset).getSeries(idx);
     }
 
-
-    /**
-     * This method creates new instances of AxisDataset.
-     *
-     * @param idx The symbol for the new AxisDataset.
-     */
-    @Override
-    protected AxisDataset createAxisDataset(int idx) {
-        log.debug("Create a new AxisDataset for index: " + idx);
-        return new AxisDataset(idx);
-    }
-
-
     @Override
     protected void combineXBounds(Bounds bounds, int index) {
         if (bounds != null) {
@@ -634,13 +621,14 @@
         boolean    visible,
         int        axisIndex
     ) {
+        final String facetName = aandf.getFacetName();
         String seriesName = aandf.getFacetDescription();
-        TimeSeries series = new StyledTimeSeries(seriesName, theme);
+        
+        TimeSeries series = new StyledTimeSeries(facetName, seriesName, theme);
 
         // Add text annotations for single points.
-        List<XYTextAnnotation> xy = new ArrayList<XYTextAnnotation>();
-        HashMap<FixedMillisecond, String> names =
-            new HashMap<FixedMillisecond, String>();
+        List<XYTextAnnotation> xy = new ArrayList<>();
+        HashMap<FixedMillisecond, String> names = new HashMap<>();
 
         try {
             JSONArray points = new JSONArray((String) o);
--- a/artifacts/src/main/java/org/dive4elements/river/exports/XYChartGenerator.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/XYChartGenerator.java	Tue Oct 23 16:26:48 2018 +0200
@@ -36,6 +36,7 @@
 import org.jfree.data.xy.XYDataset;
 import org.jfree.data.xy.XYSeries;
 import org.jfree.data.xy.XYSeriesCollection;
+import org.jfree.ui.RectangleInsets;
 import org.json.JSONArray;
 import org.json.JSONException;
 
@@ -77,9 +78,9 @@
     /** The log that is used in this generator. */
     private static Logger log = Logger.getLogger(XYChartGenerator.class);
 
-    protected List<Marker> domainMarkers = new ArrayList<Marker>();
+    protected List<Marker> domainMarkers = new ArrayList<>();
 
-    protected List<Marker> valueMarkers = new ArrayList<Marker>();
+    protected List<Marker> valueMarkers = new ArrayList<>();
 
     /** The max X range to include all X values of all series for each axis. */
     protected Map<Integer, Bounds> xBounds;
@@ -93,8 +94,8 @@
     public XYChartGenerator() {
         super();
 
-        xBounds  = new HashMap<Integer, Bounds>();
-        yBounds  = new HashMap<Integer, Bounds>();
+        xBounds  = new HashMap<>();
+        yBounds  = new HashMap<>();
     }
 
 
@@ -127,7 +128,9 @@
 
         //debugAxis(plot);
 
-        addDatasets(plot);
+        final LegendAggregator legendBuilder = createLegendBuilder();
+
+        addDatasets(plot, legendBuilder);
 
         //debugDatasets(plot);
 
@@ -151,13 +154,14 @@
 
         //debugAxis(plot);
 
+        
         // These have to go after the autozoom.
-        addAnnotationsToRenderer(plot);
+        addAnnotationsToRenderer(plot, legendBuilder);
 
         // Add a logo (maybe).
         addLogo(plot);
 
-        aggregateLegendEntries(plot);
+        legendBuilder.apply(context, plot);
 
         return chart;
     }
@@ -315,14 +319,6 @@
         return ((XYSeriesCollection) dataset).getSeries(idx);
     }
 
-
-    @Override
-    protected AxisDataset createAxisDataset(int idx) {
-        log.debug("Create new AxisDataset for index: " + idx);
-        return new AxisDataset(idx);
-    }
-
-
     /**
      * Put debug output about datasets.
      */
@@ -911,7 +907,7 @@
         int        axisIndex
     ) {
         String seriesName = aandf.getFacetDescription();
-        XYSeries series = new StyledXYSeries(seriesName, theme);
+        XYSeries series = new StyledXYSeries(aandf.getFacetName(), seriesName, theme);
 
         // Add text annotations for single points.
         List<XYTextAnnotation> xy = new ArrayList<XYTextAnnotation>();
@@ -946,44 +942,6 @@
         addAxisSeries(series, axisIndex, visible);
     }
 
-
-    /**
-     * Create a hash from a legenditem.
-     * This hash can then be used to merge legend items labels.
-     * @return hash for given legenditem to identify mergeables.
-     */
-    public static String legendItemHash(LegendItem li) {
-        // TODO Do proper implementation.
-        // Ensure that only mergable sets are created.
-        // getFillPaint()
-        // getFillPaintTransformer()
-        // getLabel()
-        // getLine()
-        // getLinePaint()
-        // getLineStroke()
-        // getOutlinePaint()
-        // getOutlineStroke()
-        // Shape getShape()
-        // String getToolTipText()
-        // String getURLText()
-        // boolean isLineVisible()
-        // boolean isShapeFilled()
-        // boolean isShapeOutlineVisible()
-        // boolean isShapeVisible()
-        String hash = li.getLinePaint().toString();
-        String label = li.getLabel();
-        if (label.startsWith("W (") || label.startsWith("W(")) {
-            hash += "-W-";
-        }
-        else if (label.startsWith("Q(") || label.startsWith("Q (")) {
-            hash += "-Q-";
-        }
-
-        // WQ.java holds example of using regex Matcher/Pattern.
-
-        return hash;
-    }
-
     /** True if x axis has been inverted. */
     public boolean isInverted() {
         return inverted;
--- a/artifacts/src/main/java/org/dive4elements/river/jfree/AxisDataset.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/jfree/AxisDataset.java	Tue Oct 23 16:26:48 2018 +0200
@@ -21,25 +21,13 @@
  */
 public class AxisDataset {
 
-    /** Symbolic integer, but also coding the priority (0 goes first). */
-    protected int axisSymbol;
-
     /** List of assigned datasets (in order). */
-    protected List<XYDataset> datasets;
+    private final List<XYDataset> datasets = new ArrayList<>();
 
     /** Range to use to include all given datasets. */
-    protected Range range;
-
-    /** Index of axis in plot. */
-    protected int plotAxisIndex;
+    private Range range;
 
-    protected boolean rangeDirty;
-
-    /** Create AxisDataset. */
-    public AxisDataset(final int symb) {
-        this.axisSymbol = symb;
-        this.datasets = new ArrayList<>();
-    }
+    private boolean rangeDirty;
 
     /** Add a dataset to internal list for this axis. */
     public void addDataset(final XYDataset dataset) {
@@ -99,14 +87,4 @@
     public boolean isEmpty() {
         return this.datasets.isEmpty();
     }
-
-    /** Set the 'real' axis index that this axis is mapped to. */
-    public void setPlotAxisIndex(final int axisIndex) {
-        this.plotAxisIndex = axisIndex;
-    }
-
-    /** Get the 'real' axis index that this axis is mapped to. */
-    public int getPlotAxisIndex() {
-        return this.plotAxisIndex;
-    }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/jfree/StripedAreaDataset.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/jfree/StripedAreaDataset.java	Tue Oct 23 16:26:48 2018 +0200
@@ -10,15 +10,15 @@
 package org.dive4elements.river.jfree;
 
 import java.awt.Color;
-import java.awt.Font;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.dive4elements.artifactdatabase.state.Facet;
 import org.dive4elements.artifacts.CallMeta;
+import org.dive4elements.river.exports.LegendAggregator;
 import org.dive4elements.river.java2d.ShapeUtils;
 import org.dive4elements.river.themes.ThemeDocument;
 import org.jfree.chart.LegendItem;
-import org.jfree.chart.LegendItemCollection;
 import org.jfree.chart.plot.XYPlot;
 import org.jfree.data.xy.XYSeries;
 import org.jfree.data.xy.XYSeriesCollection;
@@ -67,9 +67,16 @@
 
     private final List<Stripe> stripes = new ArrayList<>();
 
+    /**
+     * A 'type' that allows to categorize themes by it. Tyically this is simply the facet-name of the originating
+     * {@link Facet}.
+     */
+    private final String themeType;
+
     private final ThemeDocument theme;
 
-    public StripedAreaDataset(final ThemeDocument theme) {
+    public StripedAreaDataset(final String themeType, final ThemeDocument theme) {
+        this.themeType = themeType;
         this.theme = theme;
     }
 
@@ -93,28 +100,18 @@
     }
 
     @Override
-    public void applyTheme(final CallMeta callMeta, final XYPlot plot, final int datasetIndex, final Font legendFont) {
+    public void applyTheme(final CallMeta callMeta, final XYPlot plot, final LegendAggregator legendBuilder, final int datasetIndex) {
 
         final StripedAreaDatasetRenderer renderer = createRenderer();
 
         plot.setRenderer(datasetIndex, renderer);
 
-        final LegendItemCollection newItems = new LegendItemCollection();
-        final LegendItemCollection oldItems = plot.getFixedLegendItems();
-
         final int seriesCount = getSeriesCount();
         for (int i = 0; i < seriesCount; i++) {
             final LegendItem legendItem = renderer.getLegendItem(datasetIndex, i);
-            if (legendItem != null) {
-                legendItem.setLabelFont(legendFont);
-                newItems.add(legendItem);
-            }
+            if (legendItem != null)
+                legendBuilder.addLegendItem(this.themeType, legendItem);
         }
-
-        if (oldItems != null)
-            newItems.addAll(oldItems);
-
-        plot.setFixedLegendItems(newItems);
     }
 
     private StripedAreaDatasetRenderer createRenderer() {
@@ -130,4 +127,9 @@
 
         return renderer;
     }
+
+    @Override
+    public void applyAggregatedLegendTheme(final LegendItem item, final ThemeDocument legendTheme) {
+        /* not implemented at the moment */
+    }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/jfree/Style.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/jfree/Style.java	Tue Oct 23 16:26:48 2018 +0200
@@ -8,6 +8,8 @@
 
 package org.dive4elements.river.jfree;
 
+import org.dive4elements.river.themes.ThemeDocument;
+import org.jfree.chart.LegendItem;
 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
 
 
@@ -19,5 +21,6 @@
     XYLineAndShapeRenderer applyTheme(XYLineAndShapeRenderer r, int idx);
 
     XYLineAndShapeRenderer getRenderer();
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+
+    void applyAggregatedLegendTheme(LegendItem item, ThemeDocument theme);
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/jfree/StyledAreaSeriesCollection.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/jfree/StyledAreaSeriesCollection.java	Tue Oct 23 16:26:48 2018 +0200
@@ -10,7 +10,6 @@
 
 import java.awt.BasicStroke;
 import java.awt.Color;
-import java.awt.Font;
 import java.awt.Paint;
 import java.awt.Stroke;
 import java.awt.TexturePaint;
@@ -19,13 +18,14 @@
 import java.awt.image.BufferedImage;
 
 import org.apache.log4j.Logger;
+import org.dive4elements.artifactdatabase.state.Facet;
 import org.dive4elements.artifacts.CallMeta;
 import org.dive4elements.river.artifacts.resources.Resources;
+import org.dive4elements.river.exports.LegendAggregator;
 import org.dive4elements.river.java2d.ShapeUtils;
 import org.dive4elements.river.themes.ThemeDocument;
 import org.dive4elements.river.utils.Formatter;
 import org.jfree.chart.LegendItem;
-import org.jfree.chart.LegendItemCollection;
 import org.jfree.chart.plot.XYPlot;
 import org.jfree.data.xy.XYSeriesCollection;
 
@@ -55,14 +55,26 @@
     private final ThemeDocument theme;
 
     /**
+     * A 'type' that allows to categorize themes by it. Tyically this is simply the facet-name of the originating
+     * {@link Facet}.
+     */
+    private final String themeType;
+
+    /**
      * @param theme
      *            the theme-document.
+     * @param string
      */
-    public StyledAreaSeriesCollection(final ThemeDocument theme) {
+    public StyledAreaSeriesCollection(final String themeType, final ThemeDocument theme) {
+        this.themeType = themeType;
         this.theme = theme;
         this.mode = FILL_MODE.BETWEEN;
     }
 
+    public String getThemeType() {
+        return this.themeType;
+    }
+
     /** Gets the Fill mode. */
     private FILL_MODE getMode() {
         return this.mode;
@@ -74,10 +86,7 @@
     }
 
     @Override
-    public void applyTheme(final CallMeta callMeta, final XYPlot plot, final int datasetIndex, final Font legendFont) {
-
-        final LegendItemCollection lic = new LegendItemCollection();
-        final LegendItemCollection anno = plot.getFixedLegendItems();
+    public void applyTheme(final CallMeta callMeta, final XYPlot plot, final LegendAggregator legendBuilder, final int datasetIndex) {
 
         log.debug("Registering an 'area'renderer at idx: " + datasetIndex);
 
@@ -98,17 +107,10 @@
 
         final LegendItem legendItem = dRenderer.getLegendItem(datasetIndex, 0);
         if (legendItem != null) {
-            legendItem.setLabelFont(legendFont);
-            lic.add(legendItem);
+            legendBuilder.addLegendItem(this.themeType, legendItem);
         } else {
-            log.warn("Could not get LegentItem for renderer: " + datasetIndex + ", series-idx " + 0);
+            log.warn("Could not get LegengItem for renderer: " + datasetIndex + ", series-idx " + 0);
         }
-
-        if (anno != null) {
-            lic.addAll(anno);
-        }
-
-        plot.setFixedLegendItems(lic);
     }
 
     /**
@@ -274,5 +276,9 @@
     public boolean shouldCalculateRange() {
         return this.theme.parseCalculateRange();
     }
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+
+    @Override
+    public void applyAggregatedLegendTheme(final LegendItem item, final ThemeDocument legendTheme) {
+        /* not implemented */
+    }
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/jfree/StyledSeries.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/jfree/StyledSeries.java	Tue Oct 23 16:26:48 2018 +0200
@@ -8,14 +8,17 @@
 
 package org.dive4elements.river.jfree;
 
+import org.dive4elements.river.themes.ThemeDocument;
+import org.jfree.chart.LegendItem;
 
 /**
  * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
  */
 public interface StyledSeries {
 
-    void setStyle(Style style);
+    String getThemeType();
 
     Style getStyle();
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+
+    void applyAggregatedLegendTheme(LegendItem item, ThemeDocument theme);
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/jfree/StyledTimeSeries.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/jfree/StyledTimeSeries.java	Tue Oct 23 16:26:48 2018 +0200
@@ -11,7 +11,7 @@
 import java.awt.Shape;
 
 import org.dive4elements.river.themes.ThemeDocument;
-
+import org.jfree.chart.LegendItem;
 import org.jfree.data.time.TimeSeries;
 
 /**
@@ -19,25 +19,35 @@
  */
 public class StyledTimeSeries extends TimeSeries implements StyledSeries {
 
-    private Style style;
+    private static final long serialVersionUID = 1L;
 
-    public StyledTimeSeries(String key, ThemeDocument theme) {
+    private final Style style;
+
+    private final String themeType;
+
+    public StyledTimeSeries(final String themeType, final String key, final ThemeDocument theme) {
+        this(themeType, key, theme, null);
+    }
+
+    public StyledTimeSeries(final String themeType, final String key, final ThemeDocument theme, final Shape shape) {
         super(key);
-        setStyle(new XYStyle(theme));
-    }
-    
-    public StyledTimeSeries(String key, ThemeDocument theme, Shape shape) {
-        super(key);
-        setStyle(new XYStyle(theme, shape));
+
+        this.style = new XYStyle(theme, shape);
+        this.themeType = themeType;
     }
 
     @Override
-    public void setStyle(Style style) {
-        this.style = style;
+    public String getThemeType() {
+        return this.themeType;
     }
 
     @Override
     public Style getStyle() {
-        return style;
+        return this.style;
+    }
+
+    @Override
+    public void applyAggregatedLegendTheme(final LegendItem item, final ThemeDocument theme) {
+        this.style.applyAggregatedLegendTheme(item, theme);
     }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/jfree/StyledXYDataset.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/jfree/StyledXYDataset.java	Tue Oct 23 16:26:48 2018 +0200
@@ -9,16 +9,18 @@
  */
 package org.dive4elements.river.jfree;
 
-import java.awt.Font;
-
 import org.dive4elements.artifacts.CallMeta;
+import org.dive4elements.river.exports.LegendAggregator;
+import org.dive4elements.river.themes.ThemeDocument;
+import org.jfree.chart.LegendItem;
 import org.jfree.chart.plot.XYPlot;
 import org.jfree.data.xy.XYDataset;
 
 /**
  * @author Domenico Nardi Tironi
- *
  */
 public interface StyledXYDataset extends XYDataset {
-    void applyTheme(CallMeta callMeta, XYPlot plot, int datasetIndex, Font legendFont);
+    void applyTheme(CallMeta callMeta, XYPlot plot, LegendAggregator legendBuilder, int datasetIndex);
+
+    void applyAggregatedLegendTheme(LegendItem item, ThemeDocument theme);
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/jfree/StyledXYSeries.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/jfree/StyledXYSeries.java	Tue Oct 23 16:26:48 2018 +0200
@@ -8,170 +8,129 @@
 
 package org.dive4elements.river.jfree;
 
-import java.util.List;
+import java.awt.Shape;
 import java.util.Map;
 
-import org.apache.log4j.Logger;
-
-import org.dive4elements.river.model.River;
-
+import org.dive4elements.artifactdatabase.state.Facet;
 import org.dive4elements.artifacts.Artifact;
 import org.dive4elements.artifacts.CallContext;
 import org.dive4elements.river.artifacts.D4EArtifact;
 import org.dive4elements.river.artifacts.access.RiverAccess;
 import org.dive4elements.river.artifacts.resources.Resources;
+import org.dive4elements.river.model.River;
 import org.dive4elements.river.themes.ThemeDocument;
-
-import org.jfree.data.xy.XYDataItem;
+import org.jfree.chart.LegendItem;
 import org.jfree.data.xy.XYSeries;
 
-import java.awt.Shape;
-
 /**
  * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
  */
-public class StyledXYSeries
-extends XYSeries
-implements StyledSeries, HasLabel, XYMetaDataset {
+public class StyledXYSeries extends XYSeries implements StyledSeries, HasLabel, XYMetaDataset {
 
-    private static final Logger log = Logger.getLogger(StyledXYSeries.class);
+    private static final long serialVersionUID = 1L;
 
-    protected Style style;
+    private final Style style;
 
     /** If this Series is to be labelled, use this String as label. */
-    protected String label;
+    private String label;
 
     /** The meta data for this series. */
-    protected Map<String, String> metaData;
+    private Map<String, String> metaData;
 
-    public StyledXYSeries(String key, ThemeDocument theme) {
-        this(key, true, theme, (Shape)null);
+    /**
+     * A 'type' that allows to categorize themes by it. Tyically this is simply the facet-name of the originating
+     * {@link Facet}.
+     * REMARK: stictly there should be a type per dataset, not per series, but flys uses (for line themes) generic datasets
+     * with one series.
+     */
+    private final String themeType;
+
+    public StyledXYSeries(final String facetName, final String key, final ThemeDocument theme) {
+        this(facetName, key, true, theme, (Shape) null);
     }
 
-
-    public StyledXYSeries(
-        String key,
-        ThemeDocument theme,
-        XYSeries unstyledSeries
-    ) {
-        this(key, theme);
-        add(unstyledSeries);
+    public StyledXYSeries(final String facetName, final String key, final boolean sorted, final ThemeDocument theme) {
+        this(facetName, key, sorted, theme, (Shape) null);
     }
 
-    public StyledXYSeries(String key, boolean sorted, ThemeDocument theme) {
-        this(key, sorted, theme, (Shape)null);
-    }
-
-
-    public StyledXYSeries(String key, ThemeDocument theme, Shape shape) {
-        this(key, true, theme, shape);
+    public StyledXYSeries(final String facetName, final String key, final ThemeDocument theme, final Shape shape) {
+        this(facetName, key, true, theme, shape);
     }
 
     /**
-     * @param sorted whether or not to sort the points. Sorting will move NANs
-     *               to one extrema which can cause problems in certain
-     *               algorithms.
+     * @param sorted
+     *            whether or not to sort the points. Sorting will move NANs
+     *            to one extrema which can cause problems in certain
+     *            algorithms.
      */
-    public StyledXYSeries(
-        String key,
-        boolean sorted,
-        ThemeDocument theme,
-        Shape shape
-    ) {
+    public StyledXYSeries(final String facetName, final String key, final boolean sorted, final ThemeDocument theme, final Shape shape) {
         super(key, sorted);
-        setStyle(new XYStyle(theme, shape));
+
+        this.style = new XYStyle(theme, shape);
         this.label = key.toString();
+        this.themeType = facetName;
     }
 
-    public StyledXYSeries(
-        String        key,
-        boolean       sorted,
-        boolean       allowDuplicateXValues,
-        ThemeDocument theme
-    ) {
-        this(key, sorted, allowDuplicateXValues, theme, (Shape)null);
+    public StyledXYSeries(final String themeType, final String key, final boolean sorted, final boolean allowDuplicateXValues, final ThemeDocument theme) {
+        this(themeType, key, sorted, allowDuplicateXValues, theme, (Shape) null);
     }
 
-    public StyledXYSeries(
-        String        key,
-        boolean       sorted,
-        boolean       allowDuplicateXValues,
-        ThemeDocument theme,
-        Shape         shape
-    ) {
+    public StyledXYSeries(final String themeType, final String key, final boolean sorted, final boolean allowDuplicateXValues, final ThemeDocument theme,
+            final Shape shape) {
         super(key, sorted, allowDuplicateXValues);
-        setStyle(new XYStyle(theme, shape));
+
+        this.style = new XYStyle(theme, shape);
         this.label = key.toString();
+        this.themeType = themeType;
     }
 
-
     @Override
-    public void setStyle(Style style) {
-        this.style = style;
+    public String getThemeType() {
+        return this.themeType;
     }
 
-
     @Override
     public Style getStyle() {
-        return style;
+        return this.style;
     }
 
-
     @Override
     public String getLabel() {
-        return label;
+        return this.label;
     }
 
     @Override
-    public void setLabel(String label) {
+    public void setLabel(final String label) {
         this.label = label;
     }
 
-    protected void add(XYSeries series) {
-        List<XYDataItem> items = series.getItems();
-        add(items);
-    }
-
-    protected void add(List<XYDataItem> items) {
-        for(XYDataItem item : items) {
-            add(item.getXValue(), item.getYValue());
-        }
-    }
-
-
     @Override
     public Map<String, String> getMetaData() {
-        return metaData;
+        return this.metaData;
     }
 
-
     @Override
     // FIXME: bad! method with undocumented side-effects; given metadata will be changed inline
-    public void putMetaData(Map<String, String> metaData,
-        Artifact artifact,
-        CallContext context) {
+    public void putMetaData(final Map<String, String> metaData, final Artifact artifact, final CallContext context) {
         this.metaData = metaData;
-        River river = new RiverAccess((D4EArtifact)artifact).getRiver();
+        final River river = new RiverAccess((D4EArtifact) artifact).getRiver();
         String rivername = "";
         String unit = "";
         if (river != null) {
             rivername = river.getName();
             // FIXME: this will always return the wst unit, regardless if the series is a water level or not!
-            unit      = river.getWstUnit().getName();
+            unit = river.getWstUnit().getName();
         }
         if (metaData.containsKey("X")) {
-            this.metaData.put("X",
-                Resources.getMsg(
-                    context.getMeta(),
-                    metaData.get("X"),
-                    new Object[] { rivername }));
+            this.metaData.put("X", Resources.getMsg(context.getMeta(), metaData.get("X"), new Object[] { rivername }));
         }
         if (metaData.containsKey("Y")) {
-            this.metaData.put("Y",
-                Resources.getMsg(
-                    context.getMeta(),
-                    metaData.get("Y"), new Object[] { unit }));
+            this.metaData.put("Y", Resources.getMsg(context.getMeta(), metaData.get("Y"), new Object[] { unit }));
         }
     }
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+
+    @Override
+    public void applyAggregatedLegendTheme(final LegendItem item, final ThemeDocument theme) {
+        this.style.applyAggregatedLegendTheme(item, theme);
+    }
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/jfree/XYStyle.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/jfree/XYStyle.java	Tue Oct 23 16:26:48 2018 +0200
@@ -14,6 +14,7 @@
 import java.awt.geom.Ellipse2D;
 
 import org.dive4elements.river.themes.ThemeDocument;
+import org.jfree.chart.LegendItem;
 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
 
 /**
@@ -170,8 +171,8 @@
 
         if (c != null)
             r.setSeriesFillPaint(idx, c);
-        r.setUseFillPaint(c!= null);
-        
+        r.setUseFillPaint(c != null);
+
         r.setSeriesShapesFilled(idx, show);
 
         r.setSeriesOutlinePaint(idx, c);
@@ -219,4 +220,35 @@
     public XYLineAndShapeRenderer getRenderer() {
         return this.renderer;
     }
+
+    @Override
+    public void applyAggregatedLegendTheme(final LegendItem item, final ThemeDocument legendTheme) {
+
+        if (this.theme == null)
+            return;
+
+        if (this.shape != null)
+            item.setShape(this.shape);
+
+        final Color lineColor = legendTheme.parseLineColorField();
+        if (lineColor != null)
+            item.setLinePaint(lineColor);
+
+        if (legendTheme.getValue(ThemeDocument.SHOW_LINE) != null)
+            item.setLineVisible(legendTheme.parseShowLine());
+
+        /* remark cant set line stroke on legend item */
+        // applyLineSize(r, idx);
+        // applyLineType(r, idx);
+
+        /*
+         * REMARK: we only change the colors, as visibility and such are interdependend and should be the same as the original
+         * items
+         */
+        final Color c = legendTheme.parsePointColor();
+        if (c != null) {
+            item.setFillPaint(c);
+            item.setOutlinePaint(c);
+        }
+    }
 }
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/themes/DefaultTheme.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/themes/DefaultTheme.java	Tue Oct 23 16:26:48 2018 +0200
@@ -11,139 +11,83 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import org.dive4elements.artifacts.common.utils.XMLUtils;
+import org.dive4elements.artifacts.common.utils.XMLUtils.ElementCreator;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 
-import org.dive4elements.artifacts.common.utils.XMLUtils;
-import org.dive4elements.artifacts.common.utils.XMLUtils.ElementCreator;
-
 
 /**
  * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
  */
-public class DefaultTheme implements Theme {
+final class DefaultTheme implements Theme {
 
     /** The name of the theme.*/
-    protected String name;
+    private final String name;
 
-    /** The description of the theme.*/
-    protected String description;
+    private String facet;
 
-    protected String facet;
-
-    protected int index;
-
+    private int index;
 
     /** The map storing the fields of this theme.*/
-    protected Map<String, ThemeField> fields;
+    private final Map<String, ThemeField> fields;
 
     /** The map storing the attributes of this theme.*/
-    protected Map<String, String> attr;
+    private final Map<String, String> attr;
 
+    private final String description;
 
     /**
      * Initializes the components of this Theme.
      */
-    public DefaultTheme(String name, String description) {
+    public DefaultTheme(final String name, final String description) {
         this.name        = name;
         this.description = description;
-        this.fields      = new HashMap<String, ThemeField>();
-        this.attr        = new HashMap<String, String>();
-    }
-
-
-    public void init(Node config) {
+        this.fields      = new HashMap<>();
+        this.attr        = new HashMap<>();
     }
 
-
+    @Override
     public String getName() {
-        return name;
+        return this.name;
     }
 
-
     public String getDescription() {
-        return description;
+        return this.description;
     }
 
-
-    public String getFacet() {
-        return facet;
-    }
-
-
-    public void setFacet(String facet) {
+    @Override
+    public void setFacet(final String facet) {
         this.facet = facet;
     }
 
-
-    public int getIndex() {
-        return index;
-    }
-
-
-    public void setIndex(int index) {
+    @Override
+    public void setIndex(final int index) {
         this.index = index;
     }
 
-
-    public void addAttribute(String name, String value) {
+    public void addAttribute(final String name, final String value) {
         if (name != null && value != null) {
-            attr.put(name, value);
+            this.attr.put(name, value);
         }
     }
 
-
-    public String getAttribute(String name) {
-        return attr.get(name);
-    }
-
-
-    public void addField(String name, ThemeField field) {
+    public void addField(final String name, final ThemeField field) {
         if (name != null && field != null) {
-            fields.put(name, field);
-        }
-    }
-
-
-    public void setFieldValue(String name, Object value) {
-        if (name != null && value != null) {
-            ThemeField field = fields.get(name);
-
-            if (field != null) {
-                field.setValue(value);
-            }
+            this.fields.put(name, field);
         }
     }
 
-
-    public ThemeField getField(String name) {
-        return fields.get(name);
-    }
-
-
-    public String getFieldType(String name) {
-        ThemeField field = fields.get(name);
-
-        return field != null ? field.getType() : null;
-    }
-
+    @Override
+    public Document toXML() {
+        final Document doc = XMLUtils.newDocument();
 
-    public Object getFieldValue(String name) {
-        ThemeField field = fields.get(name);
-
-        return field != null ? field.getValue() : null;
-    }
-
+        final ElementCreator cr = new ElementCreator(doc, null, null);
 
-    public Document toXML() {
-        Document doc = XMLUtils.newDocument();
-
-        ElementCreator cr = new ElementCreator(doc, null, null);
-
-        Element theme = cr.create("theme");
-        theme.setAttribute("facet", facet);
-        theme.setAttribute("index", String.valueOf(index));
+        final Element theme = cr.create("theme");
+        theme.setAttribute("facet", this.facet);
+        theme.setAttribute("index", String.valueOf(this.index));
 
         appendAttributes(cr, theme);
         appendFields(cr, theme);
@@ -160,11 +104,11 @@
      * @param cr The ElementCreator.
      * @param theme The document root element.
      */
-    protected void appendAttributes(ElementCreator cr, Element theme) {
+    protected void appendAttributes(final ElementCreator cr, final Element theme) {
 
-        for (Map.Entry<String, String> entry: attr.entrySet()) {
-            String key = entry.getKey();
-            String val = entry.getValue();
+        for (final Map.Entry<String, String> entry: this.attr.entrySet()) {
+            final String key = entry.getKey();
+            final String val = entry.getValue();
 
             if (key != null && val != null) {
                 cr.addAttr(theme, key, val);
@@ -179,14 +123,13 @@
      * @param cr The ElementCreator.
      * @param theme The document root element.
      */
-    protected void appendFields(ElementCreator cr, Element theme) {
+    protected void appendFields(final ElementCreator cr, final Element theme) {
 
-        for (ThemeField field: fields.values()) {
-            Document doc = field.toXML();
-            Node    root = doc.getFirstChild();
+        for (final ThemeField field: this.fields.values()) {
+            final Document doc = field.toXML();
+            final Node    root = doc.getFirstChild();
 
             theme.appendChild(theme.getOwnerDocument().importNode(root, true));
         }
     }
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/themes/Theme.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/themes/Theme.java	Tue Oct 23 16:26:48 2018 +0200
@@ -9,8 +9,6 @@
 package org.dive4elements.river.themes;
 
 import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-
 
 /**
  * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
@@ -18,110 +16,22 @@
 public interface Theme {
 
     /**
-     * Method to initialize the theme.
-     *
-     * @param config The configuration node.
-     */
-    void init(Node config);
-
-
-    /**
      * Returns the name of the theme.
      *
      * @return the name of the theme.
      */
     String getName();
 
-
-    /**
-     * Returns the description of the theme.
-     *
-     * @return the description of the theme.
-     */
     String getDescription();
 
-
-    String getFacet();
-
     void setFacet(String facet);
 
-    int getIndex();
-
     void setIndex(int index);
 
-
-    /**
-     * Adds a new attribute.
-     *
-     * @param name The name of the attribute.
-     * @param value The value of the attribute.
-     */
-    void addAttribute(String name, String value);
-
-
-    /**
-     * Returns the value of a specific attribute.
-     *
-     * @param name the name of the attribute.
-     *
-     * @return the value of the attribute <i>name</i>.
-     */
-    String getAttribute(String name);
-
-
-    /**
-     * Adds a new field to the theme.
-     *
-     * @param name The name of the field.
-     * @param field The field.
-     */
-    void addField(String name, ThemeField field);
-
-
-    /**
-     * Sets the value of an field.
-     *
-     * @param name The name of the field.
-     * @param value The new value of the field.
-     */
-    void setFieldValue(String name, Object value);
-
-
-    /**
-     * Returns the field specified by name.
-     *
-     * @param name The name of the desired field.
-     *
-     * @return an field.
-     */
-    ThemeField getField(String name);
-
-
-    /**
-     * Returns the typename of a field.
-     *
-     * @param name the name of the field.
-     *
-     * @return the typename of a field.
-     */
-    String getFieldType(String name);
-
-
-    /**
-     * Returns the value of a field.
-     *
-     * @param name The name of the field.
-     *
-     * @return the value of a field.
-     */
-    Object getFieldValue(String name);
-
-
     /**
      * Dumps the theme to XML.
      *
      * @return a document.
      */
     Document toXML();
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+}
\ No newline at end of file
--- a/artifacts/src/main/java/org/dive4elements/river/themes/ThemeDocument.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/themes/ThemeDocument.java	Tue Oct 23 16:26:48 2018 +0200
@@ -34,7 +34,7 @@
     private static final String MSG_ISOBATH_CLASS = "floodmap.isobath.class";
 
     private static final String MSG_ISOBATH_LASTCLASS =
-        "floodmap.isobath.lastclass";
+            "floodmap.isobath.lastclass";
 
     public final static String FILL_COLOR = "fillcolor";
 
@@ -132,35 +132,34 @@
     public ThemeDocument() {
     }
 
-    public ThemeDocument(Document document) {
-        values = extractValues(document);
-    }
-
-    public ThemeDocument(ThemeDocument other) {
-        values = new HashMap<String, String>(other.values);
+    public ThemeDocument(final Document document) {
+        this.values = extractValues(document);
     }
 
-
-    public String getValue(String key) {
-        return values.get(key);
+    public ThemeDocument(final ThemeDocument other) {
+        this.values = new HashMap<>(other.values);
     }
 
-    public void setValue(String key, String value) {
-        values.put(key, value);
+    public String getValue(final String key) {
+        return this.values.get(key);
     }
 
-    private static Map<String, String> extractValues(Document document) {
-        Map<String, String> values = new HashMap<String, String>();
+    public void setValue(final String key, final String value) {
+        this.values.put(key, value);
+    }
+
+    private static Map<String, String> extractValues(final Document document) {
+        final Map<String, String> values = new HashMap<>();
         if (document == null) {
             log.error("Invalid null document given.");
             return values;
         }
 
-        NodeList fields = document.getElementsByTagName("field");
+        final NodeList fields = document.getElementsByTagName("field");
         for (int i = 0, N = fields.getLength(); i < N; ++i) {
-            Element field = (Element)fields.item(i);
-            String name   = field.getAttribute("name");
-            String value  = field.getAttribute("default");
+            final Element field = (Element)fields.item(i);
+            final String name   = field.getAttribute("name");
+            final String value  = field.getAttribute("default");
             if (!name.isEmpty() && !value.isEmpty()) {
                 values.put(name, value);
             }
@@ -172,7 +171,7 @@
     }
 
     /** Parse string to be boolean with default if empty or unrecognized. */
-    private static boolean parseBoolean(String value, boolean defaultsTo) {
+    private static boolean parseBoolean(final String value, final boolean defaultsTo) {
         if (value == null) {
             return defaultsTo;
         }
@@ -193,7 +192,7 @@
      * @param defaultsTo Default to return if conversion failed.
      * @return \param value as integer or defaultsto if conversion failed.
      */
-    private static int parseInteger(String value, int defaultsTo) {
+    private static int parseInteger(final String value, final int defaultsTo) {
         if (value == null) {
             return defaultsTo;
         }
@@ -201,7 +200,7 @@
         try {
             return Integer.parseInt(value);
         }
-        catch (NumberFormatException nfe) {
+        catch (final NumberFormatException nfe) {
             // do nothing
         }
 
@@ -216,7 +215,7 @@
      * @param defaultsTo Default to return if conversion failed.
      * @return \param value as integer or defaultsto if conversion failed.
      */
-    private static double parseDouble(String value, double defaultsTo) {
+    private static double parseDouble(final String value, final double defaultsTo) {
         if (value == null) {
             return defaultsTo;
         }
@@ -224,7 +223,7 @@
         try {
             return Double.parseDouble(value);
         }
-        catch (NumberFormatException nfe) {
+        catch (final NumberFormatException nfe) {
             // do nothing
         }
 
@@ -232,60 +231,60 @@
     }
 
     public boolean parseShowLineLabel() {
-        String show = getValue(SHOW_LINE_LABEL);
+        final String show = getValue(SHOW_LINE_LABEL);
         return parseBoolean(show, false);
     }
 
     public boolean parseShowWidth() {
-        String show = getValue(SHOW_WIDTH);
+        final String show = getValue(SHOW_WIDTH);
         return parseBoolean(show, false);
     }
 
     public boolean parseShowLevel() {
-        String show = getValue(SHOW_LEVEL);
+        final String show = getValue(SHOW_LEVEL);
         return parseBoolean(show, false);
     }
 
     public String parseTextOrientation() {
-        String o = getValue(TEXT_ORIENTATION);
+        final String o = getValue(TEXT_ORIENTATION);
 
         return o != null && "true".equals(o)
-            ? "horizontal"
-            : "vertical";
+                ? "horizontal"
+                        : "vertical";
     }
 
     public boolean parseShowMiddleHeight() {
-        String show = getValue(SHOW_MIDDLE_HEIGHT);
+        final String show = getValue(SHOW_MIDDLE_HEIGHT);
         return parseBoolean(show, false);
     }
 
     public boolean parseLabelShowBackground() {
-        String show = getValue(LABEL_SHOW_BACKGROUND);
+        final String show = getValue(LABEL_SHOW_BACKGROUND);
         return parseBoolean(show, false);
     }
 
     public Font parseFont() {
-        String font = getValue(FONT);
+        final String font = getValue(FONT);
         log.debug(" font is " + font);
         if (font == null) {
             return null;
         }
 
-        int size = parseFontSize();
-        int style = parseFontStyle();
-        Font f = new Font(font, style, size);
+        final int size = parseFontSize();
+        final int style = parseFontStyle();
+        final Font f = new Font(font, style, size);
         return f;
     }
 
     public Font parseTextFont() {
-        String font = getValue(LABEL_FONT_FACE);
+        final String font = getValue(LABEL_FONT_FACE);
         if (font == null) {
             return null;
         }
 
-        int size = parseTextSize();
-        int style = parseTextStyle();
-        Font f = new Font(font, style, size);
+        final int size = parseTextSize();
+        final int style = parseTextStyle();
+        final Font f = new Font(font, style, size);
         return f;
     }
 
@@ -298,10 +297,10 @@
     }
 
     public Color parseTextBackground() {
-        String color = getLabelBackgroundColorString();
+        final String color = getLabelBackgroundColorString();
         return color != null
-            ? parseRGB(color)
-            : Color.WHITE;
+                ? parseRGB(color)
+                        : Color.WHITE;
     }
 
     private String getLabelBackgroundColorString() {
@@ -309,7 +308,7 @@
     }
 
     public int parseLineWidth() {
-        String size = getValue(LINE_SIZE);
+        final String size = getValue(LINE_SIZE);
         if (size == null) {
             return 0;
         }
@@ -317,65 +316,65 @@
         try {
             return Integer.parseInt(size);
         }
-        catch (NumberFormatException nfe) {
+        catch (final NumberFormatException nfe) {
             log.warn("Unable to set line size from string: '" + size + "'");
         }
         return 0;
     }
 
     public float [] parseLineStyle() {
-        String dash = getValue(LINE_STYLE);
+        final String dash = getValue(LINE_STYLE);
 
-        float[] def = {10};
+        final float[] def = {10};
         if (dash == null) {
             return def;
         }
 
-        String[] pattern = dash.split(",");
+        final String[] pattern = dash.split(",");
         if(pattern.length == 1) {
             return def;
         }
 
         try {
-            float[] dashes = new float[pattern.length];
+            final float[] dashes = new float[pattern.length];
             for (int i = 0; i < pattern.length; i++) {
                 dashes[i] = Float.parseFloat(pattern[i]);
             }
             return dashes;
         }
-        catch (NumberFormatException nfe) {
+        catch (final NumberFormatException nfe) {
             log.warn("Unable to set dash from string: '" + dash + "'");
             return def;
         }
     }
 
     public int parsePointWidth() {
-        String width = getValue(POINT_SIZE);
+        final String width = getValue(POINT_SIZE);
         return parseInteger(width, 3);
     }
 
     public Color parsePointColor() {
-        String color = getValue(POINT_COLOR);
+        final String color = getValue(POINT_COLOR);
         return parseColor(color);
     }
 
     public boolean parseShowPoints() {
-        String show = getValue(SHOW_POINTS);
+        final String show = getValue(SHOW_POINTS);
         return parseBoolean(show, false);
     }
 
     public boolean parseShowPointsOutline() {
-        String show = getValue(SHOW_POINTS_OUTLINE);
+        final String show = getValue(SHOW_POINTS_OUTLINE);
         return parseBoolean(show, false);
     }
 
     public boolean parseShowLine() {
-        String show = getValue(SHOW_LINE);
+        final String show = getValue(SHOW_LINE);
         return parseBoolean(show, false);
     }
 
     public int parseFontStyle() {
-        String style = getValue(TEXT_STYLE);
+        final String style = getValue(TEXT_STYLE);
         if (style == null) {
             return Font.PLAIN;
         }
@@ -390,7 +389,7 @@
     }
 
     public int parseTextStyle() {
-        String style = getValue(LABEL_FONT_STYLE);
+        final String style = getValue(LABEL_FONT_STYLE);
         if (style == null) {
             return Font.PLAIN;
         }
@@ -412,35 +411,35 @@
             font = parseFont();
         }
         return new TextStyle(
-            parseTextColor(),
-            font,
-            parseTextBackground(),
-            parseLabelShowBackground(),
-            !parseTextOrientation().equals("horizontal"));
+                parseTextColor(),
+                font,
+                parseTextBackground(),
+                parseLabelShowBackground(),
+                !parseTextOrientation().equals("horizontal"));
     }
 
     public LineStyle parseComplexLineStyle() {
         return new LineStyle(
-            parseLineColorField(),
-            Integer.valueOf(parseLineWidth()));
+                parseLineColorField(),
+                Integer.valueOf(parseLineWidth()));
     }
 
     public boolean parseShowVerticalLine() {
-        String show = getValue(SHOW_VERTICAL_LINE);
+        final String show = getValue(SHOW_VERTICAL_LINE);
         return parseBoolean(show, true);
     }
 
     public boolean parseShowHorizontalLine() {
-        String show = getValue(SHOW_HORIZONTAL_LINE);
+        final String show = getValue(SHOW_HORIZONTAL_LINE);
         return parseBoolean(show, true);
     }
 
     public double parseBandWidth() {
-        String bandWidth = getValue(BANDWIDTH);
+        final String bandWidth = getValue(BANDWIDTH);
         return parseDouble(bandWidth, 0);
     }
 
-    private static Color parseColor(String colorString) {
+    private static Color parseColor(final String colorString) {
         if (colorString == null) {
             return null;
         }
@@ -462,35 +461,35 @@
      *
      * @return a Color or null, if <i>hex</i> is empty.
      */
-    private static Color parseHexColor(String hex) {
+    private static Color parseHexColor(final String hex) {
         return hex != null
-            ? Color.decode(hex)
-            : null;
+                ? Color.decode(hex)
+                        : null;
     }
 
 
     public boolean parseShowArea() {
-        String show = getValue(SHOW_AREA);
+        final String show = getValue(SHOW_AREA);
         return parseBoolean(show, false);
     }
 
     public boolean parseShowAreaLabel() {
-        String show = getValue(SHOW_AREA_LABEL);
+        final String show = getValue(SHOW_AREA_LABEL);
         return parseBoolean(show, false);
     }
 
     public boolean parseShowPointLabel() {
-        String show = getValue(SHOW_POINT_LABEL);
+        final String show = getValue(SHOW_POINT_LABEL);
         return parseBoolean(show, false);
     }
 
     public boolean parseShowExtraMark() {
-        String show = getValue(SHOWEXTRAMARK);
+        final String show = getValue(SHOWEXTRAMARK);
         return parseBoolean(show, false);
     }
 
     public int parseFontSize() {
-        String size = getValue(TEXT_SIZE);
+        final String size = getValue(TEXT_SIZE);
         if (size == null) {
             return 10;
         }
@@ -498,14 +497,14 @@
         try {
             return Integer.parseInt(size);
         }
-        catch (NumberFormatException nfe) {
+        catch (final NumberFormatException nfe) {
             // Do nothing
         }
         return 10;
     }
 
     public int parseTextSize() {
-        String size = getValue(LABEL_FONT_SIZE);
+        final String size = getValue(LABEL_FONT_SIZE);
         if (size == null) {
             return 10;
         }
@@ -513,7 +512,7 @@
         try {
             return Integer.parseInt(size);
         }
-        catch (NumberFormatException nfe) {
+        catch (final NumberFormatException nfe) {
             // Do nothing
         }
         return 10;
@@ -524,18 +523,18 @@
      * @param rgbtext Color as string representation, e.g. "255,0,20".
      * @return Color, null in case of issues.
      */
-    public static Color parseRGB(String rgbtext) {
+    public static Color parseRGB(final String rgbtext) {
         if (rgbtext == null) {
             return null;
         }
-        String rgb[] = rgbtext.split(",");
+        final String rgb[] = rgbtext.split(",");
         try {
             return new Color(
-                Integer.parseInt(rgb[0].trim()),
-                Integer.parseInt(rgb[1].trim()),
-                Integer.parseInt(rgb[2].trim()));
+                    Integer.parseInt(rgb[0].trim()),
+                    Integer.parseInt(rgb[1].trim()),
+                    Integer.parseInt(rgb[2].trim()));
         }
-        catch (NumberFormatException nfe) {
+        catch (final NumberFormatException nfe) {
             // Do nothing
         }
         return null;
@@ -603,24 +602,24 @@
      * @return color.
      */
     public Color parseLineColorField() {
-        String lineColorStr = getLineColorString();
+        final String lineColorStr = getLineColorString();
         if (log.isDebugEnabled()) {
             log.debug("parseLineColorField: lineColorStr = " +
-                (lineColorStr == null
-                     ? "null"
-                     : lineColorStr));
+                    (lineColorStr == null
+                    ? "null"
+                            : lineColorStr));
         }
         return parseColor(lineColorStr);
     }
 
     // FIXME: check, this is defined in default.xml, but never used. Instead the StyledAreaSeriesCollection used lineColor etc
     public Color parseAreaLineColorField() {
-        String lineColorStr = getAreaLineColorString();
+        final String lineColorStr = getAreaLineColorString();
         if (log.isDebugEnabled()) {
             log.debug("parseLineColorField: lineColorStr = " +
-                (lineColorStr == null
+                    (lineColorStr == null
                     ? "null"
-                    : lineColorStr));
+                            : lineColorStr));
         }
         return parseColor(lineColorStr);
     }
@@ -649,68 +648,68 @@
      * @return String representation of the MapserverStyle
      */
     public String createDynamicMapserverStyle(
-        float    from,
-        float    to,
-        float    step,
-        CallMeta meta
-    ) {
-        MapserverStyle ms = new MapserverStyle();
-
-        String strStartColor = getValue(WSPLGEN_STARTCOLOR);
-        Color startColor = strStartColor != null
-            ? parseColor(strStartColor)
-            : new Color(178, 201, 215);
-        String strEndColor = getValue(WSPLGEN_ENDCOLOR);
-        Color endColor = strEndColor != null
-            ? parseColor(strEndColor)
-            : new Color(2, 27, 42);
-
-        to = to >= 0 ? to : 9999;
-        step = to != from ? step : 1;
-
-        int numClasses = (int)((to - from) / step + 1);
+            final float    from,
+            float    to,
+            float    step,
+            final CallMeta meta
+            ) {
+        final MapserverStyle ms = new MapserverStyle();
 
-        float rd = (endColor.getRed()   - startColor.getRed())
-            / (float)numClasses;
-        float gd = (endColor.getGreen() - startColor.getGreen())
-            / (float)numClasses;
-        float bd = (endColor.getBlue()  - startColor.getBlue())
-            / (float)numClasses;
-
-        for (int n = 0; n < numClasses; n++) {
-            StringBuilder newColor = new StringBuilder();
-            newColor.append(startColor.getRed()   + Math.round(n * rd));
-            newColor.append(' ');
-            newColor.append(startColor.getGreen() + Math.round(n * gd));
-            newColor.append(' ');
-            newColor.append(startColor.getBlue()  + Math.round(n * bd));
+        final String strStartColor = getValue(WSPLGEN_STARTCOLOR);
+        final Color startColor = strStartColor != null
+                ? parseColor(strStartColor)
+                        : new Color(178, 201, 215);
+                final String strEndColor = getValue(WSPLGEN_ENDCOLOR);
+                final Color endColor = strEndColor != null
+                        ? parseColor(strEndColor)
+                                : new Color(2, 27, 42);
 
-            String expr = createWSPLGENClassExpression(
-                from + n * step, step, n + 1, numClasses);
-            String name = createWSPLGENClassName(
-                from + n * step, step, n + 1, numClasses, meta);
+                        to = to >= 0 ? to : 9999;
+                        step = to != from ? step : 1;
 
-            Clazz c = new Clazz(name);
-            Style s = new Style();
-            s.setColor(newColor.toString());
-            s.setSize(5);
+                        final int numClasses = (int)((to - from) / step + 1);
 
-            c.addItem(new Expression("(" + expr + ")"));
-            c.addItem(s);
+                        final float rd = (endColor.getRed()   - startColor.getRed())
+                                / (float)numClasses;
+                        final float gd = (endColor.getGreen() - startColor.getGreen())
+                                / (float)numClasses;
+                        final float bd = (endColor.getBlue()  - startColor.getBlue())
+                                / (float)numClasses;
 
-            ms.addClazz(c);
-        }
+                        for (int n = 0; n < numClasses; n++) {
+                            final StringBuilder newColor = new StringBuilder();
+                            newColor.append(startColor.getRed()   + Math.round(n * rd));
+                            newColor.append(' ');
+                            newColor.append(startColor.getGreen() + Math.round(n * gd));
+                            newColor.append(' ');
+                            newColor.append(startColor.getBlue()  + Math.round(n * bd));
 
-        return ms.toString();
+                            final String expr = createWSPLGENClassExpression(
+                                    from + n * step, step, n + 1, numClasses);
+                            final String name = createWSPLGENClassName(
+                                    from + n * step, step, n + 1, numClasses, meta);
+
+                            final Clazz c = new Clazz(name);
+                            final Style s = new Style();
+                            s.setColor(newColor.toString());
+                            s.setSize(5);
+
+                            c.addItem(new Expression("(" + expr + ")"));
+                            c.addItem(s);
+
+                            ms.addClazz(c);
+                        }
+
+                        return ms.toString();
     }
 
 
     protected static String createWSPLGENClassExpression(
-        float val,
-        float step,
-        int idx,
-        int maxIdx
-    ) {
+            final float val,
+            final float step,
+            final int idx,
+            final int maxIdx
+            ) {
         if (idx < maxIdx) {
             return "[DIFF] >= " + val + " AND  [DIFF] < " + (val + step);
         }
@@ -731,12 +730,12 @@
      * @return
      */
     protected static String createWSPLGENClassName(
-        float    val,
-        float    step,
-        int      idx,
-        int      maxIdx,
-        CallMeta meta
-    ) {
+            final float    val,
+            final float    step,
+            final int      idx,
+            final int      maxIdx,
+            final CallMeta meta
+            ) {
         assert meta != null : "CallMeta instance is null";
 
         if (idx < maxIdx) {
@@ -749,21 +748,21 @@
 
 
     public String createMapserverStyle() {
-        String symbol    = getSymbol();
-        String backcolor = getLabelBackgroundColorString();
+        final String symbol    = getSymbol();
+        final String backcolor = getLabelBackgroundColorString();
         String linecolor = getLineColorString();
         if (linecolor == null) {
             log.warn("createMapserverStyle: linecolor String is empty");
             linecolor = "0,128,255";
         }
 
-        int linewidth = parseLineWidth();
+        final int linewidth = parseLineWidth();
 
-        MapserverStyle ms = new MapserverStyle();
+        final MapserverStyle ms = new MapserverStyle();
 
-        Clazz c = new Clazz(" ");
+        final Clazz c = new Clazz(" ");
 
-        Style s = new Style();
+        final Style s = new Style();
         s.setOutlineColor(linecolor.replace(",", " "));
 
         if (backcolor != null) {
@@ -774,11 +773,11 @@
         s.setSymbol(symbol);
         c.addItem(s);
 
-        String textcolor = getTextColorString();
-        int    textsize  = parseTextSize();
+        final String textcolor = getTextColorString();
+        final int    textsize  = parseTextSize();
 
         if (textcolor != null && textsize > 0) {
-            Label l = new Label();
+            final Label l = new Label();
             l.setColor(textcolor.replace(",", " "));
             l.setSize(textsize);
             c.addItem(l);
@@ -804,7 +803,7 @@
         return parseAreaTransparency(50);
     }
 
-    public int parseAreaTransparency(int alpha) {
+    public int parseAreaTransparency(final int alpha) {
         return parseInteger(getAreaTransparencyString(), alpha);
     }
 
@@ -817,13 +816,13 @@
     private String getAreaShowBorderString() {
         return getValue(AREA_SHOW_BORDER);
     }
-    
-    
+
+
     public boolean parseCalculateRange() {
         return parseBoolean(getCalculateRangeString(), false);
     }
 
-    
+
     private String getCalculateRangeString() {
         return getValue(CALCULATE_RANGE);
     }
@@ -832,11 +831,11 @@
         final String patternName = getValue(AREA_BACKGROUND_PATTERN);
         if( StringUtils.isBlank(patternName) )
             return null;
-        
+
         try {
             return AreaFillPattern.valueOf(patternName);
         }
-        catch (Exception e) {
+        catch (final Exception e) {
             log.error(String.format("%s: invalid pattern name: %s", AREA_BACKGROUND_PATTERN, patternName), e);
             return null;
         }
--- a/artifacts/src/main/java/org/dive4elements/river/themes/ThemeFactory.java	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/themes/ThemeFactory.java	Tue Oct 23 16:26:48 2018 +0200
@@ -8,10 +8,6 @@
 
 package org.dive4elements.river.themes;
 
-import org.dive4elements.artifacts.common.utils.XMLUtils;
-import org.dive4elements.river.artifacts.D4EArtifact;
-import org.dive4elements.river.artifacts.context.RiverContext;
-
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -20,6 +16,9 @@
 import javax.xml.xpath.XPathConstants;
 
 import org.apache.log4j.Logger;
+import org.dive4elements.artifacts.common.utils.XMLUtils;
+import org.dive4elements.river.artifacts.D4EArtifact;
+import org.dive4elements.river.artifacts.context.RiverContext;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.NamedNodeMap;
@@ -29,7 +28,7 @@
 /**
  * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
  *
- * Mapping-matching rules:
+ *         Mapping-matching rules:
  *
  */
 public class ThemeFactory {
@@ -40,23 +39,24 @@
     private ThemeFactory() {
     }
 
-
     /**
      * Creates a new theme from <i>config</i>.
      *
-     * @param themeCfg The theme config document that is used to fetch parent
-     * themes.
-     * @param config The theme config node.
+     * @param themeCfg
+     *            The theme config document that is used to fetch parent
+     *            themes.
+     * @param config
+     *            The theme config node.
      *
      * @return a new theme.
      */
-    public static Theme createTheme(Document themeCfg, Node config) {
-        String name = getName(config);
-        String desc = getDescription(config);
+    public static Theme createTheme(final Document themeCfg, final Node config) {
+        final String name = getName(config);
+        final String desc = getDescription(config);
 
         log.trace("Create new theme: " + name);
 
-        Theme theme = new DefaultTheme(name, desc);
+        final DefaultTheme theme = new DefaultTheme(name, desc);
 
         parseInherits(themeCfg, config, theme);
         parseFields(config, theme);
@@ -65,26 +65,21 @@
         return theme;
     }
 
-
     /**
      * Get first matching theme for facet.
      *
-     * @param name    Name to match "from" of theme mapping.
-     * @param pattern String to 'compare' to pattern in mapping.
-     * @param output  Name of the current output
+     * @param name
+     *            Name to match "from" of theme mapping.
+     * @param pattern
+     *            String to 'compare' to pattern in mapping.
+     * @param output
+     *            Name of the current output
      *
      * @return First matching theme.
      */
-    public static Theme getTheme(
-        RiverContext c,
-        String name,
-        String pattern,
-        String output,
-        String groupName)
-    {
+    public static Theme getTheme(final RiverContext c, final String name, final String pattern, final String output, final String groupName) {
         if (log.isDebugEnabled()) {
-            log.debug(
-                "Search theme for: " + name + " - pattern: " + pattern);
+            log.debug("Search theme for: " + name + " - pattern: " + pattern);
         }
 
         if (c == null || name == null) {
@@ -94,15 +89,15 @@
 
         // Fetch mapping and themes.
         @SuppressWarnings("unchecked")
-        Map<String, List<ThemeMapping>> map = (Map<String, List<ThemeMapping>>)
-            c.get(RiverContext.THEME_MAPPING);
+        final
+        Map<String, List<ThemeMapping>> map = (Map<String, List<ThemeMapping>>) c.get(RiverContext.THEME_MAPPING);
 
         @SuppressWarnings("unchecked")
-        List<ThemeGroup> tgs = (List<ThemeGroup>)
-            c.get(RiverContext.THEMES);
+        final
+        List<ThemeGroup> tgs = (List<ThemeGroup>) c.get(RiverContext.THEMES);
 
         ThemeGroup group = null;
-        for (ThemeGroup tg: tgs) {
+        for (final ThemeGroup tg : tgs) {
             if (tg.getName().equals(groupName)) {
                 group = tg;
                 break;
@@ -114,16 +109,16 @@
             return null;
         }
 
-        Map<String, Theme> t = group.getThemes();
+        final Map<String, Theme> t = group.getThemes();
 
-        D4EArtifact artifact = (D4EArtifact) c.get(RiverContext.ARTIFACT_KEY);
+        final D4EArtifact artifact = (D4EArtifact) c.get(RiverContext.ARTIFACT_KEY);
 
         if (map == null || map.isEmpty() || t == null || t.isEmpty()) {
             log.warn("No mappings or themes found. Cannot retrieve theme!");
             return null;
         }
 
-        List<ThemeMapping> mapping = map.get(name);
+        final List<ThemeMapping> mapping = map.get(name);
 
         if (mapping == null) {
             log.warn("No theme found for mapping: " + name);
@@ -131,77 +126,60 @@
         }
 
         // Take first mapping of which all conditions are satisfied.
-        for (ThemeMapping tm: mapping) {
-            if (name.equals(tm.getFrom())
-                && tm.applyPattern(pattern)
-                && tm.masterAttrMatches(artifact)
-                && tm.outputMatches(output))
-            {
-                String target = tm.getTo();
+        for (final ThemeMapping tm : mapping) {
+            if (name.equals(tm.getFrom()) && tm.applyPattern(pattern) && tm.masterAttrMatches(artifact) && tm.outputMatches(output)) {
+                final String target = tm.getTo();
 
                 log.debug("Found theme '" + target + "'");
                 return t.get(target);
             }
         }
 
-        String msg =
-            "No theme found for '" + name +
-            "' with pattern '" + pattern + "' and output " + output + ".";
+        final String msg = "No theme found for '" + name + "' with pattern '" + pattern + "' and output " + output + ".";
 
         log.warn(msg);
 
         return null;
     }
 
-
     @SuppressWarnings("unchecked")
-    public static List<ThemeGroup> getThemeGroups(RiverContext c) {
-        List<ThemeGroup> tgs = (List<ThemeGroup>)
-            c.get(RiverContext.THEMES);
+    public static List<ThemeGroup> getThemeGroups(final RiverContext c) {
+        final List<ThemeGroup> tgs = (List<ThemeGroup>) c.get(RiverContext.THEMES);
         return tgs;
     }
 
-
     @SuppressWarnings("unchecked")
-    public static List<Theme> getThemes (RiverContext c, String name) {
-        List<ThemeGroup> tgs = (List<ThemeGroup>)
-            c.get(RiverContext.THEMES);
+    public static List<Theme> getThemes(final RiverContext c, final String name) {
+        final List<ThemeGroup> tgs = (List<ThemeGroup>) c.get(RiverContext.THEMES);
         if (tgs == null) {
             return null;
         }
 
-        List<Theme> themes = new ArrayList<Theme>();
-        for (ThemeGroup tg: tgs) {
+        final List<Theme> themes = new ArrayList<>();
+        for (final ThemeGroup tg : tgs) {
             themes.add(tg.getThemeByName(name));
         }
         return themes;
     }
 
-    protected static String getName(Node config) {
-        return ((Element)config).getAttribute("name");
+    private static String getName(final Node config) {
+        return ((Element) config).getAttribute("name");
     }
 
-
-    protected static String getDescription(Node config) {
-        return ((Element)config).getAttribute("desc");
+    private static String getDescription(final Node config) {
+        return ((Element) config).getAttribute("desc");
     }
 
-
-    protected static void parseInherits(Document themeCfg, Node cfg, Theme t) {
+    private static void parseInherits(final Document themeCfg, final Node cfg, final DefaultTheme t) {
         parseInherits(themeCfg, cfg, t, null);
     }
 
-    protected static void parseInherits(
-        Document themeCfg,
-        Node     cfg,
-        Theme    t,
-        Map<String, Node> themes
-    ) {
+    private static void parseInherits(final Document themeCfg, final Node cfg, final DefaultTheme t, Map<String, Node> themes) {
         log.trace("ThemeFactory.parseInherits");
 
-        NodeList inherits = ((Element)cfg).getElementsByTagName("inherit");
+        final NodeList inherits = ((Element) cfg).getElementsByTagName("inherit");
 
-        int num = inherits.getLength();
+        final int num = inherits.getLength();
 
         if (num == 0) {
             log.trace("Theme does not inherit from other themes.");
@@ -215,43 +193,41 @@
         }
 
         for (int i = 0; i < num; i++) {
-            Node inherit = inherits.item(i);
-            String from = ((Element)inherit).getAttribute("from");
+            final Node inherit = inherits.item(i);
+            final String from = ((Element) inherit).getAttribute("from");
 
-            Node tmp = themes.get(from);
+            final Node tmp = themes.get(from);
 
             parseInherits(themeCfg, tmp, t, themes);
             parseFields(tmp, t);
         }
     }
 
-    protected static Map<String, Node> buildThemeMap(Document themeCfg) {
-        Map<String, Node> map = new HashMap<String, Node>();
-        String xpath = "/themes/themegroup/theme";
+    private static Map<String, Node> buildThemeMap(final Document themeCfg) {
+        final Map<String, Node> map = new HashMap<>();
+        final String xpath = "/themes/themegroup/theme";
 
-        NodeList nodes = (NodeList)XMLUtils.xpath(
-            themeCfg, xpath, XPathConstants.NODESET);
+        final NodeList nodes = (NodeList) XMLUtils.xpath(themeCfg, xpath, XPathConstants.NODESET);
 
         if (nodes != null) {
             for (int i = 0, N = nodes.getLength(); i < N; ++i) {
-                Node node = nodes.item(i);
-                String name = ((Element)node).getAttribute("name");
+                final Node node = nodes.item(i);
+                final String name = ((Element) node).getAttribute("name");
                 map.put(name, node);
             }
         }
         return map;
     }
 
-
-    protected static void parseFields(Node config, Theme theme) {
+    private static void parseFields(final Node config, final DefaultTheme theme) {
         if (config == null || theme == null) {
             log.warn("Parsing fields without node or theme is senseless!");
             return;
         }
 
-        NodeList fields = ((Element)config).getElementsByTagName("field");
+        final NodeList fields = ((Element) config).getElementsByTagName("field");
 
-        int num = fields.getLength();
+        final int num = fields.getLength();
 
         log.trace("Found " + num + " own fields in this theme.");
 
@@ -261,34 +237,33 @@
         }
 
         for (int i = 0; i < num; i++) {
-            Node field = fields.item(i);
+            final Node field = fields.item(i);
 
             addField(theme, field);
         }
     }
 
-
-    protected static void addField(Theme theme, Node field) {
-        String name = ((Element)field).getAttribute("name");
+    private static void addField(final DefaultTheme theme, final Node field) {
+        final String name = ((Element) field).getAttribute("name");
 
         log.trace("Add field " + name + " to theme " + theme.getName());
 
-        NamedNodeMap attrs = field.getAttributes();
+        final NamedNodeMap attrs = field.getAttributes();
 
-        int num = attrs != null ? attrs.getLength() : 0;
+        final int num = attrs != null ? attrs.getLength() : 0;
 
         if (num == 0) {
             log.warn("This field has no attributes.");
             return;
         }
 
-        ThemeField theField = new DefaultThemeField(name);
+        final ThemeField theField = new DefaultThemeField(name);
 
         for (int i = 0; i < num; i++) {
-            Node attr    = attrs.item(i);
+            final Node attr = attrs.item(i);
 
-            String key   = attr.getNodeName();
-            String value = attr.getNodeValue();
+            final String key = attr.getNodeName();
+            final String value = attr.getNodeValue();
 
             theField.setAttribute(key, value);
         }
@@ -296,11 +271,10 @@
         theme.addField(name, theField);
     }
 
+    private static void parseAttrs(final Node config, final DefaultTheme theme) {
+        final NamedNodeMap attrs = config.getAttributes();
 
-    protected static void parseAttrs(Node config, Theme theme) {
-        NamedNodeMap attrs = config.getAttributes();
-
-        int num = attrs != null ? attrs.getLength() : 0;
+        final int num = attrs != null ? attrs.getLength() : 0;
 
         if (num == 0) {
             log.trace("Theme has no attributes set.");
@@ -308,13 +282,20 @@
         }
 
         for (int i = 0; i < num; i++) {
-            Node attr = attrs.item(i);
+            final Node attr = attrs.item(i);
 
-            String name  = attr.getNodeName();
-            String value = attr.getNodeValue();
+            final String name = attr.getNodeName();
+            final String value = attr.getNodeValue();
 
             theme.addAttribute(name, value);
         }
     }
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+
+    public static Theme getLegendTheme(final RiverContext flysContext, final String themeType) {
+
+        @SuppressWarnings("unchecked")
+        final
+        Map<String, Theme> themes = (Map<String, Theme>) flysContext.get(RiverContext.LEGEND);
+        return themes.get(themeType);
+    }
+}
\ No newline at end of file
--- a/artifacts/src/main/resources/messages.properties	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/resources/messages.properties	Tue Oct 23 16:26:48 2018 +0200
@@ -1361,4 +1361,12 @@
 state.title.distance_part_state = Select Sub-range
 state.title.salix.historical.distance_part_state =  Select Sub-range
 state.title.uinfo.bezugswst.miss_vol.distance_part_state = Choose sub-range for the missing depths to be calculated [km]
-state.title.distance_only_inundationduration_veg_state = Choose Display Area [km]
\ No newline at end of file
+state.title.distance_only_inundationduration_veg_state = Choose Display Area [km]
+
+legend.aggregator.merged = merged
+
+longitudinal_section.w.legend = h(Q)
+longitudinal_section.q.legend = Q
+cross_section_water_line.legend = h(Q)
+other.wqkms.w.legend = W (Event)
+other.wqkms.q.legend = Q (Event)
\ No newline at end of file
--- a/artifacts/src/main/resources/messages_de.properties	Mon Oct 22 18:26:05 2018 +0200
+++ b/artifacts/src/main/resources/messages_de.properties	Tue Oct 23 16:26:48 2018 +0200
@@ -1361,4 +1361,12 @@
 state.title.distance_part_state = Festlegen eines Teilabschnitts
 state.title.salix.historical.distance_part_state = Festlegen eines Teilabschnitts
 state.title.uinfo.bezugswst.miss_vol.distance_part_state = Strecke der zu ermittelnden Fehltiefen w\u00e4hlen [km]
-state.title.distance_only_inundationduration_veg_state = Darstellungsbereich w\u00e4hlen [km]
\ No newline at end of file
+state.title.distance_only_inundationduration_veg_state = Darstellungsbereich w\u00e4hlen [km]
+
+legend.aggregator.merged = zusammengefasst
+
+longitudinal_section.w.legend = W(Q)
+longitudinal_section.q.legend = Q
+cross_section_water_line.legend = W(Q)
+other.wqkms.w.legend = W (Ereignis)
+other.wqkms.q.legend = Q (Ereignis)
\ No newline at end of file

http://dive4elements.wald.intevation.org