OpenMS
Annotation1DPeakItem.h
Go to the documentation of this file.
1 // --------------------------------------------------------------------------
2 // OpenMS -- Open-Source Mass Spectrometry
3 // --------------------------------------------------------------------------
4 // Copyright The OpenMS Team -- Eberhard Karls University Tuebingen,
5 // ETH Zurich, and Freie Universitaet Berlin 2002-2023.
6 //
7 // This software is released under a three-clause BSD license:
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 // * Neither the name of any author or any participating institution
14 // may be used to endorse or promote products derived from this software
15 // without specific prior written permission.
16 // For a full list of authors, refer to the file AUTHORS.
17 // --------------------------------------------------------------------------
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 // ARE DISCLAIMED. IN NO EVENT SHALL ANY OF THE AUTHORS OR THE CONTRIBUTING
22 // INSTITUTIONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
27 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
28 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 //
30 // --------------------------------------------------------------------------
31 // $Maintainer: Johannes Veit $
32 // $Authors: Johannes Junker $
33 // --------------------------------------------------------------------------
34 
35 #pragma once
36 
38 
42 
43 #include <QtGui/QColor>
44 
45 namespace OpenMS
46 {
47 
51  template <class DataPoint> // e.g. Peak1D
53  public Annotation1DItem
54  {
55 public:
57  Annotation1DPeakItem(const DataPoint& peak_position, const QString& text, const QColor& color) :
58  Annotation1DItem(text), peak_position_(peak_position), position_(peak_position), color_(color)
59  {
60  }
61 
64 
66  ~Annotation1DPeakItem() override = default;
67 
68  // Docu in base class
69  void draw(Plot1DCanvas* const canvas, QPainter& painter, bool flipped = false) override
70  {
71  painter.save();
72 
73  painter.setPen(color_);
74 
75  QPoint position_widget, peak_position_widget;
76 
77  // translate units to pixel coordinates
78  canvas->dataToWidget(canvas->getMapper().map(position_), position_widget, flipped);
79  canvas->dataToWidget(canvas->getMapper().map(peak_position_), peak_position_widget, flipped);
80 
81  // pre-compute bounding box of text_item
82  const auto prebox = QApplication::fontMetrics().boundingRect(position_widget.x(), position_widget.y(), 0, 0, Qt::AlignCenter, getText());
83  // Shift position of the widget/text, so it sits 'on top' of the peak
84  // We can only do that there, since we do not know the state of 'flipped' in general
85  // Compute the delta in data-units, NOT pixels, since the shift (up/down, or even left/right) depends on state of 'flipped' and axis
86  const auto deltaXY_in_units = canvas->widgetToDataDistance(prebox.width(), prebox.height()).abs(); // abs() to make sure y axis is not negative
87  const auto delta_gravity_in_units = canvas->getGravitator().swap().gravitateZero(deltaXY_in_units); // only keep gravity dim
88  // recompute 'position_widget', shifting the text up by 1/2 box
89  canvas->dataToWidget(canvas->getMapper().map(position_) + delta_gravity_in_units / 2, position_widget, flipped);
90  // re-compute bounding box of text_item on with new position!
91  bounding_box_ = QApplication::fontMetrics().boundingRect(position_widget.x(), position_widget.y(), 0, 0, Qt::AlignCenter, getText());
92 
93 
94  // draw connection line between anchor point and current position if pixel coordinates differ significantly
95  if ((position_widget - peak_position_widget).manhattanLength() > 2)
96  {
97  QPointF border_point = GUIHelpers::intersectionPoint(bounding_box_, peak_position_widget);
98  if (bounding_box_.center() != border_point)
99  {
100  painter.save();
101  painter.setPen(Qt::DashLine);
102  painter.drawLine(peak_position_widget, border_point);
103  painter.restore();
104  }
105  }
106 
107  // some pretty printing
108  QString text = text_;
109  if (!text.contains(R"(<\)")) // don't process HTML strings again
110  {
111  // extract ion index
112  {
113  QRegExp reg_exp(R"([abcdwxyz](\d+))");
114  int match_pos = reg_exp.indexIn(text);
115 
116  if (match_pos == 0)
117  {
118  QString index_str = reg_exp.cap(1);
119 
120  // put sub html tag around number
121  text = text[match_pos] + QString("<sub>") + index_str + QString("</sub>") + text.right(text.size() - match_pos - index_str.size() - 1);
122  }
123  else // protein-protein XL specific ion names
124  {
125  QRegExp reg_exp_xlms(R"((ci|xi)[$][abcxyz](\d+))");
126  match_pos = reg_exp_xlms.indexIn(text);
127  if ((match_pos == 6) || (match_pos == 7))
128  {
129  // set the match_pos to the position of the ion index
130  match_pos += 3;
131  QString index_str = reg_exp.cap(1);
132 
133  // put sub html tag around number
134  text = text.left(match_pos) + text[match_pos] + QString("<sub>") + index_str + QString("</sub>") + text.right(text.size() - match_pos - index_str.size() - 1);
135  }
136  }
137  }
138 
139  // common losses
140  text.replace("H2O1", "H<sub>2</sub>O"); // mind the order with H2O substitution
141  text.replace("H2O", "H<sub>2</sub>O");
142  text.replace("NH3", "NH<sub>3</sub>");
143  text.replace("H3N1", "NH<sub>3</sub>");
144  text.replace("C1H4O1S1", "H<sub>4</sub>COS"); // methionine sulfoxide loss
145 
146  // nucleotide XL related losses
147  text.replace("H3PO4", "H<sub>3</sub>PO<sub>4</sub>");
148  text.replace("HPO3", "HPO<sub>3</sub>");
149  text.replace("C3O", "C<sub>3</sub>O");
150 
151  // charge format: +z
152  QRegExp charge_rx(R"([\+|\-](\d+)$)");
153  int match_pos = charge_rx.indexIn(text);
154  if (match_pos > 0)
155  {
156  text = text.left(match_pos) + QString("<sup>") + text[match_pos] // + or -
157  + charge_rx.cap(1) + QString("</sup>"); // charge
158  }
159 
160  // charge format: z+
161  charge_rx = QRegExp(R"((\d+)[\+|\-]$)");
162  match_pos = charge_rx.indexIn(text);
163  if (match_pos > 0)
164  {
165  text = text.left(match_pos) + QString("<sup>") + charge_rx.cap(1) // charge
166  + text[match_pos + charge_rx.cap(1).size()] + QString("</sup>"); // + or -
167  }
168 
169  text.replace(QRegExp(R"(\+\+$)"), "<sup>2+</sup>");
170  text.replace(QRegExp(R"(\+$)"), "");
171  text.replace(QRegExp(R"(\-\-$)"), "<sup>2-</sup>");
172  text.replace(QRegExp(R"(\-$)"), "");
173  }
174 
175  text = "<font color=\"" + color_.name() + "\">" + text + "</font>";
176 
177  // draw html text
178  {
179  QTextDocument td;
180  td.setHtml(text);
181  painter.save();
182  double w = td.size().width();
183  double h = td.size().height();
184  painter.translate(position_widget.x() - w / 2, position_widget.y() - h / 2);
185  td.drawContents(&painter);
186  painter.restore();
187  }
188 
189  if (selected_)
190  {
191  drawBoundingBox_(painter);
192  }
193 
194  painter.restore();
195  }
196 
197  // Docu in base class
198  void move(const PointXYType delta, const Gravitator& /*gr*/, const DimMapper<2>& dim_mapper) override
199  {
200  auto pos_xy = dim_mapper.map(position_);
201  pos_xy += delta;
202  dim_mapper.fromXY(pos_xy, position_);
203  }
204 
206  void setPosition(const DataPoint& position)
207  {
208  position_ = position;
209  }
210 
212  const DataPoint& getPosition() const
213  {
214  return position_;
215  }
216 
218  const DataPoint& getPeakPosition() const
219  {
220  return peak_position_;
221  }
222 
223  // Docu in base class
224  void ensureWithinDataRange(Plot1DCanvas* const canvas, const int layer_index) override
225  {
226  canvas->pushIntoDataRange(position_, layer_index);
227  }
228 
230  void setColor(const QColor& color)
231  {
232  color_ = color;
233  }
234 
236  const QColor& getColor() const
237  {
238  return color_;
239  }
240 
243  {
244  // add new fragment annotation
245  QString peak_anno = this->getText().trimmed();
246 
247  // check for newlines in the label and only continue with the first line for charge determination
248  peak_anno.remove('\r');
249  QStringList lines = peak_anno.split('\n');
250  // TODO: replace with 'peak_anno.split('\n', Qt::SkipEmptyParts), which is only supported in Qt 5.14 and above: CONTRIB_UPDATE_Qt_5.14
251  lines.removeAll({}); // remove empty strings
252  if (lines.size() > 1)
253  {
254  peak_anno = lines[0];
255  }
256 
257  // regular expression for a charge at the end of the annotation
258  QRegExp reg_exp(R"(([\+|\-]\d+)$)");
259 
260  // read charge and text from annotation item string
261  // we support two notations for the charge suffix: '+2' or '++'
262  // cut and convert the trailing + or - to a proper charge
263  int match_pos = reg_exp.indexIn(peak_anno);
264  int tmp_charge(0);
265  if (match_pos >= 0)
266  {
267  tmp_charge = reg_exp.cap(1).toInt();
268  peak_anno = peak_anno.left(match_pos);
269  }
270  else
271  {
272  // count number of + and - in suffix (e.g., to support "++" as charge 2 annotation)
273  int plus(0), minus(0);
274 
275  for (int p = (int)peak_anno.size() - 1; p >= 0; --p)
276  {
277  if (peak_anno[p] == '+')
278  {
279  ++plus;
280  continue;
281  }
282  else if (peak_anno[p] == '-')
283  {
284  ++minus;
285  continue;
286  }
287  else // not '+' or '-'?
288  {
289  if (plus > 0 && minus == 0) // found pluses?
290  {
291  tmp_charge = plus;
292  peak_anno = peak_anno.left(peak_anno.size() - plus);
293  break;
294  }
295  else if (minus > 0 && plus == 0) // found minuses?
296  {
297  tmp_charge = -minus;
298  peak_anno = peak_anno.left(peak_anno.size() - minus);
299  break;
300  }
301  break;
302  }
303  }
304  }
305 
307  fa.charge = tmp_charge;
308  fa.mz = this->getPeakPosition().getMZ();
309  fa.intensity = this->getPeakPosition().getIntensity();
310  if (lines.size() > 1)
311  {
312  peak_anno.append("\n").append(lines[1]);
313  }
314  fa.annotation = peak_anno;
315 
316  return fa;
317  }
318 
319  // Docu in base class
320  Annotation1DItem* clone() const override
321  {
322  return new Annotation1DPeakItem(*this);
323  }
324 
325  protected:
327  DataPoint peak_position_;
328 
330  DataPoint position_;
331 
333  QColor color_;
334  };
335 } // namespace OpenMS
An abstract class acting as an interface for the different 1D annotation items.
Definition: Annotation1DItem.h:62
QRectF bounding_box_
The current bounding box of this item on the canvas where it has last been drawn.
Definition: Annotation1DItem.h:109
QString text_
The displayed text.
Definition: Annotation1DItem.h:115
void drawBoundingBox_(QPainter &painter)
Draws the bounding_box_.
const QString & getText() const
Returns the text of the item.
bool selected_
Determines whether this item is currently selected on the canvas.
Definition: Annotation1DItem.h:112
A peak annotation item.
Definition: Annotation1DPeakItem.h:54
const QColor & getColor() const
Returns the color of the label.
Definition: Annotation1DPeakItem.h:236
Annotation1DItem * clone() const override
Creates a copy of the item on the heap and returns a pointer.
Definition: Annotation1DPeakItem.h:320
~Annotation1DPeakItem() override=default
Destructor.
const DataPoint & getPosition() const
Returns the position of the label (peak)
Definition: Annotation1DPeakItem.h:212
void draw(Plot1DCanvas *const canvas, QPainter &painter, bool flipped=false) override
Draws the item on painter.
Definition: Annotation1DPeakItem.h:69
const DataPoint & getPeakPosition() const
Returns the position of the annotated peak.
Definition: Annotation1DPeakItem.h:218
QColor color_
The color of the label.
Definition: Annotation1DPeakItem.h:333
Annotation1DPeakItem(const Annotation1DPeakItem &rhs)=default
Copy constructor.
void ensureWithinDataRange(Plot1DCanvas *const canvas, const int layer_index) override
Ensures that the item has coordinates within the visible area of the canvas.
Definition: Annotation1DPeakItem.h:224
PeptideHit::PeakAnnotation toPeakAnnotation() const
Convert the 'text()' to a Peptide::PeakAnnotation.
Definition: Annotation1DPeakItem.h:242
DataPoint peak_position_
The position of the anchor (e.g. the Peak1D)
Definition: Annotation1DPeakItem.h:327
void setColor(const QColor &color)
Set the color of the label.
Definition: Annotation1DPeakItem.h:230
Annotation1DPeakItem(const DataPoint &peak_position, const QString &text, const QColor &color)
Constructor.
Definition: Annotation1DPeakItem.h:57
void setPosition(const DataPoint &position)
Sets the position of the label.
Definition: Annotation1DPeakItem.h:206
void move(const PointXYType delta, const Gravitator &, const DimMapper< 2 > &dim_mapper) override
Moves the item on the drawing canvas; behavior depends on item type and is implemented in the subclas...
Definition: Annotation1DPeakItem.h:198
DataPoint position_
The position of the label (e.g. the Peak1D)
Definition: Annotation1DPeakItem.h:330
DPosition & abs() noexcept
Make all dimension values positive.
Definition: DPosition.h:141
void fromXY(const DRange< N_DIM > &in, RangeManager< Ranges... > &output) const
Definition: DimMapper.h:735
Point map(const T &data) const
convert an OpenMS datatype (such as Feature) to an N_DIM-dimensional point
Definition: DimMapper.h:699
Manipulates X or Y component of points in the X-Y plane, by assuming one axis (either X or Y axis) ha...
Definition: Plot1DCanvas.h:68
QPoint gravitateZero(QPoint p) const
Definition: Plot1DCanvas.h:230
Gravitator swap() const
Swap gravity axis (from X to Y, or vice versa)
Definition: Plot1DCanvas.h:134
Canvas for visualization of one or several spectra.
Definition: Plot1DCanvas.h:321
void dataToWidget(const DPosition< 2 > &peak, QPoint &point, bool flipped=false)
For convenience - calls dataToWidget.
const Gravitator & getGravitator() const
Get gravity manipulation object to apply gravity to points.
Definition: Plot1DCanvas.h:524
void pushIntoDataRange(T &data_point, const int layer_index)
Pushes a data point back into the valid data range of the current layer area. Useful for annotation i...
Definition: Plot1DCanvas.h:454
PointXYType widgetToDataDistance(double x, double y)
compute distance in data coordinates (unit axis as shown) when moving x/y pixel in chart/widget coord...
Definition: Plot1DCanvas.h:438
const DimMapper< 2 > & getMapper() const
Get Mapper to translate between values for axis (X/Y) and units (m/z, RT, intensity,...
const double h
Definition: Constants.h:167
QPointF intersectionPoint(const QRectF &rect, const QPointF &p)
Find the point on a rectangle where a ray/line from a point p to its center would intersect at.
Main OpenMS namespace.
Definition: FeatureDeconvolution.h:48
Contains annotations of a peak.
Definition: PeptideHit.h:84
double intensity
Definition: PeptideHit.h:88
double mz
Definition: PeptideHit.h:87
String annotation
Definition: PeptideHit.h:85
int charge
Definition: PeptideHit.h:86