diff --git a/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/parts/FeatureTablePart.java b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/parts/FeatureTablePart.java index 23f103cd30..623c21709b 100644 --- a/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/parts/FeatureTablePart.java +++ b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/parts/FeatureTablePart.java @@ -9,18 +9,19 @@ * Contributors: * Jan Holy - initial API and implementation * Philip Wenig - get rid of JavaFX, feature selection + * Lorenz Gerber - update feature table selection from loading plot *******************************************************************************/ package org.eclipse.chemclipse.xxd.process.supplier.pca.ui.parts; import java.util.List; -import jakarta.inject.Inject; - import org.eclipse.chemclipse.xxd.process.supplier.pca.model.EvaluationPCA; import org.eclipse.chemclipse.xxd.process.supplier.pca.ui.swt.ExtendedFeatureListUI; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; +import jakarta.inject.Inject; + public class FeatureTablePart extends AbstractPartPCA { @Inject diff --git a/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/swt/ExtendedFeatureListUI.java b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/swt/ExtendedFeatureListUI.java index fab28bf712..7861087fdb 100644 --- a/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/swt/ExtendedFeatureListUI.java +++ b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/swt/ExtendedFeatureListUI.java @@ -8,6 +8,7 @@ * * Contributors: * Philip Wenig - initial API and implementation + * Lorenz Gerber - update feature table selection from loading plot *******************************************************************************/ package org.eclipse.chemclipse.xxd.process.supplier.pca.ui.swt; @@ -15,6 +16,7 @@ import java.io.FileNotFoundException; import java.util.Arrays; import java.util.Iterator; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.chemclipse.model.statistics.IVariable; @@ -30,6 +32,8 @@ import org.eclipse.chemclipse.swt.ui.components.InformationUI; import org.eclipse.chemclipse.swt.ui.components.SearchSupportUI; import org.eclipse.chemclipse.swt.ui.notifier.UpdateNotifierUI; +import org.eclipse.chemclipse.ux.extension.xxd.ui.part.support.DataUpdateSupport; +import org.eclipse.chemclipse.ux.extension.xxd.ui.part.support.IDataUpdateListener; import org.eclipse.chemclipse.ux.extension.xxd.ui.swt.IExtendedPartUI; import org.eclipse.chemclipse.ux.extension.xxd.ui.swt.ISettingsHandler; import org.eclipse.chemclipse.xxd.process.supplier.pca.core.ProcessorPCA; @@ -38,9 +42,11 @@ import org.eclipse.chemclipse.xxd.process.supplier.pca.model.Feature; import org.eclipse.chemclipse.xxd.process.supplier.pca.model.FeatureDataMatrix; import org.eclipse.chemclipse.xxd.process.supplier.pca.preferences.PreferenceSupplier; +import org.eclipse.chemclipse.xxd.process.supplier.pca.ui.Activator; import org.eclipse.chemclipse.xxd.process.supplier.pca.ui.preferences.PreferencePage; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; @@ -61,11 +67,37 @@ public class ExtendedFeatureListUI extends Composite implements IExtendedPartUI // private EvaluationPCA evaluationPCA = null; private FeatureDataMatrix featureDataMatrix = null; + // + private Composite control; public ExtendedFeatureListUI(Composite parent, int style) { super(parent, style); createControl(); + // + DataUpdateSupport dataUpdateSupport = new DataUpdateSupport(Activator.getDefault().getEventBroker()); + dataUpdateSupport.subscribe(IChemClipseEvents.TOPIC_PCA_UPDATE_RESULT, IChemClipseEvents.EVENT_BROKER_DATA); + dataUpdateSupport.add(new IDataUpdateListener() { + + @Override + public void update(String topic, List objects) { + + if(evaluationPCA != null) { + if(DataUpdateSupport.isVisible(control)) { + if(IChemClipseEvents.TOPIC_PCA_UPDATE_RESULT.equals(topic)) { + if(objects.size() == 1) { + Object object = objects.get(0); + if(object instanceof Feature feature) { + if(evaluationPCA.getFeatureDataMatrix().getFeatures().contains(feature)) { + listControl.get().setSelection(new StructuredSelection(feature)); + } + } + } + } + } + } + } + }); } public void setInput(EvaluationPCA evaluationPCA) { @@ -91,6 +123,7 @@ private void createControl() { createToolbarInfo(this); // initialize(); + control = this; } private void initialize() { diff --git a/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/swt/ExtendedLoadingsPlot.java b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/swt/ExtendedLoadingsPlot.java index fbb793a92e..6121f7d12f 100644 --- a/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/swt/ExtendedLoadingsPlot.java +++ b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/swt/ExtendedLoadingsPlot.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2020, 2022 Lablicate GmbH. + * Copyright (c) 2020, 2024 Lablicate GmbH. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,29 +8,50 @@ * * Contributors: * Philip Wenig - initial API and implementation + * Lorenz Gerber - update feature table selection from loading plot *******************************************************************************/ package org.eclipse.chemclipse.xxd.process.supplier.pca.ui.swt; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.chemclipse.model.statistics.IVariable; +import org.eclipse.chemclipse.numeric.core.IPoint; +import org.eclipse.chemclipse.numeric.core.Point; +import org.eclipse.chemclipse.support.events.IChemClipseEvents; +import org.eclipse.chemclipse.swt.ui.notifier.UpdateNotifierUI; import org.eclipse.chemclipse.ux.extension.xxd.ui.swt.IExtendedPartUI; import org.eclipse.chemclipse.ux.extension.xxd.ui.swt.ISettingsHandler; import org.eclipse.chemclipse.xxd.process.supplier.pca.model.EvaluationPCA; +import org.eclipse.chemclipse.xxd.process.supplier.pca.model.Feature; +import org.eclipse.chemclipse.xxd.process.supplier.pca.model.FeatureDelta; import org.eclipse.chemclipse.xxd.process.supplier.pca.model.IAnalysisSettings; +import org.eclipse.chemclipse.xxd.process.supplier.pca.model.IResultPCA; +import org.eclipse.chemclipse.xxd.process.supplier.pca.model.IResultsPCA; import org.eclipse.chemclipse.xxd.process.supplier.pca.ui.chart2d.LoadingsPlot; import org.eclipse.chemclipse.xxd.process.supplier.pca.ui.preferences.PreferencePage; import org.eclipse.chemclipse.xxd.process.supplier.pca.ui.preferences.PreferencePageLoadingPlot; import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swtchart.Range; +import org.eclipse.swtchart.extensions.core.BaseChart; +import org.eclipse.swtchart.extensions.core.IChartSettings; +import org.eclipse.swtchart.extensions.core.IMouseSupport; +import org.eclipse.swtchart.extensions.events.IHandledEventProcessor; public class ExtendedLoadingsPlot extends Composite implements IExtendedPartUI { private AtomicReference plotControl = new AtomicReference<>(); - private PrincipalComponentUI principalComponentUI; + private AtomicReference principalComponentControl = new AtomicReference<>(); // private EvaluationPCA evaluationPCA = null; @@ -68,7 +89,7 @@ private void createToolbarMain(Composite parent) { composite.setLayoutData(gridData); composite.setLayout(new GridLayout(2, false)); // - principalComponentUI = createPrincipalComponentUI(composite); + createPrincipalComponentUI(composite); createSettingsButton(composite); } @@ -77,10 +98,104 @@ private void createPlot(Composite parent) { LoadingsPlot plot = new LoadingsPlot(parent, SWT.BORDER); plot.setLayoutData(new GridData(GridData.FILL_BOTH)); // + IChartSettings chartSettings = plot.getChartSettings(); + chartSettings.addHandledEventProcessor(new IHandledEventProcessor() { + + @Override + public int getEvent() { + + return IMouseSupport.EVENT_MOUSE_DOUBLE_CLICK; + } + + @Override + public int getButton() { + + return IMouseSupport.MOUSE_BUTTON_LEFT; + } + + @Override + public int getStateMask() { + + return SWT.NONE; + } + + @Override + public void handleEvent(BaseChart baseChart, Event event) { + + if(evaluationPCA != null) { + /* + * Determine the x|y coordinates. + */ + Rectangle rectangle = baseChart.getPlotArea().getBounds(); + int x = event.x; + int y = event.y; + int width = rectangle.width; + int height = rectangle.height; + /* + * Calculate the selected point. + */ + Range rangeX = baseChart.getAxisSet().getXAxis(BaseChart.ID_PRIMARY_X_AXIS).getRange(); + Range rangeY = baseChart.getAxisSet().getYAxis(BaseChart.ID_PRIMARY_Y_AXIS).getRange(); + double pX = rangeX.lower + (rangeX.upper - rangeX.lower) * ((1.0d / width) * x); + double pY = rangeY.lower + (rangeY.upper - rangeY.lower) * ((1.0d / height) * y); + /* + * Map the result deltas. + */ + PrincipalComponentUI principalComponentUI = principalComponentControl.get(); + int pcX = principalComponentUI.getPCX(); + int pcY = principalComponentUI.getPCY(); + IResultsPCA resultsPCA = evaluationPCA.getResults(); + List featureDeltas = new ArrayList<>(); + // + // Here need to prepare a result object with loading vectors per variable + // + for(int i = 0; i < resultsPCA.getExtractedVariables().size(); i++) { + double[] variableLoading = getVariableLoading(resultsPCA, i); + IPoint pointResult = getPoint(variableLoading, pcX, pcY, i); + double deltaX = Math.abs(pointResult.getX() - pX); + double deltaY = Math.abs(pointResult.getY() - pY); + featureDeltas.add(new FeatureDelta(evaluationPCA.getFeatureDataMatrix().getFeatures().get(i), deltaX, deltaY)); + } + /* + * Get the closest result. + */ + if(!featureDeltas.isEmpty()) { + Collections.sort(featureDeltas, Comparator.comparing(FeatureDelta::getDeltaX).thenComparing(FeatureDelta::getDeltaY)); + FeatureDelta featureDelta = featureDeltas.get(0); + Feature feature = featureDelta.getFeature(); + UpdateNotifierUI.update(event.display, IChemClipseEvents.TOPIC_PCA_UPDATE_RESULT, feature); + } + } + } + }); + plot.applySettings(chartSettings); + // plotControl.set(plot); } - private PrincipalComponentUI createPrincipalComponentUI(Composite parent) { + private double[] getVariableLoading(IResultsPCA results, int number) { + + double[] variableLoading = new double[results.getLoadingVectors().size()]; + for(int i = 0; i < results.getLoadingVectors().size(); i++) { + variableLoading[i] = results.getLoadingVectors().get(i)[number]; + } + return variableLoading; + } + + private IPoint getPoint(double[] variableLoading, int pcX, int pcY, int i) { + + double rX = 0; + if(pcX != 0) { + rX = variableLoading[pcX - 1]; // e.g. 0 = PC1 + } else { + rX = i; + } + double rY = variableLoading[pcY - 1]; // e.g. 1 = PC2 + // + return new Point(rX, rY); + } + + private void createPrincipalComponentUI(Composite parent) { PrincipalComponentUI principalComponentUI = new PrincipalComponentUI(parent, SWT.NONE, PrincipalComponentUI.SPINNER_X | PrincipalComponentUI.SPINNER_Y); principalComponentUI.setSelectionListener(new ISelectionListenerPCs() { @@ -92,7 +207,7 @@ public void update(int pcX, int pcY, int pcZ) { } }); // - return principalComponentUI; + principalComponentControl.set(principalComponentUI); } private void createSettingsButton(Composite parent) { @@ -109,6 +224,7 @@ public void apply(Display display) { private void applySettings() { + PrincipalComponentUI principalComponentUI = principalComponentControl.get(); int pcX = principalComponentUI.getPCX(); int pcY = principalComponentUI.getPCY(); updatePlot(pcX, pcY); @@ -116,6 +232,7 @@ private void applySettings() { private void updateWidgets() { + PrincipalComponentUI principalComponentUI = principalComponentControl.get(); if(evaluationPCA != null) { IAnalysisSettings analysisSettings = evaluationPCA.getSamples().getAnalysisSettings(); principalComponentUI.setInput(analysisSettings); diff --git a/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca/src/org/eclipse/chemclipse/xxd/process/supplier/pca/model/FeatureDelta.java b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca/src/org/eclipse/chemclipse/xxd/process/supplier/pca/model/FeatureDelta.java new file mode 100644 index 0000000000..518bfed80f --- /dev/null +++ b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca/src/org/eclipse/chemclipse/xxd/process/supplier/pca/model/FeatureDelta.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2024 Lablicate GmbH. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Lorenz Gerber - initial API and implementation + *******************************************************************************/ +package org.eclipse.chemclipse.xxd.process.supplier.pca.model; + +import java.util.Objects; + +public class FeatureDelta { + + private Feature feature = null; + private double deltaX = 0; + private double deltaY = 0; + + public FeatureDelta(Feature feature, double deltaX, double deltaY) { + + this.feature = feature; + this.deltaX = deltaX; + this.deltaY = deltaY; + } + + public Feature getFeature() { + + return feature; + } + + public double getDeltaX() { + + return deltaX; + } + + public double getDeltaY() { + + return deltaY; + } + + @Override + public int hashCode() { + + return Objects.hash(feature); + } + + @Override + public boolean equals(Object obj) { + + if(this == obj) + return true; + if(obj == null) + return false; + if(getClass() != obj.getClass()) + return false; + FeatureDelta other = (FeatureDelta)obj; + return Objects.equals(feature, other.feature); + } + + @Override + public String toString() { + + return "FeatureDelta [featurePCA=" + feature + ", deltaX=" + deltaX + ", deltaY=" + deltaY + "]"; + } +}