/**
* 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.orbisgis.commons.progress.ProgressMonitor;
import org.orbisgis.coremap.renderer.se.Symbolizer;
import org.orbisgis.legend.thematic.EnablesStroke;
import org.orbisgis.legend.thematic.LineParameters;
import org.orbisgis.legend.thematic.OnVertexOnInterior;
import org.orbisgis.legend.thematic.map.MappedLegend;
import org.orbisgis.legend.thematic.uom.StrokeUom;
import org.orbisgis.legend.thematic.uom.SymbolUom;
import org.orbisgis.view.toc.actions.cui.LegendContext;
import org.orbisgis.view.toc.actions.cui.components.CanvasSE;
import org.orbisgis.view.toc.actions.cui.legend.IClassificationLegend;
import org.orbisgis.view.toc.actions.cui.legend.components.ColorScheme;
import org.orbisgis.view.toc.actions.cui.legend.panels.SettingsPanel;
import org.orbisgis.view.toc.actions.cui.legend.panels.TablePanel;
import org.orbisgis.view.toc.actions.cui.legend.panels.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xnap.commons.i18n.I18n;
import javax.sql.DataSource;
import javax.swing.JPanel;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;
/**
* Root class for Value and Interval Classifications. Provides some methods for
* color generation and JTable management.
*
* @author Alexis Guéganno
*/
public abstract class PnlAbstractTableAnalysis<K, U extends LineParameters>
extends AbstractFieldPanel implements IClassificationLegend {
public static final Logger LOGGER = LoggerFactory.getLogger(PnlAbstractTableAnalysis.class);
private MappedLegend<K,U> legend;
private DataSource ds;
private String table;
private SettingsPanel<K, U> settingsPanel;
protected CanvasSE fallbackPreview;
protected TablePanel<K, U> tablePanel;
private String id;
public static final String FALLBACK = "Fallback";
public static final String CREATE_CLASSIF = "Create classification";
public static final String ENABLE_BORDER = I18n.marktr("Enable border");
protected static final String CLASSIFICATION_SETTINGS = I18n.marktr("Classification settings");
/**
* Sets the DataSource to the LegendContext's layer's DataSource and keeps
* a reference to the given legend.
*
* @param lc LegendContext
* @param legend Legend
*/
public PnlAbstractTableAnalysis(LegendContext lc,
MappedLegend<K, U> legend) {
this.ds = lc.getLayer().getDataManager().getDataSource();
this.table = lc.getLayer().getTableReference();
this.legend = legend;
}
protected void setLegendImpl(MappedLegend<K,U> leg){
this.legend = leg;
}
/**
* Gets the associated DataSource
* @return The inner DataSource.
*/
public DataSource getDataSource() {
return ds;
}
/**
* @return Table identifier [[Catalog.]Schema.]table
*/
public String getTable() {
return table;
}
@Override
public MappedLegend<K,U> getLegend() {
return legend;
}
protected abstract String getTitleBorder();
@Override
public String getId() {
return id;
}
/**
* We take the fallback configuration and copy it for each key, changing the colour. The colour management is
* made thanks to {@link #getColouredParameters(org.orbisgis.legend.thematic.LineParameters, java.awt.Color)}.
* The base colours are found in the given color scheme. We will put the colors from it. If there are more
* elements in the input set than in the ColorScheme, we compute colours from the one in the colour scheme.
* To achieve this goal, we make linear interpolations in the RGB space.
* @param set A set of keys we use as a basis.
* @param pm The progress monitor that can be used to stop the process.
* @param scheme the input palette
* @return A fresh unique value analysis.
*/
public final MappedLegend<K,U> createColouredClassification(SortedSet<K> set,
ProgressMonitor pm,
ColorScheme scheme) {
int expected = set.size();
int included = scheme.getColors().size();
List<Color> colors;
if(expected <= included ){
colors = scheme.getSubset(expected);
} else {
List<Color> known = scheme.getColors();
colors = new ArrayList<Color>();
int others = expected - included;
int intervals = included - 1;
int div = others / intervals;
int remain = others - intervals * div;
for(int i = 0; i<intervals; i++){
int ins = remain > 0 ? div +1 : div;
remain --;
Color start = known.get(i);
Color end = known.get(i+1);
List<Color> temp = getColorList(ins,start, end);
colors.add(known.get(i));
colors.addAll(temp);
}
colors.add(known.get(known.size()-1));
}
U lp = (getLegend()).getFallbackParameters();
MappedLegend<K,U> newRL = getEmptyAnalysis();
newRL.setFallbackParameters(lp);
if(set.size() != colors.size()){
throw new IllegalStateException("Wrong state");
}
Iterator<Color> it = colors.iterator();
ProgressMonitor colourParamProgress = pm.startTask(set.size());
for(K s : set){
U value = getColouredParameters(lp, it.next());
newRL.put(s, value);
if(pm.isCancelled()){
return null;
}
colourParamProgress.endTask();
}
postProcess(newRL);
return newRL;
}
/**
* Makes a postProcess operation on {@code ml} using the inner
* legend. We keep properties handled by the following interfaces :
* <ul><li>{@link OnVertexOnInterior},</li>
* <li>{@link EnablesStroke}</li>
* <li>{@link StrokeUom}</li>
* <li>{@link SymbolUom}</li>
* </ul>
*
* @param ml The legend we want to process.
*/
protected void postProcess(MappedLegend<K, U> ml) {
MappedLegend<K,U> inner = getLegend();
if (inner instanceof OnVertexOnInterior &&
ml instanceof OnVertexOnInterior) {
if (((OnVertexOnInterior) inner).isOnVertex()) {
((OnVertexOnInterior) ml).setOnVertex();
} else {
((OnVertexOnInterior) ml).setOnInterior();
}
}
if (inner instanceof EnablesStroke &&
ml instanceof EnablesStroke) {
((EnablesStroke) ml).setStrokeEnabled(
((EnablesStroke) inner).isStrokeEnabled());
}
ml.setStrokeUom(inner.getStrokeUom());
if (inner instanceof SymbolUom &&
ml instanceof SymbolUom) {
((SymbolUom) ml).setSymbolUom(
((SymbolUom) inner).getSymbolUom());
}
}
/**
* Retrieve a list of colours that are on the start-end line in the RGB space. Note that the input colours
* are not included in the returned list.
* @param num The number of colours we want
* @param start The starting colour
* @param end The ending colour
* @return The list of computed colours
*/
public List<Color> getColorList(int num, Color start, Color end){
int actual = num+2;
List<Color> ret = new ArrayList<Color>();
int redStart = start.getRed();
int greenStart = start.getGreen();
int blueStart = start.getBlue();
int alphaStart = start.getAlpha();
double redThreshold;
double greenThreshold;
double blueThreshold;
double alphaThreshold;
if(actual <= 1){
redThreshold = 0;
greenThreshold = 0;
blueThreshold = 0;
alphaThreshold = 0;
} else {
redThreshold = ((double)(redStart-end.getRed()))/(actual+1);
greenThreshold = ((double)(greenStart-end.getGreen()))/(actual+1);
blueThreshold = ((double)(blueStart-end.getBlue()))/(actual+1);
alphaThreshold = ((double)(alphaStart-end.getAlpha()))/(actual+1);
}
for(int j=0; j<actual; j++){
Color newCol = new Color(redStart-(int)(redThreshold*j),
greenStart-(int)(j*greenThreshold),
blueStart-(int)(j*blueThreshold),
alphaStart-(int)(j*alphaThreshold));
ret.add(newCol);
}
ret.remove(0);
ret.remove(ret.size() - 1);
return ret;
}
/**
* Gets a unique symbol configuration whose only difference with {@code fallback} is one of its color set to {@code
* c}.
* @param fallback The original configuration
* @param c The new colour
* @return A new configuration.
*/
public abstract U getColouredParameters(U fallback, Color c);
@Override
public void setId(String newId) {
id = newId;
}
@Override
public CanvasSE getPreview() {
if (fallbackPreview == null) {
initPreview();
}
return fallbackPreview;
}
@Override
public final void buildUI() {
// TODO: Without this call to removeAll(), if the Simple Style Editor
// is reopened, then the Classification UI is replaced by a Unique
// Symbol UI. Also, this call is necessary because the whole UI is
// recreated every time the user clicks "Create" (create classification).
this.removeAll();
JPanel glob = new JPanel(new MigLayout("wrap 2"));
tablePanel = new TablePanel<K, U>(legend,
getTitleBorder(),
getTableModel(),
getKeyCellEditor(),
getKeyColumn(),
getPreviewCellEditor(),
getPreviewColumn(),
getPreviewClass());
settingsPanel = new SettingsPanel<K, U>(legend,
getDataSource(),table,
getPreview(),
tablePanel);
glob.add(settingsPanel);
// Classification generator
glob.add(getCreateClassificationPanel());
// Table
glob.add(tablePanel, "span 2, growx");
this.add(glob);
this.revalidate();
}
/**
* Gets the JPanel that gathers all the buttons and labels to create a classification from scratch;
* @return The JPanel used to create a classification from scratch.
*/
public abstract JPanel getCreateClassificationPanel();
/**
* Gets an empty analysis that can be used ot build a panel equivalent to the caller.
* @return an empty analysis
*/
public abstract MappedLegend<K,U> getEmptyAnalysis();
/**
* Gets the model used to build the JTable.
* @return The table model.
*/
public abstract AbstractTableModel getTableModel();
/**
* Gets the editor used to configure a cell with a preview.
* @return A cell editor.
*/
public abstract TableCellEditor getPreviewCellEditor();
/**
* Gets the editor used to configure a key of the table
* @return A cell editor.
*/
public abstract TableCellEditor getKeyCellEditor();
/**
* Gets the index of the column used to display previews in the table.
* @return the index of the preview column.
*/
public abstract int getPreviewColumn();
/**
* Gets the index of the column used to display keys in the table.
* @return the index of the key column.
*/
public abstract int getKeyColumn();
/**
* Gets the constant Symbolizer obtained when using all the constant and fallback values of the original Symbolizer.
* @return The fallback Symbolizer.
*/
public Symbolizer getFallbackSymbolizer() {
return Util.getFallbackSymbolizer(legend);
}
/**
* Gets the Class of the keys used in the map.
* @return The Class of the type used for the map's keys.
*/
public abstract Class getPreviewClass();
/**
* Gets the name of the field on which we will perform the analysis
* @return The name of the field.
*/
public String getFieldName() {
return settingsPanel.getSelectedField();
}
}