/** * DataCleaner (community edition) * Copyright (C) 2014 Neopost - Customer Information Management * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * 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 Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.datacleaner.widgets.visualization; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import java.util.Set; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JLabel; import org.apache.commons.collections15.Predicate; import org.apache.commons.collections15.Transformer; import org.apache.commons.collections15.functors.TruePredicate; import org.apache.metamodel.schema.Table; import org.datacleaner.api.AnalyzerResult; import org.datacleaner.api.InputColumn; import org.datacleaner.descriptors.ComponentDescriptor; import org.datacleaner.job.ComponentRequirement; import org.datacleaner.job.CompoundComponentRequirement; import org.datacleaner.job.FilterOutcome; import org.datacleaner.job.HasFilterOutcomes; import org.datacleaner.job.builder.ComponentBuilder; import org.datacleaner.user.UserPreferences; import org.datacleaner.util.GraphUtils; import org.datacleaner.util.IconUtils; import org.datacleaner.util.ImageManager; import org.datacleaner.util.LabelUtils; import org.datacleaner.util.ReflectionUtils; import org.datacleaner.util.WidgetUtils; import com.google.common.base.Strings; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.Context; import edu.uci.ics.jung.visualization.decorators.EdgeShape; import edu.uci.ics.jung.visualization.renderers.EdgeLabelRenderer; /** * Collection of {@link Transformer} (and {@link Predicate} and so on) instances * to use in the {@link JobGraph}. */ public class JobGraphTransformers { public static final String USER_PREFERENCES_PROPERTY_SHOW_CANVAS_HINTS = "JobGraph.ShowCanvasHints"; public static final String USER_PREFERENCES_PROPERTY_FONT_SIZE_FACTOR = "JobGraph.FontSizeFactor"; public static final String USER_PREFERENCES_PROPERTY_EDGE_STYLE = "JobGraph.LineStyle"; public static final String EDGE_STYLE_NAME_STRAIGHT = "straight"; public static final String EDGE_STYLE_NAME_CURVED = "curved"; public static final String EDGE_STYLE_NAME_ORTOGHONAL = "orthogonal"; public static final Predicate<Context<Graph<Object, JobGraphLink>, JobGraphLink>> EDGE_ARROW_PREDICATE = TruePredicate.getInstance(); public static final Transformer<JobGraphLink, String> EDGE_LABEL_TRANSFORMER = JobGraphLink::getLinkLabel; public static final Transformer<Context<Graph<Object, JobGraphLink>, JobGraphLink>, Shape> EDGE_ARROW_TRANSFORMER = input -> GraphUtils.ARROW_SHAPE; public static final Transformer<Context<Graph<Object, JobGraphLink>, JobGraphLink>, Number> EDGE_LABEL_CLOSENESS_TRANSFORMER = input -> 0.4d; public static final Transformer<Object, String> VERTEX_LABEL_TRANSFORMER = obj -> { if (obj instanceof InputColumn) { return ((InputColumn<?>) obj).getName(); } if (obj instanceof ComponentBuilder) { return LabelUtils.getLabel((ComponentBuilder) obj); } if (obj instanceof FilterOutcome) { return ((FilterOutcome) obj).getCategory().name(); } if (obj instanceof Table) { return ((Table) obj).getName(); } if (obj instanceof Class) { final Class<?> cls = (Class<?>) obj; if (ReflectionUtils.is(cls, AnalyzerResult.class)) { return "Analyzer result"; } return cls.getSimpleName(); } return obj.toString(); }; public static final Transformer<Object, Shape> VERTEX_SHAPE_TRANSFORMER = input -> { final int size = IconUtils.ICON_SIZE_LARGE; final int offset = -size / 2; return new Rectangle(new Point(offset, offset), new Dimension(size, size)); }; private static final ImageManager imageManager = ImageManager.get(); public static final Transformer<Object, Icon> VERTEX_ICON_TRANSFORMER = obj -> { if (obj == JobGraph.MORE_COLUMNS_VERTEX || obj instanceof InputColumn) { return imageManager.getImageIcon(IconUtils.MODEL_COLUMN, IconUtils.ICON_SIZE_MEDIUM); } if (obj instanceof ComponentBuilder) { final ComponentBuilder componentBuilder = (ComponentBuilder) obj; final ComponentDescriptor<?> descriptor = componentBuilder.getDescriptor(); final boolean configured; if (componentBuilder.getInput().length == 0) { configured = true; } else { configured = componentBuilder.isConfigured(false); } return IconUtils.getDescriptorIcon(descriptor, configured, IconUtils.ICON_SIZE_LARGE); } if (obj instanceof FilterOutcome) { return imageManager.getImageIcon(IconUtils.FILTER_OUTCOME_PATH, IconUtils.ICON_SIZE_MEDIUM); } if (obj instanceof Table) { return imageManager.getImageIcon(IconUtils.MODEL_TABLE, IconUtils.ICON_SIZE_LARGE); } if (obj instanceof Class) { final Class<?> cls = (Class<?>) obj; if (ReflectionUtils.is(cls, AnalyzerResult.class)) { return imageManager.getImageIcon(IconUtils.MODEL_RESULT, IconUtils.ICON_SIZE_LARGE); } } return imageManager.getImageIcon(IconUtils.STATUS_ERROR); }; private final UserPreferences _userPreferences; private final Set<Object> _highlighedVertexes; private final Font _normalFont; private final Font _boldFont; public JobGraphTransformers(final UserPreferences userPreferences, final Set<Object> highlighedVertexes) { _userPreferences = userPreferences; _highlighedVertexes = highlighedVertexes; final float fontFactor; final String fontFactorString = userPreferences.getAdditionalProperties().get(USER_PREFERENCES_PROPERTY_FONT_SIZE_FACTOR); if (Strings.isNullOrEmpty(fontFactorString)) { fontFactor = 1.0f; } else { fontFactor = 1.0f * Float.parseFloat(fontFactorString); } _normalFont = font(WidgetUtils.FONT_SMALL, fontFactor); _boldFont = _normalFont.deriveFont(Font.BOLD); } private Font font(final Font font, final float fontFactor) { if (fontFactor == 1.0) { return font; } return font.deriveFont(font.getSize() * fontFactor); } public Transformer<Context<Graph<Object, JobGraphLink>, JobGraphLink>, Shape> getEdgeShapeTransformer() { final String edgeStyle = _userPreferences.getAdditionalProperties().get(USER_PREFERENCES_PROPERTY_EDGE_STYLE); final Transformer<Context<Graph<Object, JobGraphLink>, JobGraphLink>, Shape> baseTransformer = getBaseEdgeShapeTransformer(edgeStyle); return input -> { final Shape result = baseTransformer.transform(input); final JobGraphLink link = input.element; if (isCompoundRequirementLink(link)) { // make a double link (actually a wedge, but close // enough) to show that there are more than one filter // outcome coming from this source return new EdgeShape.Wedge<Object, JobGraphLink>(10).transform(input); } return result; }; } protected boolean isCompoundRequirementLink(final JobGraphLink link) { final ComponentRequirement req = link.getRequirement(); final Object from = link.getFrom(); if (req instanceof CompoundComponentRequirement && from instanceof HasFilterOutcomes) { if (((CompoundComponentRequirement) req).hasMultipleRequirementsFrom((HasFilterOutcomes) from)) { return true; } } return false; } private Transformer<Context<Graph<Object, JobGraphLink>, JobGraphLink>, Shape> getBaseEdgeShapeTransformer( final String edgeStyle) { if (edgeStyle == null) { return new EdgeShape.QuadCurve<>(); } switch (edgeStyle) { case EDGE_STYLE_NAME_STRAIGHT: return new EdgeShape.Line<>(); case EDGE_STYLE_NAME_CURVED: return new EdgeShape.QuadCurve<>(); case EDGE_STYLE_NAME_ORTOGHONAL: return new EdgeShape.Orthogonal<>(); default: return new EdgeShape.QuadCurve<>(); } } public final EdgeLabelRenderer getEdgeLabelRenderer() { return new EdgeLabelRenderer() { @Override public boolean isRotateEdgeLabels() { return true; } @Override public void setRotateEdgeLabels(final boolean state) { } @Override public <T> Component getEdgeLabelRendererComponent(final JComponent vv, final Object value, final Font font, final boolean isSelected, final T edge) { final String labelText; final String iconPath; if (edge instanceof JobGraphLink) { final JobGraphLink link = (JobGraphLink) edge; if (link.getOutputDataStream() != null) { labelText = link.getLinkLabel(); iconPath = IconUtils.OUTPUT_DATA_STREAM_PATH; } else if (isCompoundRequirementLink(link)) { final HasFilterOutcomes from = (HasFilterOutcomes) link.getFrom(); final CompoundComponentRequirement req = (CompoundComponentRequirement) link.getRequirement(); final Set<FilterOutcome> outcomesFrom = req.getOutcomesFrom(from); labelText = new CompoundComponentRequirement(outcomesFrom).getSimpleName(); iconPath = IconUtils.FILTER_OUTCOME_PATH; } else { labelText = link.getLinkLabel(); iconPath = IconUtils.FILTER_OUTCOME_PATH; } } else { labelText = value + ""; iconPath = IconUtils.FILTER_OUTCOME_PATH; } final Icon icon = imageManager.getImageIcon(iconPath, IconUtils.ICON_SIZE_SMALL); final JLabel label = new JLabel(labelText, icon, JLabel.LEFT); label.setFont(_normalFont); return label; } }; } public Transformer<Object, Font> getVertexFontTransformer() { return vertex -> { if (_highlighedVertexes.contains(vertex)) { return _boldFont; } return _normalFont; }; } }