NEW: added financial graphs (Candlestick or OHLC) and example for these

This commit is contained in:
jkriege2 2024-02-11 14:18:50 +01:00
parent 863f083bf9
commit de68d8b8d6
41 changed files with 1336 additions and 3 deletions

View File

@ -60,6 +60,7 @@ This software is licensed under the term of the [GNU Lesser General Public Licen
- large variety of [image plots](http://jkriege2.github.io/JKQtPlotter/group__jkqtplotter__imagelots.html) (inclusing different color-scale modes, RGBA-plots, overlays/masks)
- [contour plots](https://jkriege2.github.io/JKQtPlotter/group__jkqtplotter__imagelots__contour.html)
- [vector field graphs/quiver plots](https://jkriege2.github.io/JKQtPlotter/group__jkqtplotter__vectorfieldgraphs.html)
- [financial graphs (candlestick/OHLC)](https://jkriege2.github.io/JKQtPlotter/group__jkqtplotter__financialgraphs.html)
- [geometric forms](http://jkriege2.github.io/JKQtPlotter/group__jkqtplotter__geoplots.html) / [annotations](http://jkriege2.github.io/JKQtPlotter/group__jkqtplotter__annotations.html)
- can be easily extended by deriving a new graph from [JKQTPPlotElement](http://jkriege2.github.io/JKQtPlotter/class_j_k_q_t_p_plot_element.html), [JKQTPPlotAnnotationElement](http://jkriege2.github.io/JKQtPlotter/class_j_k_q_t_p_plot_annotation_element.html), [JKQTPGeometricPlotElement](http://jkriege2.github.io/JKQtPlotter/class_j_k_q_t_p_geometric_plot_element.html), [JKQTPGraph](http://jkriege2.github.io/JKQtPlotter/class_j_k_q_t_p_graph.html)
- optional: [OpenCV interface](http://jkriege2.github.io/JKQtPlotter/group__jkqtpinterfaceopencv.html), [CImg interfaces](http://jkriege2.github.io/JKQtPlotter/group__jkqtpinterfacecimg.html)

View File

@ -247,6 +247,7 @@ if(JKQtPlotter_BUILD_EXAMPLES)
graphlabels/graphlabels,graphlabels_hor
vectorfield
paramvectorfield
financialgraphs
)
@ -277,6 +278,7 @@ if(JKQtPlotter_BUILD_EXAMPLES)
graphlabels/JKQTPGLabelAwayFromXAxis,JKQTPGLabelAwayFromYAxis,JKQTPGLabelTowardsXAxis,JKQTPGLabelTowardsYAxis,JKQTPGLabelAboveData,JKQTPGLabelRightHandSide,JKQTPGLabelBelowData,JKQTPGLabelLeftHandSide,JKQTPGLabelCenteredOnData,JKQTPGLabelCenteredOnDataVertical,JKQTPGLSimpleBox,JKQTPGLSimpleBoxVertical,JKQTPGLSimpleBoxAndLine,JKQTPGLSimpleBoxAndLineVertical,JKQTPGLSimpleBoxAndLineONLYLABELS,JKQTPGLSimpleBoxAndLineONLYLABELSVertical/--iteratefunctorsteps--smallscreenshotplot
vectorfield/JKQTPVectorFieldGraph,JKQTPVectorFieldGraphAnchorBottom,JKQTPVectorFieldGraphAnchorMid,JKQTPVectorFieldGraphAnchorTip,JKQTPVectorFieldGraphAutoscaleLength,JKQTPVectorFieldGraphLengthFromData,JKQTPVectorFieldGraphIgnoreLength,JKQTPVectorFieldGraphIgnoreLengthAutoscaleLineWidthFromLength,JKQTPVectorFieldGraphAutoscaleLengthAutoscaleLineWidthFromLength/--iteratefunctorsteps
paramvectorfield/JKQTPParametrizedVectorFieldGraph,JKQTPParametrizedVectorFieldGraphColorFromMagnitude,JKQTPParametrizedVectorFieldGraphColorFromAngle,JKQTPParametrizedVectorFieldGraphDefaultColor/--iteratefunctorsteps
financialgraphs/JKQTPFinancialGraph,JKQTPFinancialGraphCandleStick,JKQTPFinancialGraphSetCandlestickTwoColor,JKQTPFinancialGraphSetCandlestickTwoColor2,JKQTPFinancialGraphSetCandlestickOneColor,JKQTPFinancialGraphOHLC,JKQTPFinancialGraphSetOHLCTwoColor,JKQTPFinancialGraphSidyBySide/--iteratefunctorsteps
)

View File

@ -100,6 +100,9 @@ All test-projects are Qt-projects that use qmake to build. You can load them int
<tr><td> \image html paramvectorfield_small.png
<td> \subpage JKQTPParametrizedVectorFieldGraphExample
<td> `JKQTPParametrizedVectorFieldGraph`
<tr><td> \image html financialgraph_small.png
<td> \subpage JKQTPlotterFinancialChartExample
<td> `JKQTPFinancialGraph` (Candlestick/OHLC graphs), date-axis, rotated labels
</table>

View File

@ -303,6 +303,20 @@ This group assembles graphs that represent vector fields (i.e. sets of quadruple
</table>
\defgroup jkqtplotter_financialgraphs Financial Graphs
\ingroup jkqtplotter_concretegraphs
This group assembles graphs that represent financial data (such as price mmovement of stocks, derivatives, ... and other traded goods) in a plot:
<table>
<tr>
<th> Screenshot
<th> Classes
<tr>
<td>\image html financialgraphs_small.png
<td> JKQTPFinancialGraph
</table>
\defgroup jkqtplotter_imagelots Matrix/Image Plotting
\ingroup jkqtplotter_concretegraphs

View File

@ -42,6 +42,7 @@
- \ref jkqtplotter_imagelots_elements "extensive library of image plots" (inclusing different color-scale modes, RGBA-plots, overlays/masks)
- \ref jkqtplotter_imagelots_contour "contour plots"
- \ref jkqtplotter_vectorfieldgraphs "vector field plots"
- \ref jkqtplotter_financialgraphs "financial charts (candlestick, OHLC, ...)
- \ref jkqtplotter_geoplots "geometric forms/annotations"
- can be easily extended by deriving a new graph from JKQTPPlotElement or JKQTPGeometricPlotElement
- <b>OPTIONAL: Interfaces to external libraries</b>

View File

@ -14,8 +14,6 @@ This page lists several todos and wishes for future version of JKQTPlotter
<li>graphic elements: annotation graphic element with text positionable like legend, or with (0..1),(0..1)-coordinates within plot</li>
<li>graphic elements: cubic/bezier curves for graphic elements</li>
<li>graphic elements: make coordinate systems selectable for all: x/y-axis, 0..1/0..1, topleft/topright... </li>
<li>graphs: add OHLC charts (financial, see https://en.m.wikipedia.org/wiki/Open-high-low-close_chart)</li>
<li>graphs: add candlestick charts (financial, see https://en.m.wikipedia.org/wiki/Candlestick_chart)</li>
<li>graphs: barchart/ranges chart with (x,y1,y2) or (x1,x2,y)</li>
<li>graphs: gant-chart as simplified vector field with (x,y1,y2) or (x1,x2,y), or (x,y,dx), (x,y,dy) ... different head/tail style</li>
<li>graphs: matrix plots with boxes, labels, ...</li>

View File

@ -131,6 +131,7 @@ Changes, compared to \ref page_whatsnew_V4_0_0 "v4.0.0" include:
<li>NEW: Base class JKQTPXYAndVectorGraph for graphs like vector fields, e.g. (x,y,dx,dy) or (x,y,angle,length) </li>
<li>NEW: JKQTPVectorFieldGraph for drawing "simple" vector fields/quiver plots (+example \ref JKQTPlotterVectorFieldExample)</li>
<li>NEW: JKQTPParametrizedVectorFieldGraph for drawing color-coded vector fields/quiver plots (+example \ref JKQTPParametrizedVectorFieldGraphExample)</li>
<li>NEW: JKQTPFinancialGraph for drawing candlestick or OHLC graphs of financial data, such as stock amrket prices (+example \ref JKQTPlotterFinancialChartExample)</li>
</ul></li>
<li>JKQTMathText:<ul>

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

After

Width:  |  Height:  |  Size: 298 KiB

View File

@ -69,6 +69,7 @@ if (JKQtPlotter_BUILD_LIB_JKQTPLOTTER)
add_subdirectory(evalcurve)
add_subdirectory(filledgraphs)
add_subdirectory(filledgraphs_errors)
add_subdirectory(financialgraphs)
add_subdirectory(functionplot)
add_subdirectory(geo_arrows)
add_subdirectory(geo_simple)

View File

@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.23)
set(EXAMPLE_NAME financialgraphs)
set(EXENAME jkqtptest_${EXAMPLE_NAME})
message( STATUS ".. Building Example ${EXAMPLE_NAME}" )
add_executable(${EXENAME} WIN32 ${EXAMPLE_NAME}.cpp)
target_link_libraries(${EXENAME} JKQTPExampleToolsLib)
target_include_directories(${EXENAME} PRIVATE ../../lib)
target_link_libraries(${EXENAME} ${jkqtplotter_namespace}JKQTPlotter${jkqtplotter_LIBNAME_VERSION_PART})
# precomiled headers to speed up compilation
if (JKQtPlotter_BUILD_WITH_PRECOMPILED_HEADERS)
target_precompile_headers(${EXENAME} REUSE_FROM jkqtptest_simpletest)
endif (JKQtPlotter_BUILD_WITH_PRECOMPILED_HEADERS)
# Installation
install(TARGETS ${EXENAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
#Installation of Qt DLLs on Windows
jkqtplotter_deployqt(${EXENAME})

View File

@ -0,0 +1,27 @@
# Example (JKQTPlotter): Financial Chart (Candlestick/OHLC) Example {#JKQTPlotterFinancialChartExample}
This project (see [`financialgraphs`](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/financialgraphs) demonstrates the use of JKQTPFinancialGraph to visualize financial data (i.e. market price development of stocks, derivatives, ...).
The source code of the main application is (see [`financialgraphs.cpp`](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/financialgraphs/financialgraphs.cpp).
Here is a short summary of the important parts of the code:
```.cpp
```
The result looks like this:
![financialgraphs](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/financialgraphs.png)
By default, the length of the drawn vector is determined from the actual length in the data via an autoscaling algorithm that is supposed to prevent the vectors from overlapping.
But you can modify this behaviour by adding a line
```.cpp
graph1->setVectorLengthMode(JKQTPVectorFieldGraph::LengthFromData);
```
which will use the given lengths directly (only scaled by an optional factor defined by JKQTPVectorFieldGraph::setLengthScaleFactor() ). The result then looks like this:
![financialgraphs](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/doc/images/JKQTPVectorFieldGraphLengthFromData.png)

View File

@ -0,0 +1,175 @@
/** \example financialgraphs.cpp
* Display financial data
*
* \ref JKQTPlotterFinancialChartExample
*/
#include "jkqtpexampleapplication.h"
#include <QApplication>
#include "jkqtplotter/jkqtplotter.h"
#include "jkqtplotter/graphs/jkqtpfinancial.h"
#include "jkqtpexampleapplication.h"
#include <random>
int main(int argc, char* argv[])
{
JKQTPAppSettingController highDPIController(argc,argv);
JKQTPExampleApplication app(argc, argv);
// 1. setup a plotter window and get a pointer to the internal datastore (for convenience)
JKQTPlotter plot;
plot.getPlotter()->setUseAntiAliasingForGraphs(true); // nicer (but slower) plotting
plot.getPlotter()->setUseAntiAliasingForSystem(true); // nicer (but slower) plotting
plot.getPlotter()->setUseAntiAliasingForText(true); // nicer (but slower) text rendering
JKQTPDatastore* ds=plot.getDatastore();
// 2. now we create 5 datacolumns with length Ndays entries in the datastore
// these will later hold the time-step and simulated market data of two stocks
const size_t NDays=31;
const size_t columnT=ds->addColumn("time");
const size_t columnO1=ds->addColumn("open(stock 1)");
const size_t columnC1=ds->addColumn("close(stock 1)");
const size_t columnL1=ds->addColumn("low(stock 1)");
const size_t columnH1=ds->addColumn("high(stock 1)");
const size_t columnO2=ds->addColumn("open(stock 2)");
const size_t columnC2=ds->addColumn("close(stock 2)");
const size_t columnL2=ds->addColumn("low(stock 2)");
const size_t columnH2=ds->addColumn("high(stock 2)");
const QDate t0(2024,1,8); // start date
// 3. now we simulate stock market data with a simple random walk
// with a few steps per day that are summmarized into the four
// properties Open,Close,Low,High for each day
double price1=30; // start price of stock 1
double price2=25; // start price of stock 2
const size_t stepsPerDay=20;
std::random_device rd; // random number generators:
std::mt19937 gen{rd()};
gen.seed(12352);
std::normal_distribution<double> dist1(0.001, 0.6);
std::normal_distribution<double> dist2(-0.0001, 0.4);
for (size_t t=0; t<NDays; t+=1) {// iterate over all simulated days (we don't care for weekends)
// step time from noon to noon in steps of days
const QDate d=t0.addDays(t);
ds->appendToColumn(columnT, QDateTime(d,QTime(12,0,0)).toMSecsSinceEpoch());
// the open-Value is the start-price of each day:
ds->appendToColumn(columnO1, price1);
ds->appendToColumn(columnO2, price2);
// now we iterate the market over the day
double L1=price1, L2=price2, H1=price1, H2=price2;
for (size_t i=0; i<stepsPerDay; i++) { // iterate over week days (Mon-Fri)
// prices are calculated from a sinple random walk (the while-loops ensure that we have positive prices only
double np;
while ((np=price1+dist1(gen))<=0.0); price1=np;
while ((np=price2+dist2(gen))<=0.0); price2=np;
// now we track the highest/lowest prices
L1=qMin(L1, price1);
L2=qMin(L2, price2);
H1=qMax(H1, price1);
H2=qMax(H2, price2);
}
// the close-Value is the final price of each day:
ds->appendToColumn(columnC1, price1);
ds->appendToColumn(columnC2, price2);
// finally we just have to add the highest and lowest prices of each day:
ds->appendToColumn(columnL1, L1);
ds->appendToColumn(columnL2, L2);
ds->appendToColumn(columnH1, H1);
ds->appendToColumn(columnH2, H2);
}
// 3. create two JKQTPFinancialGraph to display the data:
JKQTPFinancialGraph* graphCandleStick=new JKQTPFinancialGraph(&plot);
graphCandleStick->setXColumn(columnT);
graphCandleStick->setOpenColumn(columnO1);
graphCandleStick->setHighColumn(columnH1);
graphCandleStick->setLowColumn(columnL1);
graphCandleStick->setCloseColumn(columnC1);
graphCandleStick->setGraphType(JKQTPFinancialGraph::CandleStick);
graphCandleStick->setTitle(QObject::tr("stock 1 (candlestick)"));
graphCandleStick->setCandlestickTwoColor(QColor("darkgreen"), QColor("maroon"));
JKQTPFinancialGraph* graphOHLC=new JKQTPFinancialGraph(&plot);
graphOHLC->setXColumn(columnT);
graphOHLC->setOpenColumn(columnO2);
graphOHLC->setHighColumn(columnH2);
graphOHLC->setLowColumn(columnL2);
graphOHLC->setCloseColumn(columnC2);
graphOHLC->setGraphType(JKQTPFinancialGraph::OHLC);
graphOHLC->setTitle(QObject::tr("stock 2 (OHLC)"));
graphOHLC->setOHLCTwoColor(QColor("darkgreen"), QColor("maroon"));
// 4. add the graphs to the plot, so it is actually displayed
plot.addGraph(graphCandleStick);
plot.addGraph(graphOHLC);
// 5. scale the plot so the graph is contained and make x-axis a date-axis
plot.getXAxis()->setTickLabelType(JKQTPCALTdate);
plot.getXAxis()->setAxisLabel("time");
plot.getXAxis()->setTickLabelAngle(35); // rotate axis tick label by 35°, so they are readable
plot.getYAxis()->setAxisLabel("stock price [\\$]");
plot.getMainKey()->setPosition(JKQTPKeyInsideTopLeft);
plot.zoomToFit();
// show plotter and make it a decent size
plot.setWindowTitle("JKQTPVectorFieldGraph example");
plot.show();
plot.resize(600/plot.devicePixelRatioF(),450/plot.devicePixelRatioF());
app.addExportStepFunctor([&](){
graphCandleStick->setVisible(true);
graphOHLC->setVisible(false);
plot.zoomToFit();
plot.setX(plot.getXMin(), plot.getXMin()+(plot.getXMax()-plot.getXMin())/2.0);
plot.redrawPlot();
});
app.addExportStepFunctor([&](){
graphCandleStick->setCandlestickTwoColor(QColor("blue"), QColor("orange"));
plot.redrawPlot();
});
app.addExportStepFunctor([&](){
graphCandleStick->setCandlestickTwoColor(QColor("green"), QColor("red"), QColor("black"));
plot.redrawPlot();
});
app.addExportStepFunctor([&](){
graphCandleStick->setCandlestickOneColor(QColor("black"));
plot.redrawPlot();
});
app.addExportStepFunctor([&](){
graphCandleStick->setVisible(false);
graphOHLC->setVisible(true);
plot.zoomToFit();
plot.setX(plot.getXMin(), plot.getXMin()+(plot.getXMax()-plot.getXMin())/2.0);
plot.redrawPlot();
});
app.addExportStepFunctor([&](){
graphOHLC->setOHLCTwoColor(QColor("darkblue"), QColor("darkorange"));
plot.redrawPlot();
});
app.addExportStepFunctor([&](){
for (size_t t=0; t<NDays; t+=1) {
const double drift=-3.0*static_cast<double>(t)/static_cast<double>(NDays);
ds->set(columnO2, t, ds->get(columnO1,t)+drift);
ds->set(columnH2, t, ds->get(columnH1,t)+drift);
ds->set(columnL2, t, ds->get(columnL1,t)+drift);
ds->set(columnC2, t, ds->get(columnC1,t)+drift);
}
graphCandleStick->setVisible(true);
graphOHLC->setVisible(true);
graphCandleStick->setCandlestickTwoColor(QColor("darkgreen"), QColor("maroon"));
graphOHLC->setCandlestickTwoColor(QColor("blue"), QColor("orange"));
graphCandleStick->autoscaleBoxWidthAndShiftSeparatedGroups();
plot.zoomToFit();
plot.setX(plot.getXMin(), plot.getXMin()+(plot.getXMax()-plot.getXMin())/2.0);
plot.redrawPlot();
});
return app.exec();
}

View File

@ -60,6 +60,7 @@ isEmpty(JKQTP_PLOTTER_PRI_INCLUDED) {
$$PWD/jkqtplotter/graphs/jkqtpgraphlabelstylemixin.h \
$$PWD/jkqtplotter/graphs/jkqtpgraphlabels.h \
$$PWD/jkqtplotter/graphs/jkqtpvectorfield.h \
$$PWD/jkqtplotter/graphs/jkqtpfinancial.h \
$$PWD/jkqtplotter/gui/jkqtpcomboboxes.h \
$$PWD/jkqtplotter/gui/jkqtpenhancedspinboxes.h \
$$PWD/jkqtplotter/gui/jkqtpenhancedtableview.h \
@ -117,6 +118,7 @@ isEmpty(JKQTP_PLOTTER_PRI_INCLUDED) {
$$PWD/jkqtplotter/graphs/jkqtpgraphlabelstylemixin.cpp \
$$PWD/jkqtplotter/graphs/jkqtpgraphlabels.cpp \
$$PWD/jkqtplotter/graphs/jkqtpvectorfield.cpp \
$$PWD/jkqtplotter/graphs/jkqtpfinancial.cpp \
$$PWD/jkqtplotter/gui/jkqtpcomboboxes.cpp \
$$PWD/jkqtplotter/gui/jkqtpenhancedspinboxes.cpp \
$$PWD/jkqtplotter/gui/jkqtpenhancedtableview.cpp \

View File

@ -13,6 +13,7 @@ target_sources(${lib_name} PRIVATE
jkqtpevaluatedfunctionbase.cpp
jkqtpevaluatedfunction.cpp
jkqtpfilledcurve.cpp
jkqtpfinancial.cpp
jkqtpgeometric.cpp
jkqtpgeoannotations.cpp
jkqtpgeobase.cpp
@ -44,6 +45,7 @@ target_sources(${lib_name} PUBLIC FILE_SET HEADERS TYPE HEADERS
jkqtpevaluatedfunctionbase.h
jkqtpevaluatedfunction.h
jkqtpfilledcurve.h
jkqtpfinancial.h
jkqtpgeometric.h
jkqtpgeoannotations.h
jkqtpgeobase.h

View File

@ -0,0 +1,508 @@
/*
Copyright (c) 2008-2024 Jan W. Krieger (<jan@jkrieger.de>)
This software is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License (LGPL) as published by
the Free Software Foundation, either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License (LGPL) for more details.
You should have received a copy of the GNU Lesser General Public License (LGPL)
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "jkqtplotter/graphs/jkqtpfinancial.h"
#include "jkqtplotter/jkqtpbaseplotter.h"
#include <stdlib.h>
#include <QDebug>
#include <iostream>
#include "jkqtplotter/jkqtptools.h"
#include "jkqtplotter/jkqtplotter.h"
#define SmallestGreaterZeroCompare_xvsgz() if ((xvsgz>10.0*DBL_MIN)&&((smallestGreaterZero<10.0*DBL_MIN) || (xvsgz<smallestGreaterZero))) smallestGreaterZero=xvsgz;
JKQTPFinancialGraph::JKQTPFinancialGraph(JKQTBasePlotter* parent):
JKQTPXGraph(parent),
graphType(FinancialGraphType::CandleStick),
openColumn(-1),
closeColumn(-1),
highColumn(-1),
lowColumn(-1),
width(0.8),
shift(0)
{
m_fillStylePositive.initFillStyle(parent, parentPlotStyle, JKQTPPlotStyleType::Boxplot);
m_fillStyleNegative.initFillStyleInvertedColor(&m_fillStylePositive);
m_lineStylePositive.initLineStyle(parent, parentPlotStyle, JKQTPPlotStyleType::Boxplot);
m_lineStyleNegative.initLineStyle(parent, parentPlotStyle, JKQTPPlotStyleType::Boxplot);
m_lineStyleNegative.setLineColor(m_fillStyleNegative.getFillColor());
static QSet<JKQTBasePlotter*> parentsAlreadySeen;
if (!parentsAlreadySeen.contains(parent)) {
// ensure, the first graph in a plot has default red/green colors
// further plots will have colors
m_fillStylePositive.setFillColor(QColor("darkgreen"));
m_fillStyleNegative.setFillColor(QColor("maroon"));
m_lineStylePositive.setLineColor(QColor("darkgreen"));
m_lineStyleNegative.setLineColor(QColor("maroon"));
}
parentsAlreadySeen.insert(parent);
}
JKQTPFinancialGraph::JKQTPFinancialGraph(JKQTPlotter* parent):
JKQTPFinancialGraph(parent->getPlotter())
{
}
void JKQTPFinancialGraph::drawKeyMarker(JKQTPEnhancedPainter& painter, const QRectF& r) {
painter.save(); auto __finalpaint=JKQTPFinally([&painter]() {painter.restore();});
const QPen pp=m_lineStylePositive.getKeyLinePen(painter, r, parent);
const QPen pn=m_lineStyleNegative.getKeyLinePen(painter, r, parent);
const QBrush bp=m_fillStylePositive.getFillBrush(painter, parent);
const QBrush bn=m_fillStyleNegative.getFillBrush(painter, parent);
if (graphType==CandleStick) {
painter.setPen(pp);
painter.setBrush(bp);
QRectF rect=r;
rect.setWidth(r.width()-pp.widthF());
rect.setHeight(r.height()*0.8);
rect.setX(rect.x()+pp.widthF()/2.0);
rect.setY(rect.y()+r.height()*0.1);
painter.drawPolygon(QPolygonF()<<rect.topLeft()<<rect.topRight()<<rect.bottomLeft());
painter.drawLine(r.center().x(),r.top(),r.center().x(),rect.top());
painter.setPen(pn);
painter.setBrush(bn);
painter.drawPolygon(QPolygonF()<<rect.topRight()<<rect.bottomRight()<<rect.bottomLeft());
painter.drawLine(r.center().x(),r.bottom(),r.center().x(),rect.bottom());
} else if (graphType==OHLC) {
painter.setPen(pp);
painter.setBrush(bp);
painter.drawLine(r.center().x(),r.top(),r.center().x(),r.center().y());
painter.drawLine(r.center().x(),r.top()+r.height()/4.0,r.center().x()+r.width()/4,r.top()+r.height()/4.0);
painter.setPen(pn);
painter.setBrush(bn);
painter.drawLine(r.center().x(),r.center().y(),r.center().x(),r.bottom());
painter.drawLine(r.center().x(),r.top()+r.height()*0.8,r.center().x()-r.width()/4,r.top()+r.height()*0.8);
}
}
QColor JKQTPFinancialGraph::getKeyLabelColor() const {
return m_lineStylePositive.getLineColor();
}
void JKQTPFinancialGraph::draw(JKQTPEnhancedPainter &painter)
{
#ifdef JKQTBP_AUTOTIMER
JKQTPAutoOutputTimer jkaaot("JKQTPFinancialGraph::draw");
#endif
if (parent==nullptr) return;
const JKQTPDatastore* datastore=parent->getDatastore();
if (datastore==nullptr) return;
drawErrorsBefore(painter);
const QPen pp=m_lineStylePositive.getLinePenForRects(painter, parent);
const QPen pn=m_lineStyleNegative.getLinePenForRects(painter, parent);
const QBrush bp=m_fillStylePositive.getFillBrush(painter, parent);
const QBrush bn=m_fillStyleNegative.getFillBrush(painter, parent);
int imax=0;
int imin=0;
double left=-1e6;
double right=1e6;
bool firstXY=true;
if (getIndexRange(imin, imax)) {
painter.save(); auto __finalpaint=JKQTPFinally([&painter]() {painter.restore();});
double delta=1;
double deltap=0;
double deltam=0;
intSortData();
for (int iii=imin; iii<imax; iii++) {
const int i=qBound(imin, getDataIndex(iii), imax);
const double xv=datastore->get(static_cast<size_t>(xColumn),static_cast<size_t>(i));
const int sr=datastore->getNextLowerIndex(xColumn, i);
const int lr=datastore->getNextHigherIndex(xColumn, i);
const double vO=datastore->get(static_cast<size_t>(openColumn),static_cast<size_t>(i));
const double vC=datastore->get(static_cast<size_t>(closeColumn),static_cast<size_t>(i));
const double vL=datastore->get(static_cast<size_t>(lowColumn),static_cast<size_t>(i));
const double vH=datastore->get(static_cast<size_t>(highColumn),static_cast<size_t>(i));
if (sr<0 && lr<0) { // only one x-value
deltam=0.5;
deltap=0.5;
} else if (lr<0) { // the right-most x-value
deltap=deltam=fabs(xv-datastore->get(xColumn,sr))/2.0;
} else if (sr<0) { // the left-most x-value
deltam=deltap=fabs(datastore->get(xColumn,lr)-xv)/2.0;
} else {
deltam=fabs(xv-datastore->get(xColumn,sr))/2.0;
deltap=fabs(datastore->get(xColumn,lr)-xv)/2.0;
}
delta=deltap+deltam;
if (JKQTPIsOKFloat(xv)) {
const double x=transformX(xv+shift*delta);
const double xm=transformX(xv+shift*delta-qMin(width*deltam,width*deltap));
const double xp=transformX(xv+shift*delta+qMin(width*deltam,width*deltap));
const double yO=transformY(vO);
const double yC=transformY(vC);
const double yL=[&]() { double y=transformY(vL); if (!JKQTPIsOKFloat(y)) y=transformY(qMin(vO,vC)); return y; }();
const double yH=[&]() { double y=transformY(vH); if (!JKQTPIsOKFloat(y)) y=transformY(qMax(vO,vC)); return y; }();
if (JKQTPIsOKFloat(x) && JKQTPIsOKFloat(xm) && JKQTPIsOKFloat(xp) && JKQTPIsOKFloat(yC) && JKQTPIsOKFloat(yO)) {
const bool isPos=(vC>=vO);
if (firstXY) {
left=xm;
right=xp;
} else {
left=qMin(left, xm);
right=qMax(right, xp);
}
firstXY=false;
if (isPos) {
painter.setPen(pp);
painter.setBrush(bp);
} else {
painter.setPen(pn);
painter.setBrush(bn);
}
switch(graphType) {
case OHLC:
painter.drawLine(QLineF(x,yH,x,yL));
painter.drawLine(QLineF(xm,yO,x,yO));
painter.drawLine(QLineF(xp,yC,x,yC));
break;
case CandleStick: {
const QRectF rec(xm, qMin(yO,yC),xp-xm,fabs(yO-yC));
painter.drawLine(QLineF(x,yH,x,rec.top()));
painter.drawLine(QLineF(x,yL,x,rec.bottom()));
painter.drawRect(rec);
}
break;
}
}
}
}
}
drawErrorsAfter(painter);
}
void JKQTPFinancialGraph::autoscaleBoxWidthAndShift(double maxWidth, double shrinkFactor)
{
if (parent) {
double cntH=0;
for (size_t i=0; i<parent->getGraphCount(); i++) {
JKQTPPlotElement* g=parent->getGraph(i);
JKQTPFinancialGraph* gb=qobject_cast<JKQTPFinancialGraph*>(g);
if (gb && considerForAutoscaling(gb)) {
cntH++;
}
}
double widthH=1.0/cntH*maxWidth*shrinkFactor;
double dH=maxWidth/(cntH);
double h=0.1+dH/2.0;
for (size_t i=0; i<parent->getGraphCount(); i++) {
JKQTPPlotElement* g=parent->getGraph(i);
JKQTPFinancialGraph* gb=qobject_cast<JKQTPFinancialGraph*>(g);
if (gb && considerForAutoscaling(gb)) {
if (cntH>1) {
gb->width=widthH;
gb->shift=h-0.5;
h=h+dH;
} else {
gb->width=maxWidth;
gb->shift=0.0;
}
}
}
}
}
void JKQTPFinancialGraph::autoscaleBoxWidthAndShiftSeparatedGroups(double groupWidth) {
autoscaleBoxWidthAndShift(groupWidth, 0.8);
}
void JKQTPFinancialGraph::setShift(double __value)
{
this->shift = __value;
}
double JKQTPFinancialGraph::getShift() const
{
return this->shift;
}
void JKQTPFinancialGraph::setWidth(double __value)
{
this->width = __value;
}
double JKQTPFinancialGraph::getWidth() const
{
return this->width;
}
JKQTPGraphFillStyleMixin &JKQTPFinancialGraph::fillStyleNegative()
{
return m_fillStyleNegative;
}
const JKQTPGraphFillStyleMixin &JKQTPFinancialGraph::fillStyleNegative() const
{
return m_fillStyleNegative;
}
JKQTPGraphFillStyleMixin &JKQTPFinancialGraph::fillStylePositive()
{
return m_fillStylePositive;
}
const JKQTPGraphFillStyleMixin &JKQTPFinancialGraph::fillStylePositive() const
{
return m_fillStylePositive;
}
JKQTPGraphLineStyleMixin &JKQTPFinancialGraph::lineStyleNegative()
{
return m_lineStyleNegative;
}
const JKQTPGraphLineStyleMixin &JKQTPFinancialGraph::lineStyleNegative() const
{
return m_lineStyleNegative;
}
JKQTPGraphLineStyleMixin &JKQTPFinancialGraph::lineStylePositive()
{
return m_lineStylePositive;
}
const JKQTPGraphLineStyleMixin &JKQTPFinancialGraph::lineStylePositive() const
{
return m_lineStylePositive;
}
bool JKQTPFinancialGraph::getIndexRange(int &imin, int &imax) const
{
bool ok=JKQTPXGraph::getIndexRange(imin, imax);
/*if (ok) {
if (parent==nullptr) return false;
const JKQTPDatastore* datastore=parent->getDatastore();
if (openColumn<0) return false;
if (closeColumn<0) return false;
if (highColumn<0) return false;
if (lowColumn<0) return false;
const int rowsO=static_cast<int>(datastore->getRows(static_cast<size_t>(openColumn)));
const int rowsC=static_cast<int>(datastore->getRows(static_cast<size_t>(closeColumn)));
const int rowsH=static_cast<int>(datastore->getRows(static_cast<size_t>(highColumn)));
const int rowsL=static_cast<int>(datastore->getRows(static_cast<size_t>(lowColumn)));
imax=qMin<int>(imax, rowsO);
imax=qMin<int>(imax, rowsC);
imax=qMin<int>(imax, rowsH);
imax=qMin<int>(imax, rowsL);
}*/
return ok;
}
bool JKQTPFinancialGraph::getYMinMax(double &miny, double &maxy, double &smallestGreaterZero)
{
bool start=true;
miny=0;
maxy=0;
smallestGreaterZero=0;
if (parent==nullptr) return false;
const JKQTPDatastore* datastore=parent->getDatastore();
int imin=0;
int imax=0;
if (getIndexRange(imin, imax)) {
for (int i=imin; i<imax; i++) {
const double yvO=datastore->get(static_cast<size_t>(openColumn),static_cast<size_t>(i));
if (JKQTPIsOKFloat(yvO)) {
if (start || yvO>maxy) maxy=yvO;
if (start || yvO<miny) miny=yvO;
double xvsgz;
xvsgz=yvO; SmallestGreaterZeroCompare_xvsgz();
start=false;
}
const double yvC=datastore->get(static_cast<size_t>(closeColumn),static_cast<size_t>(i));
if (JKQTPIsOKFloat(yvC)) {
if (start || yvC>maxy) maxy=yvC;
if (start || yvC<miny) miny=yvC;
double xvsgz;
xvsgz=yvC; SmallestGreaterZeroCompare_xvsgz();
start=false;
}
const double yvL=datastore->get(static_cast<size_t>(lowColumn),static_cast<size_t>(i));
if (JKQTPIsOKFloat(yvL)) {
if (start || yvL>maxy) maxy=yvL;
if (start || yvL<miny) miny=yvL;
double xvsgz;
xvsgz=yvL; SmallestGreaterZeroCompare_xvsgz();
start=false;
}
const double yvH=datastore->get(static_cast<size_t>(highColumn),static_cast<size_t>(i));
if (JKQTPIsOKFloat(yvH)) {
if (start || yvH>maxy) maxy=yvH;
if (start || yvH<miny) miny=yvH;
double xvsgz;
xvsgz=yvH; SmallestGreaterZeroCompare_xvsgz();
start=false;
}
}
return !start;
}
return false;
}
bool JKQTPFinancialGraph::usesColumn(int column) const
{
return JKQTPXGraph::usesColumn(column)||(column==openColumn)||(column==closeColumn)||(column==highColumn)||(column==lowColumn);
}
int JKQTPFinancialGraph::getOpenColumn() const
{
return openColumn;
}
int JKQTPFinancialGraph::getCloseColumn() const
{
return closeColumn;
}
int JKQTPFinancialGraph::getHighColumn() const
{
return highColumn;
}
int JKQTPFinancialGraph::getLowColumn() const
{
return lowColumn;
}
JKQTPFinancialGraph::FinancialGraphType JKQTPFinancialGraph::getGraphType() const
{
return graphType;
}
void JKQTPFinancialGraph::setGraphType(JKQTPFinancialGraph::FinancialGraphType t)
{
graphType=t;
}
void JKQTPFinancialGraph::setColor(QColor cPositive, QColor cNegative)
{
m_fillStylePositive.setFillColor(cPositive);
m_fillStyleNegative.setFillColor(cNegative);
m_lineStylePositive.setLineColor(cPositive);
m_lineStyleNegative.setLineColor(cNegative);
}
void JKQTPFinancialGraph::setColor(QColor linePositive, QColor fillPositive, QColor lineNegative, QColor fillNegative)
{
m_fillStylePositive.setFillColor(fillPositive);
m_fillStyleNegative.setFillColor(fillNegative);
m_lineStylePositive.setLineColor(linePositive);
m_lineStyleNegative.setLineColor(lineNegative);
}
void JKQTPFinancialGraph::setCandlestickTwoColor(QColor cPositive, QColor cNegative)
{
setGraphType(CandleStick);
setColor(cPositive, cNegative);
}
void JKQTPFinancialGraph::setCandlestickTwoColor(QColor cPositive, QColor cNegative, QColor lineColor)
{
setGraphType(CandleStick);
setColor(lineColor, cPositive, lineColor, cNegative);
}
void JKQTPFinancialGraph::setCandlestickOneColor(QColor cLine)
{
setGraphType(CandleStick);
if (parent) setColor(cLine,parent->getCurrentPlotterStyle().plotBackgroundBrush.color(),cLine,cLine);
else setColor(cLine,Qt::transparent,cLine,cLine);
}
void JKQTPFinancialGraph::setOHLCTwoColor(QColor cPositive, QColor cNegative)
{
setGraphType(OHLC);
setColor(cPositive, cNegative);
}
void JKQTPFinancialGraph::setOpenColumn(int __value)
{
openColumn=static_cast<int>(__value);
}
void JKQTPFinancialGraph::setOpenColumn(size_t __value)
{
openColumn=static_cast<int>(__value);
}
void JKQTPFinancialGraph::setCloseColumn(int __value)
{
closeColumn=static_cast<int>(__value);
}
void JKQTPFinancialGraph::setCloseColumn(size_t __value)
{
closeColumn=static_cast<int>(__value);
}
void JKQTPFinancialGraph::setHighColumn(int __value)
{
highColumn=static_cast<int>(__value);
}
void JKQTPFinancialGraph::setHighColumn(size_t __value)
{
highColumn=static_cast<int>(__value);
}
void JKQTPFinancialGraph::setLowColumn(int __value)
{
lowColumn=static_cast<int>(__value);
}
void JKQTPFinancialGraph::setLowColumn(size_t __value)
{
lowColumn=static_cast<int>(__value);
}
bool JKQTPFinancialGraph::considerForAutoscaling(JKQTPFinancialGraph *other) const
{
return (dynamic_cast<JKQTPFinancialGraph*>(other)!=nullptr);
}

View File

@ -0,0 +1,337 @@
/*
Copyright (c) 2008-2024 Jan W. Krieger (<jan@jkrieger.de>)
This software is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License (LGPL) as published by
the Free Software Foundation, either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License (LGPL) for more details.
You should have received a copy of the GNU Lesser General Public License (LGPL)
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef jkqtpfinancial_H
#define jkqtpfinancial_H
#include <QString>
#include <QPainter>
#include <QPair>
#include "jkqtplotter/jkqtptools.h"
#include "jkqtplotter/jkqtplotter_imexport.h"
#include "jkqtplotter/jkqtpgraphsbase.h"
#include "jkqtplotter/jkqtpgraphsbasestylingmixins.h"
/** \brief This class draws an Open-High-Low-Close (OHLC) or candle-stick graph typically representing financial data
* \ingroup jkqtplotter_financialgraphs
*
* This graph draws either <b>candlstick charts</b>:
* \image html financialgraphs_basics_candlestick.png
* or <b>Open-High-Low-Close (OHLC) charts</b> (depending on what you set in setGraphType() ).
* \image html financialgraphs_basics_ohlc.png
*
* Here is an example graph showcasing both variants:
*
* \image html JKQTPFinancialGraph.png
*
* \see For details on the graph types see https://en.wikipedia.org/wiki/Candlestick_chart and https://en.wikipedia.org/wiki/Open-high-low-close_chart
* \see JKQTPXGraph
*
*
* \section JKQTPFinancialGraphStyling Styling the Financial Graphs
* You can control the type of graph and it's appearance with these functions (example images are in the documentation of the functions):
* - setGraphType()
* - setCandlestickTwoColor()
* - setCandlestickOneColor()
* - setOHLCTwoColor()
* .
*
* For fine-tuning, you can access the line- and fill-styles for positiove (close>open) and negative (close<open)
* items separately via:
* - lineStylePositive() and fillStylePositive()
* - lineStyleNegative() and fillStyleNegative()
* .
*
* Note that the width of the single elements for each x-value is automatically from the distance to the
* next neighbours. Here set parameter width (setWidth(), getWidth() ) is employed. It sets the relative
* amount of space that an element occupies in its part of the graph. Typically the default width is 0.8 (or 80%).
*
* \image html financialgraphs_width.png
*
*
* \section JKQTPFinancialGraphSideBySide Severyl Financial Graphs side-by-side
* In case you want to display several of these graphs in the same plot, you can make them appear side-by-side, even
* if they all cover the same x-values and would thus be draw atop each other. We apply the same principles that
* were used for barcharts in JKQTPBarGraphBase and add a shift-parameter (setShift() ) that shifts each element
* in the available space:
*
* \image html financialgraphs_basics.png
*
* For example for two financial graph elements per x-value one would set:
* \verbatim
* width=0.4
* shift=-0.5 / +0.5
* \endverbatim
*
* \image html financialgraphs_shiftwidth.png
*
* Two functions are provided (JKQTPFinancialGraph::autoscaleBoxWidthAndShift(), JKQTPFinancialGraph::autoscaleBoxWidthAndShiftSeparatedGroups() )
* that can be applied to any JKQTPFinancialGraph in the plot and will calculate all JKQTPFinancialGraphs' shift and width parameter
* in such a way that the graphs appear tidied up into groups by x-value. With these functions you don't have to calculate
* the shift and width values by hand! A call always only affects the JKQTPFinancialGraph in the plot with the same orientation
* (vertical or horizontal).
*
* \image html JKQTPFinancialGraphSidyBySide.png
*
*/
class JKQTPLOTTER_LIB_EXPORT JKQTPFinancialGraph: public JKQTPXGraph {
Q_OBJECT
public:
/** \brief types of financial graphs that can be drawn by this class */
enum FinancialGraphType {
CandleStick, //!< \brief candlestick graphs \image html JKQTPFinancialGraphCandleStick.png
OHLC //!< \brief OHLC (Open-High-Low-Close) graphs \image html JKQTPFinancialGraphOHLC.png
};
Q_ENUM(FinancialGraphType)
/** \brief class constructor */
JKQTPFinancialGraph(JKQTBasePlotter* parent=nullptr);
/** \brief class constructor */
JKQTPFinancialGraph(JKQTPlotter* parent);
/** \copydoc JKQTPXGraph::drawKeyMarker() */
virtual void drawKeyMarker(JKQTPEnhancedPainter& painter, const QRectF& rect) override;
/** \copydoc JKQTPXGraph::getKeyLabelColor() */
virtual QColor getKeyLabelColor() const override;
/** \copydoc JKQTPXGraph::draw() */
virtual void draw(JKQTPEnhancedPainter& painter) override;
/** \copydoc shift */
double getShift() const;
/** \copydoc width */
double getWidth() const;
/** \copydoc m_fillStyleNegative */
JKQTPGraphFillStyleMixin &fillStyleNegative();
/** \copydoc m_fillStyleNegative */
const JKQTPGraphFillStyleMixin& fillStyleNegative() const;
/** \copydoc m_fillStylePositive */
JKQTPGraphFillStyleMixin &fillStylePositive();
/** \copydoc m_fillStylePositive */
const JKQTPGraphFillStyleMixin& fillStylePositive() const;
/** \copydoc m_lineStyleNegative */
JKQTPGraphLineStyleMixin &lineStyleNegative();
/** \copydoc m_lineStyleNegative */
const JKQTPGraphLineStyleMixin& lineStyleNegative() const;
/** \copydoc m_lineStylePositive */
JKQTPGraphLineStyleMixin &lineStylePositive();
/** \copydoc m_lineStylePositive */
const JKQTPGraphLineStyleMixin& lineStylePositive() const;
/** \copydoc JKQTPXGraph::getIndexRange() */
virtual bool getIndexRange(int &imin, int &imax) const override;
/** \copydoc JKQTPXGraph::getYMinMax() */
virtual bool getYMinMax(double& miny, double& maxy, double& smallestGreaterZero) override;
/** \copydoc JKQTPXGraph::usesColumn() */
virtual bool usesColumn(int column) const override;
/** \copydoc openColumn */
int getOpenColumn() const;
/** \copydoc closeColumn */
int getCloseColumn() const;
/** \copydoc highColumn */
int getHighColumn() const;
/** \copydoc lowColumn */
int getLowColumn() const;
/** \copydoc graphType */
FinancialGraphType getGraphType() const;
Q_PROPERTY(int openColumn READ getOpenColumn WRITE setOpenColumn)
Q_PROPERTY(int closeColumn READ getCloseColumn WRITE setCloseColumn)
Q_PROPERTY(int highColumn READ getHighColumn WRITE setHighColumn)
Q_PROPERTY(int lowColumn READ getLowColumn WRITE setLowColumn)
Q_PROPERTY(FinancialGraphType graphType READ getGraphType WRITE setGraphType)
public Q_SLOTS:
/** \brief finds all financial charts of the same orientation and determines width and shift, so they stand side by side
*
* \param maxWidth the maximum (relative) width, that all boxes will span of the (doubled) inter-box distance
* \param shrinkFactor factor, by which the boxes are shrinked compared to the available space
*
* \note This function will scale ALL graphs of the parent plot, which were derived from JKQTPFinancialGraph, that match in orientation (currently all).
*/
virtual void autoscaleBoxWidthAndShift(double maxWidth=0.8, double shrinkFactor=0.8);
/** \brief equivalent to \c autoscaleBoxWidthAndShift(groupWidth,0.8);
*/
void autoscaleBoxWidthAndShiftSeparatedGroups(double groupWidth=0.65);
/** \copydoc shift */
void setShift(double __value);
/** \copydoc width */
void setWidth(double __value);
/** \copydoc graphType */
void setGraphType(FinancialGraphType __value);
/** \brief set outline and fill color at the same time, for both positive and negative boxes
*
* \param cPositive color for positive items (close>open)
* \param cNegative color for negative items (close<open)
*/
virtual void setColor(QColor cPositive, QColor cNegative);
/** \brief set outline and fill color at the same time, for both positive and negative boxes
*
* \param linePositive line-color for positive items (close>open)
* \param fillPositive fill-color for positive items (close>open)
* \param lineNegative line-color for negative items (close<open)
* \param fillNegative fill-color for negative items (close<open)
*/
virtual void setColor(QColor linePositive, QColor fillPositive, QColor lineNegative, QColor fillNegative);
/** \brief sets graphType = FinancialGraphType::CandleStick and sets the positive and negative color (for fill and line equal!)
*
* \param cPositive color for positive items (close>open)
* \param cNegative color for negative items (close<open)
*
* Here is an example output for <code>setCandlestickTwoColor(QColor("blue"), QColor("orange"))</code>:
*
* \image html JKQTPFinancialGraphSetCandlestickTwoColor.png
*/
void setCandlestickTwoColor(QColor cPositive, QColor cNegative);
/** \brief sets graphType = FinancialGraphType::CandleStick and sets the positive and negative fill color and a single line-color
*
* \param cPositive color for fill of positive items (close>open)
* \param cNegative color for fill of negative items (close<open)
*
* Here is an example output for <code>setCandlestickTwoColor(QColor("green"), QColor("red"), QColor("black"))</code>:
*
* \image html JKQTPFinancialGraphSetCandlestickTwoColor2.png
*/
void setCandlestickTwoColor(QColor cPositive, QColor cNegative, QColor lineCOlor);
/** \brief sets graphType = FinancialGraphType::CandleStick and sets one color \a cLine for positive and negative items, where positive items are unfilled and negative items are filled
*
* \param cLine line-color for all items and also fill-color for positive items (close>open), negative items are unfilled
*
* Here is an example output for <code>setCandlestickOneColor(QColor("black"))</code>:
*
* \image html JKQTPFinancialGraphSetCandlestickOneColor.png
*/
void setCandlestickOneColor(QColor cLine);
/** \brief sets graphType = FinancialGraphType::OHLC and sets the positive and negative color (for fill and line equal!)
*
* \param cPositive color for positive items (close>open)
* \param cNegative color for negative items (close<open)
*
* Here is an example output for <code>setOHLCTwoColor(QColor("darkblue"), QColor("darkorange"))</code>:
*
* \image html JKQTPFinancialGraphSetOHLCTwoColor.png
*/
void setOHLCTwoColor(QColor cPositive, QColor cNegative);
/** \copydoc openColumn */
void setOpenColumn(int __value);
/** \copydoc openColumn */
void setOpenColumn (size_t __value);
/** \copydoc closeColumn */
void setCloseColumn(int __value);
/** \copydoc closeColumn */
void setCloseColumn (size_t __value);
/** \copydoc highColumn */
void setHighColumn(int __value);
/** \copydoc highColumn */
void setHighColumn (size_t __value);
/** \copydoc lowColumn */
void setLowColumn(int __value);
/** \copydoc lowColumn */
void setLowColumn (size_t __value);
protected:
/** \brief type of the financial graph (OHLC or candle-stick)
*
* \see setGraphType(), getGraphType(), FinancialGraphType
*/
FinancialGraphType graphType;
/** \brief column for the "Open" value of the financial graph
*
* \see setOpenColumn(), getOpenColumn()
*/
int openColumn;
/** \brief column for the "Close" value of the financial graph
*
* \see setCloseColumn(), getCloseColumn()
*/
int closeColumn;
/** \brief column for the "High" value of the financial graph
*
* \see setHighColumn(), getHighColumn()
*/
int highColumn;
/** \brief column for the "Low" value of the financial graph
*
* \see setLowColumn(), getLowColumn()
*/
int lowColumn;
/** \brief the width of the boxes, relative to the distance between the current and the next x-value
*
* See the following graphic to understand this concept (same principle as described in the JKQTPBarGraphBase documentation):
* \image html financialgraphs_shiftwidth.png
*/
double width;
/** \brief the shift of the boxes, relative to the distance between the current and the next x-value
*
* See the following graphic to understand this concept (same principle as described in the JKQTPBarGraphBase documentation):
* \image html financialgraphs_shiftwidth.png
*/
double shift;
/** \brief fill style for positive items (close>open)
*
* \see fillStylePositive()
*/
JKQTPGraphFillStyleMixin m_fillStylePositive;
/** \brief fill style for negative items (close<open)
*
* \see fillStyleNegative()
*/
JKQTPGraphFillStyleMixin m_fillStyleNegative;
/** \brief fill style for positive items (close>open)
*
* \see lineStylePositive()
*/
JKQTPGraphLineStyleMixin m_lineStylePositive;
/** \brief fill style for negative items (close<open)
*
* \see lineStyleNegative()
*/
JKQTPGraphLineStyleMixin m_lineStyleNegative;
/** \brief this function is used by autoscaleBoxWidthAndShift() to determine whether a given graph shall be taken into account when autoscaling.
* Typically this returns \c true for all JKQTPFinancialGraph-derved objects with the same orientation (horizontal or vertical, i.e. currently all) */
virtual bool considerForAutoscaling( JKQTPFinancialGraph* other) const;
};
#endif // jkqtpfinancial_H

View File

@ -1306,3 +1306,127 @@ bool JKQTPXYAndVectorGraph::getIndexRange(int &imin, int &imax) const
}
return ok;
}
JKQTPXGraph::JKQTPXGraph(JKQTBasePlotter *parent):
JKQTPGraph(parent), xColumn(-1), sortData(Unsorted)
{
}
bool JKQTPXGraph::getXMinMax(double &minx, double &maxx, double &smallestGreaterZero)
{
bool start=true;
minx=0;
maxx=0;
smallestGreaterZero=0;
if (parent==nullptr) return false;
const JKQTPDatastore* datastore=parent->getDatastore();
int imin=0;
int imax=0;
getIndexRange(imin, imax);
for (int i=imin; i<imax; i++) {
double xv=datastore->get(static_cast<size_t>(xColumn),static_cast<size_t>(i));
if (JKQTPIsOKFloat(xv)) {
if (start || xv>maxx) maxx=xv;
if (start || xv<minx) minx=xv;
double xvsgz;
xvsgz=xv; SmallestGreaterZeroCompare_xvsgz();
start=false;
}
}
return !start;
}
bool JKQTPXGraph::usesColumn(int column) const
{
return (column==xColumn);
}
int JKQTPXGraph::getXColumn() const
{
return xColumn;
}
JKQTPXGraph::DataSortOrder JKQTPXGraph::getDataSortOrder() const
{
return sortData;
}
int JKQTPXGraph::getKeyColumn() const
{
return getXColumn();
}
void JKQTPXGraph::setDataSortOrder(int __value)
{
sortData=static_cast<DataSortOrder>(__value);
}
void JKQTPXGraph::setDataSortOrder(DataSortOrder __value)
{
sortData=__value;
}
void JKQTPXGraph::setXColumn(int __value)
{
xColumn = static_cast<int>(__value);
}
void JKQTPXGraph::setXColumn(size_t __value)
{
xColumn = static_cast<int>(__value);
}
void JKQTPXGraph::setKeyColumn(int __value)
{
setXColumn(__value);
}
void JKQTPXGraph::intSortData()
{
sortedIndices.clear();
if (parent==nullptr) return ;
if (sortData==JKQTPXYLineGraph::Unsorted) return ;
JKQTPDatastore* datastore=parent->getDatastore();
int imin=0;
int imax=0;
getIndexRange(imin, imax);
QVector<double> datas;
if (sortData==SortedX) {
for (int i=0; i<imax; i++) {
double xv=datastore->get(static_cast<size_t>(xColumn),static_cast<size_t>(i));
sortedIndices<<i;
datas<<xv;
}
jkqtpQuicksortDual(datas.data(), sortedIndices.data(), datas.size());
}
}
bool JKQTPXGraph::getIndexRange(int &imin, int &imax) const
{
if (parent==nullptr) return false;
if (xColumn>=0) {
JKQTPDatastore* datastore=parent->getDatastore();
imin=0;
imax=static_cast<int>(datastore->getRows(static_cast<size_t>(xColumn)));
// ensure correct order, i.e. imin<=imax
if (imax<imin) qSwap(imin,imax);
// ranges are always >=0
if (imin<0) imin=0;
if (imax<0) imax=0;
return true;
}
return false;
}

View File

@ -1092,4 +1092,102 @@ protected:
virtual bool getIndexRange(int &imin, int &imax) const override;
};
/** \brief This virtual JKQTPGraph descendent may be used as base for all graphs that use at least one column
* that specifies x coordinates for the single plot points.
* \ingroup jkqtplotter_basegraphs
*
* This class implements basic management facilities for the data columns:
* - setXColumn() to set the columns to be used for the graph data
* - setDataSortOrder() to specify whether and how the data should be sorted before drawing
* \image html jkqtplotter_unsorted.png "Unsorted Data"
* \image html jkqtplotter_sortedx.png "Data sorted along x-axis (DataSortOrder::SortedX)"
* .
*
* ... and overrides/implements the functions:
* - getXMinMax()
* - usesColumn()
* .
*
*/
class JKQTPLOTTER_LIB_EXPORT JKQTPXGraph: public JKQTPGraph {
Q_OBJECT
public:
/** \brief specifies how to sort the data in a JKQTPXGraph before drawing
*
* \image html jkqtplotter_unsorted.png "Unsorted Data"
*
* \image html jkqtplotter_sortedx.png "Data sorted along x-axis (DataSortOrder::SortedX)"
*/
enum DataSortOrder {
Unsorted=0, /*!< \brief the data for a JKQTPXYGraph is not sorted before drawing */
SortedX=1, /*!< \brief the data for a JKQTPXYGraph is sorted so the x-values appear in ascending before drawing */
};
Q_ENUM(DataSortOrder)
/** \brief class constructor */
JKQTPXGraph(JKQTBasePlotter* parent=nullptr);
/** \copydoc JKQTPGraph::getXMinMax() */
virtual bool getXMinMax(double& minx, double& maxx, double& smallestGreaterZero) override;
/** \copydoc JKQTPGraph::usesColumn() */
virtual bool usesColumn(int column) const override;
/** \copydoc xColumn */
int getXColumn() const;
/** \copydoc sortData */
DataSortOrder getDataSortOrder() const;
/** \brief returns the column used as "key" for the current graph (typically this call getXColumn(), but for horizontal graphs like filled curves or barcharts it may call getYColumn() ) */
virtual int getKeyColumn() const;
Q_PROPERTY(DataSortOrder sortData READ getDataSortOrder WRITE setDataSortOrder)
Q_PROPERTY(int xColumn READ getXColumn WRITE setXColumn)
public Q_SLOTS:
/** \copydoc sortData */
void setDataSortOrder(int __value);
/** \copydoc sortData */
void setDataSortOrder(DataSortOrder __value);
/** \copydoc xColumn */
void setXColumn(int __value);
/** \copydoc xColumn */
void setXColumn (size_t __value);
/** \brief sets the column used as "key" for the current graph (typically this call setXColumn(), but for horizontal graphs like filled curves or barcharts it may call setYColumn() ) */
virtual void setKeyColumn(int __value);
protected:
/** \brief the column that contains the x-component of the datapoints */
int xColumn;
/** \brief if \c !=Unsorted, the data is sorted before plotting */
DataSortOrder sortData;
/** \brief this array contains the order of indices, in which to access the data in the data columns */
QVector<int> sortedIndices;
/** \brief sorts data according to the specified criterion in \a sortData ... The result is stored as a index-map in sorted Indices */
virtual void intSortData();
/** \brief returns the index of the i-th datapoint (where i is an index into the SORTED datapoints)
*
* This function can beu used to get the correct datapoint after sorting the datapoints,
* As sorting is done by sorting an index and not reordering the data in the columns themselves.
*
* \see setDataSortOrder(), getDataSortOrder()
* */
inline int getDataIndex(int i) const {
if (sortData==Unsorted) return i;
return sortedIndices.value(i,i);
}
/** \brief determines the range of row indexes available in the data columns of this graph
*
* \param[out] imin first usable row-index
* \param[out] imax last usable row-index
* \return \c true on success and \c false if the information is not available
*/
virtual bool getIndexRange(int &imin, int &imax) const;
};
#endif // JKQTPGRAPHSBASE_H

View File

@ -77,6 +77,19 @@ QColor JKQTPGraphLineStyleMixin::getLineColor() const
return m_linePen.color();
}
void JKQTPGraphLineStyleMixin::setLineColorInvertedFrom(QColor __noninvertedColor)
{
#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
float h=0,s=0,v=0,a=0;
#else
qreal h=0,s=0,v=0,a=0;
#endif
__noninvertedColor.getHsvF(&h, &s, &v, &a);
h=std::fmod(h+120.0/360.0, 1.0);
__noninvertedColor.setHsvF(h,s,v,a);
setLineColor(__noninvertedColor);
}
void JKQTPGraphLineStyleMixin::setLineStyle(Qt::PenStyle __value)
{
m_linePen.setStyle(__value);

View File

@ -55,7 +55,6 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPGraphLineStyleMixin {
JKQTPGraphLineStyleMixin();
/** \brief initiaize the line style (from the parent plotter) */
void initLineStyle(JKQTBasePlotter *parent, int &parentPlotStyle, JKQTPPlotStyleType styletype=JKQTPPlotStyleType::Default);
virtual ~JKQTPGraphLineStyleMixin();
/** \brief set the color of the graph line */
@ -64,6 +63,8 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPGraphLineStyleMixin {
void setLineColor(const QColor & __value, double alpha);
/** \brief get the color of the graph line */
QColor getLineColor() const;
/** \brief sets the line-color as an inverted version of the given color */
void setLineColorInvertedFrom(QColor __noninvertedColor);
/** \brief set the style of the graph line */
void setLineStyle(Qt::PenStyle __value);

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB