org.datacleaner.widgets.result.AbstractCrosstabResultSwingRenderer Maven / Gradle / Ivy
/**
* 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.result;
import java.awt.FlowLayout;
import java.awt.event.ActionListener;
import java.text.NumberFormat;
import java.util.List;
import javax.inject.Inject;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import org.apache.commons.lang.math.NumberUtils;
import org.datacleaner.api.Provided;
import org.datacleaner.bootstrap.WindowContext;
import org.datacleaner.panels.DCPanel;
import org.datacleaner.result.Crosstab;
import org.datacleaner.result.CrosstabDimension;
import org.datacleaner.result.CrosstabResult;
import org.datacleaner.result.ResultProducer;
import org.datacleaner.result.renderer.AbstractRenderer;
import org.datacleaner.result.renderer.CrosstabRenderer;
import org.datacleaner.result.renderer.CrosstabRendererCallback;
import org.datacleaner.result.renderer.RendererFactory;
import org.datacleaner.util.ChartUtils;
import org.datacleaner.util.IconUtils;
import org.datacleaner.util.LabelUtils;
import org.datacleaner.util.ReflectionUtils;
import org.datacleaner.util.WidgetFactory;
import org.datacleaner.widgets.Alignment;
import org.datacleaner.widgets.table.CrosstabPanel;
import org.datacleaner.widgets.table.DCTable;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.DefaultCategoryDataset;
public abstract class AbstractCrosstabResultSwingRenderer
extends AbstractRenderer {
private final class RendererCallback implements CrosstabRendererCallback {
private boolean headersIncluded;
private TableModel _tableModel;
private int _row = 0;
private int _col = 0;
private Alignment _alignment = Alignment.LEFT;
@Override
public void beginTable(final Crosstab> crosstab, final List horizontalDimensions,
final List verticalDimensions) {
int rows = 1;
int cols = 1;
for (final CrosstabDimension crosstabDimension : verticalDimensions) {
rows = rows * crosstabDimension.getCategoryCount();
}
rows += horizontalDimensions.size();
for (final CrosstabDimension crosstabDimension : horizontalDimensions) {
cols = cols * crosstabDimension.getCategoryCount();
}
cols += verticalDimensions.size();
final String[] columnNames = new String[cols];
if (horizontalDimensions.size() == 1) {
headersIncluded = true;
final CrosstabDimension horizontalDimension = horizontalDimensions.get(0);
final List categories = horizontalDimension.getCategories();
columnNames[0] = "";
for (int i = 1; i < columnNames.length; i++) {
columnNames[i] = categories.get(i - 1);
}
// minus one row, because the header is included
rows--;
_row--;
} else {
headersIncluded = false;
for (int i = 0; i < columnNames.length; i++) {
columnNames[i] = "";
}
}
_tableModel = new DefaultTableModel(columnNames, rows);
if (ReflectionUtils.isNumber(crosstab.getValueClass())) {
_alignment = Alignment.RIGHT;
}
}
public Alignment getAlignment() {
return _alignment;
}
@Override
public void endTable() {
}
@Override
public void beginRow() {
}
@Override
public void endRow() {
_row++;
_col = 0;
}
@Override
public void horizontalHeaderCell(final String category, final CrosstabDimension dimension, final int width) {
AbstractCrosstabResultSwingRenderer.this.horizontalHeaderCell(category, _tableModel, _row, _col);
_col++;
}
@Override
public void verticalHeaderCell(final String category, final CrosstabDimension dimension, final int height) {
AbstractCrosstabResultSwingRenderer.this.verticalHeaderCell(category, _tableModel, _row, _col);
_col++;
}
@Override
public void valueCell(final Object value, final ResultProducer drillToDetailResultProducer) {
AbstractCrosstabResultSwingRenderer.this
.valueCell(value, drillToDetailResultProducer, _tableModel, _row, _col, headersIncluded,
_alignment);
_col++;
}
@Override
public TableModel getResult() {
return _tableModel;
}
@Override
public void emptyHeader(final CrosstabDimension verticalDimension,
final CrosstabDimension horizontalDimension) {
if (_row >= 0) {
_tableModel.setValueAt("", _row, _col);
}
_col++;
}
}
private static final String IMAGE_PATH_BAR_CHART = IconUtils.CHART_BAR;
private static final String IMAGE_PATH_DRILL_TO_DETAIL = IconUtils.ACTION_DRILL_TO_DETAIL;
@Inject
@Provided
WindowContext _windowContext;
@Inject
@Provided
RendererFactory _rendererFactory;
private DrillToDetailsCallback _drillToDetailsCallback;
/**
* Constructor used for programmatic composition.
*
* @param windowContext
*/
public AbstractCrosstabResultSwingRenderer(final WindowContext windowContext,
final RendererFactory rendererFactory) {
_windowContext = windowContext;
_rendererFactory = rendererFactory;
}
/**
* Default constructor, used by {@link RendererFactory}.
*/
public AbstractCrosstabResultSwingRenderer() {
}
private static String getLabelText(final Object value) {
if (value == null) {
return LabelUtils.NULL_LABEL;
} else if ("".equals(value)) {
return LabelUtils.BLANK_LABEL;
} else if (value instanceof Double || value instanceof Float) {
return NumberFormat.getInstance().format(value);
} else {
return value.toString();
}
}
public static DCPanel createActionableValuePanel(final Object value, final Alignment alignment,
final ActionListener action, final String iconImagePath) {
final JLabel label = new JLabel(getLabelText(value));
final DCPanel panel = new DCPanel();
panel.add(label);
panel.setLayout(new FlowLayout(alignment.getFlowLayoutAlignment(), 0, 0));
if (action != null && iconImagePath != null) {
final JButton button = WidgetFactory.createSmallButton(iconImagePath);
button.addActionListener(action);
panel.add(Box.createHorizontalStrut(4));
panel.add(button);
}
return panel;
}
@Override
public JComponent render(final R result) {
return renderInternal(result);
}
public RendererFactory getRendererFactory() {
return _rendererFactory;
}
/**
* Alternative render method, provided to have a more precise return type
* (while still allowing this class to be extended and only have a
* {@link JComponent} return type.
*
* @param result
* @return
*/
protected CrosstabPanel renderInternal(final R result) {
_drillToDetailsCallback = new DrillToDetailsCallbackImpl(_windowContext, getRendererFactory());
final DCTable table = renderTable(result.getCrosstab());
final CrosstabPanel crosstabPanel = new CrosstabPanel(table);
decorate(result, table, crosstabPanel.getDisplayChartCallback());
// make the first column packed to fit it's size.
table.packColumn(0, 2);
return crosstabPanel;
}
protected void decorate(final R result, final DCTable table, final DisplayChartCallback displayChartCallback) {
if (result.getCrosstab().getDimensionCount() == 2 && table.getColumnCount() > 2) {
addDefaultBarCharts(table, displayChartCallback);
}
}
protected void addDefaultBarCharts(final DCTable table, final DisplayChartCallback displayChartCallback) {
final int rowCount = table.getRowCount();
final int columnCount = table.getColumnCount();
for (int i = 0; i < rowCount; i++) {
boolean entirelyNumbers = true;
for (int j = 1; j < columnCount; j++) {
final String value = table.getTextValueAt(i, j);
if (!NumberUtils.isNumber(value)) {
entirelyNumbers = false;
break;
}
}
if (entirelyNumbers) {
final Object firstRowCell = table.getValueAt(i, 0);
if (firstRowCell instanceof String) {
final String measureName = firstRowCell.toString();
addDefaultBarChart(table, displayChartCallback, i, measureName);
}
}
}
}
protected void addDefaultBarChart(final DCTable table, final DisplayChartCallback displayChartCallback,
final int row, final String measureName) {
final ActionListener action = e -> {
final DefaultCategoryDataset dataset = new DefaultCategoryDataset();
final int columnCount = table.getColumnCount();
for (int j = 1; j < columnCount; j++) {
final String textValue = table.getTextValueAt(row, j);
final Number value = NumberUtils.createNumber(textValue);
dataset.setValue(value, table.getColumnName(j), "");
}
final JFreeChart chart = ChartFactory
.createBarChart("", "", measureName, dataset, PlotOrientation.VERTICAL, true, true, false);
ChartUtils.applyStyles(chart);
final ChartPanel chartPanel = ChartUtils.createPanel(chart, true);
displayChartCallback.displayChart(chartPanel);
};
final DCPanel panel = createActionableValuePanel(measureName, Alignment.LEFT, action, IMAGE_PATH_BAR_CHART);
table.setValueAt(panel, row, 0);
}
private DCTable renderTable(final Crosstab> crosstab) {
final CrosstabRenderer renderer = new CrosstabRenderer(crosstab);
final RendererCallback rendererCallback = new RendererCallback();
final TableModel tableModel = renderer.render(rendererCallback);
final Alignment alignment = rendererCallback.getAlignment();
final DCTable table = new DCTable(tableModel);
table.setColumnControlVisible(false);
table.setSortable(false);
table.setAlignment(0, Alignment.LEFT);
final int columnCount = table.getColumnCount();
for (int i = 1; i < columnCount; i++) {
table.setAlignment(i, alignment);
}
table.autoSetHorizontalScrollEnabled();
table.setRowHeight(22);
return table;
}
protected void horizontalHeaderCell(final String category, final TableModel tableModel, final int row,
final int col) {
if (row >= 0) {
tableModel.setValueAt(getLabelText(category), row, col);
}
}
protected void verticalHeaderCell(final String category, final TableModel tableModel, final int row,
final int col) {
if (row >= 0) {
tableModel.setValueAt(getLabelText(category), row, col);
}
}
protected void valueCell(final Object value, final ResultProducer drillToDetailResultProducer,
final TableModel tableModel, final int row, final int col, final boolean headersIncluded,
final Alignment alignment) {
final Object resultValue;
ActionListener action = null;
if (drillToDetailResultProducer != null) {
final StringBuilder sb = new StringBuilder("Detailed result for [");
sb.append(getLabelText(value));
sb.append(" (");
final String cat1;
if (headersIncluded) {
cat1 = tableModel.getColumnName(col);
} else {
cat1 = tableModel.getValueAt(0, col).toString();
}
sb.append(cat1).append(", ");
final String cat2 = tableModel.getValueAt(row, 0).toString();
sb.append(cat2);
sb.append(")]");
action = e -> _drillToDetailsCallback.drillToDetails(sb.toString(), drillToDetailResultProducer);
resultValue = createActionableValuePanel(value, alignment, action, IMAGE_PATH_DRILL_TO_DETAIL);
} else {
resultValue = getLabelText(value);
}
tableModel.setValueAt(resultValue, row, col);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy