/* * Licensed Materials - Property of Perforce Software, Inc. * © Copyright Perforce Software, Inc. 2014, 2021 * © Copyright IBM Corp. 2009, 2014 * © Copyright ILOG 1996, 2009 * All Rights Reserved. * * Note to U.S. Government Users Restricted Rights: * The Software and Documentation were developed at private expense and * are "Commercial Items" as that term is defined at 48 CFR 2.101, * consisting of "Commercial Computer Software" and * "Commercial Computer Software Documentation", as such terms are * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4, * as applicable. */ package treemap; import java.awt.Font; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.Serializable; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.faces.context.FacesContext; import javax.faces.event.ValueChangeEvent; import javax.faces.model.DataModel; import javax.faces.model.SelectItem; import javax.swing.table.TableModel; import javax.swing.tree.TreePath; import ilog.views.chart.IlvChart; import ilog.views.chart.IlvChartLayout; import ilog.views.chart.data.IlvTreeTableDataSource; import ilog.views.chart.datax.IlvColumnUtilities; import ilog.views.chart.datax.IlvDataColumnInfo; import ilog.views.chart.datax.IlvDefaultDataColumnInfo; import ilog.views.chart.datax.adapter.IlvClusterNode; import ilog.views.chart.datax.adapter.partition.IlvClusterId; import ilog.views.chart.datax.adapter.partition.IlvColumnValueClusterId; import ilog.views.chart.datax.adapter.partition.IlvPartitionerFactory; import ilog.views.chart.datax.adapter.partition.IlvStringPartitionerFactory; import ilog.views.chart.datax.adapter.partition.IlvUniformScalePartitionerFactory; import ilog.views.chart.datax.flat.table.IlvDefaultFlatTableModel; import ilog.views.chart.datax.flat.table.IlvFlatTableModel; import ilog.views.chart.datax.tree.list.IlvTreeListModel; import ilog.views.chart.graphic.IlvDataLabelAnnotation; import ilog.views.chart.renderer.IlvColorScheme; import ilog.views.chart.renderer.IlvTreemapChartRenderer; import ilog.views.util.IlvResourceUtil; import ilog.views.util.data.IlvCSVReader; import treemap.table.FilteredFlatTableModel; /** * The application bean */ public class TreemapBean implements Serializable { // The datasource private IlvTreeTableDataSource dataSource; // The file containing the data. private final String dataFile = "/data/energy.csv"; // Information about the columns in the Excel data. private final String[] columnNames = { "Country", "Region", "Population", "GDP", "GDP per Capita", "Ranking", "Year", "Energy Type", "Production", "Consumption" }; // Information about the columns type in the Excel data. private final Class<?>[] columnTypes = { String.class, String.class, Double.class, Double.class, Double.class, Integer.class, String.class, String.class, Double.class, Double.class }; // The current color model private String colorColumn = columnNames[5]; // The current area private String areaColumn = columnNames[4]; // The color chooser model private List<SelectItem> colorColumnModel = null; // The area chooser model private List<SelectItem> areaColumnModel = null; // The current partition private String partitionLabel; // The partition names. private String partitionNames[] = { "Country", "Region", "Population", "GDP", "GDP per Capita", "Ranking", "Year", "Energy Type", "Production", "Consumption" }; // The current partition separator private String partitionValue = partitionNames[1]; private final static String NONE = "<none>"; // The first partitionning criteria private String partitionCriterion1 = partitionNames[0]; // The second partitionning criteria private String partitionCriterion2 = NONE; // The treemap chart private TreemapWithHistory treemap; // The treepath finder private TreemapPathFinder treepathFinder = new TreemapPathFinder(); // The table model private FilteredFlatTableModel tableModel; // The current selection path private String selectionPath; /** * Creates a new <code>TreemapBean</code> instance. */ public TreemapBean() { initDataSource(); initTableModel(); initTreemap("Country"); } /** * Loads the data into memory and makes it accessible through a data source. */ private void initDataSource() { TableModel table; try { InputStream input = FacesContext.getCurrentInstance().getExternalContext().getResourceAsStream(dataFile); Reader reader = new InputStreamReader(new BufferedInputStream(input), "UTF-8"); try { IlvCSVReader parser = IlvCSVReader.getInstance('\t', 0); table = parser.read(reader); } finally { reader.close(); } } catch (IOException e) { e.printStackTrace(); return; } // Copy the data into an IlvFlatTableModel, with appropriate // conversion of values. int numColumns = table.getColumnCount(); if (numColumns != columnNames.length) { String msg = IlvResourceUtil.getCurrentLocaleString(TreemapBean.class, "columnNamesNotUpToDate"); throw new RuntimeException(msg); } IlvDataColumnInfo[] columns = new IlvDataColumnInfo[numColumns]; for (int col = 0; col < numColumns; col++) { columns[col] = new IlvDefaultDataColumnInfo(columnNames[col], columnTypes[col]); } int numRows = table.getRowCount(); IlvDefaultFlatTableModel tableData = new IlvDefaultFlatTableModel(numRows, columns); for (int col = 0; col < numColumns; col++) { if (columnTypes[col] == Double.class) { for (int row = 0; row < numRows; row++) { double value = Double.parseDouble(table.getValueAt(row, col).toString()); tableData.setDoubleAt(value, row, col); } } else if (columnTypes[col] == Integer.class) { for (int row = 0; row < numRows; row++) { int value = (int) Double.parseDouble(table.getValueAt(row, col).toString()); tableData.setValueAt(Integer.valueOf(value), row, col); } } else if (columnTypes[col] == String.class) { for (int row = 0; row < numRows; row++) { String value = table.getValueAt(row, col).toString(); tableData.setValueAt(value, row, col); } } else { for (int row = 0; row < numRows; row++) { Object value = table.getValueAt(row, col); tableData.setValueAt(value, row, col); } } } // Set the types of some columns to enumerated. This is a hint to the // QUALITATIVE color scheme how to distribute the colors. ((IlvDefaultDataColumnInfo) IlvColumnUtilities.getColumnByName(tableData, "Country")).setEnumerated(true); ((IlvDefaultDataColumnInfo) IlvColumnUtilities.getColumnByName(tableData, "Region")).setEnumerated(true); // Create a data source taking the worksheet as input. dataSource = new IlvTreeTableDataSource(tableData); } /** * Initializes the table model */ private void initTableModel() { IlvFlatTableModel model = dataSource.getFlatTableModel(); tableModel = new FilteredFlatTableModel(model); } /** * Initializes the treemap display. */ private void initTreemap(String nameColumn) { // Start customizing it. treemap = new TreemapWithHistory(dataSource); treemap.addSelectionListener(new TreemapWithHistory.SelectionListener() { Override public void selectionChanged(TreePath treepath) { Map<String, String> criteria = new HashMap<String, String>(); if (treepath != null) { int nb = treepath.getPathCount() - 1; // -1 = root if (nb == 0) selectionPath = ""; Object object = treepath.getLastPathComponent(); if (object instanceof IlvClusterNode) { StringBuffer tmpSelectionPath = new StringBuffer(); Object[] path = treepath.getPath(); for (int i = 1; i <= nb; i++) { IlvClusterNode node = (IlvClusterNode) path[i]; IlvColumnValueClusterId clusterId = (IlvColumnValueClusterId) node.getId(); String columnName = clusterId.getColumn().getName(); criteria.put(columnName, path[i].toString()); tmpSelectionPath.append(path[i].toString()); if (i != nb) tmpSelectionPath.append("/"); } selectionPath = tmpSelectionPath.toString(); tableModel.setCriteria(criteria); } else { // A leaf = a single record. int row = dataSource.getFlatModel().getObjects().indexOf(object); Object companyName = tableModel.getValueAt(row, "Country"); selectionPath = companyName.toString(); tableModel.setRowVisible(row); } } else { tableModel.setCriteria(new HashMap<String, String>()); selectionPath = null; } } }); IlvChart chart = treemap.getChart(); IlvTreemapChartRenderer renderer = (IlvTreemapChartRenderer) chart.getRenderer(0); // Set the packing algorithm. renderer.setPacking(IlvTreemapChartRenderer.SQUARIFIED_CORNER_PACKING); // Add labels. // The label visibility is set in class TreemapDemo, // method updateLabeling(). renderer.setLabelColumnName(nameColumn); renderer.setAnnotation(new IlvDataLabelAnnotation()); // Add tooltips. renderer.setLabelColumnName(nameColumn); // init colors updateColorColumn(); updateAreaColumn(); updateLabeling(); updatePartitioners(); } /** * Returns <code>true</code> if the path specified is valid against the * specified tree list model, <code>false</code> otherwise. * * @param path * The tree path to test. * @param tmodel * The tree list model. * @return <code>true</code> if the path is valid, <code>false</code> * otherwise. */ public boolean isValidPath(TreePath path, IlvTreeListModel tmodel) { TreePath tp = path; Object[] chain = tp.getPath(); if (chain[0] != tmodel.getRoot()) return false; for (int i = 1; i < chain.length; i++) if (!tmodel.getChildren(chain[i - 1]).contains(chain[i])) return false; return true; } /** * Updates the treemap when a particular color column has been chosen. */ private void updateColorColumn() { String columnName = colorColumn; IlvChart chart = treemap.getChart(); IlvTreemapChartRenderer renderer = (IlvTreemapChartRenderer) chart.getRenderer(0); // Reset the LegendLabelFormat, since the previous LegendLabelFormat // may be a DecimalFormat and therefore not suitable for non-numeric // columns. renderer.setLegendLabelFormat(null); // Set the color column and color scheme. // Also make a legend appear when suitable. IlvDataColumnInfo column; if (columnName != null && (column = IlvColumnUtilities.getColumnByName(dataSource.getTreeModel(), columnName)) != null) { renderer.setColorColumnName(columnName); if (column.getType() == String.class) // For non-numeric columns, use "qualitative" colors. renderer.setColorScheme(IlvTreemapChartRenderer.COLORSCHEME_QUALITATIVE); else if (column.getName().equals("Profits")) // For numeric columns with positive and negative values, use // red for negative and green for positive values. Take care // that frontier between red and green is exactly at 0. renderer.setColorScheme( IlvColorScheme.createAdjustedColorScheme(IlvTreemapChartRenderer.COLORSCHEME_DIVERGING_RED_GREEN, 0.0)); else // For other numeric columns, use red for low and green for // high values. renderer.setColorScheme( IlvColorScheme.createAdjustedColorScheme(IlvTreemapChartRenderer.COLORSCHEME_DIVERGING_RED_GREEN)); if (Number.class.isAssignableFrom(column.getType())) { // Ensure there is a legend. renderer.setLegendLabelFormat(new DecimalFormat("#.##")); chart.setLegendPosition(IlvChartLayout.EAST); chart.setLegendVisible(true); } else { // Ensure there is no legend. chart.setLegendVisible(true); } } else { renderer.setColorColumnName(null); renderer.setColorScheme(IlvTreemapChartRenderer.COLORSCHEME_DEPTH); // Ensure there is no legend. chart.setLegendVisible(true); } } /** * Updates the treemap when a particular area column has been chosen. */ private void updateAreaColumn() { String columnName = areaColumn; IlvChart chart = treemap.getChart(); IlvTreemapChartRenderer renderer = (IlvTreemapChartRenderer) chart.getRenderer(0); if (columnName != null && IlvColumnUtilities.getColumnByName(dataSource.getTreeModel(), columnName) != null) renderer.setAreaColumnName(columnName); else renderer.setAreaColumnName(null); } /** * Updates the treemap when a particular labeling mode has been chosen. */ private void updateLabeling() { IlvChart chart = treemap.getChart(); IlvTreemapChartRenderer renderer = (IlvTreemapChartRenderer) chart.getRenderer(0); renderer.setAnnotationVisibility(IlvTreemapChartRenderer.VISIBILITY_SMART); renderer.setClippedAnnotationVisibility(false); IlvDataLabelAnnotation annotation = new IlvDataLabelAnnotation(); annotation.getLabelRenderer().setFont(new Font("SansSerif", Font.PLAIN, 12)); renderer.setAnnotation(annotation); } /** * Updates the data source and with it also the treemap and tree-table when a * particular partitioning has been chosen. */ private void updatePartitioners() { IlvFlatTableModel model = dataSource.getFlatTableModel(); boolean criteria1Set = !NONE.equals(partitionCriterion1); boolean criteria2Set = !NONE.equals(partitionCriterion2); int count = criteria1Set ? 1 : 0; count += criteria2Set ? 1 : 0; SuppressWarnings("unchecked") IlvPartitionerFactory<? extends IlvClusterId>[] newPartitionerFactories = new IlvPartitionerFactory[count]; if (criteria1Set) newPartitionerFactories[0] = createPartitioner(model, partitionCriterion1); if (criteria2Set) newPartitionerFactories[count - 1] = createPartitioner(model, partitionCriterion2); if (!Arrays.equals(dataSource.getPartitionerFactories(), newPartitionerFactories)) { dataSource.setPartitionerFactories(newPartitionerFactories); tableModel.setCriteria(new HashMap<String, String>()); TreePath selection = treemap.getSelection(); if (selection != null && !isValidPath(selection, dataSource.getTreeModel())) treemap.setSelection(null); treemap.clearHistory(); StringBuffer headerLabel = new StringBuffer(); if (count == 0) { String msg = IlvResourceUtil.getCurrentLocaleString(TreemapBean.class, "noPartitioning"); headerLabel.append(msg); } else { if (criteria1Set) { headerLabel.append(partitionCriterion1); if (criteria2Set) { headerLabel.append("/"); headerLabel.append(partitionCriterion2); } } else if (criteria2Set) headerLabel.append(partitionCriterion2); } partitionLabel = headerLabel.toString(); } } private IlvPartitionerFactory<? extends IlvClusterId> createPartitioner(IlvFlatTableModel model, String columnName) { IlvDataColumnInfo column = IlvColumnUtilities.getColumnByName(model, columnName); // Set the corresponding partitioner. IlvPartitionerFactory<? extends IlvClusterId> partitioner = (Number.class.isAssignableFrom(column.getType()) ? new IlvUniformScalePartitionerFactory(column, 4, 5) : new IlvStringPartitionerFactory(column)); return partitioner; } /** * JSF action to change the color model. * * @param event * The value change event. */ public void colorColumnChanged(ValueChangeEvent event) { colorColumn = event.getNewValue().toString(); updateColorColumn(); } /** * JSF action to change the area. * * @param event * The value change event. */ public void areaColumnChanged(ValueChangeEvent event) { areaColumn = event.getNewValue().toString(); updateAreaColumn(); } /** * JSF action to change the partitionners. * * @param event * The value change event. */ public void partitionCriterion1Changed(ValueChangeEvent event) { partitionCriterion1 = event.getNewValue().toString(); updatePartitioners(); } /** * JSF action to change the partitionners. * * @param event * The value change event. */ public void partitionCriterion2Changed(ValueChangeEvent event) { partitionCriterion2 = event.getNewValue().toString(); updatePartitioners(); } /** * JSF action to change the partitionners. * * @param event * The value change event. */ public void partitionColumnChanged(ValueChangeEvent event) { partitionValue = event.getNewValue().toString(); updatePartitioners(); } /** * Returns the color select one chooser model. * * @return The color chooser model. */ public List<SelectItem> getColorColumnModel() { if (colorColumnModel == null) { colorColumnModel = new ArrayList<SelectItem>(); String msg = IlvResourceUtil.getCurrentLocaleString(TreemapBean.class, "allSameColorLabel"); colorColumnModel.add(new SelectItem("(all same color)", msg)); for (int col = 0; col < columnNames.length; col++) if (!columnNames[col].equals("Country")) colorColumnModel.add(new SelectItem(columnNames[col])); } return colorColumnModel; } /** * Returns the area select one chooser model. * * @return The area chooser model. */ public List<SelectItem> getAreaColumnModel() { if (areaColumnModel == null) { areaColumnModel = new ArrayList<SelectItem>(); String msg = IlvResourceUtil.getCurrentLocaleString(TreemapBean.class, "allEqualAreaLabel"); areaColumnModel.add(new SelectItem("(all equal area)", msg)); for (int col = 0; col < columnNames.length; col++) if (columnTypes[col] == Double.class) areaColumnModel.add(new SelectItem(columnNames[col])); } return areaColumnModel; } private List<SelectItem> partitionModel; /** * Returns the area select one chooser model. * * @return The area chooser model. */ public List<SelectItem> getPartitionColumnModel() { if (partitionModel == null) { partitionModel = new ArrayList<SelectItem>(); partitionModel.add(new SelectItem(NONE)); for (int col = 0; col < partitionNames.length; col++) partitionModel.add(new SelectItem(partitionNames[col])); } return partitionModel; } /** * Returns the current area. * * @return The current area. */ public String getAreaColumn() { return areaColumn; } /** * Sets the current area * * @param areaColumn * The area to set. */ public void setAreaColumn(String areaColumn) { this.areaColumn = areaColumn; } /** * Returns the current color scheme. * * @return The color scheme. */ public String getColorColumn() { return colorColumn; } /** * Sets the current color scheme. * * @param colorColumn * The color scheme to set. */ public void setColorColumn(String colorColumn) { this.colorColumn = colorColumn; } /** * Returns the current partition. * * @return The current partition. */ public String getPartitionLabel() { return partitionLabel; } /** * Sets the current partition value separator * * @return The partition separator. */ public String getPartitionValue() { return partitionValue; } /** * Sets the partition separator * * @param partitionValue * The paration separator to set. */ public void setPartitionValue(String partitionValue) { this.partitionValue = partitionValue; } /** * Returns the first criterion used to partition the treemap. * * @return The first partition criterion. */ public String getPartitionCriterion1() { return partitionCriterion1; } /** * Sets the first criterion used to partition the treemap. * * @param partitionCriteria1 * The criterion to set. */ public void setPartitionCriterion1(String partitionCriteria1) { this.partitionCriterion1 = partitionCriteria1; } /** * Returns the second criterion used to partition the treemap. * * @return The second partition criterion. */ public String getPartitionCriterion2() { return partitionCriterion2; } /** * Sets the second criterion used to partition the treemap. * * @param partitionCriteria2 * The criterion to set. */ public void setPartitionCriterion2(String partitionCriteria2) { this.partitionCriterion2 = partitionCriteria2; } /** * Returns the treemap object (the graphic component + the selection and * history of selection) * * @return The treemap. */ public TreemapWithHistory getTreemap() { return treemap; } /** * Returns the object finder that returns the tree path of a picked element of * the treemap chart. * * @return The tree path finder. */ public TreemapPathFinder getTreepathFinder() { return treepathFinder; } /** * Returns the table model. * * @return The table model */ public DataModel getTableModel() { return tableModel; } /** * Returns <code>true</code> if the table should be rendered * * @return <code>true</code> if the table should be rendered, * <code>false</code> otherwise. */ public boolean getTableRendered() { return getTableModel().getRowCount() > 0; } /** * Returns the table column names. * * @return The table column names. */ public List<String> getColumnNames() { return Arrays.asList(columnNames); } /** * * @return Return the column CSSs classes. */ public String getTableColumnCssClasses() { String res = ""; // hack: in the page the 2 first column are inverted res += "column-left,column-center,"; for (int i = 2; i < columnTypes.length; i++) { Class<?> clazz = columnTypes[i]; if (String.class.equals(clazz)) res += "column-left,"; else res += "column-right,"; } res = res.substring(0, res.length() - 1); return res; } /** * Returns the current path selected. * * @return The selection path. */ public String getSelectionPath() { return selectionPath; } /** * Return <code>true</code> if the selection path should be displayed, * <code>false</code> otherwise. * * @return The rendered state of the selection path display. */ public boolean isSelectionPathRendered() { return selectionPath != null && !"".equals(selectionPath); } }