diff --git a/calibration-gui/pom.xml b/calibration-gui/pom.xml index 024914f2..d52d506f 100644 --- a/calibration-gui/pom.xml +++ b/calibration-gui/pom.xml @@ -5,7 +5,7 @@ gov.llnl.gnem.apps.coda.calibration coda-calibration - 1.0.15 + 1.0.16 calibration-gui diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/AbstractMeasurementController.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/AbstractMeasurementController.java index 7136e1d2..ddf3ad63 100644 --- a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/AbstractMeasurementController.java +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/AbstractMeasurementController.java @@ -65,6 +65,7 @@ import gov.llnl.gnem.apps.coda.common.gui.plotting.PlotPoint; import gov.llnl.gnem.apps.coda.common.gui.plotting.SymbolStyleMapFactory; import gov.llnl.gnem.apps.coda.common.gui.util.CellBindingUtils; +import gov.llnl.gnem.apps.coda.common.gui.util.HiddenHeaderTableView; import gov.llnl.gnem.apps.coda.common.gui.util.MaybeNumericStringComparator; import gov.llnl.gnem.apps.coda.common.gui.util.NumberFormatFactory; import gov.llnl.gnem.apps.coda.common.gui.util.SnapshotUtils; @@ -90,6 +91,7 @@ import javafx.scene.control.Tab; import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumn.CellDataFeatures; +import javafx.scene.control.TableRow; import javafx.scene.control.TableView; import javafx.scene.input.MouseEvent; import javafx.scene.layout.StackPane; @@ -218,11 +220,14 @@ public abstract class AbstractMeasurementController implements MapListeningContr @FXML protected TableColumn bandCoverageCol; + @FXML + protected TableColumn likelyPoorlyConstrainedCol; + @FXML protected TableColumn stationCol; @FXML - protected TableView> summaryTable; + protected HiddenHeaderTableView> summaryTable; @FXML protected TableColumn, String> summaryNameCol; @@ -317,6 +322,7 @@ protected AbstractMeasurementController(final SpectraClient spectraClient, final public void initialize() { evidCombo.setItems(evids); + evidCombo.setVisibleRowCount(5); configureAxisShrink(xAxisShrink, () -> { shouldXAxisShrink = !shouldXAxisShrink; @@ -335,6 +341,7 @@ public void initialize() { final AxisLimits mwXaxis = new AxisLimits(Axis.Type.X, 0.0, 10.0); final AxisLimits mwYaxis = new AxisLimits(Axis.Type.Y, 0.0, 10.0); mwPlot.setAxisLimits(mwXaxis, mwYaxis); + mwPlot.setMargin(30, 40, 50, 50); mwPlot.attachToDisplayNode(mwPlotPane); stressPlot = plotFactory.basicPlot(); @@ -344,14 +351,16 @@ public void initialize() { final AxisLimits stressXaxis = new AxisLimits(Axis.Type.X, 1.0, 1.0); final AxisLimits stressYaxis = new AxisLimits(Axis.Type.Y, 1.0, 1.0); stressPlot.setAxisLimits(stressXaxis, stressYaxis); + stressPlot.setMargin(30, 40, 70, 50); stressPlot.attachToDisplayNode(stressPlotPane); sdPlot = plotFactory.basicPlot(); - sdPlot.getTitle().setText("Site correction overview"); + sdPlot.getTitle().setText("Site correction overview (# stations)"); sdPlot.getTitle().setFontSize(16); sdPlot.addAxes(plotFactory.axis(Axis.Type.LOG_X, "Frequency (Hz)"), plotFactory.axis(Axis.Type.Y, "Standard Deviation")); sdPlot.setAxisLimits(new AxisLimits(Axis.Type.LOG_X, 0.0, 1.0), new AxisLimits(Axis.Type.Y, 0.0, 2.0)); sdPlot.showLegend(false); + sdPlot.setMargin(30, 40, 50, null); sdPlot.attachToDisplayNode(sdPlotPane); energyVsMomentPlot = plotFactory.basicPlot(); @@ -362,6 +371,7 @@ public void initialize() { rightAxis.setTickFormat(TickFormat.LOG10_DYNE_CM_TO_MW); energyVsMomentPlot.addAxes(rightAxis); energyVsMomentPlot.showLegend(false); + energyVsMomentPlot.setMargin(30, 40, 50, 50); energyVsMomentPlot.attachToDisplayNode(energyVsMomentPane); apparentStressVsMomentPlot = plotFactory.basicPlot(); @@ -372,6 +382,7 @@ public void initialize() { rightAxis.setTickFormat(TickFormat.LOG10_DYNE_CM_TO_MW); apparentStressVsMomentPlot.addAxes(rightAxis); apparentStressVsMomentPlot.showLegend(false); + apparentStressVsMomentPlot.setMargin(30, 40, 50, 50); apparentStressVsMomentPlot.attachToDisplayNode(apparentStressVsMomentPane); cornerFreqVsMomentPlot = plotFactory.basicPlot(); @@ -382,6 +393,7 @@ public void initialize() { rightAxis.setTickFormat(TickFormat.LOG10_DYNE_CM_TO_MW); cornerFreqVsMomentPlot.addAxes(rightAxis); cornerFreqVsMomentPlot.showLegend(false); + cornerFreqVsMomentPlot.setMargin(30, 40, 50, 50); cornerFreqVsMomentPlot.attachToDisplayNode(cornerFreqVsMomentPane); evidCombo.valueProperty().addListener((observable, oldValue, newValue) -> { @@ -417,6 +429,7 @@ public void initialize() { CellBindingUtils.attachTextCellFactories(measuredMwUq2LowCol, MeasuredMwDetails::getMw2Min, dfmt4); CellBindingUtils.attachTextCellFactories(measuredMwUq2HighCol, MeasuredMwDetails::getMw2Max, dfmt4); CellBindingUtils.attachTextCellFactories(bandCoverageCol, MeasuredMwDetails::getBandCoverage, dfmt4); + CellBindingUtils.attachTextCellFactoriesString(likelyPoorlyConstrainedCol, mw -> mw.isLikelyPoorlyConstrained().toString()); stationCountCol.setCellValueFactory( x -> Bindings.createIntegerBinding(() -> Optional.ofNullable(x).map(CellDataFeatures::getValue).map(MeasuredMwDetails::getStationCount).orElseGet(() -> 0)).asObject()); @@ -436,6 +449,16 @@ public void initialize() { menu.getItems().add(exclude); summaryTable.setItems(summaryValues); + summaryTable.setRowFactory(table -> new TableRow>() { + @Override + protected void updateItem(Pair entry, boolean b) { + super.updateItem(entry, b); + if (entry != null && entry.getLeft() != null && entry.getLeft().contains("(Model Fit)")) { + setStyle("-fx-font-weight:bold;"); + } + } + }); + CellBindingUtils.attachTextCellFactoriesString(summaryNameCol, Pair::getLeft); CellBindingUtils.attachTextCellFactoriesString(summaryValueCol, Pair::getRight); summaryNameCol.setCellFactory(param -> new TextWrappingTableCell()); @@ -469,6 +492,9 @@ private void plotSpectra() { }); final List fittingSpectra; + boolean likelyPoorlyConstrained = false; + MeasuredMwDetails mwDetails = null; + if (evidCombo != null && evidCombo.getSelectionModel().getSelectedIndex() > 0) { filteredMeasurements = filterToEvent(evidCombo.getSelectionModel().getSelectedItem(), spectralMeasurements); fittingSpectra = getFitSpectra(); @@ -476,45 +502,59 @@ private void plotSpectra() { fittingSpectra.add(referenceSpectra); final Spectra validationSpectra = spectraClient.getValidationSpectra(evidCombo.getSelectionModel().getSelectedItem()).block(Duration.ofSeconds(2)); fittingSpectra.add(validationSpectra); + if (filteredMeasurements != null && !filteredMeasurements.isEmpty() && filteredMeasurements.get(0).getWaveform() != null) { final Event event = filteredMeasurements.get(0).getWaveform().getEvent(); - summaryValues.add(new Pair<>("Date", DateTimeFormatter.ISO_LOCAL_DATE.withZone(ZoneId.of("UTC")).format(event.getOriginTime().toInstant()))); - summaryValues.add(new Pair<>("JDay", Integer.toString(new TimeT(event.getOriginTime().toInstant().getEpochSecond()).getJDay()))); + String date = DateTimeFormatter.ISO_LOCAL_DATE.withZone(ZoneId.of("UTC")).format(event.getOriginTime().toInstant()); + String jDay = Integer.toString(new TimeT(event.getOriginTime()).getJdate()).substring(4); + summaryValues.add(new Pair<>("Date", String.format("%s (%s)", date, jDay))); summaryValues.add(new Pair<>("Origin Time", DateTimeFormatter.ISO_TIME.withZone(ZoneId.of("UTC")).format(event.getOriginTime().toInstant()))); - summaryValues.add(new Pair<>("Latitude", dfmt4.format(event.getLatitude()))); summaryValues.add(new Pair<>("Longitude", dfmt4.format(event.getLongitude()))); - summaryValues.add(new Pair<>("Depth", dfmt2.format(event.getDepth()))); + summaryValues.add(new Pair<>("Depth (km)", dfmt2.format(event.getDepth()))); + summaryValues.add(null); // Adds space between sections + if (mwParameters != null && !mwParameters.isEmpty()) { + mwDetails = mwParameters.stream().filter(mwDetail -> event.getEventId().equalsIgnoreCase(mwDetail.getEventId())).findAny().orElseGet(MeasuredMwDetails::new); + likelyPoorlyConstrained = mwDetails.isLikelyPoorlyConstrained(); + } } else { filteredMeasurements = Collections.emptyList(); } final Spectra fitSpectra = fittingSpectra.get(0); - summaryValues.add(new Pair<>("Observed Total Energy", dfmt2.format(fitSpectra.getLogTotalEnergy()) + " J")); - summaryValues.add(new Pair<>("Observed Apparent Stress", dfmt2.format(fitSpectra.getObsAppStress()) + " MPa")); - summaryValues.add(new Pair<>("Observed to Extrapolated energy", dfmt2.format(100.0 * (Math.pow(10, fitSpectra.getObsEnergy()) / Math.pow(10, fitSpectra.getLogTotalEnergy()))))); + summaryValues.add(new Pair<>("Mw (Model Fit)", dfmt2.format(fitSpectra.getMw()))); - summaryValues.add(new Pair<>("Model Fit Mw", dfmt2.format(fitSpectra.getMw()))); - if (fitSpectra.getApparentStress() != 0.0) { - summaryValues.add(new Pair<>("Model Fit Apparent Stress", dfmt2.format(fitSpectra.getApparentStress()) + " MPa")); + if (fitSpectra.getApparentStress() > 0.0) { + summaryValues.add(new Pair<>("Apparent Stress (Model Fit)", dfmt2.format(fitSpectra.getApparentStress()) + " MPa")); } - summaryValues.add(new Pair<>("Model Fit Energy", dfmt2.format(fitSpectra.getlogTotalEnergyMDAC()) + " J")); + summaryValues.add(new Pair<>("Energy (Model Fit)", dfmt2.format(fitSpectra.getlogTotalEnergyMDAC()) + " J")); + + summaryValues.add(new Pair<>("Observed Total Energy", dfmt2.format(fitSpectra.getLogTotalEnergy()) + " J")); + summaryValues.add(new Pair<>("Observed Apparent Stress", dfmt2.format(fitSpectra.getObsAppStress()) + " MPa")); + summaryValues.add(new Pair<>("Observed / Total Energy", dfmt2.format(100.0 * (Math.pow(10, fitSpectra.getObsEnergy()) / Math.pow(10, fitSpectra.getLogTotalEnergy()))) + " %")); + summaryValues.add( + new Pair<>("Extrapolated / Total Energy", dfmt2.format(100.0 - (100.0 * (Math.pow(10, fitSpectra.getObsEnergy()) / Math.pow(10, fitSpectra.getLogTotalEnergy())))) + " %")); if (referenceSpectra != null && SPECTRA_TYPES.REF.equals(referenceSpectra.getType())) { summaryValues.add(new Pair<>("Reference Mw", dfmt2.format(referenceSpectra.getMw()))); - if (referenceSpectra.getApparentStress() != 0.0) { + if (referenceSpectra.getApparentStress() > 0.0) { summaryValues.add(new Pair<>("Reference Apparent Stress", dfmt2.format(referenceSpectra.getApparentStress()) + " MPa")); } } - if (validationSpectra != null && SPECTRA_TYPES.REF.equals(validationSpectra.getType())) { + if (validationSpectra != null && SPECTRA_TYPES.VAL.equals(validationSpectra.getType())) { summaryValues.add(new Pair<>("Validation Mw", dfmt2.format(validationSpectra.getMw()))); - if (validationSpectra.getApparentStress() != 0.0) { + if (validationSpectra.getApparentStress() > 0.0) { summaryValues.add(new Pair<>("Validation Apparent Stress", dfmt2.format(validationSpectra.getApparentStress()) + " MPa")); } } + if (mwDetails != null) { + summaryValues.add(null); + summaryValues.add(new Pair<>("Iterations", Integer.toString(mwDetails.getIterations()))); + summaryValues.add(new Pair<>("Data Count", Integer.toString(mwDetails.getDataCount()))); + } } else { filteredMeasurements = spectralMeasurements; fittingSpectra = null; @@ -522,6 +562,8 @@ private void plotSpectra() { final List selectedEventMeasurements = filteredMeasurements; + final boolean showPoorlyConstrainedBanner = likelyPoorlyConstrained; + spectraControllers.forEach(spc -> { if (fittingSpectra != null && spc.shouldShowFits()) { spc.getSpectralPlot().plotXYdata(toPlotPoints(selectedEventMeasurements, spc.getDataFunc()), fittingSpectra); @@ -529,6 +571,8 @@ private void plotSpectra() { spc.getSpectralPlot().plotXYdata(toPlotPoints(selectedEventMeasurements, spc.getDataFunc()), null); } spc.getSpectraMeasurementMap().putAll(mapSpectraToPoint(selectedEventMeasurements, spc.getDataFunc())); + + spc.showConstraintWarningBanner(showPoorlyConstrainedBanner); }); mapMeasurements(selectedEventMeasurements); @@ -669,8 +713,8 @@ protected void reloadData() { double maxMw = 0.0; double minEnergy = 10.0; double maxEnergy = 0.0; - double minStress = 1.0; - double maxStress = 0.0; + double minStress = 0.01; + double maxStress = 100.0; for (final MeasuredMwDetails ev : evs) { mwParameters.add(ev); @@ -712,7 +756,7 @@ protected void reloadData() { maxMw = ref; } - final Symbol mwSym = plotFactory.createSymbol(SymbolStyles.CIRCLE, "Reference", mw, ref, Color.RED, Color.RED, Color.RED, ev.getEventId(), false); + final Symbol mwSym = plotFactory.createSymbol(SymbolStyles.CIRCLE, "Ref.", mw, ref, Color.RED, Color.RED, Color.RED, ev.getEventId(), false); mwPlotSymbols.add(mwSym); } @@ -725,7 +769,7 @@ protected void reloadData() { maxMw = valMw; } - final Symbol valSym = plotFactory.createSymbol(SymbolStyles.SQUARE, "Validation", mw, valMw, Color.BLACK, Color.BLACK, Color.BLACK, ev.getEventId(), false); + final Symbol valSym = plotFactory.createSymbol(SymbolStyles.SQUARE, "Val.", mw, valMw, Color.BLACK, Color.BLACK, Color.BLACK, ev.getEventId(), false); valSym.setZindex(VALIDATION_Z_ORDER); mwPlotSymbols.add(valSym); } @@ -753,7 +797,7 @@ protected void reloadData() { maxStress = refStress; } - final Symbol stressSym = plotFactory.createSymbol(SymbolStyles.CIRCLE, "Reference", stress, refStress, Color.RED, Color.RED, Color.RED, ev.getEventId(), false); + final Symbol stressSym = plotFactory.createSymbol(SymbolStyles.CIRCLE, "Ref.", stress, refStress, Color.RED, Color.RED, Color.RED, ev.getEventId(), false); stressPlotSymbols.add(stressSym); } @@ -766,7 +810,7 @@ protected void reloadData() { maxStress = valStress; } - final Symbol valSym = plotFactory.createSymbol(SymbolStyles.SQUARE, "Validation", stress, valStress, Color.BLACK, Color.BLACK, Color.BLACK, ev.getEventId(), false); + final Symbol valSym = plotFactory.createSymbol(SymbolStyles.SQUARE, "Val.", stress, valStress, Color.BLACK, Color.BLACK, Color.BLACK, ev.getEventId(), false); valSym.setZindex(VALIDATION_Z_ORDER); stressPlotSymbols.add(valSym); } @@ -1067,38 +1111,47 @@ private void listener(final WaveformChangeEvent wce) { protected void handlePlotObjectClicked(final PlotObjectClick poc, final Function measurementFunc) { List points = poc.getPlotPoints(); Set waveforms = new HashSet<>(); + List selectedData = spectraControllers.stream() + .flatMap(spc -> spc.getSpectralPlot().getSelectedPoints().stream()) + .map(measurementFunc::apply) + .map(SpectraMeasurement::getWaveform) + .collect(Collectors.toList()); + + if (poc.getMouseEvent().isPrimaryButtonDown() || selectedData.isEmpty()) { + // FIXME: This entire scheme is tremendously inefficient and needs a rework at + // some point. + for (SpectraPlotController spc : spectraControllers) { + spc.getSpectralPlot().deselectAllPoints(); + } - // FIXME: This entire scheme is tremendously inefficient and needs a rework at - // some point. - for (SpectraPlotController spc : spectraControllers) { - spc.getSpectralPlot().deselectAllPoints(); - } - - for (Point2D point : points) { - SpectraMeasurement spectra = measurementFunc.apply(point); - if (spectra != null && spectra.getWaveform() != null) { - waveforms.add(spectra.getWaveform()); - for (SpectraPlotController spc : spectraControllers) { - if (poc.getMouseEvent().isPrimaryButtonDown()) { - final Map> symbolMap = spc.getSymbolMap(); - final SpectralPlot plot = spc.getSpectralPlot(); - //Dyne-cm to Newton-meter - Point2D testPoint = new Point2D(point.getX(), point.getY() - 7.0); - final boolean existsInPlot = symbolMap.containsKey(testPoint); - if (existsInPlot) { - plot.selectPoint(testPoint); + for (Point2D point : points) { + SpectraMeasurement spectra = measurementFunc.apply(point); + if (spectra != null && spectra.getWaveform() != null) { + waveforms.add(spectra.getWaveform()); + for (SpectraPlotController spc : spectraControllers) { + if (poc.getMouseEvent().isPrimaryButtonDown()) { + final Map> symbolMap = spc.getSymbolMap(); + final SpectralPlot plot = spc.getSpectralPlot(); + //Dyne-cm to Newton-meter + Point2D testPoint = new Point2D(point.getX(), point.getY() - 7.0); + final boolean existsInPlot = symbolMap.containsKey(testPoint); + if (existsInPlot) { + plot.selectPoint(testPoint); + } } } } } - } - spectraControllers.forEach(spc -> spc.getSpectralPlot().getSubplot().replot()); - - if (poc.getMouseEvent().isPrimaryButtonDown()) { - showWaveformPopup(waveforms.stream().map(Waveform::getId).collect(Collectors.toSet()).toArray(new Long[0])); - Platform.runLater(() -> menu.hide()); + spectraControllers.forEach(spc -> spc.getSpectralPlot().getSubplot().replot()); + if (poc.getMouseEvent().isPrimaryButtonDown()) { + showWaveformPopup(waveforms.stream().map(Waveform::getId).collect(Collectors.toSet()).toArray(new Long[0])); + Platform.runLater(() -> menu.hide()); + } else { + showContextMenu(waveforms, points, poc.getMouseEvent(), this::setSymbolsActive); + } } else if (poc.getMouseEvent().isSecondaryButtonDown()) { + waveforms.addAll(selectedData); showContextMenu(waveforms, points, poc.getMouseEvent(), this::setSymbolsActive); } } diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/DataController.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/DataController.java index 5445aa65..1491d690 100644 --- a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/DataController.java +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/DataController.java @@ -29,6 +29,7 @@ import java.util.stream.Collectors; import javax.annotation.PreDestroy; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -56,7 +57,6 @@ import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.event.ActionEvent; -import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.scene.control.CheckBox; import javafx.scene.control.ContextMenu; @@ -72,374 +72,363 @@ @Component public class DataController implements MapListeningController, RefreshableController { - private static final Logger log = LoggerFactory.getLogger(DataController.class); + private static final Logger log = LoggerFactory.getLogger(DataController.class); - @FXML - private MenuItem importWaveforms; + @FXML + private MenuItem importWaveforms; - @FXML - private ScrollPane scrollPane; + @FXML + private ScrollPane scrollPane; - @FXML - private TableView tableView; - - @FXML - private CheckBox selectAllCheckbox; - - @FXML - private TableColumn usedCol; - - @FXML - private TableColumn stationCol; - - @FXML - private TableColumn networkCol; - - @FXML - private TableColumn eventCol; - - @FXML - private TableColumn lowFreqCol; - - @FXML - private TableColumn highFreqCol; - - @FXML - private TableColumn depthCol; - - private ObservableList listData = FXCollections - .synchronizedObservableList(FXCollections.observableArrayList()); - - private GeoMap mapImpl; - - private MapPlottingUtilities iconFactory; - - private WaveformClient client; - - private EventBus bus; - - private NumberFormat dfmt2 = NumberFormatFactory.twoDecimalOneLeadingZero(); - - private EventStaFreqStringComparator eventStaFreqComparator = new EventStaFreqStringComparator(); - - private ListChangeListener tableChangeListener; - - private final BiConsumer eventSelectionCallback; - private final BiConsumer stationSelectionCallback; - private ScheduledExecutorService scheduled = Executors.newSingleThreadScheduledExecutor(r -> { - Thread thread = new Thread(r); - thread.setName("Data-Scheduled"); - thread.setDaemon(true); - return thread; - }); - - private List dataUpdateList = Collections.synchronizedList(new ArrayList<>()); - private List dataDeleteList = Collections.synchronizedList(new ArrayList<>()); - private List updatedData = Collections.synchronizedList(new ArrayList<>()); - - private boolean isVisible = false; - - @Autowired - public DataController(WaveformClient client, GeoMap mapImpl, MapPlottingUtilities iconFactory, EventBus bus) { - super(); - this.client = client; - this.mapImpl = mapImpl; - this.iconFactory = iconFactory; - this.bus = bus; - bus.register(this); - tableChangeListener = buildTableListener(); - scheduled.scheduleWithFixedDelay(() -> updateData(), 1000l, 1000l, TimeUnit.MILLISECONDS); - - eventSelectionCallback = (selected, eventId) -> { - selectDataByCriteria(bus, selected, - (w) -> w.getEvent() != null && w.getEvent().getEventId().equalsIgnoreCase(eventId)); - }; - - stationSelectionCallback = (selected, stationId) -> { - selectDataByCriteria(bus, selected, (w) -> w.getStream() != null && w.getStream().getStation() != null - && w.getStream().getStation().getStationName().equalsIgnoreCase(stationId)); - }; - } - - private void selectDataByCriteria(EventBus bus, Boolean selected, Function matchCriteria) { - List selection = new ArrayList<>(); - List selectionIndices = new ArrayList<>(); - tableView.getSelectionModel().clearSelection(); - if (selected) { - tableView.getSelectionModel().getSelectedItems().removeListener(tableChangeListener); - synchronized (listData) { - for (int i = 0; i < listData.size(); i++) { - Waveform w = listData.get(i); - if (matchCriteria.apply(w)) { - selection.add(w); - tableView.getSelectionModel().select(i); - } - } - } - tableView.getSelectionModel().getSelectedItems().addListener(tableChangeListener); - if (!selection.isEmpty()) { - selection.sort(eventStaFreqComparator); - Long[] ids = selection.stream().sequential().map(w -> w.getId()).collect(Collectors.toList()) - .toArray(new Long[0]); - bus.post(new WaveformSelectionEvent(ids)); - } - } else { - selection.addAll(tableView.getSelectionModel().getSelectedItems()); - selectionIndices.addAll(tableView.getSelectionModel().getSelectedIndices()); - for (int i = 0; i < selection.size(); i++) { - if (matchCriteria.apply(selection.get(i))) { - tableView.getSelectionModel().clearSelection(selectionIndices.get(i)); - } - } - } - } - - private ListChangeListener buildTableListener() { - return (ListChangeListener) change -> { - List selection = new ArrayList<>(); - selection.addAll(tableView.getSelectionModel().getSelectedItems()); - selection.sort(eventStaFreqComparator); - Long[] ids = getIds(selection).toArray(new Long[0]); - bus.post(new WaveformSelectionEvent(ids)); - }; - } - - private List getIds(List selection) { - return selection.stream().sequential().map(w -> w.getId()).collect(Collectors.toList()); - } - - @FXML - private void reloadTable(ActionEvent e) { - CompletableFuture.runAsync(getRefreshFunction()); - } - - @FXML - public void initialize() { - tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); - eventCol.setCellValueFactory( - x -> Bindings.createStringBinding(() -> Optional.ofNullable(x).map(CellDataFeatures::getValue) - .map(Waveform::getEvent).map(Event::getEventId).orElseGet(String::new))); - eventCol.comparatorProperty().set(new MaybeNumericStringComparator()); - - CellBindingUtils.attachTextCellFactories(lowFreqCol, Waveform::getLowFrequency, dfmt2); - CellBindingUtils.attachTextCellFactories(highFreqCol, Waveform::getHighFrequency, dfmt2); - - depthCol.setCellValueFactory( - x -> Bindings.createStringBinding(() -> Optional.ofNullable(x).map(CellDataFeatures::getValue) - .map(Waveform::getEvent).map(ev -> dfmt2.format(ev.getDepth())).orElseGet(String::new))); - depthCol.comparatorProperty().set(new MaybeNumericStringComparator()); - - usedCol.setCellValueFactory(x -> Bindings - .createObjectBinding(() -> Optional.ofNullable(x).map(CellDataFeatures::getValue).map(waveform -> { - CheckBox box = new CheckBox(); - box.setSelected(waveform.isActive()); - if (!waveform.isActive()) { - box.setStyle("-fx-background-color: red"); - } else { - box.setStyle(""); - } - box.selectedProperty().addListener((obs, o, n) -> { - if (n != null && !o.equals(n)) { - client.setWaveformsActiveByIds(Collections.singletonList(waveform.getId()), n).subscribe(); - } - }); - return box; - }).orElseGet(CheckBox::new))); - usedCol.comparatorProperty().set((c1, c2) -> Boolean.compare(c1.isSelected(), c2.isSelected())); - - stationCol.setCellValueFactory(x -> Bindings.createStringBinding( - () -> Optional.ofNullable(x).map(CellDataFeatures::getValue).map(Waveform::getStream) - .map(Stream::getStation).map(Station::getStationName).orElseGet(String::new))); - - networkCol.setCellValueFactory(x -> Bindings.createStringBinding( - () -> Optional.ofNullable(x).map(CellDataFeatures::getValue).map(Waveform::getStream) - .map(Stream::getStation).map(Station::getNetworkName).orElseGet(String::new))); - - tableView.getSelectionModel().getSelectedItems().addListener(tableChangeListener); - // Workaround for https://bugs.openjdk.java.net/browse/JDK-8095943, for now we - // just clear the selection to avoid dumping a stack trace in the logs and - // mucking up event bubbling - tableView.setOnSort(event -> { - if (tableView.getSelectionModel().getSelectedIndices().size() > 1) { - tableView.getSelectionModel().clearSelection(); - } - }); - - ContextMenu menu = new ContextMenu(); - MenuItem include = new MenuItem("Include Selected"); - include.setOnAction(evt -> includeWaveforms()); - menu.getItems().add(include); - MenuItem exclude = new MenuItem("Exclude Selected"); - exclude.setOnAction(evt -> excludeWaveforms()); - menu.getItems().add(exclude); - - tableView.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler() { - @Override - public void handle(MouseEvent t) { - - if (MouseButton.SECONDARY == t.getButton()) { - menu.show(tableView, t.getScreenX(), t.getScreenY()); - } else { - menu.hide(); - } - } - }); - tableView.setItems(listData); - - DataFilterController filterController = new DataFilterController<>(tableView); - - filterController.addFilterToColumn(eventCol, (data, item) -> data.getEvent().getEventId().equals(item)); - filterController.addFilterToColumn(depthCol, - (data, item) -> dfmt2.format(data.getEvent().getDepth()).equals(item)); - filterController.addFilterToColumn(lowFreqCol, (data, item) -> data.getLowFrequency().toString().equals(item)); - filterController.addFilterToColumn(highFreqCol, - (data, item) -> data.getHighFrequency().toString().equals(item)); - filterController.addFilterToColumn(stationCol, - (data, item) -> data.getStream().getStation().getStationName().equals(item)); - filterController.addFilterToColumn(networkCol, - (data, item) -> data.getStream().getStation().getNetworkName().equals(item)); - } - - @Override - public void refreshView() { - CompletableFuture.runAsync(() -> { - synchronized (listData) { - if (!listData.isEmpty()) { - refreshMap(); - } - } - Platform.runLater(() -> { - if (tableView != null) { - tableView.refresh(); - } - }); - }); - } - - private void refreshMap() { - if (isVisible) { - mapImpl.clearIcons(); - synchronized (listData) { - mapImpl.addIcons( - iconFactory.genIconsFromWaveforms(eventSelectionCallback, stationSelectionCallback, listData)); - } - } - } - - @Override - public Runnable getRefreshFunction() { - return () -> requestData(); - } - - private void requestData() { - synchronized (listData) { - listData.clear(); - } - mapImpl.clearIcons(); - client.getUniqueEventStationMetadataForStacks().filter(Objects::nonNull).doOnComplete(() -> { - tableView.sort(); - refreshView(); - }).subscribe(waveform -> { - synchronized (listData) { - listData.add(waveform); - } - }, err -> log.error(err.getMessage(), err)); - } - - private void requestUpdates() { - List updates = new ArrayList<>(); - List deletes = new ArrayList<>(); - synchronized (dataUpdateList) { - updates.addAll(dataUpdateList); - dataUpdateList.clear(); - deletes.addAll(dataDeleteList); - dataDeleteList.clear(); - } - - if (!deletes.isEmpty()) { - synchronized (listData) { - deletes.forEach(id -> { - synchronized (listData) { - int idx = -1; - for (int i = 0; i < listData.size(); i++) { - if (listData.get(i).getId().equals(id)) { - idx = i; - break; - } - } - if (idx >= 0) { - listData.remove(idx); - } - } - }); - } - } - - client.getWaveformMetadataFromIds(updates).filter(Objects::nonNull) - .subscribe(waveform -> updatedData.add(waveform), err -> log.error(err.getMessage(), err)); - } - - private void updateData() { - List updates = new ArrayList<>(); - synchronized (updatedData) { - updates.addAll(updatedData); - updatedData.clear(); - } - if (!updates.isEmpty()) { - synchronized (listData) { - tableView.getSelectionModel().getSelectedItems().removeListener(tableChangeListener); - updates.forEach(waveform -> { - int idx = -1; - for (int i = 0; i < listData.size(); i++) { - if (listData.get(i).getId().equals(waveform.getId())) { - idx = i; - break; - } - } - if (idx >= 0) { - listData.set(idx, waveform); - } else { - listData.add(waveform); - } - }); - tableView.getSelectionModel().getSelectedItems().addListener(tableChangeListener); - } - refreshMap(); - } - } - - private void excludeWaveforms() { - client.setWaveformsActiveByIds(getSelectedWaveforms(), false).subscribe(); - } - - private void includeWaveforms() { - client.setWaveformsActiveByIds(getSelectedWaveforms(), true).subscribe(); - } - - private List getSelectedWaveforms() { - return getIds(tableView.getSelectionModel().getSelectedItems()); - } - - @Subscribe - private void listener(WaveformChangeEvent wce) { - List nonNull = wce.getIds().stream().filter(Objects::nonNull).collect(Collectors.toList()); - synchronized (dataUpdateList) { - if (wce.isAddOrUpdate()) { - dataUpdateList.addAll(nonNull); - } else if (wce.isDelete()) { - dataDeleteList.addAll(nonNull); - } - } - requestUpdates(); - } - - @PreDestroy - private void cleanup() { - scheduled.shutdownNow(); - } - - @Override - public void setVisible(boolean visible) { - isVisible = visible; - } + @FXML + private TableView tableView; + + @FXML + private CheckBox selectAllCheckbox; + + @FXML + private TableColumn usedCol; + + @FXML + private TableColumn stationCol; + + @FXML + private TableColumn networkCol; + + @FXML + private TableColumn eventCol; + + @FXML + private TableColumn lowFreqCol; + + @FXML + private TableColumn highFreqCol; + + @FXML + private TableColumn depthCol; + + private ObservableList listData = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); + + private GeoMap mapImpl; + + private MapPlottingUtilities iconFactory; + + private WaveformClient client; + + private EventBus bus; + + private NumberFormat dfmt2 = NumberFormatFactory.twoDecimalOneLeadingZero(); + + private EventStaFreqStringComparator eventStaFreqComparator = new EventStaFreqStringComparator(); + + private ListChangeListener tableChangeListener; + + private final BiConsumer eventSelectionCallback; + private final BiConsumer stationSelectionCallback; + private ScheduledExecutorService scheduled = Executors.newSingleThreadScheduledExecutor(r -> { + Thread thread = new Thread(r); + thread.setName("Data-Scheduled"); + thread.setDaemon(true); + return thread; + }); + + private List dataUpdateList = Collections.synchronizedList(new ArrayList<>()); + private List dataDeleteList = Collections.synchronizedList(new ArrayList<>()); + private List updatedData = Collections.synchronizedList(new ArrayList<>()); + + private boolean isVisible = false; + + @Autowired + public DataController(WaveformClient client, GeoMap mapImpl, MapPlottingUtilities iconFactory, EventBus bus) { + this.client = client; + this.mapImpl = mapImpl; + this.iconFactory = iconFactory; + this.bus = bus; + bus.register(this); + tableChangeListener = buildTableListener(); + scheduled.scheduleWithFixedDelay(this::updateData, 1000l, 1000l, TimeUnit.MILLISECONDS); + + eventSelectionCallback = (selected, eventId) -> { + selectDataByCriteria(bus, selected, w -> w.getEvent() != null && w.getEvent().getEventId().equalsIgnoreCase(eventId)); + }; + + stationSelectionCallback = (selected, stationId) -> { + selectDataByCriteria(bus, selected, w -> w.getStream() != null && w.getStream().getStation() != null && w.getStream().getStation().getStationName().equalsIgnoreCase(stationId)); + }; + } + + private void selectDataByCriteria(EventBus bus, Boolean selected, Function matchCriteria) { + List selection = new ArrayList<>(); + List selectionIndices = new ArrayList<>(); + tableView.getSelectionModel().clearSelection(); + if (selected) { + tableView.getSelectionModel().getSelectedItems().removeListener(tableChangeListener); + synchronized (listData) { + for (int i = 0; i < listData.size(); i++) { + Waveform w = listData.get(i); + if (matchCriteria.apply(w)) { + selection.add(w); + tableView.getSelectionModel().select(i); + } + } + } + tableView.getSelectionModel().getSelectedItems().addListener(tableChangeListener); + if (!selection.isEmpty()) { + if (tableView.getSortOrder().isEmpty()) { + selection.sort(eventStaFreqComparator); + } + Long[] ids = selection.stream().sequential().map(Waveform::getId).collect(Collectors.toList()).toArray(new Long[0]); + bus.post(new WaveformSelectionEvent(ids)); + } + } else { + selection.addAll(tableView.getSelectionModel().getSelectedItems()); + selectionIndices.addAll(tableView.getSelectionModel().getSelectedIndices()); + for (int i = 0; i < selection.size(); i++) { + if (matchCriteria.apply(selection.get(i))) { + tableView.getSelectionModel().clearSelection(selectionIndices.get(i)); + } + } + } + } + + private ListChangeListener buildTableListener() { + return (ListChangeListener) change -> { + if (!change.getList().isEmpty()) { + List selection = new ArrayList<>(tableView.getSelectionModel().getSelectedItems()); + if (tableView.getSortOrder().isEmpty()) { + selection.sort(eventStaFreqComparator); + } + Long[] ids = getIds(selection).toArray(new Long[0]); + bus.post(new WaveformSelectionEvent(ids)); + } + }; + } + + private List getIds(List selection) { + return selection.stream().sequential().map(Waveform::getId).collect(Collectors.toList()); + } + + @FXML + private void reloadTable(ActionEvent e) { + CompletableFuture.runAsync(getRefreshFunction()); + } + + @FXML + public void initialize() { + tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + eventCol.setCellValueFactory( + x -> Bindings.createStringBinding(() -> Optional.ofNullable(x).map(CellDataFeatures::getValue).map(Waveform::getEvent).map(Event::getEventId).orElseGet(String::new))); + eventCol.comparatorProperty().set(new MaybeNumericStringComparator()); + + CellBindingUtils.attachTextCellFactories(lowFreqCol, Waveform::getLowFrequency, dfmt2); + CellBindingUtils.attachTextCellFactories(highFreqCol, Waveform::getHighFrequency, dfmt2); + + depthCol.setCellValueFactory( + x -> Bindings.createStringBinding(() -> Optional.ofNullable(x).map(CellDataFeatures::getValue).map(Waveform::getEvent).map(ev -> dfmt2.format(ev.getDepth())).orElseGet(String::new))); + depthCol.comparatorProperty().set(new MaybeNumericStringComparator()); + + usedCol.setCellValueFactory(x -> Bindings.createObjectBinding(() -> Optional.ofNullable(x).map(CellDataFeatures::getValue).map(waveform -> { + CheckBox box = new CheckBox(); + box.setSelected(waveform.isActive()); + if (!waveform.isActive()) { + box.setStyle("-fx-background-color: red"); + } else { + box.setStyle(""); + } + box.selectedProperty().addListener((obs, o, n) -> { + if (n != null && !o.equals(n)) { + client.setWaveformsActiveByIds(Collections.singletonList(waveform.getId()), n).subscribe(); + } + }); + return box; + }).orElseGet(CheckBox::new))); + usedCol.comparatorProperty().set((c1, c2) -> Boolean.compare(c1.isSelected(), c2.isSelected())); + + stationCol.setCellValueFactory( + x -> Bindings.createStringBinding( + () -> Optional.ofNullable(x).map(CellDataFeatures::getValue).map(Waveform::getStream).map(Stream::getStation).map(Station::getStationName).orElseGet(String::new))); + + networkCol.setCellValueFactory( + x -> Bindings.createStringBinding( + () -> Optional.ofNullable(x).map(CellDataFeatures::getValue).map(Waveform::getStream).map(Stream::getStation).map(Station::getNetworkName).orElseGet(String::new))); + + tableView.getSelectionModel().getSelectedItems().addListener(tableChangeListener); + // Workaround for https://bugs.openjdk.java.net/browse/JDK-8095943, for now we + // just clear the selection to avoid dumping a stack trace in the logs and + // mucking up event bubbling + tableView.setOnSort(event -> { + if (tableView.getSelectionModel().getSelectedIndices().size() > 1) { + tableView.getSelectionModel().clearSelection(); + } + }); + + ContextMenu menu = new ContextMenu(); + MenuItem include = new MenuItem("Include Selected"); + include.setOnAction(evt -> includeWaveforms()); + menu.getItems().add(include); + MenuItem exclude = new MenuItem("Exclude Selected"); + exclude.setOnAction(evt -> excludeWaveforms()); + menu.getItems().add(exclude); + + tableView.addEventHandler(MouseEvent.MOUSE_CLICKED, t -> { + + if (MouseButton.SECONDARY == t.getButton()) { + menu.show(tableView, t.getScreenX(), t.getScreenY()); + } else { + menu.hide(); + } + }); + + //The filter controller will manage the list so don't add the listData directly + //to the table, otherwise the event handlers will trip over themselves + DataFilterController filterController = new DataFilterController<>(tableView, listData); + + filterController.addFilterToColumn(eventCol, (data, item) -> data.getEvent().getEventId().equals(item)); + filterController.addFilterToColumn(depthCol, (data, item) -> dfmt2.format(data.getEvent().getDepth()).equals(item)); + filterController.addFilterToColumn(lowFreqCol, (data, item) -> data.getLowFrequency().toString().equals(item)); + filterController.addFilterToColumn(highFreqCol, (data, item) -> data.getHighFrequency().toString().equals(item)); + filterController.addFilterToColumn(stationCol, (data, item) -> data.getStream().getStation().getStationName().equals(item)); + filterController.addFilterToColumn(networkCol, (data, item) -> data.getStream().getStation().getNetworkName().equals(item)); + } + + @Override + public void refreshView() { + CompletableFuture.runAsync(() -> { + synchronized (listData) { + if (!listData.isEmpty()) { + refreshMap(); + } + } + Platform.runLater(() -> { + if (tableView != null) { + tableView.refresh(); + } + }); + }); + } + + private void refreshMap() { + if (isVisible) { + mapImpl.clearIcons(); + synchronized (listData) { + mapImpl.addIcons(iconFactory.genIconsFromWaveforms(eventSelectionCallback, stationSelectionCallback, listData)); + } + } + } + + @Override + public Runnable getRefreshFunction() { + return this::requestData; + } + + private void requestData() { + synchronized (listData) { + listData.clear(); + } + mapImpl.clearIcons(); + client.getUniqueEventStationMetadataForStacks().filter(Objects::nonNull).doOnComplete(() -> { + tableView.sort(); + refreshView(); + }).subscribe(waveform -> { + synchronized (listData) { + listData.add(waveform); + } + }, err -> log.error(err.getMessage(), err)); + } + + private void requestUpdates() { + List updates = new ArrayList<>(); + List deletes = new ArrayList<>(); + synchronized (dataUpdateList) { + updates.addAll(dataUpdateList); + dataUpdateList.clear(); + deletes.addAll(dataDeleteList); + dataDeleteList.clear(); + } + + if (!deletes.isEmpty()) { + synchronized (listData) { + deletes.forEach(id -> { + synchronized (listData) { + int idx = -1; + for (int i = 0; i < listData.size(); i++) { + if (listData.get(i).getId().equals(id)) { + idx = i; + break; + } + } + if (idx >= 0) { + listData.remove(idx); + } + } + }); + } + } + + client.getWaveformMetadataFromIds(updates).filter(Objects::nonNull).subscribe(waveform -> updatedData.add(waveform), err -> log.error(err.getMessage(), err)); + } + + private void updateData() { + List updates = new ArrayList<>(); + synchronized (updatedData) { + updates.addAll(updatedData); + updatedData.clear(); + } + if (!updates.isEmpty()) { + synchronized (listData) { + tableView.getSelectionModel().getSelectedItems().removeListener(tableChangeListener); + updates.forEach(waveform -> { + int idx = -1; + for (int i = 0; i < listData.size(); i++) { + if (listData.get(i).getId().equals(waveform.getId())) { + idx = i; + break; + } + } + if (idx >= 0) { + listData.set(idx, waveform); + } else { + listData.add(waveform); + } + }); + tableView.getSelectionModel().getSelectedItems().addListener(tableChangeListener); + } + refreshMap(); + } + } + + private void excludeWaveforms() { + client.setWaveformsActiveByIds(getSelectedWaveforms(), false).subscribe(); + } + + private void includeWaveforms() { + client.setWaveformsActiveByIds(getSelectedWaveforms(), true).subscribe(); + } + + private List getSelectedWaveforms() { + return getIds(tableView.getSelectionModel().getSelectedItems()); + } + + @Subscribe + private void listener(WaveformChangeEvent wce) { + List nonNull = wce.getIds().stream().filter(Objects::nonNull).collect(Collectors.toList()); + synchronized (dataUpdateList) { + if (wce.isAddOrUpdate()) { + dataUpdateList.addAll(nonNull); + } else if (wce.isDelete()) { + dataDeleteList.addAll(nonNull); + } + } + requestUpdates(); + } + + @PreDestroy + private void cleanup() { + scheduled.shutdownNow(); + } + + @Override + public void setVisible(boolean visible) { + isVisible = visible; + } } diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/DataFilterController.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/DataFilterController.java index eda9188d..6102c21e 100644 --- a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/DataFilterController.java +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/DataFilterController.java @@ -13,7 +13,6 @@ */ package gov.llnl.gnem.apps.coda.calibration.gui.controllers; -import java.io.InputStream; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -24,16 +23,15 @@ import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener.Change; import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; import javafx.scene.control.Button; import javafx.scene.control.ContentDisplay; import javafx.scene.control.Label; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; class DataFilterController { - TableView tableView; // The list of unfiltered table items ObservableList items; FilterDialogController filterDialog; @@ -41,38 +39,41 @@ class DataFilterController { // The filter buttons attached to columns private List