/* * RapidMiner * * Copyright (C) 2001-2011 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.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.plotter.charts; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.jfree.chart.ChartFactory; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.SymbolAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.block.BlockBorder; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.XYBarRenderer; import org.jfree.chart.title.LegendTitle; import org.jfree.data.Range; import org.jfree.data.statistics.HistogramDataset; import org.jfree.ui.HorizontalAlignment; import org.jfree.ui.RectangleEdge; import com.rapidminer.datatable.DataTable; import com.rapidminer.datatable.DataTableRow; import com.rapidminer.gui.MainFrame; import com.rapidminer.gui.plotter.PlotterConfigurationModel; import com.rapidminer.gui.plotter.PlotterConfigurationModel.PlotterSettingsChangedListener; import com.rapidminer.gui.plotter.RangeablePlotterAdapter; import com.rapidminer.gui.plotter.settings.ListeningJCheckBox; import com.rapidminer.gui.plotter.settings.ListeningJSlider; import com.rapidminer.operator.ports.InputPort; import com.rapidminer.parameter.ParameterType; import com.rapidminer.parameter.ParameterTypeBoolean; import com.rapidminer.parameter.ParameterTypeInt; import com.rapidminer.tools.LogService; import com.rapidminer.tools.ParameterService; import com.rapidminer.tools.math.MathFunctions; /** * This is the histogram plotter based on JFreeCharts. * * @author Ingo Mierswa */ public class HistogramChart extends RangeablePlotterAdapter { public static final String PARAMETER_OPAQUENESS = "opaqueness"; public static final String PARAMETER_NUMBER_OF_BINS = "number_of_bins"; public static final String PARAMETER_LOG_SCALE = "log_scale"; private static final long serialVersionUID = 9140046811324105445L; public static final int MIN_BIN_NUMBER = 1; public static final int MAX_BIN_NUMBER = 100; public static final int DEFAULT_BIN_NUMBER = 40; protected transient DataTable dataTable; private HistogramDataset histogramDataset; /** Indicates which columns will be plotted. */ private boolean[] columns = new boolean[0]; protected int binNumber = DEFAULT_BIN_NUMBER; protected boolean logScale = false; private boolean absolute = false; protected boolean drawLegend = true; protected float opaqueness = 1.0f; private ListeningJCheckBox logScaleBox; private ListeningJSlider binNumberSlider; private ListeningJSlider opaquenessSlider; public HistogramChart(final PlotterConfigurationModel settings) { super(settings); logScaleBox = new ListeningJCheckBox(PARAMETER_LOG_SCALE, "Log Scale", false); logScaleBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { settings.setParameterAsBoolean(PARAMETER_LOG_SCALE, logScaleBox.isSelected()); } }); binNumberSlider = new ListeningJSlider(PARAMETER_NUMBER_OF_BINS, MIN_BIN_NUMBER, MAX_BIN_NUMBER, DEFAULT_BIN_NUMBER); binNumberSlider.setMajorTickSpacing(MAX_BIN_NUMBER - MIN_BIN_NUMBER); binNumberSlider.setMinorTickSpacing((MAX_BIN_NUMBER - MIN_BIN_NUMBER) / 10); binNumberSlider.setPaintTicks(true); binNumberSlider.setPaintLabels(true); binNumberSlider.setToolTipText("Set the number of bins which should be displayed."); binNumberSlider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { if (!binNumberSlider.getValueIsAdjusting()) settings.setParameterAsInt(PARAMETER_NUMBER_OF_BINS, binNumberSlider.getValue()); } }); opaquenessSlider = new ListeningJSlider(PARAMETER_OPAQUENESS, 0, 100, (int) (this.opaqueness * 100)); opaquenessSlider.setPaintTicks(true); opaquenessSlider.setPaintLabels(true); opaquenessSlider.setToolTipText("Set the amount of opaqueness / transparency."); opaquenessSlider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { if (!opaquenessSlider.getValueIsAdjusting()) settings.setParameterAsInt(PARAMETER_OPAQUENESS, opaquenessSlider.getValue()); } }); } public HistogramChart(PlotterConfigurationModel settings, DataTable dataTable) { this(settings); setDataTable(dataTable); } @Override public void dataTableSet() { this.dataTable = getDataTable(); this.columns = new boolean[this.dataTable.getNumberOfColumns()]; updatePlotter(); } @Override public Icon getIcon(int index) { return null; } public boolean isLogScale() { return this.logScale; } @Override public void setAbsolute(boolean absolute) { this.absolute = absolute; updatePlotter(); } @Override public boolean isSupportingAbsoluteValues() { return true; } public void setDrawLegend(boolean drawLegend) { this.drawLegend = drawLegend; } @Override public void setPlotColumn(int index, boolean plot) { columns[index] = plot; updatePlotter(); super.repaint(); revalidate(); } @Override public boolean getPlotColumn(int index) { return columns[index]; } @Override public int getValuePlotSelectionType() { return MULTIPLE_SELECTION; } protected int getNumberOfPlots() { int counter = 0; for (boolean column : columns) { if (column) counter++; } return counter; } public void prepareData() { histogramDataset = new RapidHistogramDataset(isLogScale()); if (getNumberOfPlots() == 0) { return; } synchronized (dataTable) { for (int c = 0; c < this.dataTable.getNumberOfColumns(); c++) { if (this.columns[c]) { // double[] values = new double[this.dataTable.getNumberOfRows()]; // if (this.dataTable.getSelectionCount() > 0) { double[] values = new double[this.dataTable.getSelectionCount() > 0 ? this.dataTable.getSelectionCount() : this.dataTable.getNumberOfRows()]; int index = 0; int valueIndex = 0; Iterator<DataTableRow> i = dataTable.iterator(); while (i.hasNext()) { DataTableRow row = i.next(); if (this.dataTable.getSelectionCount() == 0 || !dataTable.isDeselected(row.getId())) { // if (this.dataTable.getSelectionCount() == 0 || !dataTable.isDeselected(index)) { double value = row.getValue(c); if (this.absolute) value = Math.abs(value); if (!Double.isNaN(value)) { values[valueIndex++] = value; } } index++; } if (valueIndex != values.length) { double[] newValues = new double[valueIndex]; for (int j = 0; j < valueIndex; j++) { newValues[j] = values[j]; } values = newValues; } if (values.length > 0) histogramDataset.addSeries(this.dataTable.getColumnName(c), values, this.binNumber); // } } } } } @Override protected void updatePlotter() { prepareData(); String maxClassesProperty = ParameterService.getParameterValue(MainFrame.PROPERTY_RAPIDMINER_GUI_PLOTTER_COLORS_CLASSLIMIT); int maxClasses = 20; try { if (maxClassesProperty != null) maxClasses = Integer.parseInt(maxClassesProperty); } catch (NumberFormatException e) { LogService.getGlobal().log("Deviation plotter: cannot parse property 'rapidminer.gui.plotter.colors.classlimit', using maximal 20 different classes.", LogService.WARNING); } int categoryCount = this.histogramDataset.getSeriesCount(); boolean createLegend = categoryCount > 0 && categoryCount < maxClasses && this.drawLegend; JFreeChart chart = ChartFactory.createHistogram(null, // title "Value", "Frequency", histogramDataset, PlotOrientation.VERTICAL, createLegend, true, // tooltips false); // urls XYPlot plot = chart.getXYPlot(); plot.setDomainGridlinePaint(Color.LIGHT_GRAY); plot.setRangeGridlinePaint(Color.LIGHT_GRAY); plot.setBackgroundPaint(Color.WHITE); plot.setForegroundAlpha(this.opaqueness); XYBarRenderer renderer = new XYBarRenderer(); if (histogramDataset.getSeriesCount() == 1) { renderer.setSeriesPaint(0, Color.RED); renderer.setSeriesFillPaint(0, Color.RED); } else { for (int i = 0; i < histogramDataset.getSeriesCount(); i++) { Color color = getColorProvider().getPointColor((double) i / (double) (histogramDataset.getSeriesCount() - 1)); renderer.setSeriesPaint(i, color); renderer.setSeriesFillPaint(i, color); } } renderer.setBarPainter(new RapidXYBarPainter()); // renderer.setBarPainter(new StandardXYBarPainter()); renderer.setDrawBarOutline(true); renderer.setShadowVisible(false); plot.setRenderer(renderer); plot.getRangeAxis().setLabelFont(LABEL_FONT_BOLD); plot.getRangeAxis().setTickLabelFont(LABEL_FONT); plot.getDomainAxis().setLabelFont(LABEL_FONT_BOLD); plot.getDomainAxis().setTickLabelFont(LABEL_FONT); setRange(plot.getDomainAxis()); // rotate labels if (isLabelRotating()) { plot.getDomainAxis().setTickLabelsVisible(true); plot.getDomainAxis().setVerticalTickLabels(true); } if (histogramDataset.getSeriesCount() == 1) { String key = histogramDataset.getSeriesKey(0).toString(); int index = this.dataTable.getColumnIndex(key); if (index >= 0) { if (this.dataTable.isNominal(index)) { String[] values = new String[dataTable.getNumberOfValues(index)]; for (int i = 0; i < values.length; i++) { values[i] = dataTable.mapIndex(index, i); } plot.setDomainAxis(new SymbolAxis(key, values)); } } } // set the background color for the chart... chart.setBackgroundPaint(Color.white); // legend settings LegendTitle legend = chart.getLegend(); if (legend != null) { legend.setPosition(RectangleEdge.TOP); legend.setFrame(BlockBorder.NONE); legend.setHorizontalAlignment(HorizontalAlignment.LEFT); legend.setItemFont(LABEL_FONT); } AbstractChartPanel panel = getPlotterPanel(); if (panel == null) { panel = createPanel(chart); } else { panel.setChart(chart); } // ATTENTION: WITHOUT THIS WE GET SEVERE MEMORY LEAKS!!! panel.getChartRenderingInfo().setEntityCollection(null); } private void setRange(ValueAxis axis) { Range range = null; for (int c = 0; c < this.dataTable.getNumberOfColumns(); c++) { if (this.columns[c]) { if (range == null) range = getRangeForDimension(c); else { Range newRange = getRangeForDimension(c); if (newRange != null) range = new Range(MathFunctions.robustMin(range.getLowerBound(), newRange.getLowerBound()), MathFunctions.robustMax(range.getUpperBound(), newRange.getUpperBound())); } } } if (range != null) axis.setRange(range); else axis.setAutoRange(true); } @Override public JComponent getOptionsComponent(int index) { if (index == 0) { return logScaleBox; } else if (index == 1) { return getRotateLabelComponent(); } else if (index == 2) { JLabel label = new JLabel("Number of Bins"); label.setToolTipText("Set the number of bins which should be displayed."); return label; } else if (index == 3) { return binNumberSlider; } else if (index == 4) { JLabel label = new JLabel("Opaqueness"); label.setToolTipText("Sets the amount of opaqueness / transparency."); return label; } else if (index == 5) { return opaquenessSlider; } else { return null; } } @Override public List<ParameterType> getAdditionalParameterKeys(InputPort inputPort) { List<ParameterType> types = super.getAdditionalParameterKeys(inputPort); types.add(new ParameterTypeBoolean(PARAMETER_LOG_SCALE, "Indicates if the bin heights should be transformed with logarithm base 10.", false)); types.add(new ParameterTypeInt(PARAMETER_NUMBER_OF_BINS, "The number of bins for each histogram.", MIN_BIN_NUMBER, MAX_BIN_NUMBER, DEFAULT_BIN_NUMBER)); types.add(new ParameterTypeInt(PARAMETER_OPAQUENESS, "Indicates the opaqueness / transparency of the bins in percent.", 0, 100, 100)); return types; } @Override public void setAdditionalParameter(String key, String value) { super.setAdditionalParameter(key, value); if (PARAMETER_LOG_SCALE.equals(key)) { this.logScale = Boolean.parseBoolean(value); updatePlotter(); } else if (PARAMETER_NUMBER_OF_BINS.equals(key)) { this.binNumber = (int) Double.parseDouble(value); updatePlotter(); } else if (PARAMETER_OPAQUENESS.equals(key)) { this.opaqueness = (int) Double.parseDouble(value) / 100f; updatePlotter(); } } @Override public List<PlotterSettingsChangedListener> getListeningObjects() { List<PlotterSettingsChangedListener> list = super.getListeningObjects(); list.add(opaquenessSlider); list.add(binNumberSlider); list.add(logScaleBox); return list; } @Override public String getPlotterName() { return PlotterConfigurationModel.HISTOGRAM_PLOT; } @Override public Collection<String> resolveXAxis(int axisIndex) { Collection<String> names = new LinkedList<String>(); for (int i = 0; i <columns.length; i++) { if (columns[i]) names.add(dataTable.getColumnName(i)); } return names; } @Override public Collection<String> resolveYAxis(int axisIndex) { return Collections.emptyList(); } }