/** * OrbisGIS is a java GIS application dedicated to research in GIScience. * OrbisGIS is developed by the GIS group of the DECIDE team of the * Lab-STICC CNRS laboratory, see <http://www.lab-sticc.fr/>. * * The GIS group of the DECIDE team is located at : * * Laboratoire Lab-STICC – CNRS UMR 6285 * Equipe DECIDE * UNIVERSITÉ DE BRETAGNE-SUD * Institut Universitaire de Technologie de Vannes * 8, Rue Montaigne - BP 561 56017 Vannes Cedex * * OrbisGIS is distributed under GPL 3 license. * * Copyright (C) 2007-2014 CNRS (IRSTV FR CNRS 2488) * Copyright (C) 2015-2017 CNRS (Lab-STICC UMR CNRS 6285) * * This file is part of OrbisGIS. * * OrbisGIS is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * OrbisGIS 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along with * OrbisGIS. If not, see <http://www.gnu.org/licenses/>. * * For more information, please consult: <http://www.orbisgis.org/> * or contact directly: * info_at_ orbisgis.org */ package org.orbisgis.view.toc.actions.cui.legend.ui; import net.miginfocom.swing.MigLayout; import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.h2gis.utilities.TableLocation; import org.orbisgis.corejdbc.MetaData; import org.orbisgis.coremap.renderer.se.parameter.Categorize; import org.orbisgis.legend.thematic.LineParameters; import org.orbisgis.legend.thematic.categorize.AbstractCategorizedLegend; import org.orbisgis.legend.thematic.map.MappedLegend; import org.orbisgis.commons.progress.NullProgressMonitor; import org.orbisgis.sif.common.ContainerItemProperties; import org.orbisgis.sif.components.WideComboBox; import org.orbisgis.view.toc.actions.cui.LegendContext; import org.orbisgis.view.toc.actions.cui.legend.components.ColorConfigurationPanel; import org.orbisgis.view.toc.actions.cui.legend.components.ColorScheme; import org.orbisgis.view.toc.actions.cui.legend.model.TableModelInterval; import org.orbisgis.view.toc.actions.cui.legend.panels.AbsPanel; import org.orbisgis.view.toc.actions.cui.legend.stats.Thresholds; import org.xnap.commons.i18n.I18n; import org.xnap.commons.i18n.I18nFactory; import javax.sql.DataSource; import javax.swing.*; import java.awt.event.ActionListener; import java.beans.EventHandler; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.SortedSet; import static org.orbisgis.coremap.renderer.se.parameter.Categorize.CategorizeMethod; /** * Root class for Interval Classifications. * * @author Alexis Guéganno */ public abstract class PnlAbstractCategorized<U extends LineParameters> extends PnlAbstractTableAnalysis<Double,U> { public static final Logger LOGGER = LoggerFactory.getLogger(PnlAbstractCategorized.class); private static final I18n I18N = I18nFactory.getI18n(PnlAbstractCategorized.class); private ColorConfigurationPanel colorConfig; private Thresholds thresholds; public static final Integer[] THRESHOLDS_NUMBER = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; public static final Integer[] THRESHOLDS_SQUARE = new Integer[]{2, 4, 8, 16}; private WideComboBox<Integer> numberCombo; private JButton createCl; private WideComboBox methodCombo; private DefaultComboBoxModel comboModel; /** * Contructor * * @param lc LegendContext * @param legend Legend */ public PnlAbstractCategorized(LegendContext lc, AbstractCategorizedLegend<U> legend) { super(lc, legend); } @Override public int getPreviewColumn(){ return TableModelInterval.PREVIEW_COLUMN; } @Override public int getKeyColumn(){ return TableModelInterval.KEY_COLUMN; } @Override public Class getPreviewClass() { return Double.class; } @Override public String getTitleBorder(){ return I18N.tr("Interval classification"); } private Thresholds computeStats(String fieldName){ DescriptiveStatistics stats = new DescriptiveStatistics(); try(Connection connection = getDataSource().getConnection(); Statement st = connection.createStatement(); ResultSet rs = st.executeQuery("select "+ TableLocation.quoteIdentifier(fieldName)+ " from "+getTable()+ " where "+ TableLocation.quoteIdentifier(fieldName) + " is not null" )) { while(rs.next()) { stats.addValue(rs.getDouble(1)); } } catch (SQLException e) { LOGGER.warn(I18N.tr("The application has ended unexpectedly"),e); } return new Thresholds(stats,fieldName); } /** * Retrieve the panel that gathers all the components needed to create the classification. * @return The panel gathering the graphic elements that can be used to create the classification. */ public JPanel getCreateClassificationPanel(){ if(numberCombo == null){ numberCombo = new WideComboBox<>(getThresholdsNumber()); } comboModel = (DefaultComboBoxModel) numberCombo.getModel(); createCl = new JButton(I18N.tr("Create")); createCl.setActionCommand("click"); createCl.addActionListener( EventHandler.create(ActionListener.class, this, "onComputeClassification")); createCl.setEnabled(false); JPanel inner = new JPanel( new MigLayout("wrap 2", "[align r][align l]")); inner.add(new JLabel(I18N.tr("Method"))); inner.add(getMethodCombo(), "width ::130"); inner.add(new JLabel(I18N.tr("Classes"))); inner.add(numberCombo, "split 2"); inner.add(createCl, "gapleft push"); JPanel outside = new JPanel(new MigLayout("wrap 1", "[" + AbsPanel.FIXED_WIDTH + ", align c]")); outside.setBorder(BorderFactory.createTitledBorder( I18N.tr(CLASSIFICATION_SETTINGS))); if(colorConfig == null){ ArrayList<String> names = new ArrayList<String>(ColorScheme.rangeColorSchemeNames()); names.addAll(ColorScheme.discreteColorSchemeNames()); colorConfig = new ColorConfigurationPanel(names); } outside.add(new JLabel(I18N.tr("Color scheme:")), "align l"); outside.add(colorConfig, "growx"); outside.add(inner, "growx"); return outside; } /** * Change what is displayed by the combo box. */ private void changeModelContent(){ Integer selItem = (Integer)numberCombo.getSelectedItem(); Integer[] vals = getThresholdsNumber(); int index = Arrays.binarySearch(vals,selItem); int real = index < 0 ? -index-1 : index; comboModel.removeAllElements(); for(int i=0;i<vals.length;i++){ comboModel.addElement(vals[i]); } numberCombo.setSelectedIndex(real); numberCombo.invalidate(); } /** * Gets the JComboBox used to select the classification method. * @return The JComboBox. */ public JComboBox getMethodCombo(){ if(methodCombo == null){ ContainerItemProperties[] categorizeMethods = getCategorizeMethods(); methodCombo = new WideComboBox(categorizeMethods); methodCombo.addActionListener( EventHandler.create(ActionListener.class, this, "methodChanged")); methodCombo.setSelectedItem(CategorizeMethod.MANUAL.toString()); } methodChanged(); return methodCombo; } /** * Gets the supported number of thresholds for the currently selected classification. * @return The number of thresholds. */ private Integer[] getThresholdsNumber(){ if(methodCombo == null){ return THRESHOLDS_SQUARE; } else { ContainerItemProperties selectedItem = (ContainerItemProperties) methodCombo.getSelectedItem(); CategorizeMethod cm = CategorizeMethod.valueOf(selectedItem.getKey()); switch(cm){ case BOXED_MEANS: return THRESHOLDS_SQUARE; default : return THRESHOLDS_NUMBER; } } } /** * The selected classification has changed. Called by EventHandler. */ public void methodChanged(){ ContainerItemProperties selectedItem = (ContainerItemProperties) methodCombo.getSelectedItem(); changeModelContent(); boolean b = CategorizeMethod.valueOf(selectedItem.getKey()).equals(CategorizeMethod.MANUAL); if(createCl != null){ createCl.setEnabled(!b); } } /** * This method is called by EventHandler when clicking on the button dedicated to classification creation. */ public void onComputeClassification(){ String name = getFieldName(); if(thresholds == null || !thresholds.getFieldName().equals(name)){ thresholds = computeStats(name); } ContainerItemProperties selectedItem = (ContainerItemProperties) methodCombo.getSelectedItem(); CategorizeMethod cm = CategorizeMethod.valueOf(selectedItem.getKey()); Integer number = (Integer) numberCombo.getSelectedItem(); SortedSet<Double> set = thresholds.getThresholds(cm,number); if(!set.isEmpty()){ ColorScheme sc = colorConfig.getColorScheme(); MappedLegend<Double,U> cl = createColouredClassification( set, new NullProgressMonitor(), sc); cl.setLookupFieldName(((MappedLegend)getLegend()).getLookupFieldName()); cl.setName(getLegend().getName()); setLegend(cl); } } /** * Gets the value contained in the {@code Methods} enum with their * internationalized representation in a {@code * ContainerItemProperties} array. * @return {@link Categorize.CategorizeMethod} in an array of containers. */ public ContainerItemProperties[] getCategorizeMethods(){ CategorizeMethod[] us = CategorizeMethod.values(); ArrayList<ContainerItemProperties> temp = new ArrayList<ContainerItemProperties>(); for (CategorizeMethod u : us) { if (isSupported(u)) { ContainerItemProperties cip = new ContainerItemProperties(u.name(), u.toLocalizedString()); temp.add(cip); } } return temp.toArray(new ContainerItemProperties[temp.size()]); } /** * Return if the given method is supported * @param cm The tested method * @return true if cm is supported */ private boolean isSupported(CategorizeMethod cm){ switch(cm){ case EQUAL_INTERVAL : return true; case MANUAL : return true; case STANDARD_DEVIATION: return true; case QUANTILES: return true; case BOXED_MEANS: return true; default : return false; } } }