OpenMS
Loading...
Searching...
No Matches
Annotation1DPeakItem.h
Go to the documentation of this file.
1// Copyright (c) 2002-present, OpenMS Inc. -- 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
14
19
20#include <QtGui/QColor>
21#include <QtGui/QFontMetricsF>
22
23namespace OpenMS
24{
25
29 template <class DataPoint> // e.g. Peak1D
31 public Annotation1DItem
32 {
33public:
48 Annotation1DPeakItem(const DataPoint& peak_position, const QString& text, const QColor& color, int charge = 0) :
49 Annotation1DItem(text), peak_position_(peak_position), position_(peak_position), color_(color), charge_(charge)
50 {
51 }
52
55
57 ~Annotation1DPeakItem() override = default;
58
59 // Docu in base class
60 void draw(Plot1DCanvas* const canvas, QPainter& painter, bool flipped = false) override
61 {
62 painter.save();
63
64 painter.setPen(color_);
65
66 QPoint position_widget, peak_position_widget;
67
68 // translate units to pixel coordinates
69 canvas->dataToWidget(canvas->getMapper().map(position_), position_widget, flipped);
70 canvas->dataToWidget(canvas->getMapper().map(peak_position_), peak_position_widget, flipped);
71
72 // pre-compute bounding box of text_item
73 const QFontMetricsF fm(QApplication::font());
74 const auto prebox = fm.boundingRect(QRectF(position_widget.x(), position_widget.y(), 0, 0), Qt::AlignCenter, getText());
75 // Shift position of the widget/text, so it sits 'on top' of the peak
76 // We can only do that there, since we do not know the state of 'flipped' in general
77 // Compute the delta in data-units, NOT pixels, since the shift (up/down, or even left/right) depends on state of 'flipped' and axis
78 const auto deltaXY_in_units = canvas->widgetToDataDistance(prebox.width(), prebox.height()).abs(); // abs() to make sure y axis is not negative
79 const auto delta_gravity_in_units = canvas->getGravitator().swap().gravitateZero(deltaXY_in_units); // only keep gravity dim
80 // recompute 'position_widget', shifting the text up by 1/2 box
81 canvas->dataToWidget(canvas->getMapper().map(position_) + delta_gravity_in_units / 2, position_widget, flipped);
82 // re-compute bounding box of text_item on with new position!
83 bounding_box_ = fm.boundingRect(QRectF(position_widget.x(), position_widget.y(), 0, 0), Qt::AlignCenter, getText());
84
85
86 // draw connection line between anchor point and current position if pixel coordinates differ significantly
87 if ((position_widget - peak_position_widget).manhattanLength() > 2)
88 {
89 QPointF border_point = GUIHelpers::intersectionPoint(bounding_box_, peak_position_widget);
90 if (bounding_box_.center() != border_point)
91 {
92 painter.save();
93 painter.setPen(Qt::DashLine);
94 painter.drawLine(peak_position_widget, border_point);
95 painter.restore();
96 }
97 }
98
99 // some pretty printing
100 QString text = text_;
101 if (!text.contains("<\\")) // don't process HTML strings again
102 {
103 // extract ion index
104 {
105 QRegularExpression reg_exp(R"(([abcdwxyz])(\d+))");
106 QRegularExpressionMatch match = reg_exp.match(text);
107 if (text.indexOf(reg_exp) == 0) // only process if at the beginning of the string
108 {
109 text.replace(reg_exp, "\\1<sub>\\2</sub>");
110 }
111 else // protein-protein XL specific ion names
112 { // e.g. "[alpha|ci$y1]"
113 QRegularExpression reg_exp_xlms(R"((ci|xi)[$][abcxyz](\d+))");
114 auto match_pos = text.indexOf(reg_exp_xlms);
115 if ((match_pos == 6) || (match_pos == 7))
116 {
117 // set the match_pos to the position of the ion index
118 match_pos += 3; // skip "ci$" or "xi$"
119 ++match_pos; // skip the ion type (=captured(1))
120 QString charge_str = match.captured(2);
121 // put sub html tag around number
122 text = text.left(match_pos) + QString("<sub>") + charge_str + QString("</sub>") + text.right(text.size() - match_pos - charge_str.size());
123 }
124 }
125 }
126
127 // common losses
128 text.replace("H2O1", "H<sub>2</sub>O"); // mind the order with H2O substitution
129 text.replace("H2O", "H<sub>2</sub>O");
130 text.replace("NH3", "NH<sub>3</sub>");
131 text.replace("H3N1", "NH<sub>3</sub>");
132 text.replace("C1H4O1S1", "H<sub>4</sub>COS"); // methionine sulfoxide loss
133
134 // nucleotide XL related losses
135 text.replace("H3PO4", "H<sub>3</sub>PO<sub>4</sub>");
136 text.replace("HPO3", "HPO<sub>3</sub>");
137 text.replace("C3O", "C<sub>3</sub>O");
138
139 // A charge that is not in the name at all (see the constructor) is drawn from the member, so
140 // that it never has to travel through the label and get written back into the stored annotation.
141 // Charge 1 is implied and left off, exactly as it is for a named ion like "y3+" whose lone sign
142 // is stripped below; only |charge| >= 2 is drawn, and next to the ion name (end of the first
143 // line) rather than after a trailing free-text comment.
145 {
146 // magnitude in a wider type: std::abs(INT_MIN) is undefined behaviour
147 const long long magnitude = (charge_ < 0) ? -static_cast<long long>(charge_) : static_cast<long long>(charge_);
148 if (magnitude > 1)
149 {
150 const QString sup = QString("<sup>") + QString::number(magnitude) + ((charge_ < 0) ? "-" : "+") + QString("</sup>");
151 static const QRegularExpression first_break(R"([\r\n])");
152 const int eol = text.indexOf(first_break);
153 text.insert((eol < 0) ? text.size() : eol, sup);
154 }
155 }
156
157 // charge format: +z
158 QRegularExpression charge_rx(R"([\+|\-](\d+)$)");
159 int match_pos = text.indexOf(charge_rx);
160 if (match_pos > 0)
161 {
162 text = text.left(match_pos) + QString("<sup>") + text[match_pos] // + or -
163 + charge_rx.match(text).captured(1) + QString("</sup>"); // charge
164 }
165
166 // charge format: z+
167 charge_rx = QRegularExpression(R"((\d+)[\+|\-]$)");
168 match_pos = text.indexOf(charge_rx);
169 if (match_pos > 0)
170 {
171 auto charge_match = charge_rx.match(text).captured(1);
172 text = text.left(match_pos) + QString("<sup>") + charge_match // charge
173 + text[match_pos + charge_match.size()] + QString("</sup>"); // + or -
174 }
175
176 text.replace(QRegularExpression(R"(\+\+$)"), "<sup>2+</sup>");
177 text.replace(QRegularExpression(R"(\+$)"), "");
178 text.replace(QRegularExpression(R"(\-\-$)"), "<sup>2-</sup>");
179 text.replace(QRegularExpression(R"(\-$)"), "");
180 }
181
182 text = "<font color=\"" + color_.name() + "\">" + text + "</font>";
183
184 // draw html text
185 {
186 QTextDocument td;
187 td.setHtml(text);
188 painter.save();
189 double w = td.size().width();
190 double h = td.size().height();
191 painter.translate(position_widget.x() - w / 2, position_widget.y() - h / 2);
192 td.drawContents(&painter);
193 painter.restore();
194 }
195
196 if (selected_)
197 {
198 drawBoundingBox_(painter);
199 }
200
201 painter.restore();
202 }
203
204 // Docu in base class
205 void move(const PointXYType delta, const Gravitator& /*gr*/, const DimMapper<2>& dim_mapper) override
206 {
207 auto pos_xy = dim_mapper.map(position_);
208 pos_xy += delta;
209 dim_mapper.fromXY(pos_xy, position_);
210 }
211
213 void setPosition(const DataPoint& position)
214 {
215 position_ = position;
216 }
217
219 const DataPoint& getPosition() const
220 {
221 return position_;
222 }
223
225 const DataPoint& getPeakPosition() const
226 {
227 return peak_position_;
228 }
229
230 // Docu in base class
231 void ensureWithinDataRange(Plot1DCanvas* const canvas, const int layer_index) override
232 {
233 canvas->pushIntoDataRange(position_, layer_index);
234 }
235
237 void setColor(const QColor& color)
238 {
239 color_ = color;
240 }
241
243 const QColor& getColor() const
244 {
245 return color_;
246 }
247
250 {
251 // add new fragment annotation
252 QString peak_anno = this->getText().trimmed();
253
254 // check for newlines in the label and only continue with the first line for charge determination.
255 // KeepEmptyParts: a blank line inside a label is the user's text, and dropping it here would
256 // silently rewrite the label every time the annotations are read back.
257 // Same rule as the identification view uses when it builds the label: "\r\n" first in the
258 // alternation so a CRLF is one break, a lone CR is a break of its own, and KeepEmptyParts so a
259 // blank line the user typed survives. IonNaming::chargeFromName likewise ends the ion name at
260 // either character, so all three agree on where the first line stops.
261 static const QRegularExpression line_break(R"(\r\n|[\r\n])");
262 QStringList lines = peak_anno.split(line_break, Qt::KeepEmptyParts);
263 if (lines.size() > 1)
264 {
265 peak_anno = lines[0];
266 }
267
268 // Read the charge from the label but leave the label alone. Cutting the charge off used to
269 // corrupt the stored annotation: the identification view puts the charge back on redraw, so a
270 // round trip through this function turned "y3+" into "y3" and then into "y3++".
271 // IonNaming::chargeFromName understands all notations that occur ('+2', '++' and mzPAF's '^2').
272 // A charge the user typed into the label wins; otherwise fall back to the one the item was built
273 // with. The ion name (the first line) is returned exactly, so viewing a spectrum never rewrites
274 // it. The free-text remainder is intentionally normalised -- edge whitespace is trimmed (above)
275 // and CR/CRLF line endings fold to LF (the split and rejoin below); that is a no-op on producer
276 // annotations, which are single-line ion names, and only tidies a comment a user typed. So the
277 // round trip is identity for the ion name and any interior blank line, not byte-for-byte for a
278 // free-text comment's surrounding whitespace or line endings.
279 const int named_charge = IonNaming::chargeFromName(fromQString(peak_anno));
280 const int tmp_charge = (named_charge != 0) ? named_charge : charge_;
281
283 fa.charge = tmp_charge;
284 fa.mz = this->getPeakPosition().getMZ();
285 fa.intensity = this->getPeakPosition().getIntensity();
286 for (int l = 1; l < lines.size(); ++l)
287 { // keep every extra label line, not just the first (they are free text below the ion name)
288 peak_anno.append("\n").append(lines[l]);
289 }
290 fa.annotation = fromQString(peak_anno);
291
292 return fa;
293 }
294
295 // Docu in base class
296 Annotation1DItem* clone() const override
297 {
298 return new Annotation1DPeakItem(*this);
299 }
300
301 protected:
303 DataPoint peak_position_;
304
306 DataPoint position_;
307
309 QColor color_;
310
312 int charge_ = 0;
313 };
314} // namespace OpenMS
An abstract class acting as an interface for the different 1D annotation items.
Definition Annotation1DItem.h:38
QRectF bounding_box_
The current bounding box of this item on the canvas where it has last been drawn.
Definition Annotation1DItem.h:85
QString text_
The displayed text.
Definition Annotation1DItem.h:91
void drawBoundingBox_(QPainter &painter)
Draws the bounding_box_.
bool selected_
Determines whether this item is currently selected on the canvas.
Definition Annotation1DItem.h:88
const QString & getText() const
Returns the text of the item.
A peak annotation item.
Definition Annotation1DPeakItem.h:32
~Annotation1DPeakItem() override=default
Destructor.
const DataPoint & getPeakPosition() const
Returns the position of the annotated peak.
Definition Annotation1DPeakItem.h:225
Annotation1DItem * clone() const override
Creates a copy of the item on the heap and returns a pointer.
Definition Annotation1DPeakItem.h:296
void draw(Plot1DCanvas *const canvas, QPainter &painter, bool flipped=false) override
Draws the item on painter.
Definition Annotation1DPeakItem.h:60
const DataPoint & getPosition() const
Returns the position of the label (peak)
Definition Annotation1DPeakItem.h:219
Annotation1DPeakItem(const DataPoint &peak_position, const QString &text, const QColor &color, int charge=0)
Constructor.
Definition Annotation1DPeakItem.h:48
QColor color_
The color of the label.
Definition Annotation1DPeakItem.h:309
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:231
PeptideHit::PeakAnnotation toPeakAnnotation() const
Convert the 'text()' to a Peptide::PeakAnnotation.
Definition Annotation1DPeakItem.h:249
DataPoint peak_position_
The position of the anchor (e.g. the Peak1D)
Definition Annotation1DPeakItem.h:303
void setColor(const QColor &color)
Set the color of the label.
Definition Annotation1DPeakItem.h:237
void setPosition(const DataPoint &position)
Sets the position of the label.
Definition Annotation1DPeakItem.h:213
int charge_
Charge of the annotated peak when its name does not spell one out; 0 if unknown or already named.
Definition Annotation1DPeakItem.h:312
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:205
const QColor & getColor() const
Returns the color of the label.
Definition Annotation1DPeakItem.h:243
DataPoint position_
The position of the label (e.g. the Peak1D)
Definition Annotation1DPeakItem.h:306
DPosition & abs() noexcept
Make all dimension values positive.
Definition DPosition.h:113
Allows dynamical switching (at runtime) between a dimension (RT, m/z, int, IM, etc) and X,...
Definition DimMapper.h:662
void fromXY(const DRange< N_DIM > &in, RangeManager< Ranges... > &output) const
Definition DimMapper.h:752
Point map(const T &data) const
convert an OpenMS datatype (such as Feature) to an N_DIM-dimensional point
Definition DimMapper.h:716
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:43
QPoint gravitateZero(QPoint p) const
Definition Plot1DCanvas.h:205
Gravitator swap() const
Swap gravity axis (from X to Y, or vice versa)
Definition Plot1DCanvas.h:109
Canvas for visualization of one or several spectra.
Definition Plot1DCanvas.h:296
void dataToWidget(const DPosition< 2 > &peak, QPoint &point, bool flipped=false)
For convenience - calls dataToWidget.
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:435
const Gravitator & getGravitator() const
Get gravity manipulation object to apply gravity to points.
Definition Plot1DCanvas.h:511
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:413
const DimMapper< 2 > & getMapper() const
Get Mapper to translate between values for axis (X/Y) and units (m/z, RT, intensity,...
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.
int chargeFromName(const std::string &ion_name)
The charge an ion name spells out, or 0 if it does not spell one.
Definition IonNaming.h:78
Main OpenMS namespace.
Definition openswathalgo/include/OpenMS/OPENSWATHALGO/DATAACCESS/ISpectrumAccess.h:19
std::string fromQString(const QString &s)
Construct std::string from QString (replaces StringUtils::toStr(const QString&))
Definition Qt5Port.h:46
Contains annotations of a peak.
Definition PeptideHit.h:87
double intensity
Definition PeptideHit.h:91
double mz
Definition PeptideHit.h:90
std::string annotation
Definition PeptideHit.h:88
int charge
Definition PeptideHit.h:89