1 /* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
2  * Qwt Widget Library
3  * Copyright (C) 1997   Josef Wilgen
4  * Copyright (C) 2002   Uwe Rathmann
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the Qwt License, Version 1.0
8  *****************************************************************************/
9 
10 #include "qwt_plot_tradingcurve.h"
11 #include "qwt_scale_map.h"
12 #include "qwt_clipper.h"
13 #include "qwt_painter.h"
14 #include <qpainter.h>
15 
qwtIsSampleInside(const QwtOHLCSample & sample,double tMin,double tMax,double vMin,double vMax)16 static inline bool qwtIsSampleInside( const QwtOHLCSample &sample,
17     double tMin, double tMax, double vMin, double vMax )
18 {
19     const double t = sample.time;
20     const QwtInterval interval = sample.boundingInterval();
21 
22     const bool isOffScreen = ( t < tMin ) || ( t > tMax )
23         || ( interval.maxValue() < vMin ) || ( interval.minValue() > vMax );
24 
25     return !isOffScreen;
26 }
27 
28 class QwtPlotTradingCurve::PrivateData
29 {
30 public:
PrivateData()31     PrivateData():
32         symbolStyle( QwtPlotTradingCurve::CandleStick ),
33         symbolExtent( 0.6 ),
34         minSymbolWidth( 2.0 ),
35         maxSymbolWidth( -1.0 ),
36         paintAttributes( QwtPlotTradingCurve::ClipSymbols )
37     {
38         symbolBrush[0] = QBrush( Qt::white );
39         symbolBrush[1] = QBrush( Qt::black );
40     }
41 
42     QwtPlotTradingCurve::SymbolStyle symbolStyle;
43     double symbolExtent;
44     double minSymbolWidth;
45     double maxSymbolWidth;
46 
47     QPen symbolPen;
48     QBrush symbolBrush[2]; // Increasing/Decreasing
49 
50     QwtPlotTradingCurve::PaintAttributes paintAttributes;
51 };
52 
53 /*!
54   Constructor
55   \param title Title of the curve
56 */
QwtPlotTradingCurve(const QwtText & title)57 QwtPlotTradingCurve::QwtPlotTradingCurve( const QwtText &title ):
58     QwtPlotSeriesItem( title )
59 {
60     init();
61 }
62 
63 /*!
64   Constructor
65   \param title Title of the curve
66 */
QwtPlotTradingCurve(const QString & title)67 QwtPlotTradingCurve::QwtPlotTradingCurve( const QString &title ):
68     QwtPlotSeriesItem( QwtText( title ) )
69 {
70     init();
71 }
72 
73 //! Destructor
~QwtPlotTradingCurve()74 QwtPlotTradingCurve::~QwtPlotTradingCurve()
75 {
76     delete d_data;
77 }
78 
79 //! Initialize internal members
init()80 void QwtPlotTradingCurve::init()
81 {
82     setItemAttribute( QwtPlotItem::Legend, true );
83     setItemAttribute( QwtPlotItem::AutoScale, true );
84 
85     d_data = new PrivateData;
86     setData( new QwtTradingChartData() );
87 
88     setZ( 19.0 );
89 }
90 
91 //! \return QwtPlotItem::Rtti_PlotTradingCurve
rtti() const92 int QwtPlotTradingCurve::rtti() const
93 {
94     return QwtPlotTradingCurve::Rtti_PlotTradingCurve;
95 }
96 
97 /*!
98   Specify an attribute how to draw the curve
99 
100   \param attribute Paint attribute
101   \param on On/Off
102   \sa testPaintAttribute()
103 */
setPaintAttribute(PaintAttribute attribute,bool on)104 void QwtPlotTradingCurve::setPaintAttribute(
105     PaintAttribute attribute, bool on )
106 {
107     if ( on )
108         d_data->paintAttributes |= attribute;
109     else
110         d_data->paintAttributes &= ~attribute;
111 }
112 
113 /*!
114     \return True, when attribute is enabled
115     \sa PaintAttribute, setPaintAttribute()
116 */
testPaintAttribute(PaintAttribute attribute) const117 bool QwtPlotTradingCurve::testPaintAttribute(
118     PaintAttribute attribute ) const
119 {
120     return ( d_data->paintAttributes & attribute );
121 }
122 
123 /*!
124   Initialize data with an array of samples.
125   \param samples Vector of samples
126 
127   \sa QwtPlotSeriesItem::setData()
128 */
setSamples(const QVector<QwtOHLCSample> & samples)129 void QwtPlotTradingCurve::setSamples(
130     const QVector<QwtOHLCSample> &samples )
131 {
132     setData( new QwtTradingChartData( samples ) );
133 }
134 
135 /*!
136   Assign a series of samples
137 
138   setSamples() is just a wrapper for setData() without any additional
139   value - beside that it is easier to find for the developer.
140 
141   \param data Data
142   \warning The item takes ownership of the data object, deleting
143            it when its not used anymore.
144 */
setSamples(QwtSeriesData<QwtOHLCSample> * data)145 void QwtPlotTradingCurve::setSamples(
146     QwtSeriesData<QwtOHLCSample> *data )
147 {
148     setData( data );
149 }
150 
151 /*!
152   Set the symbol style
153 
154   \param style Symbol style
155 
156   \sa symbolStyle(), setSymbolExtent(),
157       setSymbolPen(), setSymbolBrush()
158 */
setSymbolStyle(SymbolStyle style)159 void QwtPlotTradingCurve::setSymbolStyle( SymbolStyle style )
160 {
161     if ( style != d_data->symbolStyle )
162     {
163         d_data->symbolStyle = style;
164 
165         legendChanged();
166         itemChanged();
167     }
168 }
169 
170 /*!
171   \return Symbol style
172   \sa setSymbolStyle(), symbolExtent(), symbolPen(), symbolBrush()
173 */
symbolStyle() const174 QwtPlotTradingCurve::SymbolStyle QwtPlotTradingCurve::symbolStyle() const
175 {
176     return d_data->symbolStyle;
177 }
178 
179 /*!
180   Build and assign the symbol pen
181 
182   In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it
183   non cosmetic ( see QPen::isCosmetic() ). This method has been introduced
184   to hide this incompatibility.
185 
186   \param color Pen color
187   \param width Pen width
188   \param style Pen style
189 
190   \sa pen(), brush()
191  */
setSymbolPen(const QColor & color,qreal width,Qt::PenStyle style)192 void QwtPlotTradingCurve::setSymbolPen(
193     const QColor &color, qreal width, Qt::PenStyle style )
194 {
195     setSymbolPen( QPen( color, width, style ) );
196 }
197 
198 /*!
199   \brief Set the symbol pen
200 
201   The symbol pen is used for rendering the lines of the
202   bar or candlestick symbols
203 
204   \sa symbolPen(), setSymbolBrush()
205 */
setSymbolPen(const QPen & pen)206 void QwtPlotTradingCurve::setSymbolPen( const QPen &pen )
207 {
208     if ( pen != d_data->symbolPen )
209     {
210         d_data->symbolPen = pen;
211 
212         legendChanged();
213         itemChanged();
214     }
215 }
216 
217 /*!
218   \return Symbol pen
219   \sa setSymbolPen(), symbolBrush()
220 */
symbolPen() const221 QPen QwtPlotTradingCurve::symbolPen() const
222 {
223     return d_data->symbolPen;
224 }
225 
226 /*!
227   Set the symbol brush
228 
229   \param direction Direction type
230   \param brush Brush used to fill the body of all candlestick
231                symbols with the direction
232 
233   \sa symbolBrush(), setSymbolPen()
234 */
setSymbolBrush(Direction direction,const QBrush & brush)235 void QwtPlotTradingCurve::setSymbolBrush(
236     Direction direction, const QBrush &brush )
237 {
238     // silencing -Wtautological-constant-out-of-range-compare
239     const int index = static_cast< int >( direction );
240     if ( index < 0 || index >= 2 )
241         return;
242 
243     if ( brush != d_data->symbolBrush[ index ] )
244     {
245         d_data->symbolBrush[ index ] = brush;
246 
247         legendChanged();
248         itemChanged();
249     }
250 }
251 
252 /*!
253   \param direction
254   \return Brush used to fill the body of all candlestick
255           symbols with the direction
256 
257   \sa setSymbolPen(), symbolBrush()
258 */
symbolBrush(Direction direction) const259 QBrush QwtPlotTradingCurve::symbolBrush( Direction direction ) const
260 {
261     const int index = static_cast< int >( direction );
262     if ( index < 0 || index >= 2 )
263         return QBrush();
264 
265     return d_data->symbolBrush[ index ];
266 }
267 
268 /*!
269   \brief Set the extent of the symbol
270 
271   The width of the symbol is given in scale coordinates. When painting
272   a symbol the width is scaled into paint device coordinates
273   by scaledSymbolWidth(). The scaled width is bounded by
274   minSymbolWidth(), maxSymbolWidth()
275 
276   \param extent Symbol width in scale coordinates
277 
278   \sa symbolExtent(), scaledSymbolWidth(),
279       setMinSymbolWidth(), setMaxSymbolWidth()
280 */
setSymbolExtent(double extent)281 void QwtPlotTradingCurve::setSymbolExtent( double extent )
282 {
283     extent = qMax( 0.0, extent );
284     if ( extent != d_data->symbolExtent )
285     {
286         d_data->symbolExtent = extent;
287 
288         legendChanged();
289         itemChanged();
290     }
291 }
292 
293 /*!
294   \return Extent of a symbol in scale coordinates
295   \sa setSymbolExtent(), scaledSymbolWidth(),
296       minSymbolWidth(), maxSymbolWidth()
297 */
symbolExtent() const298 double QwtPlotTradingCurve::symbolExtent() const
299 {
300     return d_data->symbolExtent;
301 }
302 
303 /*!
304   Set a minimum for the symbol width
305 
306   \param width Width in paint device coordinates
307   \sa minSymbolWidth(), setMaxSymbolWidth(), setSymbolExtent()
308  */
setMinSymbolWidth(double width)309 void QwtPlotTradingCurve::setMinSymbolWidth( double width )
310 {
311     width = qMax( width, 0.0 );
312     if ( width != d_data->minSymbolWidth )
313     {
314         d_data->minSymbolWidth = width;
315 
316         legendChanged();
317         itemChanged();
318     }
319 }
320 
321 /*!
322   \return Minmum for the symbol width
323   \sa setMinSymbolWidth(), maxSymbolWidth(), symbolExtent()
324  */
minSymbolWidth() const325 double QwtPlotTradingCurve::minSymbolWidth() const
326 {
327     return d_data->minSymbolWidth;
328 }
329 
330 /*!
331   Set a maximum for the symbol width
332 
333   A value <= 0.0 means an unlimited width
334 
335   \param width Width in paint device coordinates
336   \sa maxSymbolWidth(), setMinSymbolWidth(), setSymbolExtent()
337  */
setMaxSymbolWidth(double width)338 void QwtPlotTradingCurve::setMaxSymbolWidth( double width )
339 {
340     if ( width != d_data->maxSymbolWidth )
341     {
342         d_data->maxSymbolWidth = width;
343 
344         legendChanged();
345         itemChanged();
346     }
347 }
348 
349 /*!
350   \return Maximum for the symbol width
351   \sa setMaxSymbolWidth(), minSymbolWidth(), symbolExtent()
352  */
maxSymbolWidth() const353 double QwtPlotTradingCurve::maxSymbolWidth() const
354 {
355     return d_data->maxSymbolWidth;
356 }
357 
358 /*!
359   \return Bounding rectangle of all samples.
360   For an empty series the rectangle is invalid.
361 */
boundingRect() const362 QRectF QwtPlotTradingCurve::boundingRect() const
363 {
364     QRectF rect = QwtPlotSeriesItem::boundingRect();
365     if ( orientation() == Qt::Vertical )
366         rect.setRect( rect.y(), rect.x(), rect.height(), rect.width() );
367 
368     return rect;
369 }
370 
371 /*!
372   Draw an interval of the curve
373 
374   \param painter Painter
375   \param xMap Maps x-values into pixel coordinates.
376   \param yMap Maps y-values into pixel coordinates.
377   \param canvasRect Contents rectangle of the canvas
378   \param from Index of the first point to be painted
379   \param to Index of the last point to be painted. If to < 0 the
380          curve will be painted to its last point.
381 
382   \sa drawSymbols()
383 */
drawSeries(QPainter * painter,const QwtScaleMap & xMap,const QwtScaleMap & yMap,const QRectF & canvasRect,int from,int to) const384 void QwtPlotTradingCurve::drawSeries( QPainter *painter,
385     const QwtScaleMap &xMap, const QwtScaleMap &yMap,
386     const QRectF &canvasRect, int from, int to ) const
387 {
388     if ( to < 0 )
389         to = dataSize() - 1;
390 
391     if ( from < 0 )
392         from = 0;
393 
394     if ( from > to )
395         return;
396 
397     painter->save();
398 
399     if ( d_data->symbolStyle != QwtPlotTradingCurve::NoSymbol )
400         drawSymbols( painter, xMap, yMap, canvasRect, from, to );
401 
402     painter->restore();
403 }
404 
405 /*!
406   Draw symbols
407 
408   \param painter Painter
409   \param xMap x map
410   \param yMap y map
411   \param canvasRect Contents rectangle of the canvas
412   \param from Index of the first point to be painted
413   \param to Index of the last point to be painted
414 
415   \sa drawSeries()
416 */
drawSymbols(QPainter * painter,const QwtScaleMap & xMap,const QwtScaleMap & yMap,const QRectF & canvasRect,int from,int to) const417 void QwtPlotTradingCurve::drawSymbols( QPainter *painter,
418     const QwtScaleMap &xMap, const QwtScaleMap &yMap,
419     const QRectF &canvasRect, int from, int to ) const
420 {
421     const QRectF tr = QwtScaleMap::invTransform( xMap, yMap, canvasRect );
422 
423     const QwtScaleMap *timeMap, *valueMap;
424     double tMin, tMax, vMin, vMax;
425 
426     const Qt::Orientation orient = orientation();
427     if ( orient == Qt::Vertical )
428     {
429         timeMap = &xMap;
430         valueMap = &yMap;
431 
432         tMin = tr.left();
433         tMax = tr.right();
434         vMin = tr.top();
435         vMax = tr.bottom();
436     }
437     else
438     {
439         timeMap = &yMap;
440         valueMap = &xMap;
441 
442         vMin = tr.left();
443         vMax = tr.right();
444         tMin = tr.top();
445         tMax = tr.bottom();
446     }
447 
448     const bool inverted = timeMap->isInverting();
449     const bool doClip = d_data->paintAttributes & ClipSymbols;
450     const bool doAlign = QwtPainter::roundingAlignment( painter );
451 
452     double symbolWidth = scaledSymbolWidth( xMap, yMap, canvasRect );
453     if ( doAlign )
454         symbolWidth = qFloor( 0.5 * symbolWidth ) * 2.0;
455 
456     QPen pen = d_data->symbolPen;
457     pen.setCapStyle( Qt::FlatCap );
458 
459     painter->setPen( pen );
460 
461     for ( int i = from; i <= to; i++ )
462     {
463         const QwtOHLCSample s = sample( i );
464 
465         if ( !doClip || qwtIsSampleInside( s, tMin, tMax, vMin, vMax ) )
466         {
467             QwtOHLCSample translatedSample;
468 
469             translatedSample.time = timeMap->transform( s.time );
470             translatedSample.open = valueMap->transform( s.open );
471             translatedSample.high = valueMap->transform( s.high );
472             translatedSample.low = valueMap->transform( s.low );
473             translatedSample.close = valueMap->transform( s.close );
474 
475             const int brushIndex = ( s.open < s.close )
476                 ? QwtPlotTradingCurve::Increasing
477                 : QwtPlotTradingCurve::Decreasing;
478 
479             if ( doAlign )
480             {
481                 translatedSample.time = qRound( translatedSample.time );
482                 translatedSample.open = qRound( translatedSample.open );
483                 translatedSample.high = qRound( translatedSample.high );
484                 translatedSample.low = qRound( translatedSample.low );
485                 translatedSample.close = qRound( translatedSample.close );
486             }
487 
488             switch( d_data->symbolStyle )
489             {
490                 case Bar:
491                 {
492                     drawBar( painter, translatedSample,
493                         orient, inverted, symbolWidth );
494                     break;
495                 }
496                 case CandleStick:
497                 {
498                     painter->setBrush( d_data->symbolBrush[ brushIndex ] );
499                     drawCandleStick( painter, translatedSample,
500                         orient, symbolWidth );
501                     break;
502                 }
503                 default:
504                 {
505                     if ( d_data->symbolStyle >= UserSymbol )
506                     {
507                         painter->setBrush( d_data->symbolBrush[ brushIndex ] );
508                         drawUserSymbol( painter, d_data->symbolStyle,
509                             translatedSample, orient, inverted, symbolWidth );
510                     }
511                 }
512             }
513         }
514     }
515 }
516 
517 /*!
518   \brief Draw a symbol for a symbol style >= UserSymbol
519 
520   The implementation does nothing and is intended to be overloaded
521 
522   \param painter Qt painter, initialized with pen/brush
523   \param symbolStyle Symbol style
524   \param sample Samples already translated into paint device coordinates
525   \param orientation Vertical or horizontal
526   \param inverted True, when the opposite scale
527                   ( Qt::Vertical: x, Qt::Horizontal: y ) is increasing
528                   in the opposite direction as QPainter coordinates.
529   \param symbolWidth Width of the symbol in paint device coordinates
530 */
drawUserSymbol(QPainter * painter,SymbolStyle symbolStyle,const QwtOHLCSample & sample,Qt::Orientation orientation,bool inverted,double symbolWidth) const531 void QwtPlotTradingCurve::drawUserSymbol( QPainter *painter,
532     SymbolStyle symbolStyle, const QwtOHLCSample &sample,
533     Qt::Orientation orientation, bool inverted, double symbolWidth ) const
534 {
535     Q_UNUSED( painter )
536     Q_UNUSED( symbolStyle )
537     Q_UNUSED( orientation )
538     Q_UNUSED( inverted )
539     Q_UNUSED( symbolWidth )
540     Q_UNUSED( sample )
541 }
542 
543 /*!
544   \brief Draw a bar
545 
546   \param painter Qt painter, initialized with pen/brush
547   \param sample Sample, already translated into paint device coordinates
548   \param orientation Vertical or horizontal
549   \param inverted When inverted is false the open tick is painted
550                   to the left/top, otherwise it is painted right/bottom.
551                   The close tick is painted in the opposite direction
552                   of the open tick.
553                   painted in the opposite d
554                   opposite direction.
555   \param width Width or height of the candle, depending on the orientation
556 
557   \sa Bar
558 */
drawBar(QPainter * painter,const QwtOHLCSample & sample,Qt::Orientation orientation,bool inverted,double width) const559 void QwtPlotTradingCurve::drawBar( QPainter *painter,
560     const QwtOHLCSample &sample, Qt::Orientation orientation,
561     bool inverted, double width ) const
562 {
563     double w2 = 0.5 * width;
564     if ( inverted )
565         w2 *= -1;
566 
567     if ( orientation == Qt::Vertical )
568     {
569         QwtPainter::drawLine( painter,
570             sample.time, sample.low, sample.time, sample.high );
571 
572         QwtPainter::drawLine( painter,
573             sample.time - w2, sample.open, sample.time, sample.open );
574         QwtPainter::drawLine( painter,
575             sample.time + w2, sample.close, sample.time, sample.close );
576     }
577     else
578     {
579         QwtPainter::drawLine( painter, sample.low, sample.time,
580             sample.high, sample.time );
581         QwtPainter::drawLine( painter,
582             sample.open, sample.time - w2, sample.open, sample.time );
583         QwtPainter::drawLine( painter,
584             sample.close, sample.time + w2, sample.close, sample.time );
585     }
586 }
587 
588 /*!
589   \brief Draw a candle stick
590 
591   \param painter Qt painter, initialized with pen/brush
592   \param sample Samples already translated into paint device coordinates
593   \param orientation Vertical or horizontal
594   \param width Width or height of the candle, depending on the orientation
595 
596   \sa CandleStick
597 */
drawCandleStick(QPainter * painter,const QwtOHLCSample & sample,Qt::Orientation orientation,double width) const598 void QwtPlotTradingCurve::drawCandleStick( QPainter *painter,
599     const QwtOHLCSample &sample, Qt::Orientation orientation,
600     double width ) const
601 {
602     const double t = sample.time;
603     const double v1 = qMin( sample.low, sample.high );
604     const double v2 = qMin( sample.open, sample.close );
605     const double v3 = qMax( sample.low, sample.high );
606     const double v4 = qMax( sample.open, sample.close );
607 
608     if ( orientation == Qt::Vertical )
609     {
610         QwtPainter::drawLine( painter, t, v1, t, v2 );
611         QwtPainter::drawLine( painter, t, v3, t, v4 );
612 
613         QRectF rect( t - 0.5 * width, sample.open,
614             width, sample.close - sample.open );
615 
616         QwtPainter::drawRect( painter, rect );
617     }
618     else
619     {
620         QwtPainter::drawLine( painter, v1, t, v2, t );
621         QwtPainter::drawLine( painter, v3, t, v4, t );
622 
623         const QRectF rect( sample.open, t - 0.5 * width,
624             sample.close - sample.open, width );
625 
626         QwtPainter::drawRect( painter, rect );
627     }
628 }
629 
630 /*!
631   \return A rectangle filled with the color of the symbol pen
632 
633   \param index Index of the legend entry
634                 ( usually there is only one )
635   \param size Icon size
636 
637   \sa setLegendIconSize(), legendData()
638 */
legendIcon(int index,const QSizeF & size) const639 QwtGraphic QwtPlotTradingCurve::legendIcon( int index,
640     const QSizeF &size ) const
641 {
642     Q_UNUSED( index );
643     return defaultIcon( d_data->symbolPen.color(), size );
644 }
645 
646 /*!
647   Calculate the symbol width in paint coordinates
648 
649   The width is calculated by scaling the symbol extent into
650   paint device coordinates bounded by the minimum/maximum
651   symbol width.
652 
653   \param xMap Maps x-values into pixel coordinates.
654   \param yMap Maps y-values into pixel coordinates.
655   \param canvasRect Contents rectangle of the canvas
656 
657   \return Symbol width in paint coordinates
658 
659   \sa symbolExtent(), minSymbolWidth(), maxSymbolWidth()
660 */
scaledSymbolWidth(const QwtScaleMap & xMap,const QwtScaleMap & yMap,const QRectF & canvasRect) const661 double QwtPlotTradingCurve::scaledSymbolWidth(
662     const QwtScaleMap &xMap, const QwtScaleMap &yMap,
663     const QRectF &canvasRect ) const
664 {
665     Q_UNUSED( canvasRect );
666 
667     if ( d_data->maxSymbolWidth > 0.0 &&
668         d_data->minSymbolWidth >= d_data->maxSymbolWidth )
669     {
670         return d_data->minSymbolWidth;
671     }
672 
673     const QwtScaleMap *map =
674         ( orientation() == Qt::Vertical ) ? &xMap : &yMap;
675 
676     const double pos = map->transform( map->s1() + d_data->symbolExtent );
677 
678     double width = qAbs( pos - map->p1() );
679 
680     width = qMax( width,  d_data->minSymbolWidth );
681     if ( d_data->maxSymbolWidth > 0.0 )
682         width = qMin( width, d_data->maxSymbolWidth );
683 
684     return width;
685 }
686