jdplus.tramoseats.desktop.plugin.anomalydetection.ui.JTsAnomalyGrid Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jdplus-tramoseats-desktop-plugin Show documentation
Show all versions of jdplus-tramoseats-desktop-plugin Show documentation
${project.parent.artifactId} - ${project.artifactId}
The newest version!
/*
* Copyright 2013 National Bank of Belgium
*
* Licensed under the EUPL, Version 1.1 or – as soon they will be approved
* by the European Commission - subsequent versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Licence is distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and
* limitations under the Licence.
*/
package jdplus.tramoseats.desktop.plugin.anomalydetection.ui;
import jdplus.tramoseats.desktop.plugin.anomalydetection.OutlierEstimation;
import jdplus.toolkit.base.api.timeseries.TsCollection;
import jdplus.toolkit.desktop.plugin.components.parts.HasHoveredObs;
import jdplus.toolkit.desktop.plugin.components.parts.HasTsCollection;
import jdplus.toolkit.desktop.plugin.components.JTsGrid;
import jdplus.toolkit.desktop.plugin.components.TsGridObs;
import ec.util.chart.ObsIndex;
import ec.util.list.swing.JLists;
import jdplus.toolkit.desktop.plugin.components.TsSelectionBridge;
import jdplus.toolkit.desktop.plugin.ui.properties.l2fprod.OutlierColorChooser;
import jdplus.main.desktop.design.SwingComponent;
import jdplus.main.desktop.design.SwingProperty;
import jdplus.toolkit.base.api.modelling.TransformationType;
import jdplus.toolkit.base.api.timeseries.Ts;
import jdplus.toolkit.base.api.timeseries.TsInformationType;
import jdplus.tramoseats.base.api.tramo.OutlierSpec;
import jdplus.tramoseats.base.api.tramo.TramoException;
import jdplus.tramoseats.base.api.tramo.TramoSpec;
import jdplus.tramoseats.base.api.tramo.TransformSpec;
import java.awt.BorderLayout;
import java.awt.Component;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.OptionalInt;
import lombok.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.JToolTip;
import javax.swing.ListSelectionModel;
import javax.swing.SwingWorker;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import jdplus.toolkit.base.core.regsarima.regular.RegSarimaModel;
import jdplus.tramoseats.base.core.tramo.TramoKernel;
import nbbrd.design.SkipProcessing;
import org.netbeans.api.progress.ProgressHandle;
/**
* A grid component used to display outliers found in time series. The outliers
* are highlighted in the grid in color corresponding to the outlier's type
*
* @author Mats Maggi
*/
@SwingComponent
public final class JTsAnomalyGrid extends JComponent {
@SwingProperty
public static final String DEFAULT_SPEC_PROPERTY = "defaultSpec";
@SwingProperty
public static final String CRITICAL_VALUE_PROPERTY = "criticalValue";
@SkipProcessing(target = SwingProperty.class, reason = "to be refactored")
@SwingProperty
public static final String COLLECTION_PROPERTY = "collection";
@SkipProcessing(target = SwingProperty.class, reason = "to be refactored")
@SwingProperty
public static final String STATE_PROPERTY = "state";
@SkipProcessing(target = SwingProperty.class, reason = "to be refactored")
@SwingProperty
public static final String TYPES_PROPERTY = "types";
@SwingProperty
public static final String TRANSFORMATION_PROPERTY = "transformation";
@SwingProperty
public static final String HOVERED_OBS_PROPERTY = HasHoveredObs.HOVERED_OBS_PROPERTY;
private final JTsGrid grid;
private List outliers;
private TramoKernel preprocessor;
private RegSarimaModel model;
private TransformationType transformation;
private TramoSpec defaultSpec = TramoSpec.TRfull;
private TramoSpec spec = TramoSpec.TRfull;
private double criticalValue = .0;
private boolean defaultCritical = true;
private ProgressHandle progressHandle;
private SwingWorker worker;
private boolean showAO = true;
private boolean showLS = true;
private boolean showTC = true;
private boolean showSO = false;
//
/**
* Constructs a new JTsAnomalyGrid
*/
public JTsAnomalyGrid() {
super();
setLayout(new BorderLayout());
grid = new JTsGrid(TsInformationType.Data);
outliers = new ArrayList<>();
grid.setCellRenderer(new AnomalyCellRenderer(grid.getCellRenderer()));
// grid.setFreezeOnImport(true);
// Listening to a data change to calculate the new outliers
grid.addPropertyChangeListener(evt -> {
switch (evt.getPropertyName()) {
case HasTsCollection.TS_COLLECTION_PROPERTY:
onCollectionChange((TsCollection) evt.getOldValue(), (TsCollection) evt.getNewValue());
break;
case JTsGrid.ZOOM_RATIO_PROPERTY:
firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
break;
case TsSelectionBridge.TS_SELECTION_PROPERTY:
firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
break;
case HasHoveredObs.HOVERED_OBS_PROPERTY:
firePropertyChange(HOVERED_OBS_PROPERTY, evt.getOldValue(), evt.getNewValue());
break;
}
});
addPropertyChangeListener(evt -> {
String p = evt.getPropertyName();
if (p.equals(STATE_PROPERTY)) {
onStateChange();
firePropertyChange(OutliersTopComponent.STATE_CHANGED, null, worker.getState());
}
});
add(grid, BorderLayout.CENTER);
preprocessor = TramoKernel.of(spec, null);
transformation = TransformationType.None;
}
//
//
public void setDefaultCritical(boolean def) {
defaultCritical = def;
}
public boolean isDefaultCritical() {
return defaultCritical;
}
public TransformationType getTransformation() {
return transformation;
}
public void setTransformation(TransformationType transformation) {
TransformationType old = this.transformation;
this.transformation = transformation;
firePropertyChange(TRANSFORMATION_PROPERTY, old, this.transformation);
}
public void setZoomPercentage(int percentage) {
grid.setZoomRatio(percentage);
}
public int getZoomPercentage() {
return grid.getZoomRatio();
}
public RegSarimaModel getModelOfSelection() {
Ts selectedItem = getSelectedItem();
return selectedItem != null
? preprocessor.process(selectedItem.getData(), null)
: null;
}
public Ts getSelectedItem() {
OptionalInt singleSelection = JLists.getSelectionIndexStream(grid.getTsSelectionModel()).findFirst();
return singleSelection.isPresent() ? grid.getTsCollection().get(singleSelection.getAsInt()) : null;
}
public boolean isShowAO() {
return showAO;
}
public void setShowAO(boolean showAO) {
this.showAO = showAO;
refreshOutliersDisplayed();
}
public boolean isShowLS() {
return showLS;
}
public void setShowLS(boolean showLS) {
this.showLS = showLS;
refreshOutliersDisplayed();
}
public boolean isShowTC() {
return showTC;
}
public void setShowTC(boolean showTC) {
this.showTC = showTC;
refreshOutliersDisplayed();
}
public boolean isShowSO() {
return showSO;
}
public void setShowSO(boolean showSO) {
this.showSO = showSO;
refreshOutliersDisplayed();
}
public void setDefaultSpec(TramoSpec spec) {
TramoSpec old = this.spec;
this.defaultSpec = spec;
this.spec = spec;
refreshOutliersDisplayed();
firePropertyChange(DEFAULT_SPEC_PROPERTY, old, this.spec);
}
public TramoSpec getDefaultSpec() {
return defaultSpec;
}
public void setCriticalValue(double value) {
try {
double old = criticalValue;
criticalValue = value;
refreshOutliersDisplayed();
firePropertyChange(CRITICAL_VALUE_PROPERTY, old, criticalValue);
} catch (TramoException ex) {
throw new IllegalArgumentException(ex);
}
}
public double getCriticalValue() {
return criticalValue;
}
public int getSelectionIndex() {
ListSelectionModel selection = grid.getTsSelectionModel();
return selection.isSelectionEmpty() ? -1 : selection.getMinSelectionIndex();
}
public SwingWorker.StateValue getState() {
return worker != null ? worker.getState() : SwingWorker.StateValue.PENDING;
}
public TsCollection getTsCollection() {
return grid.getTsCollection();
}
@NonNull
public ObsIndex getHoveredObs() {
return grid.getHoveredObs();
}
public void setHoveredObs(@Nullable ObsIndex hoveredObs) {
grid.setHoveredObs(hoveredObs);
}
//
//
private void refreshOutliersDisplayed() {
outliers.clear();
grid.getTsSelectionModel().clearSelection();
OutlierSpec oldspec = spec.getOutliers();
OutlierSpec newspec = oldspec
.toBuilder()
.ao(showAO)
.ls(showLS)
.tc(showTC)
.so(showSO)
.criticalValue(defaultCritical ? 0.0 : criticalValue)
.build();
TransformSpec.Builder tbuilder = spec.getTransform().toBuilder()
.function(transformation);
spec = spec.toBuilder()
.transform(tbuilder.build())
.outliers(newspec)
.build();
preprocessor = TramoKernel.of(spec, null);
firePropertyChange(TYPES_PROPERTY, oldspec, newspec);
}
private void onCollectionChange(TsCollection oldcol, TsCollection newcol) {
firePropertyChange(COLLECTION_PROPERTY, oldcol, newcol);
}
protected void onStateChange() {
switch (getState()) {
case DONE:
if (progressHandle != null) {
progressHandle.finish();
}
break;
case PENDING:
break;
case STARTED:
progressHandle = ProgressHandle.createHandle("Calculating Outliers...", () -> worker.cancel(true));
progressHandle.start(100);
break;
}
}
//
//
public void calculateOutliers() {
refreshOutliersDisplayed();
stop();
start(false);
}
private class SwingWorkerImpl extends SwingWorker {
private int progressCount = 0;
private int nseries;
@Override
protected void done() {
super.done();
grid.repaint();
}
@Override
protected Void doInBackground() throws Exception {
outliers.clear();
grid.repaint();
outliers = new ArrayList<>();
List list = grid.getTsCollection().toList();
nseries = list.size();
int i = 0;
for (Ts s : list) {
if (isCancelled()) {
progressHandle.finish();
return null;
}
if (s.getData().isEmpty()) {
outliers.add(i, null);
} else {
OutlierEstimation[] o;
model = preprocessor.process(s.getData(), null);
if (model != null) {
o = OutlierEstimation.of(model);
} else {
o = null;
}
if (o != null && o.length > 0) {
outliers.add(i, o);
} else {
outliers.add(i, null);
}
}
publish(s);
++i;
}
return null;
}
@Override
protected void process(List chunks) {
// FIXME: find an alternative
// grid.fireTableDataChanged();
progressCount += chunks.size();
if (progressHandle != null && !chunks.isEmpty()) {
progressHandle.progress(100 * progressCount / nseries);
}
}
}
public boolean start(boolean local) {
worker = new JTsAnomalyGrid.SwingWorkerImpl();
worker.addPropertyChangeListener(evt -> firePropertyChange(STATE_PROPERTY, null, worker.getState()));
worker.execute();
return true;
}
public boolean stop() {
return worker != null && worker.cancel(true);
}
//
//
/*
* Renderer coloring the cells containing outliers
*/
private class AnomalyCellRenderer extends DefaultTableCellRenderer {
private final TableCellRenderer delegate;
private final DecimalFormat df = new DecimalFormat("0.0000", DecimalFormatSymbols.getInstance(Locale.getDefault(Locale.Category.FORMAT)));
private final JToolTip toolTip;
private OutlierEstimation currentOutlier;
public AnomalyCellRenderer(TableCellRenderer delegate) {
this.delegate = delegate;
this.toolTip = super.createToolTip();
this.currentOutlier = null;
setHorizontalAlignment(TRAILING);
}
@Override
public JToolTip createToolTip() {
if (currentOutlier != null) {
toolTip.setBackground(getBackground());
toolTip.setForeground(getForeground());
} else {
toolTip.setBackground(null);
toolTip.setForeground(null);
}
return toolTip;
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component resource = delegate.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (resource instanceof JLabel) {
JLabel label = (JLabel) resource;
setBackground(resource.getBackground());
setForeground(resource.getForeground());
setBorder(label.getBorder());
setFont(resource.getFont());
}
if (value instanceof TsGridObs) {
TsGridObs obs = (TsGridObs) value;
currentOutlier = null;
switch (obs.getStatus()) {
case AFTER:
case BEFORE:
case EMPTY:
case UNUSED:
break;
case PRESENT:
if (Double.isNaN(obs.getValue())) {
setText(".");
} else {
/*
* Try to find a match between the outlier and the current cell
* using the TsPeriods
*/
setText(String.valueOf(obs.getValue()));
if (outliers.size() <= obs.getSeriesIndex() || outliers.get(obs.getSeriesIndex()) == null) {
setToolTipText("Period : " + obs.getPeriod().toString() + "
"
+ "Value : " + df.format(obs.getValue()));
} else {
OutlierEstimation[] est = outliers.get(obs.getSeriesIndex());
int i = 0;
while (currentOutlier == null && i < est.length) {
if (est[i].getPeriod().equals(obs.getPeriod())) {
currentOutlier = est[i];
}
i++;
}
setText(String.valueOf(obs.getValue()));
if (currentOutlier != null) {
setToolTipText("Period : " + obs.getPeriod().toString() + "
"
+ "Value : " + df.format(obs.getValue()) + "
"
+ "Outlier Value : " + df.format(currentOutlier.getValue()) + "
"
+ "Std Err : " + df.format(currentOutlier.getStderr()) + "
"
+ "TStat : " + df.format(currentOutlier.getTstat()) + "
"
+ "Outlier type : " + currentOutlier.getCode());
setBackground(OutlierColorChooser.getColor(currentOutlier.getCode()));
setForeground(OutlierColorChooser.getForeColor(currentOutlier.getCode()));
} else {
setToolTipText("Period : " + obs.getPeriod().toString() + "
"
+ "Value : " + df.format(obs.getValue()));
}
}
}
break;
}
}
return this;
}
}
//
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy