1 /***************************************************************************
2 File : Legend.cpp
3 Project : SciDAVis
4 --------------------------------------------------------------------
5 Copyright : (C) 2006 by Ion Vasilief, Tilman Benkert
6 Email (use @ for *) : ion_vasilief*yahoo.fr, thzs*gmx.net
7 Description : Legend marker (extension to QwtPlotMarker)
8
9 ***************************************************************************/
10
11 /***************************************************************************
12 * *
13 * This program is free software; you can redistribute it and/or modify *
14 * it under the terms of the GNU General Public License as published by *
15 * the Free Software Foundation; either version 2 of the License, or *
16 * (at your option) any later version. *
17 * *
18 * This program is distributed in the hope that it will be useful, *
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
21 * GNU General Public License for more details. *
22 * *
23 * You should have received a copy of the GNU General Public License *
24 * along with this program; if not, write to the Free Software *
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, *
26 * Boston, MA 02110-1301 USA *
27 * *
28 ***************************************************************************/
29 #include "Legend.h"
30 #include "QwtPieCurve.h"
31 #include "VectorCurve.h"
32
33 #include <QPainter>
34 #include <QPolygon>
35 #include <QMessageBox>
36
37 #include <qwt_plot.h>
38 #include <qwt_scale_widget.h>
39 #include <qwt_painter.h>
40 #include <qwt_plot_layout.h>
41 #include <qwt_plot_canvas.h>
42 #include <qwt_layout_metrics.h>
43 #include <qwt_symbol.h>
44
Legend(Plot * plot)45 Legend::Legend(Plot *plot) : d_plot(plot), d_frame(0), d_angle(0)
46 {
47 d_text = new QwtText(QString(), QwtText::RichText);
48 d_text->setFont(QFont("Arial", 12, QFont::Normal, false));
49 d_text->setRenderFlags(Qt::AlignTop | Qt::AlignLeft);
50 d_text->setBackgroundBrush(QBrush(Qt::NoBrush));
51 d_text->setColor(Qt::black);
52 d_text->setBackgroundPen(QPen(Qt::NoPen));
53 d_text->setPaintAttribute(QwtText::PaintBackground);
54
55 hspace = 30;
56 left_margin = 10;
57 top_margin = 5;
58 d_shadow_size_x = 5;
59 d_shadow_size_y = 5;
60 }
61
draw(QPainter * p,const QwtScaleMap & xMap,const QwtScaleMap & yMap,const QRect &) const62 void Legend::draw(QPainter *p, const QwtScaleMap &xMap, const QwtScaleMap &yMap,
63 const QRect &) const
64 {
65 const int x = xMap.transform(xValue());
66 const int y = yMap.transform(yValue());
67
68 QwtPainter::setMetricsMap(plot(), p->device());
69 const QwtMetricsMap map = QwtPainter::metricsMap();
70
71 const int symbolLineLength = symbolsMaxLineLength();
72
73 int width, height;
74 QwtArray<long> heights = itemsHeight(map.deviceToLayoutY(y), symbolLineLength, width, height);
75
76 QRect rs = QRect(map.deviceToLayout(QPoint(x, y)), QSize(width, height));
77
78 drawFrame(p, d_frame, rs);
79 drawSymbols(p, rs, heights, symbolLineLength);
80 drawLegends(p, rs, heights, symbolLineLength);
81 }
82
setText(const QString & s)83 void Legend::setText(const QString &s)
84 {
85 d_text->setText(s);
86 }
87
setFrameStyle(int style)88 void Legend::setFrameStyle(int style)
89 {
90 if (d_frame == style)
91 return;
92
93 d_frame = style;
94 }
95
setBackgroundColor(const QColor & c)96 void Legend::setBackgroundColor(const QColor &c)
97 {
98 if (d_text->backgroundBrush().color() == c)
99 return;
100
101 d_text->setBackgroundBrush(QBrush(c));
102 }
103
rect() const104 QRect Legend::rect() const
105 {
106 const QwtScaleMap &xMap = d_plot->canvasMap(xAxis());
107 const QwtScaleMap &yMap = d_plot->canvasMap(yAxis());
108
109 const int x = xMap.transform(xValue());
110 const int y = yMap.transform(yValue());
111
112 int width, height;
113 itemsHeight(y, symbolsMaxLineLength(), width, height);
114
115 return QRect(QPoint(x, y), QSize(width - 1, height - 1));
116 }
117
boundingRect() const118 QwtDoubleRect Legend::boundingRect() const
119 {
120 QRect bounding_rect = rect();
121 const QwtScaleMap &x_map = d_plot->canvasMap(xAxis());
122 const QwtScaleMap &y_map = d_plot->canvasMap(yAxis());
123
124 double left = x_map.invTransform(bounding_rect.left());
125 double right = x_map.invTransform(bounding_rect.right());
126 double top = y_map.invTransform(bounding_rect.top());
127 double bottom = y_map.invTransform(bounding_rect.bottom());
128
129 return QwtDoubleRect(left, top, qAbs(right - left), qAbs(bottom - top));
130 }
131
setTextColor(const QColor & c)132 void Legend::setTextColor(const QColor &c)
133 {
134 if (c == d_text->color())
135 return;
136
137 d_text->setColor(c);
138 }
139
setOrigin(const QPoint & p)140 void Legend::setOrigin(const QPoint &p)
141 {
142 d_pos = p;
143
144 const QwtScaleMap &xMap = d_plot->canvasMap(xAxis());
145 const QwtScaleMap &yMap = d_plot->canvasMap(yAxis());
146
147 setXValue(xMap.invTransform(p.x()));
148 setYValue(yMap.invTransform(p.y()));
149 }
150
updateOrigin()151 void Legend::updateOrigin()
152 {
153 if (!d_plot)
154 return;
155
156 const QwtScaleMap &xMap = d_plot->canvasMap(xAxis());
157 const QwtScaleMap &yMap = d_plot->canvasMap(yAxis());
158
159 const QwtScaleDiv *xScDiv = d_plot->axisScaleDiv(xAxis());
160 double xVal = xMap.invTransform(d_pos.x());
161 if (!xScDiv->contains(xVal))
162 return;
163
164 const QwtScaleDiv *yScDiv = d_plot->axisScaleDiv(yAxis());
165 double yVal = yMap.invTransform(d_pos.y());
166 if (!yScDiv->contains(yVal))
167 return;
168
169 setXValue(xVal);
170 setYValue(yVal);
171 }
172
setOriginCoord(double x,double y)173 void Legend::setOriginCoord(double x, double y)
174 {
175 if (xValue() == x && yValue() == y)
176 return;
177
178 setXValue(x);
179 setYValue(y);
180
181 const QwtScaleMap &xMap = d_plot->canvasMap(xAxis());
182 const QwtScaleMap &yMap = d_plot->canvasMap(yAxis());
183
184 d_pos = QPoint(xMap.transform(x), yMap.transform(y));
185 }
186
setFont(const QFont & font)187 void Legend::setFont(const QFont &font)
188 {
189 if (font == d_text->font())
190 return;
191
192 d_text->setFont(font);
193 }
194
drawFrame(QPainter * p,int type,const QRect & rect) const195 void Legend::drawFrame(QPainter *p, int type, const QRect &rect) const
196 {
197 p->save();
198 p->setPen(QPen(Qt::black, 1, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin));
199 if (type == None)
200 QwtPainter::fillRect(p, rect, d_text->backgroundBrush());
201
202 if (type == Line) {
203 // drawing/filling in one go broken for PDF export:
204 // pen "inherits" alpha value of background brush
205 QwtPainter::fillRect(p, rect, d_text->backgroundBrush());
206 QwtPainter::drawRect(p, rect);
207 } else if (type == Shadow) {
208 QwtPainter::fillRect(p, rect, d_text->backgroundBrush());
209 QwtPainter::drawRect(p, rect);
210
211 QRect shadow_right =
212 QRect(rect.right(), rect.y() + d_shadow_size_y, d_shadow_size_x, rect.height() - 1);
213 QRect shadow_bottom =
214 QRect(rect.x() + d_shadow_size_x, rect.bottom(), rect.width() - 1, d_shadow_size_y);
215 QwtPainter::fillRect(p, shadow_right, QBrush(Qt::black));
216 QwtPainter::fillRect(p, shadow_bottom, QBrush(Qt::black));
217 }
218 p->restore();
219 }
220
drawVector(QPainter * p,int x,int y,int l,int curveIndex) const221 void Legend::drawVector(QPainter *p, int x, int y, int l, int curveIndex) const
222 {
223 Graph *g = (Graph *)d_plot->parent();
224 if (!g)
225 return;
226
227 VectorCurve *v = (VectorCurve *)g->curve(curveIndex);
228 if (!v)
229 return;
230
231 p->save();
232
233 QPen pen(v->color(), v->width(), Qt::SolidLine);
234 p->setPen(pen);
235 QwtPainter::drawLine(p, x, y, x + l, y);
236
237 p->translate(x + l, y);
238
239 double pi = 4 * atan(-1.0);
240 int headLength = v->headLength();
241 int d = qRound(headLength * tan(pi * (double)v->headAngle() / 180.0));
242
243 QPolygon endArray(3);
244 endArray[0] = QPoint(0, 0);
245 endArray[1] = QPoint(-headLength, d);
246 endArray[2] = QPoint(-headLength, -d);
247
248 if (v->filledArrowHead())
249 p->setBrush(QBrush(pen.color(), Qt::SolidPattern));
250
251 QwtPainter::drawPolygon(p, endArray);
252 p->restore();
253 }
254
drawSymbols(QPainter * p,const QRect & rect,QwtArray<long> height,int symbolLineLength) const255 void Legend::drawSymbols(QPainter *p, const QRect &rect, QwtArray<long> height,
256 int symbolLineLength) const
257 {
258 Graph *g = (Graph *)d_plot->parent();
259
260 int w = rect.x() + left_margin;
261 int l = symbolLineLength + 2 * left_margin;
262
263 QString text = d_text->text().trimmed();
264 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
265 QStringList titles = text.split("\n", Qt::KeepEmptyParts);
266 #else
267 QStringList titles = text.split("\n", QString::KeepEmptyParts);
268 #endif
269
270 for (int i = 0; i < (int)titles.count(); i++) {
271 if (titles[i].contains("\\c{") || titles[i].contains("\\l(")) {
272 QString aux;
273 if (titles[i].contains("\\c{")) { // SciDAVis symbol specification
274 int pos = titles[i].indexOf("{", 0);
275 int pos2 = titles[i].indexOf("}", pos);
276 aux = titles[i].mid(pos + 1, pos2 - pos - 1);
277 } else if (titles[i].contains("\\l(")) { // Origin project legend
278 int pos = titles[i].indexOf("(", 0);
279 int pos2 = titles[i].indexOf(")", pos);
280 aux = titles[i].mid(pos + 1, pos2 - pos - 1);
281 }
282
283 int cv = aux.toInt() - 1;
284 if (cv < 0)
285 continue;
286
287 if (g->curveType(cv) == Graph ::VectXYXY || g->curveType(cv) == Graph ::VectXYAM)
288 drawVector(p, w, height[i], l, cv);
289 else {
290 const QwtPlotCurve *curve = g->curve(cv);
291 if (curve && curve->rtti() != QwtPlotItem::Rtti_PlotSpectrogram) {
292 QwtSymbol symb = curve->symbol();
293 const QBrush br = curve->brush();
294 QPen pen = curve->pen();
295
296 p->save();
297
298 if (curve->style() != 0) {
299 p->setPen(pen);
300 if (br.style() != Qt::NoBrush || g->curveType(cv) == Graph::Box) {
301 QRect lr = QRect(w, height[i] - 4, l, 10);
302 p->setBrush(br);
303 QwtPainter::drawRect(p, lr);
304 } else
305 QwtPainter::drawLine(p, w, height[i], w + l, height[i]);
306 }
307 int symb_size = symb.size().width();
308 if (symb_size > 15)
309 symb_size = 15;
310 else if (symb_size < 3)
311 symb_size = 3;
312 symb.setSize(symb_size);
313 symb.draw(p, w + l / 2, height[i]);
314 p->restore();
315 }
316 }
317 } else if (titles[i].contains("\\p{")) {
318 int pos = titles[i].indexOf("{", 0);
319 int pos2 = titles[i].indexOf("}", pos);
320 QString aux = titles[i].mid(pos + 1, pos2 - pos - 1);
321
322 int id = aux.toInt();
323
324 Graph *g = (Graph *)d_plot->parent();
325 if (g->isPiePlot()) {
326 QwtPieCurve *curve = (QwtPieCurve *)d_plot->curve(1);
327 if (curve) {
328 const QBrush br = QBrush(curve->color(id - 1), curve->pattern());
329 QPen pen = curve->pen();
330
331 p->save();
332 p->setPen(QPen(pen.color(), 1, Qt::SolidLine));
333 QRect lr = QRect(w, height[i] - 4, l, 10);
334 p->setBrush(br);
335 QwtPainter::drawRect(p, lr);
336 p->restore();
337 }
338 }
339 }
340 }
341 }
342
drawLegends(QPainter * p,const QRect & rect,QwtArray<long> height,int symbolLineLength) const343 void Legend::drawLegends(QPainter *p, const QRect &rect, QwtArray<long> height,
344 int symbolLineLength) const
345 {
346 int w = rect.x() + left_margin;
347
348 QString text = d_text->text().trimmed();
349 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
350 QStringList titles = text.split("\n", Qt::KeepEmptyParts);
351 #else
352 QStringList titles = text.split("\n", QString::KeepEmptyParts);
353 #endif
354
355 for (int i = 0; i < (int)titles.count(); i++) {
356 QString str = titles[i];
357 int x = w;
358 if (str.contains("\\c{") || str.contains("\\p{") || str.contains("\\l("))
359 x += symbolLineLength + hspace;
360
361 QwtText aux(parse(str));
362 aux.setFont(d_text->font());
363 aux.setColor(d_text->color());
364
365 QSize size = aux.textSize();
366 // bug in Qwt; workaround in QwtText::textSize() only works for short texts.
367 // Thus, we work around the workaround.
368 const QwtMetricsMap map = QwtPainter::metricsMap();
369 if (!map.isIdentity()) {
370 int screen_width = map.layoutToScreenX(size.width());
371 screen_width -= 3;
372 screen_width *= 1.1;
373 size = QSize(map.screenToLayoutX(screen_width), size.height());
374 }
375
376 QRect tr = QRect(QPoint(x, height[i] - size.height() / 2), size);
377 aux.draw(p, tr);
378 }
379 }
380
itemsHeight(int y,int symbolLineLength,int & width,int & height) const381 QwtArray<long> Legend::itemsHeight(int y, int symbolLineLength, int &width, int &height) const
382 {
383 int maxL = 0;
384
385 width = 0;
386 height = 0;
387
388 QString text = d_text->text().trimmed();
389 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
390 QStringList titles = text.split("\n", Qt::KeepEmptyParts);
391 #else
392 QStringList titles = text.split("\n", QString::KeepEmptyParts);
393 #endif
394 int n = (int)titles.count();
395 QwtArray<long> heights(n);
396
397 int h = top_margin;
398
399 for (int i = 0; i < n; i++) {
400 QString str = titles[i];
401 int textL = 0;
402 if (str.contains("\\c{") || str.contains("\\p{") || str.contains("\\l("))
403 textL = symbolLineLength + hspace;
404
405 QwtText aux(parse(str));
406 QSize size = aux.textSize(d_text->font());
407 // bug in Qwt; workaround in QwtText::textSize() only works for short texts.
408 // Thus, we work around the workaround.
409 const QwtMetricsMap map = QwtPainter::metricsMap();
410 if (!map.isIdentity()) {
411 int screen_width = map.layoutToScreenX(size.width());
412 screen_width -= 3;
413 screen_width *= 1.1;
414 size = QSize(map.screenToLayoutX(screen_width), size.height());
415 }
416 textL += size.width();
417 if (textL > maxL)
418 maxL = textL;
419
420 int textH = size.height();
421 height += textH;
422
423 heights[i] = y + h + textH / 2;
424 h += textH;
425 }
426
427 height += 2 * top_margin;
428 width = 2 * left_margin + maxL;
429
430 return heights;
431 }
432
symbolsMaxLineLength() const433 int Legend::symbolsMaxLineLength() const
434 {
435 QList<int> cvs = d_plot->curveKeys();
436
437 int maxL = 0;
438 QString text = d_text->text().trimmed();
439 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
440 QStringList titles = text.split("\n", Qt::KeepEmptyParts);
441 #else
442 QStringList titles = text.split("\n", QString::KeepEmptyParts);
443 #endif
444 for (int i = 0; i < (int)titles.count(); i++) {
445 if (titles[i].contains("\\c{") && (int)cvs.size() > 0) {
446 QString aux;
447 if (titles[i].contains("\\c{")) { // SciDAVis symbol specification
448 int pos = titles[i].indexOf("{", 0);
449 int pos2 = titles[i].indexOf("}", pos);
450 aux = titles[i].mid(pos + 1, pos2 - pos - 1);
451 } else if (titles[i].contains("\\l(")) { // Origin project legend
452 int pos = titles[i].indexOf("(", 0);
453 int pos2 = titles[i].indexOf(")", pos);
454 aux = titles[i].mid(pos + 1, pos2 - pos - 1);
455 }
456
457 int cv = aux.toInt() - 1;
458 if (cv < 0 || cv >= cvs.count())
459 continue;
460
461 const QwtPlotCurve *c = (QwtPlotCurve *)d_plot->curve(cvs[cv]);
462 if (c && c->rtti() != QwtPlotItem::Rtti_PlotSpectrogram) {
463 int l = c->symbol().size().width();
464 if (l < 3)
465 l = 3;
466 else if (l > 15)
467 l = 15;
468 if (l > maxL && c->symbol().style() != QwtSymbol::NoSymbol)
469 maxL = l;
470 }
471 }
472
473 if (titles[i].contains("\\p{"))
474 maxL = 10;
475 }
476 return maxL;
477 }
478
parse(const QString & str) const479 QString Legend::parse(const QString &str) const
480 {
481 QString s = str;
482 if (s.contains("\\c{") || s.contains("\\p{") || s.contains("\\l(")) {
483 int pos = s.indexOf("}", 0);
484 if (s.contains("\\l("))
485 pos = s.indexOf(")", 0);
486 s = s.right(s.length() - pos - 1);
487 }
488
489 if (s.contains("%(")) { // curve name specification
490 int pos = s.indexOf("%(", 0);
491 int pos2 = s.indexOf(")", pos);
492 int cv = s.mid(pos + 2, pos2 - pos - 2).toInt() - 1;
493 if (cv >= 0) {
494 Graph *g = (Graph *)d_plot->parent();
495 if (g) {
496 const QwtPlotCurve *c = (QwtPlotCurve *)g->curve(cv);
497 if (c)
498 s = s.replace(pos, pos2 - pos + 1, c->title().text());
499 }
500 }
501 }
502 return s;
503 }
504
~Legend()505 Legend::~Legend()
506 {
507 delete d_text;
508 }
509