Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.datacleaner.widgets.visualization.JobGraph Maven / Gradle / Ivy
/**
* DataCleaner (community edition)
* Copyright (C) 2014 Free Software Foundation, Inc.
*
* 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.BorderLayout;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.datatransfer.Transferable;
import java.awt.event.AdjustmentListener;
import java.util.Collection;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.BoundedRangeModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
import javax.swing.TransferHandler;
import org.apache.metamodel.schema.Column;
import org.apache.metamodel.schema.Table;
import org.datacleaner.bootstrap.WindowContext;
import org.datacleaner.data.MetaModelInputColumn;
import org.datacleaner.descriptors.ComponentDescriptor;
import org.datacleaner.descriptors.ConfiguredPropertyDescriptor;
import org.datacleaner.job.AnalysisJob;
import org.datacleaner.job.builder.AnalysisJobBuilder;
import org.datacleaner.job.builder.ComponentBuilder;
import org.datacleaner.job.builder.UnconfiguredConfiguredPropertyException;
import org.datacleaner.panels.DCPanel;
import org.datacleaner.result.renderer.RendererFactory;
import org.datacleaner.user.UserPreferences;
import org.datacleaner.util.DragDropUtils;
import org.datacleaner.util.GraphUtils;
import org.datacleaner.util.IconUtils;
import org.datacleaner.util.ImageManager;
import org.datacleaner.util.LabelUtils;
import org.datacleaner.util.WidgetFactory;
import org.datacleaner.util.WidgetScreenResolutionAdjuster;
import org.datacleaner.util.WidgetUtils;
import org.datacleaner.widgets.Alignment;
import org.datacleaner.windows.ComponentConfigurationDialog;
import org.datacleaner.windows.SourceTableConfigurationDialog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import edu.uci.ics.jung.algorithms.layout.StaticLayout;
import edu.uci.ics.jung.graph.DirectedGraph;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.VisualizationServer.Paintable;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.VisualizationViewer.GraphMouse;
import edu.uci.ics.jung.visualization.control.PluggableGraphMouse;
/**
* Class capable of creating graphs that visualize {@link AnalysisJob}s or parts of them as a graph.
*/
public final class JobGraph {
public static final String MORE_COLUMNS_VERTEX = "...";
private static final Logger logger = LoggerFactory.getLogger(JobGraph.class);
private final WidgetScreenResolutionAdjuster adjuster = WidgetScreenResolutionAdjuster.get();
private final Map _componentConfigurationDialogs;
private final Map _tableConfigurationDialogs;
private final Set _highlighedVertexes;
private final AnalysisJobBuilder _analysisJobBuilder;
private final RendererFactory _presenterRendererFactory;
private final DCPanel _panel;
private final WindowContext _windowContext;
private final UserPreferences _userPreferences;
private int _scrollHorizontal;
private int _scrollVertical;
public JobGraph(final WindowContext windowContext, final UserPreferences userPreferences,
final AnalysisJobBuilder analysisJobBuilder) {
this(windowContext, userPreferences, analysisJobBuilder, null);
}
public JobGraph(final WindowContext windowContext, final UserPreferences userPreferences,
final AnalysisJobBuilder analysisJobBuilder, final RendererFactory presenterRendererFactory) {
_highlighedVertexes = new HashSet<>();
_analysisJobBuilder = analysisJobBuilder;
_userPreferences = userPreferences;
_windowContext = windowContext;
_componentConfigurationDialogs = new IdentityHashMap<>();
_tableConfigurationDialogs = new IdentityHashMap<>();
if (presenterRendererFactory == null) {
_presenterRendererFactory = new RendererFactory(analysisJobBuilder.getConfiguration());
} else {
_presenterRendererFactory = presenterRendererFactory;
}
_panel = new DCPanel();
_panel.setLayout(new BorderLayout());
}
public JobGraph highlightVertex(final Object vertex) {
_highlighedVertexes.add(vertex);
return this;
}
public DCPanel getPanel() {
if (_panel.getComponentCount() == 0) {
refresh();
}
_panel.updateUI();
return _panel;
}
public AnalysisJobBuilder getAnalysisJobBuilder() {
return _analysisJobBuilder;
}
public void refresh() {
final JobGraphNodeBuilder nodeBuilder = new JobGraphNodeBuilder(_analysisJobBuilder);
final DirectedGraph graph = nodeBuilder.buildGraph();
final JComponent newComponent = createJComponent(graph);
_panel.removeAll();
_panel.add(newComponent, BorderLayout.CENTER);
_panel.updateUI();
}
/**
* Creates the {@link JComponent} that shows the graph
*
* @return
*/
private JComponent createJComponent(final DirectedGraph graph) {
final int vertexCount = graph.getVertexCount();
logger.debug("Rendering graph with {} vertices", vertexCount);
final JobGraphLayoutTransformer layoutTransformer = new JobGraphLayoutTransformer(_analysisJobBuilder, graph);
final Dimension preferredSize = layoutTransformer.getPreferredSize();
final StaticLayout layout = new StaticLayout<>(graph, layoutTransformer, preferredSize);
final Collection vertices = graph.getVertices();
for (final Object vertex : vertices) {
// manually initialize all vertices
layout.apply(vertex);
}
if (!vertices.isEmpty() && !layoutTransformer.isTransformed()) {
throw new IllegalStateException("Layout transformer was never invoked!");
}
final VisualizationViewer visualizationViewer =
new VisualizationViewer<>(layout, preferredSize);
visualizationViewer.setTransferHandler(new TransferHandler() {
private static final long serialVersionUID = 1L;
public boolean canImport(final TransferSupport support) {
return support.isDataFlavorSupported(DragDropUtils.MODEL_DATA_FLAVOR);
}
public boolean importData(final TransferSupport support) {
final Transferable transferable = support.getTransferable();
final Object data;
try {
data = transferable.getTransferData(DragDropUtils.MODEL_DATA_FLAVOR);
} catch (final Exception ex) {
logger.warn("Unexpected error while dropping data", ex);
return false;
}
if (data == null) {
return false;
}
final Point dropPoint = support.getDropLocation().getDropPoint();
if (data instanceof Table) {
final Table table = (Table) data;
// position the table
JobGraphMetadata.setPointForTable(_analysisJobBuilder, table, dropPoint.x, dropPoint.y);
_analysisJobBuilder.addSourceColumns(table.getColumns());
}
if (data instanceof Column) {
final Column column = (Column) data;
final Table table = column.getTable();
final List columnsOfSameTable =
_analysisJobBuilder.getSourceColumnsOfTable(table);
if (columnsOfSameTable.isEmpty()) {
// the table is new - position it
JobGraphMetadata.setPointForTable(_analysisJobBuilder, table, dropPoint.x, dropPoint.y);
}
_analysisJobBuilder.addSourceColumn(column);
}
if (data instanceof ComponentDescriptor>) {
final ComponentDescriptor> descriptor = (ComponentDescriptor>) data;
final Map metadata = JobGraphMetadata.createMetadataProperties(dropPoint);
_analysisJobBuilder.addComponent(descriptor, null, null, metadata);
}
return true;
}
});
GraphUtils.applyStyles(visualizationViewer);
visualizationViewer.addPreRenderPaintable(new Paintable() {
@Override
public boolean useTransform() {
return false;
}
@Override
public void paint(final Graphics g) {
final GradientPaint paint = new GradientPaint(0, 0, WidgetUtils.BG_COLOR_BRIGHTEST, 0,
visualizationViewer.getHeight(), WidgetUtils.BG_COLOR_BRIGHTEST);
if (g instanceof Graphics2D) {
final Graphics2D g2d = (Graphics2D) g;
g2d.setPaint(paint);
} else {
g.setColor(WidgetUtils.BG_COLOR_BRIGHT);
}
g.fillRect(0, 0, visualizationViewer.getWidth(), visualizationViewer.getHeight());
final Dimension size = _panel.getSize();
if (size.height < adjuster.adjust(300) || size.width < adjuster.adjust(500)) {
// don't show the background hints - it will be too
// disturbing
return;
}
final String showCanvasHints = _userPreferences.getAdditionalProperties()
.get(JobGraphTransformers.USER_PREFERENCES_PROPERTY_SHOW_CANVAS_HINTS);
if ("false".equals(showCanvasHints)) {
// don't show the background hints - the user has decided
// not to have them.
return;
}
String title;
String subTitle;
String imagePath;
g.setColor(WidgetUtils.BG_COLOR_MEDIUM);
if (_analysisJobBuilder.getSourceColumns().size() == 0) {
title = "Select source ...";
subTitle = "Pick table/columns in the tree to the left.\n"
+ "You can drag it onto this canvas with your mouse.";
imagePath = "images/window/canvas-bg-table.png";
} else if (_analysisJobBuilder.getComponentCount() == 0) {
title = "Start building ...";
subTitle = "Add components to your job. Right-click the canvas\n"
+ "to explore the library of available components.";
imagePath = "images/window/canvas-bg-plus.png";
} else if (graph.getEdgeCount() == 0) {
title = "Connect the pieces ...";
subTitle = "Right-click the source table and select 'Link to ...'.\n"
+ "This directs the flow of data to the component.";
imagePath = "images/window/canvas-bg-connect.png";
} else if (_analysisJobBuilder.getResultProducingComponentBuilders().size() == 0
&& _analysisJobBuilder.getConsumedOutputDataStreamsJobBuilders().size() == 0
&& _analysisJobBuilder.getComponentCount() <= 3) {
title = "Your job is almost ready.";
subTitle = "Jobs need to either 'Analyze' or 'Write' something.\n"
+ "So add one or more such components.";
imagePath = "images/window/canvas-bg-plus.png";
} else {
title = null;
subTitle = null;
imagePath = null;
try {
if (_analysisJobBuilder.isConfigured(true)) {
title = "Ready to execute";
subTitle =
"Click the 'Execute' button in the upper-right\ncorner when you're ready to run the job.";
imagePath = "images/window/canvas-bg-execute.png";
g.drawImage(ImageManager.get().getImage("images/window/canvas-bg-execute-hint.png"),
size.width - adjuster.adjust(175), 0, null);
} else {
title = "Configure the job ...";
subTitle = "Job is not correctly configured";
imagePath = "images/window/canvas-bg-error.png";
}
} catch (final Exception ex) {
logger.debug("Job not correctly configured", ex);
final String errorMessage;
if (ex instanceof UnconfiguredConfiguredPropertyException) {
final UnconfiguredConfiguredPropertyException unconfiguredConfiguredPropertyException =
(UnconfiguredConfiguredPropertyException) ex;
final ConfiguredPropertyDescriptor configuredProperty =
unconfiguredConfiguredPropertyException.getConfiguredProperty();
final ComponentBuilder componentBuilder =
unconfiguredConfiguredPropertyException.getComponentBuilder();
title = "Configure " + "'" + LabelUtils.getLabel(componentBuilder) + "' ...";
errorMessage = "Please set '" + configuredProperty.getName() + "' to continue";
} else {
title = "Something went wrong ...";
errorMessage = ex.getMessage();
}
subTitle = errorMessage;
imagePath = "images/window/canvas-bg-error.png";
}
}
final int yOffset = size.height - adjuster.adjust(150);
final int xOffset = adjuster.adjust(150);
final float titleFontSize;
final float subTitleFontSize;
if (size.width < adjuster.adjust(650)) {
titleFontSize = adjuster.adjust(30f);
subTitleFontSize = adjuster.adjust(17f);
} else {
titleFontSize = adjuster.adjust(35f);
subTitleFontSize = adjuster.adjust(20f);
}
if (title != null) {
g.setFont(WidgetUtils.FONT_BANNER.deriveFont(titleFontSize));
g.drawString(title, xOffset, yOffset);
}
if (subTitle != null) {
final String[] lines = subTitle.split("\n");
g.setFont(WidgetUtils.FONT_BANNER.deriveFont(subTitleFontSize));
int y = yOffset + adjuster.adjust(10);
for (final String line : lines) {
y = y + adjuster.adjust(30);
g.drawString(line, xOffset, y);
}
}
if (imagePath != null) {
g.drawImage(ImageManager.get().getImage(imagePath), xOffset - adjuster.adjust(120),
yOffset - adjuster.adjust(30), null);
}
}
});
final JobGraphContext graphContext = new JobGraphContext(this, visualizationViewer, _analysisJobBuilder);
final JobGraphActions actions = new JobGraphActions(graphContext, _windowContext, _presenterRendererFactory,
_componentConfigurationDialogs, _tableConfigurationDialogs);
final JobGraphLinkPainter linkPainter = new JobGraphLinkPainter(graphContext, actions);
final JobGraphLinkPainterMousePlugin linkPainterMousePlugin =
new JobGraphLinkPainterMousePlugin(linkPainter, graphContext);
final GraphMouse graphMouse = visualizationViewer.getGraphMouse();
if (graphMouse instanceof PluggableGraphMouse) {
final PluggableGraphMouse pluggableGraphMouse = (PluggableGraphMouse) graphMouse;
pluggableGraphMouse.add(linkPainterMousePlugin);
}
final JobGraphMouseListener graphMouseListener =
new JobGraphMouseListener(graphContext, linkPainter, actions, _windowContext);
visualizationViewer.addGraphMouseListener(graphMouseListener);
visualizationViewer.addMouseListener(graphMouseListener);
final RenderContext renderContext = visualizationViewer.getRenderContext();
final JobGraphTransformers transformers = new JobGraphTransformers(graph, _userPreferences, _highlighedVertexes);
// instrument the render context with all our transformers and stuff
renderContext.setVertexFontTransformer(transformers.getVertexFontTransformer());
renderContext.setVertexLabelTransformer(JobGraphTransformers.VERTEX_LABEL_TRANSFORMER);
renderContext.setEdgeArrowPredicate(JobGraphTransformers.EDGE_ARROW_PREDICATE);
renderContext.setEdgeArrowTransformer(JobGraphTransformers.EDGE_ARROW_TRANSFORMER);
renderContext.setEdgeLabelTransformer(JobGraphTransformers.EDGE_LABEL_TRANSFORMER);
renderContext.setEdgeShapeTransformer(transformers.getEdgeShapeTransformer());
renderContext.setEdgeLabelClosenessTransformer(JobGraphTransformers.EDGE_LABEL_CLOSENESS_TRANSFORMER);
renderContext.setEdgeLabelRenderer(transformers.getEdgeLabelRenderer());
renderContext.setVertexIconTransformer(JobGraphTransformers.VERTEX_ICON_TRANSFORMER);
renderContext.setVertexShapeTransformer(JobGraphTransformers.VERTEX_SHAPE_TRANSFORMER);
final JButton graphPreferencesButton = createGraphPreferencesButton();
visualizationViewer.setLayout(new BorderLayout());
visualizationViewer.add(DCPanel.flow(Alignment.RIGHT, 0, 0, graphPreferencesButton), BorderLayout.SOUTH);
// we save the values of the scrollbars in order to allow refreshes to
// retain scroll position.
final GraphZoomScrollPane scrollPane = new GraphZoomScrollPane(visualizationViewer);
scrollPane.setCorner(new DCPanel(WidgetUtils.COLOR_DEFAULT_BACKGROUND));
if (_scrollHorizontal > 0) {
setScrollbarValue(scrollPane.getHorizontalScrollBar(), _scrollHorizontal);
}
if (_scrollVertical > 0) {
setScrollbarValue(scrollPane.getVerticalScrollBar(), _scrollVertical);
}
final AdjustmentListener adjustmentListener = e -> {
_scrollHorizontal = scrollPane.getHorizontalScrollBar().getValue();
_scrollVertical = scrollPane.getVerticalScrollBar().getValue();
};
scrollPane.getHorizontalScrollBar().addAdjustmentListener(adjustmentListener);
scrollPane.getVerticalScrollBar().addAdjustmentListener(adjustmentListener);
new JobGraphBindingsManager(graphContext, actions, scrollPane).register();
return scrollPane;
}
private JButton createGraphPreferencesButton() {
final JButton uiPreferencesButton = WidgetFactory
.createSmallButton(ImageManager.get().getImageIcon(IconUtils.MENU_OPTIONS, IconUtils.ICON_SIZE_MEDIUM));
uiPreferencesButton.setOpaque(false);
uiPreferencesButton.setBorder(null);
uiPreferencesButton.addActionListener(e -> {
final JobGraphPreferencesPanel panel = new JobGraphPreferencesPanel(_userPreferences, JobGraph.this);
final JPopupMenu popup = new JPopupMenu("Graph UI Preferences");
popup.add(panel);
final Dimension panelSize = panel.getPreferredSize();
popup.show(uiPreferencesButton, -1 * panelSize.width - 4, 0);
});
return uiPreferencesButton;
}
private void setScrollbarValue(final JScrollBar scrollBar, final int value) {
final BoundedRangeModel scrollModel = scrollBar.getModel();
scrollBar.setValues(value, scrollModel.getExtent(), scrollModel.getMinimum(), scrollModel.getMaximum());
}
}