OpenMS
Annotation1DPeakItem.h
Go to the documentation of this file.
1 // Copyright (c) 2002-2023, The OpenMS Team -- EKU Tuebingen, ETH Zurich, and FU Berlin
2 // SPDX-License-Identifier: BSD-3-Clause
3 //
4 // --------------------------------------------------------------------------
5 // $Maintainer: Johannes Veit $
6 // $Authors: Johannes Junker $
7 // --------------------------------------------------------------------------
8 
9 #pragma once
10 
12 
16 
17 #include <QtGui/QColor>
18 
19 namespace OpenMS
20 {
21 
25  template <class DataPoint> // e.g. Peak1D
27  public Annotation1DItem
28  {
29 public:
31  Annotation1DPeakItem(const DataPoint& peak_position, const QString& text, const QColor& color) :
32  Annotation1DItem(text), peak_position_(peak_position), position_(peak_position), color_(color)
33  {
34  }
35 
38 
40  ~Annotation1DPeakItem() override = default;
41 
42  // Docu in base class
43  void draw(Plot1DCanvas* const canvas, QPainter& painter, bool flipped = false) override
44  {
45  painter.save();
46 
47  painter.setPen(color_);
48 
49  QPoint position_widget, peak_position_widget;
50 
51  // translate units to pixel coordinates
52  canvas->dataToWidget(canvas->getMapper().map(position_), position_widget, flipped);
53  canvas->dataToWidget(canvas->getMapper().map(peak_position_), peak_position_widget, flipped);
54 
55  // pre-compute bounding box of text_item
56  const auto prebox = QApplication::fontMetrics().boundingRect(position_widget.x(), position_widget.y(), 0, 0, Qt::AlignCenter, getText());
57  // Shift position of the widget/text, so it sits 'on top' of the peak
58  // We can only do that there, since we do not know the state of 'flipped' in general
59  // Compute the delta in data-units, NOT pixels, since the shift (up/down, or even left/right) depends on state of 'flipped' and axis
60  const auto deltaXY_in_units = canvas->widgetToDataDistance(prebox.width(), prebox.height()).abs(); // abs() to make sure y axis is not negative
61  const auto delta_gravity_in_units = canvas->getGravitator().swap().gravitateZero(deltaXY_in_units); // only keep gravity dim
62  // recompute 'position_widget', shifting the text up by 1/2 box
63  canvas->dataToWidget(canvas->getMapper().map(position_) + delta_gravity_in_units / 2, position_widget, flipped);
64  // re-compute bounding box of text_item on with new position!
65  bounding_box_ = QApplication::fontMetrics().boundingRect(position_widget.x(), position_widget.y(), 0, 0, Qt::AlignCenter, getText());
66 
67 
68  // draw connection line between anchor point and current position if pixel coordinates differ significantly
69  if ((position_widget - peak_position_widget).manhattanLength() > 2)
70  {
71  QPointF border_point = GUIHelpers::intersectionPoint(bounding_box_, peak_position_widget);
72  if (bounding_box_.center() != border_point)
73  {
74  painter.save();
75  painter.setPen(Qt::DashLine);
76  painter.drawLine(peak_position_widget, border_point);
77  painter.restore();
78  }
79  }
80 
81  // some pretty printing
82  QString text = text_;
83  if (!text.contains(R"(<\)")) // don't process HTML strings again
84  {
85  // extract ion index
86  {
87  QRegExp reg_exp(R"([abcdwxyz](\d+))");
88  int match_pos = reg_exp.indexIn(text);
89 
90  if (match_pos == 0)
91  {
92  QString index_str = reg_exp.cap(1);
93 
94  // put sub html tag around number
95  text = text[match_pos] + QString("<sub>") + index_str + QString("</sub>") + text.right(text.size() - match_pos - index_str.size() - 1);
96  }
97  else // protein-protein XL specific ion names
98  {
99  QRegExp reg_exp_xlms(R"((ci|xi)[$][abcxyz](\d+))");
100  match_pos = reg_exp_xlms.indexIn(text);
101  if ((match_pos == 6) || (match_pos == 7))
102  {
103  // set the match_pos to the position of the ion index
104  match_pos += 3;
105  QString index_str = reg_exp.cap(1);
106 
107  // put sub html tag around number
108  text = text.left(match_pos) + text[match_pos] + QString("<sub>") + index_str + QString("</sub>") + text.right(text.size() - match_pos - index_str.size() - 1);
109  }
110  }
111  }
112 
113  // common losses
114  text.replace("H2O1", "H<sub>2</sub>O"); // mind the order with H2O substitution
115  text.replace("H2O", "H<sub>2</sub>O");
116  text.replace("NH3", "NH<sub>3</sub>");
117  text.replace("H3N1", "NH<sub>3</sub>");
118  text.replace("C1H4O1S1", "H<sub>4</sub>COS"); // methionine sulfoxide loss
119 
120  // nucleotide XL related losses
121  text.replace("H3PO4", "H<sub>3</sub>PO<sub>4</sub>");
122  text.replace("HPO3", "HPO<sub>3</sub>");
123  text.replace("C3O", "C<sub>3</sub>O");
124 
125  // charge format: +z
126  QRegExp charge_rx(R"([\+|\-](\d+)$)");
127  int match_pos = charge_rx.indexIn(text);
128  if (match_pos > 0)
129  {
130  text = text.left(match_pos) + QString("<sup>") + text[match_pos] // + or -
131  + charge_rx.cap(1) + QString("</sup>"); // charge
132  }
133 
134  // charge format: z+
135  charge_rx = QRegExp(R"((\d+)[\+|\-]$)");
136  match_pos = charge_rx.indexIn(text);
137  if (match_pos > 0)
138  {
139  text = text.left(match_pos) + QString("<sup>") + charge_rx.cap(1) // charge
140  + text[match_pos + charge_rx.cap(1).size()] + QString("</sup>"); // + or -
141  }
142 
143  text.replace(QRegExp(R"(\+\+$)"), "<sup>2+</sup>");
144  text.replace(QRegExp(R"(\+$)"), "");
145  text.replace(QRegExp(R"(\-\-$)"), "<sup>2-</sup>");
146  text.replace(QRegExp(R"(\-$)"), "");
147  }
148 
149  text = "<font color=\"" + color_.name() + "\">" + text + "</font>";
150 
151  // draw html text
152  {
153  QTextDocument td;
154  td.setHtml(text);
155  painter.save();
156  double w = td.size().width();
157  double h = td.size().height();
158  painter.translate(position_widget.x() - w / 2, position_widget.y() - h / 2);
159  td.drawContents(&painter);
160  painter.restore();
161  }
162 
163  if (selected_)
164  {
165  drawBoundingBox_(painter);
166  }
167 
168  painter.restore();
169  }
170 
171  // Docu in base class
172  void move(const PointXYType delta, const Gravitator& /*gr*/, const DimMapper<2>& dim_mapper) override
173  {
174  auto pos_xy = dim_mapper.map(position_);
175  pos_xy += delta;
176  dim_mapper.fromXY(pos_xy, position_);
177  }
178 
180  void setPosition(const DataPoint& position)
181  {
182  position_ = position;
183  }
184 
186  const DataPoint& getPosition() const
187  {
188  return position_;
189  }
190 
192  const DataPoint& getPeakPosition() const
193  {
194  return peak_position_;
195  }
196 
197  // Docu in base class
198  void ensureWithinDataRange(Plot1DCanvas* const canvas, const int layer_index) override
199  {
200  canvas->pushIntoDataRange(position_, layer_index);
201  }
202 
204  void setColor(const QColor& color)
205  {
206  color_ = color;
207  }
208 
210  const QColor& getColor() const
211  {
212  return color_;
213  }
214 
217  {
218  // add new fragment annotation
219  QString peak_anno = this->getText().trimmed();
220 
221  // check for newlines in the label and only continue with the first line for charge determination
222  peak_anno.remove('\r');
223  QStringList lines = peak_anno.split('\n');
224  // TODO: replace with 'peak_anno.split('\n', Qt::SkipEmptyParts), which is only supported in Qt 5.14 and above: CONTRIB_UPDATE_Qt_5.14
225  lines.removeAll({}); // remove empty strings
226  if (lines.size() > 1)
227  {
228  peak_anno = lines[0];
229  }
230 
231  // regular expression for a charge at the end of the annotation
232  QRegExp reg_exp(R"(([\+|\-]\d+)$)");
233 
234  // read charge and text from annotation item string
235  // we support two notations for the charge suffix: '+2' or '++'
236  // cut and convert the trailing + or - to a proper charge
237  int match_pos = reg_exp.indexIn(peak_anno);
238  int tmp_charge(0);
239  if (match_pos >= 0)
240  {
241  tmp_charge = reg_exp.cap(1).toInt();
242  peak_anno = peak_anno.left(match_pos);
243  }
244  else
245  {
246  // count number of + and - in suffix (e.g., to support "++" as charge 2 annotation)
247  int plus(0), minus(0);
248 
249  for (int p = (int)peak_anno.size() - 1; p >= 0; --p)
250  {
251  if (peak_anno[p] == '+')
252  {
253  ++plus;
254  continue;
255  }
256  else if (peak_anno[p] == '-')
257  {
258  ++minus;
259  continue;
260  }
261  else // not '+' or '-'?
262  {
263  if (plus > 0 && minus == 0) // found pluses?
264  {
265  tmp_charge = plus;
266  peak_anno = peak_anno.left(peak_anno.size() - plus);
267  break;
268  }
269  else if (minus > 0 && plus == 0) // found minuses?
270  {
271  tmp_charge = -minus;
272  peak_anno = peak_anno.left(peak_anno.size() - minus);
273  break;
274  }
275  break;
276  }
277  }
278  }
279 
281  fa.charge = tmp_charge;
282  fa.mz = this->getPeakPosition().getMZ();
283  fa.intensity = this->getPeakPosition().getIntensity();
284  if (lines.size() > 1)
285  {
286  peak_anno.append("\n").append(lines[1]);
287  }
288  fa.annotation = peak_anno;
289 
290  return fa;
291  }
292 
293  // Docu in base class
294  Annotation1DItem* clone() const override
295  {
296  return new Annotation1DPeakItem(*this);
297  }
298 
299  protected:
301  DataPoint peak_position_;
302 
304  DataPoint position_;
305 
307  QColor color_;
308  };
309 } // namespace OpenMS
An abstract class acting as an interface for the different 1D annotation items.
Definition: Annotation1DItem.h:36
QRectF bounding_box_
The current bounding box of this item on the canvas where it has last been drawn.
Definition: Annotation1DItem.h:83
QString text_
The displayed text.
Definition: Annotation1DItem.h:89
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:86
A peak annotation item.
Definition: Annotation1DPeakItem.h:28
const QColor & getColor() const
Returns the color of the label.
Definition: Annotation1DPeakItem.h:210
Annotation1DItem * clone() const override
Creates a copy of the item on the heap and returns a pointer.
Definition: Annotation1DPeakItem.h:294
~Annotation1DPeakItem() override=default
Destructor.
const DataPoint & getPosition() const
Returns the position of the label (peak)
Definition: Annotation1DPeakItem.h:186
void draw(Plot1DCanvas *const canvas, QPainter &painter, bool flipped=false) override
Draws the item on painter.
Definition: Annotation1DPeakItem.h:43
const DataPoint & getPeakPosition() const
Returns the position of the annotated peak.
Definition: Annotation1DPeakItem.h:192
QColor color_
The color of the label.
Definition: Annotation1DPeakItem.h:307
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:198
PeptideHit::PeakAnnotation toPeakAnnotation() const
Convert the 'text()' to a Peptide::PeakAnnotation.
Definition: Annotation1DPeakItem.h:216
DataPoint peak_position_
The position of the anchor (e.g. the Peak1D)
Definition: Annotation1DPeakItem.h:301
void setColor(const QColor &color)
Set the color of the label.
Definition: Annotation1DPeakItem.h:204
Annotation1DPeakItem(const DataPoint &peak_position, const QString &text, const QColor &color)
Constructor.
Definition: Annotation1DPeakItem.h:31
void setPosition(const DataPoint &position)
Sets the position of the label.
Definition: Annotation1DPeakItem.h:180
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:172
DataPoint position_
The position of the label (e.g. the Peak1D)
Definition: Annotation1DPeakItem.h:304
DPosition & abs() noexcept
Make all dimension values positive.
Definition: DPosition.h:115
void fromXY(const DRange< N_DIM > &in, RangeManager< Ranges... > &output) const
Definition: DimMapper.h:709
Point map(const T &data) const
convert an OpenMS datatype (such as Feature) to an N_DIM-dimensional point
Definition: DimMapper.h:673
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:42
QPoint gravitateZero(QPoint p) const
Definition: Plot1DCanvas.h:204
Gravitator swap() const
Swap gravity axis (from X to Y, or vice versa)
Definition: Plot1DCanvas.h:108
Canvas for visualization of one or several spectra.
Definition: Plot1DCanvas.h:295
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:498
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:428
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:412
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:141
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:22
Contains annotations of a peak.
Definition: PeptideHit.h:58
double intensity
Definition: PeptideHit.h:62
double mz
Definition: PeptideHit.h:61
String annotation
Definition: PeptideHit.h:59
int charge
Definition: PeptideHit.h:60