/** * Copyright (C) 2001-2017 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.com * * This program is free software: you can redistribute it and/or modify it under the terms of the * GNU Affero General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.gui.new_plotter.engine.jfreechart; import com.rapidminer.gui.new_plotter.ChartPlottimeException; import com.rapidminer.gui.new_plotter.configuration.DefaultDimensionConfig; import com.rapidminer.gui.new_plotter.configuration.DimensionConfig; import com.rapidminer.gui.new_plotter.configuration.DimensionConfig.PlotDimension; import com.rapidminer.gui.new_plotter.configuration.DistinctValueGrouping; import com.rapidminer.gui.new_plotter.configuration.LineFormat.LineStyle; import com.rapidminer.gui.new_plotter.configuration.PlotConfiguration; import com.rapidminer.gui.new_plotter.configuration.SeriesFormat; import com.rapidminer.gui.new_plotter.configuration.SeriesFormat.FillStyle; import com.rapidminer.gui.new_plotter.configuration.SeriesFormat.VisualizationType; import com.rapidminer.gui.new_plotter.configuration.ValueSource; import com.rapidminer.gui.new_plotter.data.DimensionConfigData; import com.rapidminer.gui.new_plotter.data.PlotInstance; import com.rapidminer.gui.new_plotter.engine.jfreechart.legend.CustomLegendItem; import com.rapidminer.gui.new_plotter.engine.jfreechart.legend.FlankedShapeLegendItem; import com.rapidminer.gui.new_plotter.utility.ColorProvider; import com.rapidminer.gui.new_plotter.utility.ContinuousColorProvider; import com.rapidminer.gui.new_plotter.utility.ContinuousSizeProvider; import com.rapidminer.gui.new_plotter.utility.DataStructureUtils; import com.rapidminer.gui.new_plotter.utility.ShapeProvider; import com.rapidminer.gui.new_plotter.utility.SizeProvider; import com.rapidminer.tools.I18N; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.LinearGradientPaint; import java.awt.MultipleGradientPaint.CycleMethod; import java.awt.Paint; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Stroke; import java.awt.TexturePaint; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.CubicCurve2D; import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.sql.Date; import java.text.DateFormat; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.jfree.chart.LegendItem; import org.jfree.chart.LegendItemCollection; /** * @author Marius Helf, Nils Woehler * */ public class PlotInstanceLegendCreator { // get this string by creating a closed bezier path in e.g. inkscape, save as svg and copy the // path from "c" to "z". private static final String UNDEFINED_SHAPE_PATH_STRING = "61.60505,-17.6627 4.43287,24.66755 80.8122,70.71067 32.26478,19.44992 94.06958,-36.71539 86.87313,11.11169 -11.06983,73.56922 -54.23403,-3.79434 -74.09398,31.56769 -15.44503,27.50097 110.35408,39.30287 35.87633,109.92879 -26.99372,15.97748 -24.31258,-53.77902 -48.62472,-38.78282 -18.89542,11.65506 12.73492,55.53523 -57.74657,36.48531 -30.46568,-8.23435 18.26797,-39.15956 -3.71038,-55.83427 -28.37109,-21.5248 -41.8619,91.13915 -70.90379,2.49826 -15.22703,-46.47553 37.08276,-11.0608 44.75129,-69.85281 3.03046,-23.23351 -47.77036,-82.19632 6.76649,-97.83251"; private static final String UNDEFINED_SHAPE_AND_COLOR_PATH_STRING = "-17.05687,19.94964 -48.00093,21.59475 -70.38775,35.29712 -21.74096,13.30706 -36.83506,39.34171 -61.62405,45.27914 -25.5254,6.11382 -51.52664,-10.74373 -77.69201,-12.81692 -25.4105,-2.01338 -52.92466,10.177 -76.46931,0.40989 -24.24409,-10.05727 -35.37093,-38.97845 -55.32056,-56.03532 -19.37409,-16.56478 -48.79884,-22.87497 -62.1059,-44.61593 -13.702365,-22.38682 -5.70472,-52.32474 -11.818535,-77.85014 -5.93744,-24.78899 -26.03352,-47.18948 -24.02014,-72.59998 2.07319,-26.16537 26.14049,-45.68475 36.197755,-69.92884 9.76712,-23.54465 6.67572,-53.47921 23.24049,-72.8533 17.05687,-19.949641 48.00093,-21.594751 70.38775,-35.297121 21.74096,-13.30706 36.83506,-39.341712 61.62405,-45.279142 25.5254,-6.11382 51.52664,10.74373 77.69201,12.81692 25.4105,2.01338 52.92466,-10.177 76.46931,-0.40989 24.24409,10.057272 35.37093,38.978452 55.32056,56.035323 19.37409,16.56478 48.79884,22.87497 62.1059,44.61593 13.70237,22.38682 5.70472,52.32474 11.81854,77.85014 5.93744,24.78899 26.03352,47.18948 24.02014,72.59998 -2.07319,26.16537 -26.14049,45.68475 -36.19776,69.92884 -9.76712,23.54465 -6.67572,53.47921 -23.24049,72.8533"; private static final Shape UNDEFINED_SHAPE = shapeFromSvgRelativeBezierPath(UNDEFINED_SHAPE_PATH_STRING, 1.0f / 20); private static final Shape UNDEFINED_SHAPE_AND_COLOR = shapeFromSvgRelativeBezierPath( UNDEFINED_SHAPE_AND_COLOR_PATH_STRING, 1.0f / 45f * 0.9f); private static final Color UNDEFINED_COLOR = Color.DARK_GRAY; private static final Paint UNDEFINED_COLOR_PAINT = createTransparentCheckeredPaint(UNDEFINED_COLOR, 1); private static final Paint UNDEFINED_LINE_COLOR = Color.DARK_GRAY; private static final Shape BAR_SHAPE = createBarShape(); private static final Shape AREA_SHAPE = createAreaShape(); private static final BasicStroke DEFAULT_OUTLINE_STROKE = new BasicStroke(1); private static final double MIN_LEGEND_ITEM_SCALING_FACTOR = 0.2; private static final double MAX_LEGEND_ITEM_SCALING_FACTOR = 1.5; public LegendItemCollection getLegendItems(PlotInstance plotInstance) { PlotConfiguration plotConfiguration = plotInstance.getCurrentPlotConfigurationClone(); LegendItemCollection legendItemCollection = new LegendItemCollection(); // Get list of ValueSources which get an own legend entry. // These are those ValueSources, for which useSeriesFormatForDimension() returns true // for at least one dimension (not considering X and VALUE). // get all values sources List<ValueSource> allValueSources = plotConfiguration.getAllValueSources(); // add the plots heading (if we have any value sources) if (!allValueSources.isEmpty()) { legendItemCollection.add(createTitleLegendItem(I18N.getGUILabel("plotter.legend.plots_heading.label") + ":", plotConfiguration)); } // now find those value sources for which we need a legend entry, // and also remember which dimensions are shown in the legend entry (color, shape, ...) for (ValueSource valueSource : allValueSources) { CustomLegendItem legendItem = createValueSourceLegendItem(plotConfiguration, valueSource); if (legendItem != null) { legendItemCollection.add(legendItem); } } List<Set<PlotDimension>> dimensionsWithLegend = findCompatibleDimensions(plotConfiguration, allValueSources); // create legend items for DimensionConfigs for (Set<PlotDimension> dimensionSet : dimensionsWithLegend) { PlotDimension aDimension = dimensionSet.iterator().next(); DimensionConfig dimensionConfig = plotConfiguration.getDimensionConfig(aDimension); createDimensionConfigLegendItem(plotInstance, (DefaultDimensionConfig) dimensionConfig, dimensionSet, legendItemCollection); } return legendItemCollection; } private List<Set<PlotDimension>> findCompatibleDimensions(PlotConfiguration plotConfiguration, List<ValueSource> allValueSources) { Map<PlotDimension, DefaultDimensionConfig> dimensionConfigMap = plotConfiguration.getDefaultDimensionConfigs(); // find all Dimensions for which we create a legend item List<Set<PlotDimension>> dimensionsWithLegend = new LinkedList<Set<PlotDimension>>(); for (Entry<PlotDimension, DefaultDimensionConfig> dimensionEntry : dimensionConfigMap.entrySet()) { PlotDimension dimension = dimensionEntry.getKey(); DefaultDimensionConfig dimensionConfig = dimensionEntry.getValue(); boolean createLegend = false; if (dimensionConfig.isGrouping()) { createLegend = true; } else { for (ValueSource valueSource : allValueSources) { if (!valueSource.isUsingDomainGrouping()) { createLegend = true; break; } } } if (createLegend) { if (!dimensionConfig.isNominal()) { Set<PlotDimension> newSet = new HashSet<DimensionConfig.PlotDimension>(); newSet.add(dimension); dimensionsWithLegend.add(newSet); } else { // iterate over list and find dimensions with compatible properties boolean compatibleToSomething = false; for (Set<PlotDimension> dimensionSet : dimensionsWithLegend) { boolean compatible = true; for (PlotDimension comparedDimension : dimensionSet) { DefaultDimensionConfig comparedDimensionConfig = (DefaultDimensionConfig) plotConfiguration .getDimensionConfig(comparedDimension); if (!comparedDimensionConfig.isNominal()) { compatible = false; break; } if (!dimensionConfig.getDataTableColumn().equals(comparedDimensionConfig.getDataTableColumn())) { compatible = false; break; } else if (comparedDimensionConfig.isGrouping() && comparedDimensionConfig.getGrouping() instanceof DistinctValueGrouping && !dimensionConfig.isGrouping() && dimensionConfig.isNominal()) { compatible = true; } else if (dimensionConfig.isGrouping() && dimensionConfig.getGrouping() instanceof DistinctValueGrouping && !comparedDimensionConfig.isGrouping() && comparedDimensionConfig.isNominal()) { compatible = true; } else if (dimensionConfig.isGrouping() != comparedDimensionConfig.isGrouping()) { compatible = false; break; } else if (!dimensionConfig.isGrouping()) { compatible = true; } else if (dimensionConfig.getGrouping().equals(comparedDimensionConfig.getGrouping())) { compatible = true; } else { compatible = false; break; } } if (compatible) { dimensionSet.add(dimension); compatibleToSomething = true; break; } } if (!compatibleToSomething) { Set<PlotDimension> newSet = new HashSet<DimensionConfig.PlotDimension>(); newSet.add(dimension); dimensionsWithLegend.add(newSet); } } } } return dimensionsWithLegend; } private CustomLegendItem createValueSourceLegendItem(PlotConfiguration plotConfig, ValueSource valueSource) { Set<PlotDimension> dimensions = new HashSet<PlotDimension>(); for (PlotDimension dimension : PlotDimension.values()) { switch (dimension) { case DOMAIN: case VALUE: break; default: if (valueSource.useSeriesFormatForDimension(plotConfig, dimension)) { dimensions.add(dimension); } } } if (dimensions.isEmpty()) { return null; } SeriesFormat format = valueSource.getSeriesFormat(); String description = ""; String toolTipText = ""; String urlText = ""; boolean shapeVisible = true; Shape shape; boolean shapeFilled = true; Paint fillPaint = UNDEFINED_COLOR_PAINT; boolean shapeOutlineVisible = true; Paint outlinePaint = PlotConfiguration.DEFAULT_OUTLINE_COLOR; Stroke outlineStroke = DEFAULT_OUTLINE_STROKE; boolean lineVisible = format.getLineStyle() != LineStyle.NONE && format.getSeriesType() == SeriesFormat.VisualizationType.LINES_AND_SHAPES; // configure fill paint and line paint Paint linePaint; String label = valueSource.toString(); if (label == null) { label = ""; } if (dimensions.contains(PlotDimension.COLOR)) { Color color = format.getItemColor(); fillPaint = format.getAreaFillPaint(color); linePaint = fillPaint; } else { if (format.getAreaFillStyle() == FillStyle.NONE) { fillPaint = new Color(0, 0, 0, 0); linePaint = fillPaint; } else if (format.getAreaFillStyle() == FillStyle.SOLID) { fillPaint = UNDEFINED_COLOR_PAINT; linePaint = UNDEFINED_LINE_COLOR; } else { fillPaint = format.getAreaFillPaint(UNDEFINED_COLOR); linePaint = fillPaint; } } VisualizationType seriesType = valueSource.getSeriesFormat().getSeriesType(); if (seriesType == VisualizationType.LINES_AND_SHAPES) { if (dimensions.contains(PlotDimension.SHAPE)) { shape = format.getItemShape().getShape(); } else if (dimensions.contains(PlotDimension.COLOR)) { shape = UNDEFINED_SHAPE; } else { shape = UNDEFINED_SHAPE_AND_COLOR; } if (dimensions.contains(PlotDimension.SIZE)) { AffineTransform transformation = new AffineTransform(); double scalingFactor = format.getItemSize(); transformation.scale(scalingFactor, scalingFactor); shape = transformation.createTransformedShape(shape); } } else if (seriesType == VisualizationType.BARS) { shape = BAR_SHAPE; } else if (seriesType == VisualizationType.AREA) { shape = AREA_SHAPE; } else { throw new RuntimeException("Unknown SeriesType. This should not happen."); } // configure line shape float lineLength = 0; if (lineVisible) { lineLength = format.getLineWidth(); if (lineLength < 1) { lineLength = 1; } if (lineLength > 1) { lineLength = 1 + (float) Math.log(lineLength) / 2; } // line at least 30 pixels long, and show at least 2 iterations of stroke lineLength = Math.max(lineLength * 30, format.getStrokeLength() * 2); // line at least 2x longer than shape width if (shape != null) { lineLength = Math.max(lineLength, (float) shape.getBounds().getWidth() * 2f); } } // now create line shape and stroke Shape line = new Line2D.Float(0, 0, lineLength, 0); BasicStroke lineStroke = format.getStroke(); if (lineStroke == null) { lineStroke = new BasicStroke(); } // unset line ending decoration to prevent drawing errors in legend { BasicStroke s = lineStroke; lineStroke = new BasicStroke(s.getLineWidth(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, s.getMiterLimit(), s.getDashArray(), s.getDashPhase()); } return new CustomLegendItem(label, description, toolTipText, urlText, shapeVisible, shape, shapeFilled, fillPaint, shapeOutlineVisible, outlinePaint, outlineStroke, lineVisible, line, lineStroke, linePaint); } private static GeneralPath createAreaShape() { GeneralPath areaShape = new GeneralPath(); areaShape.moveTo(0, 0); areaShape.lineTo(0, -5); areaShape.lineTo(5, -10); areaShape.lineTo(10, -5); areaShape.lineTo(15, -7); areaShape.lineTo(15, 0); areaShape.closePath(); return areaShape; } private static GeneralPath createBarShape() { GeneralPath barShape = new GeneralPath(); barShape.moveTo(0, 0); barShape.lineTo(0, -5); barShape.lineTo(5, -5); barShape.lineTo(5, 0); barShape.lineTo(5, -15); barShape.lineTo(10, -15); barShape.lineTo(10, 0); barShape.lineTo(10, -10); barShape.lineTo(15, -10); barShape.lineTo(15, 0); barShape.closePath(); return barShape; } private void createDimensionConfigLegendItem(PlotInstance plotInstance, DefaultDimensionConfig dimensionConfig, Set<PlotDimension> dimensionSet, LegendItemCollection legendItemCollection) { PlotConfiguration plotConfiguration = plotInstance.getCurrentPlotConfigurationClone(); DimensionConfigData dimensionConfigData = plotInstance.getPlotData().getDimensionConfigData(dimensionConfig); if (dimensionConfig.isGrouping()) { // create legend entry based on the grouping if (dimensionConfig.isNominal()) { // create categorical legend --> one item for each category createCategoricalLegendItems(plotInstance, dimensionSet, legendItemCollection, dimensionConfigData.getDistinctValues()); } else if (dimensionConfig.isNumerical() || dimensionConfig.isDate()) { createDimensionTitleLegendItem(plotInstance, dimensionSet, legendItemCollection); // create one continuous legend item double minValue = dimensionConfigData.getMinValue(); double maxValue = dimensionConfigData.getMaxValue(); LegendItem legendItem = createContinuousLegendItem(plotInstance, dimensionSet, minValue, maxValue, dimensionConfig.isDate() ? dimensionConfig.getDateFormat() : null); if (legendItem != null) { legendItemCollection.add(legendItem); } } else { throw new RuntimeException("unknown data type during legend creation - this should not happen"); } } else { // dimension config not grouping --> create legend item only, if there exists // at least one non-aggregated value source (otherwise the dimension config is // not used at all in the plot and thus we also don't need a legend item for it). boolean createLegend = false; for (ValueSource valueSource : plotConfiguration.getAllValueSources()) { if (!valueSource.isUsingDomainGrouping()) { createLegend = true; break; } } if (createLegend) { // create legend based on the attribute values on the dimension config if (dimensionConfig.isNominal()) { // create one legend item for each nominal value List<Double> values = dimensionConfigData.getDistinctValues(); createCategoricalLegendItems(plotInstance, dimensionSet, legendItemCollection, values); } else if (dimensionConfig.isNumerical() || dimensionConfig.isDate()) { createDimensionTitleLegendItem(plotInstance, dimensionSet, legendItemCollection); // create one continuous legend item for the value range double minValue = dimensionConfigData.getMinValue(); double maxValue = dimensionConfigData.getMaxValue(); LegendItem legendItem = createContinuousLegendItem(plotInstance, dimensionSet, minValue, maxValue, dimensionConfig.isDate() ? dimensionConfig.getDateFormat() : null); if (legendItem != null) { legendItemCollection.add(legendItem); } } else { throw new RuntimeException("unknown data type during legend creation - this should not happen"); } } } } /** * Creates a continuous legend item for one item in dimensionSet, i.e. dimensionSet must be a * set containing exactly one value. * * @param dateFormat * format used to format minValue and maxValue as dates, or null if they should be * displayed numerically instead of as dates * @throws ChartPlottimeException */ private LegendItem createContinuousLegendItem(PlotInstance plotInstance, Set<PlotDimension> dimensionSet, double minValue, double maxValue, DateFormat dateFormat) { PlotConfiguration plotConfiguration = plotInstance.getCurrentPlotConfigurationClone(); PlotDimension dimension = dimensionSet.iterator().next(); DefaultDimensionConfig dimensionConfig = (DefaultDimensionConfig) plotConfiguration.getDimensionConfig(dimension); DimensionConfigData dimensionConfigData = plotInstance.getPlotData().getDimensionConfigData(dimensionConfig); // String label = dimensionConfig.getLabel(); // if(label == null) { // label = I18N.getGUILabel("plotter.unnamed_value_label"); // } String label = ""; if (dimension == PlotDimension.COLOR) { ColorProvider colorProvider = dimensionConfigData.getColorProvider(); if (!colorProvider.supportsNumericalValues()) { throw new RuntimeException("Color provider for continuous legend item does not support numerical values."); } // shape dimensions final int width = 50; final int height = 10; // create item paint // first disable logarithmic scale on color provider ( -> linear gradient in legend) // ContinuousColorProvider continuousColorProvider = null; // if (dimensionConfig.isLogarithmic() && colorProvider instanceof // ContinuousColorProvider) { // continuousColorProvider = (ContinuousColorProvider)colorProvider; // continuousColorProvider.setLogarithmic(false); // } // calculate gradient float fractions[] = new float[width]; Color colors[] = new Color[width]; for (int i = 0; i < width; ++i) { float fraction = i / (width - 1.0f); double fractionValue; if (colorProvider instanceof ContinuousColorProvider && ((ContinuousColorProvider) colorProvider).isColorMinMaxValueDifferentFromOriginal( ((ContinuousColorProvider) colorProvider).getMinValue(), ((ContinuousColorProvider) colorProvider).getMaxValue())) { fractionValue = ((ContinuousColorProvider) colorProvider).getMinValue() + fraction * (((ContinuousColorProvider) colorProvider).getMaxValue() - ((ContinuousColorProvider) colorProvider) .getMinValue()); } else { fractionValue = minValue + fraction * (maxValue - minValue); } colors[i] = colorProvider.getColorForValue(fractionValue); fractions[i] = fraction; } LinearGradientPaint shapeFillPaint = new LinearGradientPaint(new Point(0, 0), new Point(width, 0), fractions, colors, CycleMethod.REPEAT); // reset color provider to logarithmic if necessary // if (continuousColorProvider != null && dimensionConfig.isLogarithmic()) { // continuousColorProvider.setLogarithmic(true); // } // create item shape Rectangle itemShape = new Rectangle(width, height); if (colorProvider instanceof ContinuousColorProvider) { return createFlankedShapeLegendItem(label, ((ContinuousColorProvider) colorProvider).getMinValue(), ((ContinuousColorProvider) colorProvider).getMaxValue(), itemShape, shapeFillPaint, true, dateFormat); } else { return createFlankedShapeLegendItem(label, minValue, maxValue, itemShape, shapeFillPaint, true, dateFormat); } } else if (dimension == PlotDimension.SHAPE) { // shape provider probably never supports numerical values return null; } else if (dimension == PlotDimension.SIZE) { SizeProvider sizeProvider = dimensionConfigData.getSizeProvider(); if (!sizeProvider.supportsNumericalValues()) { throw new RuntimeException("Size provider for continuous legend item does not support numerical values."); } double minScalingFactor = sizeProvider.getMinScalingFactor(); double maxScalingFactor = sizeProvider.getMaxScalingFactor(); ContinuousSizeProvider legendSizeProvider = new ContinuousSizeProvider(minScalingFactor, maxScalingFactor, MIN_LEGEND_ITEM_SCALING_FACTOR, MAX_LEGEND_ITEM_SCALING_FACTOR, false); int legendItemCount = 4; Area composedShape = new Area(); Shape originalShape = UNDEFINED_SHAPE; if (dimensionSet.contains(PlotDimension.SIZE) && dimensionSet.size() == 1) { originalShape = UNDEFINED_SHAPE_AND_COLOR; } double maxHeight = originalShape.getBounds().getHeight() * MAX_LEGEND_ITEM_SCALING_FACTOR; for (int i = 0; i < legendItemCount; ++i) { double fraction = minScalingFactor + ((double) i / legendItemCount * (maxScalingFactor - minScalingFactor)); double legendScalingFactor = legendSizeProvider.getScalingFactorForValue(fraction); double composedWidth = composedShape.getBounds().getWidth(); AffineTransform t = new AffineTransform(); t.scale(legendScalingFactor, legendScalingFactor); Shape shape = t.createTransformedShape(originalShape); t = new AffineTransform(); double shapeWidth = shape.getBounds().getWidth(); double shapeHeight = shape.getBounds().getHeight(); t.translate(composedWidth + shapeWidth * .1, (maxHeight - shapeHeight) / 2.0); t.translate(-shape.getBounds().getMinX(), -shape.getBounds().getMinY()); shape = t.createTransformedShape(shape); composedShape.add(new Area(shape)); } return createFlankedShapeLegendItem(label, minValue, maxValue, composedShape, UNDEFINED_COLOR_PAINT, false, dateFormat); } else { throw new RuntimeException("Unsupported dimension. Execution path should never reach this line."); } } private LegendItem createFlankedShapeLegendItem(String label, double minValue, double maxValue, Shape itemShape, Paint shapeFillPaint, boolean shapeOutlineVisible, DateFormat dateFormat) { // configure legend item String description = ""; String toolTipText = ""; String urlText = ""; boolean shapeVisible = true; boolean shapeFilled = true; Paint outlinePaint = Color.BLACK; Stroke outlineStroke = DEFAULT_OUTLINE_STROKE; boolean lineVisible = false; Shape line = new Line2D.Float(); Stroke lineStroke = new BasicStroke(); // basic stroke is fine here, since continuous legend // item does not show a line Paint linePaint = Color.BLACK; // create legend item FlankedShapeLegendItem legendItem = new FlankedShapeLegendItem(label, description, toolTipText, urlText, shapeVisible, itemShape, shapeFilled, shapeFillPaint, shapeOutlineVisible, outlinePaint, outlineStroke, lineVisible, line, lineStroke, linePaint); if (dateFormat != null) { legendItem.setLeftShapeLabel(dateFormat.format(new Date((long) minValue))); legendItem.setRightShapeLabel(dateFormat.format(new Date((long) maxValue))); } else { // set intelligently rounded strings as labels int powerOf10 = DataStructureUtils.getOptimalPrecision(minValue, maxValue); legendItem.setLeftShapeLabel(DataStructureUtils.getRoundedString(minValue, powerOf10 - 1)); legendItem.setRightShapeLabel(DataStructureUtils.getRoundedString(maxValue, powerOf10 - 1)); } return legendItem; } private void createCategoricalLegendItems(PlotInstance plotInstance, Set<PlotDimension> dimensionSet, LegendItemCollection legendItemCollection, Iterable<Double> values) { createDimensionTitleLegendItem(plotInstance, dimensionSet, legendItemCollection); PlotConfiguration plotConfig = plotInstance.getCurrentPlotConfigurationClone(); Shape defaultShape = new Ellipse2D.Float(-5f, -5f, 10f, 10f); Color defaultOutlineColor = PlotConfiguration.DEFAULT_OUTLINE_COLOR; ColorProvider colorProvider = null; ShapeProvider shapeProvider = null; SizeProvider sizeProvider = null; DefaultDimensionConfig dimensionConfig = (DefaultDimensionConfig) plotConfig.getDimensionConfig(dimensionSet .iterator().next()); DimensionConfigData dimensionConfigData = plotInstance.getPlotData().getDimensionConfigData(dimensionConfig); for (PlotDimension dimension : dimensionSet) { if (dimension == PlotDimension.COLOR) { colorProvider = dimensionConfigData.getColorProvider(); } else if (dimension == PlotDimension.SHAPE) { shapeProvider = dimensionConfigData.getShapeProvider(); } else if (dimension == PlotDimension.SIZE) { sizeProvider = dimensionConfigData.getSizeProvider(); } } // initialize size scale for legend ContinuousSizeProvider legendSizeProvider = null; if (sizeProvider != null) { double minScalingFactor = sizeProvider.getMinScalingFactor(); double maxScalingFactor = sizeProvider.getMaxScalingFactor(); double minLegendScalingFactor = MIN_LEGEND_ITEM_SCALING_FACTOR; double maxLegendScalingFactor = MAX_LEGEND_ITEM_SCALING_FACTOR; if (minScalingFactor > maxScalingFactor) { double tmp = minScalingFactor; minScalingFactor = maxScalingFactor; maxScalingFactor = tmp; minLegendScalingFactor = MAX_LEGEND_ITEM_SCALING_FACTOR; maxLegendScalingFactor = MIN_LEGEND_ITEM_SCALING_FACTOR; } legendSizeProvider = new ContinuousSizeProvider(minScalingFactor, maxScalingFactor, minLegendScalingFactor, maxLegendScalingFactor, false); } for (Double value : values) { // configure shape and stroke Shape shape = defaultShape; BasicStroke outlineStroke; Color outlineColor = new Color(0, 0, 0, 0); if (shapeProvider != null) { shape = shapeProvider.getShapeForCategory(value); outlineStroke = DEFAULT_OUTLINE_STROKE; outlineColor = defaultOutlineColor; } else { outlineStroke = new BasicStroke(); if (colorProvider != null) { shape = UNDEFINED_SHAPE; } else { shape = UNDEFINED_SHAPE_AND_COLOR; } } // configure fill paint Paint paint = UNDEFINED_COLOR_PAINT; if (colorProvider != null) { paint = colorProvider.getColorForValue(value); } double scalingFactor = 1; if (sizeProvider != null) { // scale shape according to sizeProvider scalingFactor = sizeProvider.getScalingFactorForValue(value); // scale shape to fit into legend scalingFactor = legendSizeProvider.getScalingFactorForValue(scalingFactor); AffineTransform transformation = new AffineTransform(); transformation.scale(scalingFactor, scalingFactor); shape = transformation.createTransformedShape(shape); } String label = dimensionConfigData.getStringForValue(value); if (label == null) { label = ""; } CustomLegendItem legendItem = new CustomLegendItem(label, null, null, null, shape, paint, outlineStroke, outlineColor); legendItemCollection.add(legendItem); } } /** * Creates the headings for the dimension config items ( like "Color (attribute X)" ) and adds * it to legendItemCollection. */ private void createDimensionTitleLegendItem(PlotInstance plotInstance, Set<PlotDimension> dimensionSet, LegendItemCollection legendItemCollection) { PlotConfiguration plotConfig = plotInstance.getCurrentPlotConfigurationClone(); StringBuilder titleBuilder = new StringBuilder(); boolean first = true; boolean showDimensionType = plotConfig.getLegendConfiguration().isShowDimensionType(); if (showDimensionType) { for (PlotDimension dimension : dimensionSet) { if (!first) { titleBuilder.append(", "); } titleBuilder.append(dimension.getShortName()); first = false; } } if (showDimensionType) { titleBuilder.append(" ("); } // get unique dimension labels: Set<String> uniqueDimensionLabels = new HashSet<String>(); first = true; for (PlotDimension dimension : dimensionSet) { DefaultDimensionConfig dimensionConfig = (DefaultDimensionConfig) plotConfig.getDimensionConfig(dimension); String label = dimensionConfig.getLabel(); if (label == null) { label = I18N.getGUILabel("plotter.unnamed_value_label"); } if (!uniqueDimensionLabels.contains(label)) { if (!first) { titleBuilder.append(", "); first = false; } titleBuilder.append(label); uniqueDimensionLabels.add(label); } } if (showDimensionType) { titleBuilder.append(")"); } titleBuilder.append(": "); legendItemCollection.add(createTitleLegendItem(titleBuilder.toString(), plotConfig)); } /** * Creates a title item (i.e. bold font etc.) with the given string. Simply gets the default * font from the plotConfig and sets it style to bold. * * @return The created legend item. */ private LegendItem createTitleLegendItem(String titleString, PlotConfiguration plotConfiguration) { LegendItem titleItem = new LegendItem(titleString, "", "", "", false, new Rectangle(), false, Color.WHITE, false, Color.WHITE, new BasicStroke(), false, new Rectangle(), new BasicStroke(), Color.WHITE); Font titleFont = titleItem.getLabelFont(); if (titleFont == null) { titleFont = plotConfiguration.getLegendConfiguration().getLegendFont(); } titleItem.setLabelFont(titleFont.deriveFont(Font.BOLD)); return titleItem; } private static Paint createTransparentCheckeredPaint(Color color, int checkerSize) { int s = checkerSize; BufferedImage bufferedImage = new BufferedImage(2 * s, 2 * s, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = bufferedImage.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // Anti-alias! RenderingHints.VALUE_ANTIALIAS_ON); Color c1 = DataStructureUtils.setColorAlpha(color, (int) (color.getAlpha() * .8)); Color c2 = DataStructureUtils.setColorAlpha(color, (int) (color.getAlpha() * .2)); g2.setStroke(new BasicStroke(0)); g2.setPaint(c2); g2.setColor(c2); g2.fillRect(0, 0, s, s); g2.fillRect(s, s, s, s); g2.setPaint(c1); g2.setColor(c1); g2.fillRect(0, s, s, s); g2.fillRect(s, 0, s, s); // paint with the texturing brush Rectangle2D rect = new Rectangle2D.Double(0, 0, 2 * s, 2 * s); return new TexturePaint(bufferedImage, rect); } public static Shape shapeFromSvgRelativeBezierPath(String pathString, float scalingFactor) { String[] points = pathString.split(" "); float x1, y1, x2, y2, cx1, cy1, cx2, cy2; x2 = y2 = 0; float s = scalingFactor; Path2D.Float path = new Path2D.Float(); for (int i = 0; i < points.length / 3; ++i) { String c1String = points[i * 3 + 0]; String c2String = points[i * 3 + 1]; String targetString = points[i * 3 + 2]; String[] c1Split = c1String.split(","); String[] c2Split = c2String.split(","); String[] targetSplit = targetString.split(","); x1 = x2; y1 = y2; x2 = s * Float.parseFloat(targetSplit[0]) + x1; y2 = s * Float.parseFloat(targetSplit[1]) + y1; cx1 = s * Float.parseFloat(c1Split[0]) + x1; cy1 = s * Float.parseFloat(c1Split[1]) + y1; cx2 = s * Float.parseFloat(c2Split[0]) + x1; cy2 = s * Float.parseFloat(c2Split[1]) + y1; CubicCurve2D.Float curve = new CubicCurve2D.Float(x1, y1, cx1, cy1, cx2, cy2, x2, y2); path.append(curve, true); } return path; } }