/**
* 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.processeditor.results;
import java.awt.BorderLayout;
import java.awt.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import com.rapidminer.core.license.ProductConstraintManager;
import com.rapidminer.example.ExampleSet;
import com.rapidminer.example.set.MappedExampleSet;
import com.rapidminer.gui.renderer.DefaultTextRenderer;
import com.rapidminer.gui.renderer.Renderer;
import com.rapidminer.gui.renderer.RendererService;
import com.rapidminer.gui.tools.ProgressThread;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.gui.tools.components.ButtonBarCardPanel;
import com.rapidminer.gui.tools.components.CardSelectionEvent;
import com.rapidminer.gui.tools.components.CardSelectionListener;
import com.rapidminer.gui.tools.components.ResourceCard;
import com.rapidminer.license.LicenseConstants;
import com.rapidminer.license.LicenseManagerRegistry;
import com.rapidminer.license.violation.LicenseConstraintViolation;
import com.rapidminer.operator.IOContainer;
import com.rapidminer.operator.IOObject;
import com.rapidminer.operator.ResultObject;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.LogService;
import com.rapidminer.tools.usagestats.ActionStatisticsCollector;
/**
* Static methods to generate result visualization components etc.
*
* @author Simon Fischer
* */
public class ResultDisplayTools {
public static final String IOOBJECT_USER_DATA_KEY_RENDERER = ResultDisplayTools.class.getName() + ".renderer";
static final String CLIENT_PROPERTY_RAPIDMINER_RESULT_NAME_HTML = "rapidminer.result.name.html";
static final String CLIENT_PROPERTY_RAPIDMINER_RESULT_ICON = "rapidminer.result.icon";
static final String CLIENT_PROPERTY_RAPIDMINER_RESULT_NAME = "rapidminer.result.name";
private static final String DEFAULT_RESULT_ICON_NAME = "presentation_chart.png";
/** the icon shown for "under construction" tabs */
private static final ImageIcon WAIT_ICON = SwingTools.createIcon("24/hourglass.png");
private static final ImageIcon ERROR_ICON = SwingTools.createIcon("24/error.png");
private static Icon defaultResultIcon = null;
/**
* In these cases the unnecessary additional panel is suppressed
*/
private static final Set<String> NO_CARD_KEYS = new HashSet<>(Arrays.asList(new String[] { "collection", "metamodel",
"delegation_model" }));
static {
defaultResultIcon = SwingTools.createIcon("16/" + DEFAULT_RESULT_ICON_NAME);
}
public static JPanel createVisualizationComponent(IOObject resultObject, IOContainer resultContainer,
String usedResultName) {
return createVisualizationComponent(resultObject, resultContainer, usedResultName, true);
}
/**
* Creates a panel which centers an error message.
*
* @param error
* the message to display
* @return
*/
public static JPanel createErrorComponent(String error) {
JPanel panel = new JPanel(new BorderLayout());
JLabel label = new JLabel(error, ERROR_ICON, SwingConstants.CENTER);
panel.add(label, BorderLayout.CENTER);
return panel;
}
/**
* @param showCards
* if <code>false</code> the cards on the left side of the visualization component
* will not be shown
*/
public static JPanel createVisualizationComponent(IOObject result, final IOContainer resultContainer,
String usedResultName, final boolean showCards) {
final String resultName = RendererService.getName(result.getClass());
ButtonBarCardPanel visualisationComponent;
Collection<Renderer> renderers = RendererService.getRenderers(resultName);
// fallback to default toString method!
if (resultName == null) {
renderers.add(new DefaultTextRenderer());
}
// constructing panel of renderers
visualisationComponent = new ButtonBarCardPanel(NO_CARD_KEYS, showCards);
final ButtonBarCardPanel cardPanel = visualisationComponent;
// check license limit for ExampleSet rows
final List<LicenseConstraintViolation<Integer, Integer>> violationList = new ArrayList<>();
if (result instanceof ExampleSet) {
LicenseConstraintViolation<Integer, Integer> violation = LicenseManagerRegistry.INSTANCE.get()
.checkConstraintViolation(ProductConstraintManager.INSTANCE.getProduct(),
LicenseConstants.DATA_ROW_CONSTRAINT, ((ExampleSet) result).size(), false);
if (violation != null) {
result = downsample((ExampleSet) result, violation.getConstraintValue());
violationList.add(violation);
ActionStatisticsCollector.INSTANCE.log(ActionStatisticsCollector.TYPE_ROW_LIMIT,
ActionStatisticsCollector.VALUE_ROW_LIMIT_DIALOG, "results_banner");
}
}
final IOObject resultObject = result;
for (final Renderer renderer : renderers) {
String cardKey = toCardName(renderer.getName());
final ResourceCard card = new ResourceCard(cardKey, "result_view." + cardKey);
// create a placeholder panel which shows "under construction" so the results can be
// displayed immediately
final JPanel inConstructionPanel = new JPanel(new BorderLayout());
String humanName = I18N.getGUIMessageOrNull("gui.cards.result_view." + cardKey + ".title");
if (humanName == null) {
humanName = cardKey;
}
JLabel waitLabel = new JLabel(I18N.getGUILabel("result_construction", humanName));
waitLabel.setIcon(WAIT_ICON);
waitLabel.setHorizontalTextPosition(SwingConstants.TRAILING);
waitLabel.setHorizontalAlignment(SwingConstants.CENTER);
inConstructionPanel.add(waitLabel, BorderLayout.CENTER);
cardPanel.addCard(card, inConstructionPanel);
try {
ProgressThread resultThread = new ProgressThread("creating_result_tab", false, humanName) {
@Override
public void run() {
getProgressListener().setTotal(100);
getProgressListener().setCompleted(1);
final Component rendererComponent = renderer.getVisualizationComponent(resultObject,
resultContainer);
getProgressListener().setCompleted(60);
if (rendererComponent != null) {
if (rendererComponent instanceof JComponent) {
((JComponent) rendererComponent).setBorder(null);
}
getProgressListener().setCompleted(80);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// update container
// renderer is finished, remove placeholder
inConstructionPanel.removeAll();
// add license information if necessary
if (!violationList.isEmpty()) {
JPanel warnPanel = new ResultLimitPanel(rendererComponent.getBackground(),
violationList.get(0));
inConstructionPanel.add(warnPanel, BorderLayout.NORTH);
}
// add real renderer
inConstructionPanel.add(rendererComponent, BorderLayout.CENTER);
inConstructionPanel.revalidate();
inConstructionPanel.repaint();
}
});
getProgressListener().complete();
}
}
};
// start result calculation progress thread
resultThread.start();
} catch (Exception e) {
LogService.getRoot().log(Level.WARNING,
I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.gui.processeditor.results.ResultDisplayTools.error_creating_renderer", e),
e);
String errorMsg = I18N.getMessage(I18N.getErrorBundle(), "result_display.error_creating_renderer",
renderer.getName());
visualisationComponent.addCard(card, new JLabel(errorMsg));
}
}
if (resultObject.getUserData(IOOBJECT_USER_DATA_KEY_RENDERER) != null) { // check for user
// specified
// settings
visualisationComponent
.selectCard(toCardName((String) resultObject.getUserData(IOOBJECT_USER_DATA_KEY_RENDERER)));
}
// report statistics
visualisationComponent.addCardSelectionListener(new CardSelectionListener() {
private String lastKey = "";
@Override
public void cardSelected(CardSelectionEvent e) {
if (e != null) {
String key = e.getCardKey();
if (key != null && !key.equals(lastKey)) {
ActionStatisticsCollector.getInstance().log(ActionStatisticsCollector.TYPE_RENDERER, key, "select");
this.lastKey = key;
}
}
}
});
// result panel
final JPanel resultPanel = new JPanel(new BorderLayout());
resultPanel.putClientProperty("main.component", visualisationComponent);
resultPanel.add(visualisationComponent, BorderLayout.CENTER);
if (resultObject instanceof ResultObject) {
if (((ResultObject) resultObject).getResultIcon() != null) {
resultPanel.putClientProperty(ResultDisplayTools.CLIENT_PROPERTY_RAPIDMINER_RESULT_ICON,
((ResultObject) resultObject).getResultIcon());
} else {
resultPanel.putClientProperty(ResultDisplayTools.CLIENT_PROPERTY_RAPIDMINER_RESULT_ICON, defaultResultIcon);
}
}
resultPanel.putClientProperty(ResultDisplayTools.CLIENT_PROPERTY_RAPIDMINER_RESULT_NAME, usedResultName);
String source = resultObject.getSource() != null ? resultObject.getSource() : "";
resultPanel.putClientProperty(ResultDisplayTools.CLIENT_PROPERTY_RAPIDMINER_RESULT_NAME_HTML, "<html>"
+ usedResultName + "<br/><small>" + source + "</small></html>");
return resultPanel;
}
private static String toCardName(String name) {
return name.toLowerCase().replace(' ', '_');
}
public static ResultDisplay makeResultDisplay() {
return new DockableResultDisplay();
}
/**
* Takes the first {@code #newSize} rows of the given example set and returns a new one with
* only the first n rows
*/
private static ExampleSet downsample(ExampleSet exampleSet, int newSize) {
int[] mapping = new int[newSize];
for (int i = 0; i < newSize; i++) {
mapping[i] = i;
}
return new MappedExampleSet(exampleSet, mapping);
}
}