/// <summary> /// Initializes a new instance of the SpectrumPlotViewModel class. /// </summary> /// <param name="dialogService">Dialog service for opening dialogs from ViewModel.</param> /// <param name="fragSeqVm">Gets or sets the view model for the fragmentation sequence (fragment ion generator)</param> /// <param name="multiplier">How much padding should be before the lowest peak and after the highest peak?</param> /// <param name="autoZoomXAxis">Should this view model automatically zoom the plot?</param> public SpectrumPlotViewModel(IMainDialogService dialogService, IFragmentationSequenceViewModel fragSeqVm, double multiplier, bool autoZoomXAxis = true) { this.dialogService = dialogService; FragmentationSequenceViewModel = fragSeqVm; this.autoZoomXAxis = autoZoomXAxis; errorMapViewModel = new ErrorMapViewModel(dialogService); ShowUnexplainedPeaks = true; ShowFilteredSpectrum = false; ShowDeconvolutedSpectrum = false; AutoAdjustYAxis = true; Title = string.Empty; XAxis = new LinearAxis { Title = "m/z", StringFormat = "0.###", Position = AxisPosition.Bottom, }; PlotModel = new AutoAdjustedYPlotModel(XAxis, multiplier) { IsLegendVisible = false, YAxis = { Title = "Intensity", StringFormat = "0e0" } }; SequenceViewerViewModel = new SequenceViewerViewModel(); ions = new LabeledIonViewModel[0]; // When Spectrum updates, clear the filtered spectrum, deconvoluted spectrum, and filtered+deconvoluted spectrum this.WhenAnyValue(x => x.Spectrum) .Subscribe(spectrum => { spectrumDirty = true; filteredSpectrum = null; deconvolutedSpectrum = null; filteredDeconvolutedSpectrum = null; }); // If deconvolution option has changed, the X Axis should change. this.WhenAnyValue(x => x.ShowDeconvolutedSpectrum) .Subscribe(_ => spectrumDirty = true); // When Spectrum or ions change, or deconvoluted or filtered spectrum are selected, update spectrum plot this.WhenAnyValue( x => x.Spectrum, x => x.FragmentationSequenceViewModel.LabeledIonViewModels, x => x.ShowDeconvolutedSpectrum, x => x.ShowDeconvolutedIons, x => x.ShowFilteredSpectrum, x => x.ShowUnexplainedPeaks, x => x.ShowOnlyTop20Peaks) .Where(x => x.Item1 != null && x.Item2 != null) .Throttle(TimeSpan.FromMilliseconds(400), RxApp.TaskpoolScheduler) .SelectMany(async x => { var vms = await FragmentationSequenceViewModel.GetLabeledIonViewModels(); return(await Task.WhenAll( vms.Select( ion => ion.GetPeaksAsync(GetSpectrum(), ShowDeconvolutedSpectrum || ShowDeconvolutedIons)))); }) .Subscribe(dataPoints => { ions = FragmentationSequenceViewModel.LabeledIonViewModels; SetTerminalResidues(dataPoints); UpdatePlotModel(dataPoints); if (FragmentationSequenceViewModel is FragmentationSequenceViewModel model) { SequenceViewerViewModel.FragmentationSequence = model; SequenceViewerViewModel.SelectedSpectrum = Spectrum as ProductSpectrum; } }); // Update plot when data changes this.WhenAnyValue(x => x.Spectrum).Where(spectrum => spectrum == null).Subscribe( _ => { PlotModel.Annotations.Clear(); PlotModel.ClearSeries(); PlotModel.InvalidatePlot(true); }); // Update ions when relative intensity threshold changes. IcParameters.Instance.WhenAnyValue(x => x.PrecursorRelativeIntensityThreshold).Subscribe(precursorRelInt => { if (FragmentationSequenceViewModel is PrecursorSequenceIonViewModel precursorFragVm) { precursorFragVm.RelativeIntensityThreshold = precursorRelInt; } }); // Update plot when settings change IcParameters.Instance.WhenAnyValue(x => x.ProductIonTolerancePpm, x => x.IonCorrelationThreshold) .Throttle(TimeSpan.FromMilliseconds(400), RxApp.TaskpoolScheduler) .SelectMany(async x => await Task.WhenAll(ions.Select(ion => ion.GetPeaksAsync(GetSpectrum(), ShowDeconvolutedSpectrum, false)))) .Subscribe(UpdatePlotModel); // When AutoAdjustYAxis changes, update value in plot model. this.WhenAnyValue(x => x.AutoAdjustYAxis) .Subscribe(autoAdjust => { PlotModel.AutoAdjustYAxis = autoAdjust; PlotModel.YAxis.IsZoomEnabled = !autoAdjust; PlotModel.YAxis.IsPanEnabled = !autoAdjust; if (autoAdjust) { PlotModel.XAxis.Reset(); PlotModel.YAxis.Reset(); } }); // Update plot axes when FeaturePlotXMin, YMin, XMax, and YMax change this.WhenAnyValue(x => x.XMinimum, x => x.XMaximum) .Throttle(TimeSpan.FromSeconds(1), RxApp.TaskpoolScheduler) .Where(x => !xAxis.ActualMinimum.Equals(x.Item1) || !xAxis.ActualMaximum.Equals(x.Item2)) .Subscribe(x => { xAxis.Zoom(x.Item1, x.Item2); PlotModel.InvalidatePlot(false); }); this.WhenAnyValue(y => y.YMinimum, y => y.YMaximum) .Throttle(TimeSpan.FromSeconds(1), RxApp.TaskpoolScheduler) .Where(y => !PlotModel.YAxis.ActualMinimum.Equals(y.Item1) || !PlotModel.YAxis.ActualMaximum.Equals(y.Item2)) .Subscribe( y => { PlotModel.YAxis.Zoom(y.Item1, y.Item2); PlotModel.InvalidatePlot(false); }); // Update X min and max properties when x axis is panned or zoomed xAxis.AxisChanged += (o, e) => { XMinimum = Math.Round(xAxis.ActualMinimum, 3); XMaximum = Math.Round(xAxis.ActualMaximum, 3); }; // Update Y min and max properties when Y axis is panned or zoomed PlotModel.YAxis.AxisChanged += (o, e) => { YMinimum = Math.Round(PlotModel.YAxis.ActualMinimum, 3); YMaximum = Math.Round(PlotModel.YAxis.ActualMaximum, 3); }; // Save As Image Command requests a file path from the user and then saves the spectrum plot as an image SaveAsImageCommand = ReactiveCommand.Create(SaveAsImageImplementation); // Error map command opens a new error map window and passes it the most abundant isotope peak data points // and the current sequence. OpenErrorMapCommand = ReactiveCommand.Create(() => dialogService.OpenErrorMapWindow(errorMapViewModel)); OpenScanSelectionCommand = ReactiveCommand.Create(OpenScanSelectionImplementation); SaveAsTsvCommand = ReactiveCommand.Create(SaveAsTsvImplementation); SaveToClipboardCommand = ReactiveCommand.Create(SaveToClipboardImplementation); }
/// <summary> /// Initializes a new instance of the <see cref="XicPlotViewModel"/> class. /// </summary> /// <param name="dialogService">Dialog service </param> /// <param name="fragSeqVm">The view model for the fragmentation sequence (fragment ion generator)</param> /// <param name="lcms">LCMS run data set for this XIC plot. </param> /// <param name="title">Title of XIC plot </param> /// <param name="xaxis">XAxis for XIC plot. </param> /// <param name="showLegend">Should a legend be shown on the plot by default? </param> public XicPlotViewModel(IDialogService dialogService, IFragmentationSequenceViewModel fragSeqVm, ILcMsRun lcms, string title, LinearAxis xaxis, bool showLegend = true, AxisPosition vertAxes = AxisPosition.Left) { this.dialogService = dialogService; this.FragmentationSequenceViewModel = fragSeqVm; this.lcms = lcms; this.showLegend = showLegend; this.xaxis = xaxis; this.pointsToSmooth = IcParameters.Instance.PointsToSmooth; this.PlotTitle = title; this.PlotModel = new SelectablePlotModel(xaxis, 1.05) { YAxis = { Title = "Intensity", StringFormat = "0e0", Position = vertAxes } }; this.ions = new LabeledIonViewModel[0]; var retentionTimeSelectedCommand = ReactiveCommand.Create(); retentionTimeSelectedCommand .Select(_ => this.PlotModel.SelectedDataPoint as XicDataPoint) .Where(dp => dp != null) .Subscribe(dp => this.SelectedScan = dp.ScanNum); this.RetentionTimeSelectedCommand = retentionTimeSelectedCommand; var saveAsImageCommand = ReactiveCommand.Create(); saveAsImageCommand.Subscribe(_ => this.SaveAsImage()); this.SaveAsImageCommand = saveAsImageCommand; // When ShowLegend is updated, IsLegendVisible on the plot should be updated this.WhenAnyValue(x => x.ShowLegend).Subscribe(v => { this.PlotModel.IsLegendVisible = v; this.PlotModel.InvalidatePlot(true); }); // When area updates, plot title should update this.WhenAnyValue(x => x.Area).Subscribe(area => { var areaStr = string.Format(CultureInfo.InvariantCulture, "{0:0.##E0}", area); this.PlotTitle = string.Format("{0} (Area: {1})", title, areaStr); }); // Update area when x Axis is zoomed/panned this.xaxis.AxisChanged += async(o, e) => { this.Area = await this.GetCurrentAreaAsync(); }; // Update point marker when selected scan changes this.WhenAnyValue(x => x.SelectedScan, x => x.IsPlotUpdating) .Where(x => x.Item2) .Throttle(TimeSpan.FromMilliseconds(50), RxApp.TaskpoolScheduler) .Subscribe(x => this.PlotModel.SetPrimaryPointMarker(this.lcms.GetElutionTime(x.Item1))); // When point markers are toggled, change the marker type on each series this.WhenAnyValue(x => x.ShowPointMarkers) .Select(showPointMarkers => showPointMarkers ? MarkerType.Circle : MarkerType.None) .Subscribe( markerType => { foreach (var lineSeries in this.PlotModel.Series.OfType <LineSeries>()) { lineSeries.MarkerType = markerType; } this.PlotModel.InvalidatePlot(true); }); this.WhenAnyValue( x => x.FragmentationSequenceViewModel.LabeledIonViewModels, x => x.PointsToSmooth, x => x.IsPlotUpdating) .Where(x => x.Item3 && x.Item1 != null) .SelectMany(async x => await this.GetXicDataPointsAsync(x.Item1, this.PointsToSmooth)) .Subscribe(xicPoints => { this.ions = this.FragmentationSequenceViewModel.LabeledIonViewModels; this.UpdatePlotModel(xicPoints); }); // Update ions when relative intensity threshold changes. IcParameters.Instance.WhenAnyValue(x => x.PrecursorRelativeIntensityThreshold).Subscribe(precRelInt => { var precFragVm = this.FragmentationSequenceViewModel as PrecursorSequenceIonViewModel; if (precFragVm != null) { precFragVm.RelativeIntensityThreshold = precRelInt; } }); // Update plot when settings change IcParameters.Instance.WhenAnyValue(x => x.ProductIonTolerancePpm) .Where(_ => this.ions != null) .Throttle(TimeSpan.FromMilliseconds(250), RxApp.TaskpoolScheduler) .SelectMany(async x => await this.GetXicDataPointsAsync(this.ions, this.PointsToSmooth, false)) .Subscribe(this.UpdatePlotModel); }