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