OpenMS  3.0.0
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-2022.
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 
63  Annotation1DPeakItem(const Annotation1DPeakItem& rhs) = default;
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  QTextDocument td;
177  td.setHtml(text);
178 
179  // draw html text
180  painter.save();
181  double w = td.size().width();
182  double h = td.size().height();
183  painter.translate(position_widget.x() - w / 2, position_widget.y() - h / 2);
184  td.drawContents(&painter);
185  painter.restore();
186 
187  if (selected_)
188  {
189  drawBoundingBox_(painter);
190  }
191 
192  painter.restore();
193  }
194 
195  // Docu in base class
196  void move(const PointXYType delta, const Gravitator& /*gr*/, const DimMapper<2>& dim_mapper) override
197  {
198  auto pos_xy = dim_mapper.map(position_);
199  pos_xy += delta;
200  dim_mapper.fromXY(pos_xy, position_);
201  }
202 
204  void setPosition(const DataPoint& position)
205  {
206  position_ = position;
207  }
208 
210  const DataPoint& getPosition() const
211  {
212  return position_;
213  }
214 
216  const DataPoint& getPeakPosition() const
217  {
218  return peak_position_;
219  }
220 
221  // Docu in base class
222  void ensureWithinDataRange(Plot1DCanvas* const canvas, const int layer_index) override
223  {
224  canvas->pushIntoDataRange(position_, layer_index);
225  }
226 
228  void setColor(const QColor& color)
229  {
230  color_ = color;
231  }
232 
234  const QColor& getColor() const
235  {
236  return color_;
237  }
238 
241  {
242  // add new fragment annotation
243  QString peak_anno = this->getText().trimmed();
244 
245  // check for newlines in the label and only continue with the first line for charge determination
246  peak_anno.remove('\r');
247  QStringList lines = peak_anno.split('\n');
248  // TODO: replace with 'peak_anno.split('\n', Qt::SkipEmptyParts), which is only supported in Qt 5.14 and above: CONTRIB_UPDATE_Qt_5.14
249  lines.removeAll({}); // remove empty strings
250  if (lines.size() > 1)
251  {
252  peak_anno = lines[0];
253  }
254 
255  // regular expression for a charge at the end of the annotation
256  QRegExp reg_exp(R"(([\+|\-]\d+)$)");
257 
258  // read charge and text from annotation item string
259  // we support two notations for the charge suffix: '+2' or '++'
260  // cut and convert the trailing + or - to a proper charge
261  int match_pos = reg_exp.indexIn(peak_anno);
262  int tmp_charge(0);
263  if (match_pos >= 0)
264  {
265  tmp_charge = reg_exp.cap(1).toInt();
266  peak_anno = peak_anno.left(match_pos);
267  }
268  else
269  {
270  // count number of + and - in suffix (e.g., to support "++" as charge 2 annotation)
271  int plus(0), minus(0);
272 
273  for (int p = (int)peak_anno.size() - 1; p >= 0; --p)
274  {
275  if (peak_anno[p] == '+')
276  {
277  ++plus;
278  continue;
279  }
280  else if (peak_anno[p] == '-')
281  {
282  ++minus;
283  continue;
284  }
285  else // not '+' or '-'?
286  {
287  if (plus > 0 && minus == 0) // found pluses?
288  {
289  tmp_charge = plus;
290  peak_anno = peak_anno.left(peak_anno.size() - plus);
291  break;
292  }
293  else if (minus > 0 && plus == 0) // found minuses?
294  {
295  tmp_charge = -minus;
296  peak_anno = peak_anno.left(peak_anno.size() - minus);
297  break;
298  }
299  break;
300  }
301  }
302  }
303 
305  fa.charge = tmp_charge;
306  fa.mz = this->getPeakPosition().getMZ();
307  fa.intensity = this->getPeakPosition().getIntensity();
308  if (lines.size() > 1)
309  {
310  peak_anno.append("\n").append(lines[1]);
311  }
312  fa.annotation = peak_anno;
313 
314  return fa;
315  }
316 
317  // Docu in base class
318  Annotation1DItem* clone() const override
319  {
320  return new Annotation1DPeakItem(*this);
321  }
322 
323  protected:
325  DataPoint peak_position_;
326 
328  DataPoint position_;
329 
331  QColor color_;
332  };
333 } // namespace OpenMS
An abstract class acting as an interface for the different 1D annotation items.
Definition: Annotation1DItem.h:61
const DataPoint & getPeakPosition() const
Returns the position of the annotated peak.
Definition: Annotation1DPeakItem.h:216
bool selected_
Determines whether this item is currently selected on the canvas.
Definition: Annotation1DItem.h:112
~Annotation1DPeakItem() override=default
Destructor.
void draw(Plot1DCanvas *const canvas, QPainter &painter, bool flipped=false) override
Draws the item on painter.
Definition: Annotation1DPeakItem.h:69
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...
void drawBoundingBox_(QPainter &painter)
Draws the bounding_box_.
Canvas for visualization of one or several spectra.
Definition: Plot1DCanvas.h:319
Gravitator swap() const
Swap gravity axis (from X to Y, or vice versa)
Definition: Plot1DCanvas.h:134
const QString & getText() const
Returns the text of the item.
Annotation1DItem * clone() const override
Creates a copy of the item on the heap and returns a pointer.
Definition: Annotation1DPeakItem.h:318
void setPosition(const DataPoint &position)
Sets the position of the label.
Definition: Annotation1DPeakItem.h:204
double mz
Definition: PeptideHit.h:87
Main OpenMS namespace.
Definition: FeatureDeconvolution.h:47
QColor color_
The color of the label.
Definition: Annotation1DPeakItem.h:331
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:196
String annotation
Definition: PeptideHit.h:85
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
Point map(const T &data) const
convert an OpenMS datatype (such as Feature) to an N_DIM-dimensional point
Definition: DimMapper.h:699
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 Gravitator & getGravitator() const
Get gravity manipulation object to apply gravity to points.
Definition: Plot1DCanvas.h:524
void setColor(const QColor &color)
Set the color of the label.
Definition: Annotation1DPeakItem.h:228
DataPoint peak_position_
The position of the anchor (e.g. the Peak1D)
Definition: Annotation1DPeakItem.h:325
QRectF bounding_box_
The current bounding box of this item on the canvas where it has last been drawn. ...
Definition: Annotation1DItem.h:109
const double h
Definition: Constants.h:167
const DimMapper< 2 > & getMapper() const
Get Mapper to translate between values for axis (X/Y) and units (m/z, RT, intensity, ...)
PeptideHit::PeakAnnotation toPeakAnnotation() const
Convert the &#39;text()&#39; to a Peptide::PeakAnnotation.
Definition: Annotation1DPeakItem.h:240
double intensity
Definition: PeptideHit.h:88
const QColor & getColor() const
Returns the color of the label.
Definition: Annotation1DPeakItem.h:234
DataPoint position_
The position of the label (e.g. the Peak1D)
Definition: Annotation1DPeakItem.h:328
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:222
int charge
Definition: PeptideHit.h:86
void fromXY(const DRange< N_DIM > &in, RangeManager< Ranges... > &output) const
Definition: DimMapper.h:735
Contains annotations of a peak.
Definition: PeptideHit.h:83
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:67
Annotation1DPeakItem(const DataPoint &peak_position, const QString &text, const QColor &color)
Constructor.
Definition: Annotation1DPeakItem.h:57
A peak annotation item.
Definition: Annotation1DPeakItem.h:52
void dataToWidget(const DPosition< 2 > &peak, QPoint &point, bool flipped=false)
For convenience - calls dataToWidget.
QString text_
The displayed text.
Definition: Annotation1DItem.h:115
QPoint gravitateZero(QPoint p) const
Definition: Plot1DCanvas.h:230
const DataPoint & getPosition() const
Returns the position of the label (peak)
Definition: Annotation1DPeakItem.h:210