mirror of
https://github.com/jkriege2/JKQtPlotter.git
synced 2024-12-24 09:31:40 +08:00
added Mandelbrot Set Explorer and an Example
This commit is contained in:
parent
9a3f321f66
commit
22578725b7
@ -52,41 +52,42 @@ defineTest(addSimpleTest) {
|
||||
export (SUBDIRS)
|
||||
}
|
||||
|
||||
addSimpleTest(advplotstyling)
|
||||
addSimpleTest(barchart)
|
||||
addSimpleTest(boxplot)
|
||||
addSimpleTest(contourplot)
|
||||
addSimpleTest(datastore)
|
||||
addSimpleTest(datastore_groupedstat)
|
||||
addSimpleTest(datastore_iterators)
|
||||
addSimpleTest(datastore_regression)
|
||||
addSimpleTest(datastore_statistics)
|
||||
addSimpleTest(datastore_statistics_2d)
|
||||
addSimpleTest(dateaxes)
|
||||
addSimpleTest(errorbarstyles)
|
||||
addSimpleTest(evalcurve)
|
||||
addSimpleTest(filledgraphs)
|
||||
addSimpleTest(functionplot)
|
||||
addSimpleTest(geo_arrows)
|
||||
addSimpleTest(geo_simple)
|
||||
addSimpleTest(geometric)
|
||||
addSimpleTest(imageplot)
|
||||
addSimpleTest(imageplot_modifier)
|
||||
addSimpleTest(imageplot_nodatastore)
|
||||
addSimpleTest(impulsesplot)
|
||||
addSimpleTest(logaxes)
|
||||
addSimpleTest(mandelbrot)
|
||||
addSimpleTest(parametriccurve)
|
||||
addSimpleTest(paramscatterplot)
|
||||
addSimpleTest(paramscatterplot_image)
|
||||
addSimpleTest(parsedfunctionplot)
|
||||
addSimpleTest(rgbimageplot)
|
||||
addSimpleTest(rgbimageplot_qt)
|
||||
addSimpleTest(speed)
|
||||
addSimpleTest(stackedbars)
|
||||
addSimpleTest(symbols_and_errors)
|
||||
addSimpleTest(symbols_and_styles)
|
||||
addSimpleTest(filledgraphs)
|
||||
addSimpleTest(speed)
|
||||
addSimpleTest(rgbimageplot)
|
||||
addSimpleTest(rgbimageplot_qt)
|
||||
addSimpleTest(impulsesplot)
|
||||
addSimpleTest(paramscatterplot)
|
||||
addSimpleTest(paramscatterplot_image)
|
||||
addSimpleTest(parametriccurve)
|
||||
addSimpleTest(parsedfunctionplot)
|
||||
addSimpleTest(functionplot)
|
||||
addSimpleTest(geometric)
|
||||
addSimpleTest(geo_simple)
|
||||
addSimpleTest(geo_arrows)
|
||||
addSimpleTest(ui)
|
||||
addSimpleTest(boxplot)
|
||||
addSimpleTest(advplotstyling)
|
||||
addSimpleTest(imageplot_nodatastore)
|
||||
addSimpleTest(datastore)
|
||||
addSimpleTest(datastore_iterators)
|
||||
addSimpleTest(datastore_statistics)
|
||||
addSimpleTest(datastore_statistics_2d)
|
||||
addSimpleTest(datastore_regression)
|
||||
addSimpleTest(datastore_groupedstat)
|
||||
addSimpleTest(contourplot)
|
||||
addSimpleTest(violinplot)
|
||||
addSimpleTest(evalcurve)
|
||||
#addSimpleTest(rgbimageplot_opencv)
|
||||
#addSimpleTest(imageplot_opencv)
|
||||
|
||||
|
@ -212,6 +212,9 @@ All test-projects are Qt-projects that use qmake to build. You can load them int
|
||||
<tr><td> \image html test_distributionplot_small.png
|
||||
<td> \subpage JKQTPlotterDistributionPlot
|
||||
<td> Combines several different graphs to draw random values, their distribution and some statistical properties
|
||||
<tr><td> \image html mandelbrot_small.png
|
||||
<td> \subpage JKQTPlotterMandelbrot
|
||||
<td>
|
||||
</table>
|
||||
|
||||
|
||||
|
@ -12,7 +12,6 @@ add_subdirectory(jkqtmathtext_test)
|
||||
|
||||
add_subdirectory(jkqtplot_test)
|
||||
|
||||
add_subdirectory(geo_arrows)
|
||||
add_subdirectory(advplotstyling)
|
||||
add_subdirectory(barchart)
|
||||
add_subdirectory(boxplot)
|
||||
@ -26,26 +25,29 @@ add_subdirectory(datastore_statistics_2d)
|
||||
add_subdirectory(dateaxes)
|
||||
add_subdirectory(distributionplot)
|
||||
add_subdirectory(errorbarstyles)
|
||||
add_subdirectory(evalcurve)
|
||||
add_subdirectory(filledgraphs)
|
||||
add_subdirectory(functionplot)
|
||||
add_subdirectory(geometric)
|
||||
add_subdirectory(geo_arrows)
|
||||
add_subdirectory(geo_simple)
|
||||
add_subdirectory(geometric)
|
||||
add_subdirectory(imageplot)
|
||||
add_subdirectory(imageplot_userpal)
|
||||
add_subdirectory(imageplot_cimg)
|
||||
add_subdirectory(imageplot_modifier)
|
||||
add_subdirectory(imageplot_nodatastore)
|
||||
add_subdirectory(imageplot_opencv)
|
||||
add_subdirectory(imageplot_cimg)
|
||||
add_subdirectory(imageplot_userpal)
|
||||
add_subdirectory(impulsesplot)
|
||||
add_subdirectory(logaxes)
|
||||
add_subdirectory(mandelbrot)
|
||||
add_subdirectory(multiplot)
|
||||
add_subdirectory(parametriccurve)
|
||||
add_subdirectory(paramscatterplot)
|
||||
add_subdirectory(paramscatterplot_image)
|
||||
add_subdirectory(parsedfunctionplot)
|
||||
add_subdirectory(rgbimageplot)
|
||||
add_subdirectory(rgbimageplot_opencv)
|
||||
add_subdirectory(rgbimageplot_cimg)
|
||||
add_subdirectory(rgbimageplot_opencv)
|
||||
add_subdirectory(rgbimageplot_qt)
|
||||
add_subdirectory(simpletest)
|
||||
add_subdirectory(speed)
|
||||
@ -58,6 +60,5 @@ add_subdirectory(symbols_and_styles)
|
||||
add_subdirectory(ui)
|
||||
add_subdirectory(user_interaction)
|
||||
add_subdirectory(violinplot)
|
||||
add_subdirectory(evalcurve)
|
||||
|
||||
|
||||
|
@ -70,6 +70,7 @@ All test-projects are Qt-projects that use qmake to build. You can load them int
|
||||
| [![](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/jkqtplotter_test_user_interaction_small.gif)](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/user_interaction) | [User Interaction](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/user_interaction) | different possibilities of user-interaction in JKQtPlotter |
|
||||
|
||||
|
||||
|
||||
## Data Management & Statistics (Tutorials)
|
||||
|
||||
| Screenshot | Description | Notes |
|
||||
@ -88,6 +89,7 @@ All test-projects are Qt-projects that use qmake to build. You can load them int
|
||||
|:-------------:| ------------- | ------------- |
|
||||
| [![](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/test_multiplot_small.png)](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/multiplot) | [Layouting Several Plots](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/multiplot) | Combining plots in Qt Layouts <br> linking plot axes <br> copy data from a `std::map` int the datastore <br> print plots/print preview |
|
||||
| [![](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/test_distributionplot_small.png)](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/distributionplot) | [Plotting a Statistical Distribution of Data](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/distributionplot) | Combines several different graphs to draw random values, their distribution and some statistical properties |
|
||||
| [![](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/mandelbrot_small.gif)](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/mandelbrot) | [Mandelbrot Set Explorer](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/mandelbrot) | |
|
||||
|
||||
|
||||
|
||||
|
29
examples/mandelbrot/CMakeLists.txt
Normal file
29
examples/mandelbrot/CMakeLists.txt
Normal file
@ -0,0 +1,29 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
|
||||
set(EXAMPLE_NAME mandelbrot)
|
||||
set(EXENAME jkqtptest_${EXAMPLE_NAME})
|
||||
|
||||
message( STATUS ".. Building Example ${EXAMPLE_NAME}" )
|
||||
|
||||
|
||||
# Set up source files
|
||||
set(SOURCES ${EXAMPLE_NAME}.cpp mandelbrotmainwindow.cpp)
|
||||
set(HEADERS mandelbrotmainwindow.h)
|
||||
set(RESOURCES )
|
||||
set(UIS mandelbrotmainwindow.ui)
|
||||
|
||||
add_executable(${EXENAME} WIN32 ${SOURCES} ${HEADERS} ${RESOURCES} ${UIS})
|
||||
target_include_directories(${EXENAME} PRIVATE ../../lib)
|
||||
if(JKQtPlotter_BUILD_STATIC_LIBS)
|
||||
target_link_libraries(${EXENAME} JKQTPlotterLib)
|
||||
elseif(JKQtPlotter_BUILD_SHARED_LIBS)
|
||||
target_link_libraries(${EXENAME} JKQTPlotterSharedLib)
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
# Installation
|
||||
install(TARGETS ${EXENAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
#Installation of Qt DLLs on Windows
|
||||
jkqtplotter_deployqt(${EXENAME})
|
200
examples/mandelbrot/README.md
Normal file
200
examples/mandelbrot/README.md
Normal file
@ -0,0 +1,200 @@
|
||||
# Example (JKQTPlotter): Mandelbrot Set Explorer {#JKQTPlotterMandelbrot}
|
||||
|
||||
## Introduction and Usage
|
||||
|
||||
This project (see `./examples/mandelbrot/`) shows how to calculate and visualize the [Mandelbrot set](https://en.wikipedia.org/wiki/Mandelbrot_set) using `JKQTPlotter` and its `JKQTPMathImage`.
|
||||
|
||||
The source code of the main application is (see [`mandelbrot.cpp`](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/mandelbrot/mandelbrotmainwindow.cpp):
|
||||
|
||||
![mandelbrot](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/mandelbrot.png)
|
||||
|
||||
You can use any of the several zooming methods (by mouse-wheel, panning, by drawing a rectangle ...) and the application will automaticaly calculate the zoomed area. Here is an example:
|
||||
|
||||
1. Select the Zoom by Mouse Rectangle tool: ![mandelbrot_zoom_pre](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/mandelbrot_zoom_pre.png)
|
||||
2. Drag open a rectangle that you want to zoom into: ![mandelbrot_zoom](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/mandelbrot_zoom.png)
|
||||
3. When you release the mouse, the new image will be calculated.
|
||||
|
||||
|
||||
## How it works
|
||||
|
||||
In the constructor, the ui, containing a JKQTPlotter `ui->plot`, is initialized. Then the JKQTPlotter is set up:
|
||||
|
||||
```.cpp
|
||||
// 1. set the graph scales manually
|
||||
ui->plot->setXY(-2,1,-1,1);
|
||||
ui->plot->setAbsoluteXY(-5,5,-5,5);
|
||||
// 2. set the asxpect ratio to width/height
|
||||
ui->plot->getPlotter()->setMaintainAspectRatio(true);
|
||||
ui->plot->getPlotter()->setAspectRatio(static_cast<double>(ui->plot->width())/static_cast<double>(ui->plot->height()));
|
||||
// 3. disable grids
|
||||
ui->plot->getXAxis()->setDrawGrid(false);
|
||||
ui->plot->getYAxis()->setDrawGrid(false);
|
||||
```
|
||||
|
||||
Then a `JKQTPMathImage` is added which displays an image column `mandelbrot_col_display`:
|
||||
|
||||
```.cpp
|
||||
graph=new JKQTPColumnMathImage(ui->plot);
|
||||
graph->setTitle("");
|
||||
// image column with the data
|
||||
graph->setImageColumn(mandelbrot_col_display);
|
||||
// image color range is calculated manually!
|
||||
graph->setAutoImageRange(false);
|
||||
graph->setImageMin(0);
|
||||
graph->setImageMax(ui->spinMaxIterations->value());
|
||||
// set image size
|
||||
graph->setX(ui->plot->getXMin());
|
||||
graph->setY(ui->plot->getYMin());
|
||||
graph->setWidth(ui->plot->getXMax()-ui->plot->getXMin());
|
||||
graph->setHeight(ui->plot->getYMax()-ui->plot->getYMin());
|
||||
// add graph to plot
|
||||
ui->plot->addGraph(graph);
|
||||
```
|
||||
|
||||
In between thise two code blocks, two image columns are added to the internal `JKQTPDatastore`:
|
||||
|
||||
```.cpp
|
||||
mandelbrot_col=ds->addImageColumn(300,200, "mandelbrot_image_calculate");
|
||||
mandelbrot_col_display=ds->copyColumn(mandelbrot_col, "mandelbrot_image_display");
|
||||
```
|
||||
|
||||
As mentioned before, `mandelbrot_col_display` is used for plotting and the baclground column (of the same size) `mandelbrot_col` is used to calculate a new image:
|
||||
|
||||
```.cpp
|
||||
calculateMandelSet(ui->plot->getXMin(), ui->plot->getXMax(), ui->plot->getYMin(), ui->plot->getYMax(), 300, 200, ui->spinMaxIterations->value());
|
||||
```
|
||||
|
||||
When calculation finished, the contents of `mandelbrot_col` is copied to `mandelbrot_col_display`:
|
||||
|
||||
```.cpp
|
||||
ui->plot->getDatastore()->copyColumnData(mandelbrot_col_display, mandelbrot_col);
|
||||
```
|
||||
|
||||
In order to implement the zoom functionality, the signal `JKQTPlotter::zoomChangedLocally` is connected to a function, which recalculates the new image for the new zoom-range:
|
||||
|
||||
|
||||
```.cpp
|
||||
void MandelbrotMainWindow::plotZoomChangedLocally(double newxmin, double newxmax, double newymin, double newymax, JKQTPlotter */*sender*/)
|
||||
{
|
||||
calculateMandelSet(newxmin, newxmax, newymin, newymax, ui->plot->getXAxis()->getParentPlotWidth(), ui->plot->getYAxis()->getParentPlotWidth(), ui->spinMaxIterations->value());
|
||||
ui->plot->getDatastore()->copyColumnData(mandelbrot_col_display, mandelbrot_col);
|
||||
if (ui->chkLogScaling->isChecked()) {
|
||||
std::transform(ui->plot->getDatastore()->begin(mandelbrot_col), ui->plot->getDatastore()->end(mandelbrot_col), ui->plot->getDatastore()->begin(mandelbrot_col), &log10);
|
||||
}
|
||||
graph->setX(newxmin);
|
||||
graph->setY(newymin);
|
||||
graph->setWidth(newxmax-newxmin);
|
||||
graph->setHeight(newymax-newymin);
|
||||
// this call ensures correctly set NX and NY
|
||||
graph->setImageColumn(mandelbrot_col_display);
|
||||
ui->plot->redrawPlot();
|
||||
}
|
||||
```
|
||||
|
||||
The actual calculation is performed in `calculateMandelSet()`:
|
||||
|
||||
```.cpp
|
||||
void MandelbrotMainWindow::calculateMandelSet(double rmin, double rmax, double imin, double imax, size_t width, size_t height, unsigned int max_iterations) {
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
auto ds=ui->plot->getDatastore();
|
||||
|
||||
// ensure the image column has the correct size
|
||||
ds->resizeImageColumn(mandelbrot_col, width, height);
|
||||
qDebug()<<"calculating for "<<width<<"x"<<height<<"pixels: real="<<rmin<<"..."<<rmax<<", imaginary="<<imin<<"..."<<imax;
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// iterate over all pixels, serial code
|
||||
for (auto pix=ds->begin(mandelbrot_col); pix!= ds->end(mandelbrot_col); ++pix) {
|
||||
// calculate the pixels coordinate in the imaginary plane
|
||||
const double r0=static_cast<double>(pix.getImagePositionX())/static_cast<double>(width)*(rmax-rmin)+rmin;
|
||||
const double i0=static_cast<double>(pix.getImagePositionY())/static_cast<double>(height)*(imax-imin)+imin;
|
||||
//qDebug()<<pix.getImagePositionX()<<","<<pix.getImagePositionY()<<": "<<r0<<","<<i0;
|
||||
|
||||
unsigned int iteration=0;
|
||||
double ri=0;
|
||||
double ii=0;
|
||||
// check for Mandelbrot series divergence at r0, i0, i.e. calculate
|
||||
// the series [r(i),i(i)]=fmanelbrot(r(i-1),i(i-1) | r0,i0) for every point in the plane [r0,i0]
|
||||
// starting from r(0)=i(0)=0. The number of iterations until |r(i),i(i)|>=2 gives the color of
|
||||
// the point.
|
||||
while(ri*ri+ii*ii<=2.0*2.0 && iteration<max_iterations) {
|
||||
const double tmp=ri*ri-ii*ii+r0;
|
||||
ii=2.0*ri*ii+i0;
|
||||
ri=tmp;
|
||||
iteration++;
|
||||
}
|
||||
*pix=iteration;
|
||||
}
|
||||
|
||||
|
||||
qDebug()<<"finished calculating after "<<static_cast<double>(timer.nsecsElapsed())/1000000.0<<"ms";
|
||||
}
|
||||
```
|
||||
|
||||
Here the actual algorithm to calculate the mandelbrot set is implemented. It iterates over all pixels `pix` in `mandelbrot_col` and updates their value according to the result of the calculation with `*pix=iteration;`.
|
||||
|
||||
In order to speed up the program, it actually uses a parallelized version of the algorithm:
|
||||
|
||||
```.cpp
|
||||
void MandelbrotMainWindow::calculateMandelSet(double rmin, double rmax, double imin, double imax, size_t width, size_t height, unsigned int max_iterations) {
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
auto ds=ui->plot->getDatastore();
|
||||
|
||||
// ensure the image column has the correct size
|
||||
ds->resizeImageColumn(mandelbrot_col, width, height);
|
||||
qDebug()<<"calculating for "<<width<<"x"<<height<<"pixels: real="<<rmin<<"..."<<rmax<<", imaginary="<<imin<<"..."<<imax;
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// iterate over all pixels, parallelized version
|
||||
|
||||
// calculate the block size for parallel processing
|
||||
const size_t blocksize=std::max<size_t>(100,width*height/std::max<size_t>(2, std::thread::hardware_concurrency()-1));
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
for (size_t offset=0; offset<width*height; offset+=blocksize) {
|
||||
threads.push_back(std::thread([=](){
|
||||
// start iterating at begin+offset
|
||||
auto pix=ds->begin(mandelbrot_col)+static_cast<int>(offset);
|
||||
// stop iterating at begin+offset+blocksize, or at the end
|
||||
const auto pix_end=pix+static_cast<int>(blocksize);
|
||||
for (; pix!=pix_end; ++pix) {
|
||||
// calculate the pixels coordinate in the imaginary plane
|
||||
const double r0=static_cast<double>(pix.getImagePositionX())/static_cast<double>(width)*(rmax-rmin)+rmin;
|
||||
const double i0=static_cast<double>(pix.getImagePositionY())/static_cast<double>(height)*(imax-imin)+imin;
|
||||
//qDebug()<<pix.getImagePositionX()<<","<<pix.getImagePositionY()<<": "<<r0<<","<<i0;
|
||||
|
||||
unsigned int iteration=0;
|
||||
double ri=0;
|
||||
double ii=0;
|
||||
// check for Mandelbrot series divergence at r0, i0, i.e. calculate
|
||||
// the series [r(i),i(i)]=fmanelbrot(r(i-1),i(i-1) | r0,i0) for every point in the plane [r0,i0]
|
||||
// starting from r(0)=i(0)=0. The number of iterations until |r(i),i(i)|>=2 gives the color of
|
||||
// the point.
|
||||
while(ri*ri+ii*ii<=2.0*2.0 && iteration<max_iterations) {
|
||||
const double tmp=ri*ri-ii*ii+r0;
|
||||
ii=2.0*ri*ii+i0;
|
||||
ri=tmp;
|
||||
iteration++;
|
||||
}
|
||||
*pix=iteration;
|
||||
}
|
||||
}));
|
||||
}
|
||||
qDebug()<<" using "<<threads.size()<<" threads with blocksize="<<blocksize;
|
||||
|
||||
// wait for threads to finish
|
||||
for (auto& thread:threads) thread.join();
|
||||
threads.clear();
|
||||
|
||||
|
||||
qDebug()<<"finished calculating after "<<static_cast<double>(timer.nsecsElapsed())/1000000.0<<"ms";
|
||||
}
|
||||
```
|
||||
|
||||
|
21
examples/mandelbrot/mandelbrot.cpp
Normal file
21
examples/mandelbrot/mandelbrot.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
/** \example mandelbrot.cpp
|
||||
* Shows how to plot the Mandelbrot set with JKQTPlotter, also providing a zooming feature.
|
||||
*
|
||||
* \ref JKQTPlotterMandelbrot
|
||||
*/
|
||||
|
||||
#include <QApplication>
|
||||
#include <cmath>
|
||||
#include "mandelbrotmainwindow.h"
|
||||
|
||||
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
MandelbrotMainWindow widMain;
|
||||
|
||||
widMain.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
27
examples/mandelbrot/mandelbrot.pro
Normal file
27
examples/mandelbrot/mandelbrot.pro
Normal file
@ -0,0 +1,27 @@
|
||||
# source code for this simple demo
|
||||
SOURCES = mandelbrot.cpp
|
||||
|
||||
# configure Qt
|
||||
CONFIG += link_prl qt
|
||||
QT += core gui xml svg
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport
|
||||
|
||||
# output executable name
|
||||
TARGET = mandelbrot
|
||||
|
||||
# include JKQTPlotter source code
|
||||
DEPENDPATH += ../../lib ../../qmake/staticlib/jkqtplotterlib
|
||||
INCLUDEPATH += ../../lib
|
||||
CONFIG (debug, debug|release) {
|
||||
LIBS += -L../../qmake/staticlib/jkqtplotterlib/debug -ljkqtplotterlib_debug
|
||||
} else {
|
||||
LIBS += -L../../qmake/staticlib/jkqtplotterlib/release -ljkqtplotterlib
|
||||
}
|
||||
message("LIBS = $$LIBS")
|
||||
|
||||
win32-msvc*: DEFINES += _USE_MATH_DEFINES
|
||||
win32-msvc*: DEFINES += NOMINMAX
|
||||
|
||||
|
||||
|
||||
|
8
examples/mandelbrot/mandelbrot_and_lib.pro
Normal file
8
examples/mandelbrot/mandelbrot_and_lib.pro
Normal file
@ -0,0 +1,8 @@
|
||||
TEMPLATE = subdirs
|
||||
|
||||
SUBDIRS += jkqtplotterlib mandelbrot
|
||||
|
||||
jkqtplotterlib.file = ../../qmake/staticlib/jkqtplotterlib/jkqtplotterlib.pro
|
||||
|
||||
mandelbrot.file=$$PWD/mandelbrot.pro
|
||||
mandelbrot.depends = jkqtplotterlib
|
209
examples/mandelbrot/mandelbrotmainwindow.cpp
Normal file
209
examples/mandelbrot/mandelbrotmainwindow.cpp
Normal file
@ -0,0 +1,209 @@
|
||||
#include "mandelbrotmainwindow.h"
|
||||
#include "ui_mandelbrotmainwindow.h"
|
||||
#include <thread>
|
||||
#include <future>
|
||||
|
||||
MandelbrotMainWindow::MandelbrotMainWindow(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::MandelbrotMainWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
// format graph:
|
||||
// 1. set the graph scales manually
|
||||
ui->plot->setXY(-2,1,-1,1);
|
||||
ui->plot->setAbsoluteXY(-5,5,-5,5);
|
||||
// 2. set the asxpect ratio to width/height
|
||||
ui->plot->getPlotter()->setMaintainAspectRatio(true);
|
||||
ui->plot->getPlotter()->setAspectRatio(static_cast<double>(ui->plot->width())/static_cast<double>(ui->plot->height()));
|
||||
// 3. disable grids
|
||||
ui->plot->getXAxis()->setDrawGrid(false);
|
||||
ui->plot->getYAxis()->setDrawGrid(false);
|
||||
|
||||
JKQTPDatastore* ds=ui->plot->getDatastore();
|
||||
|
||||
mandelbrot_col=ds->addImageColumn(300,200, "mandelbrot_image_calculate");
|
||||
mandelbrot_col_display=ds->copyColumn(mandelbrot_col, "mandelbrot_image_display");
|
||||
calculateMandelSet(ui->plot->getXMin(), ui->plot->getXMax(), ui->plot->getYMin(), ui->plot->getYMax(), 300, 200, ui->spinMaxIterations->value());
|
||||
ui->plot->getDatastore()->copyColumnData(mandelbrot_col_display, mandelbrot_col);
|
||||
|
||||
// 4. create a graph (JKQTPColumnMathImage) with the column created above as data
|
||||
// The data is color-coded with the color-palette JKQTPMathImageMATLAB
|
||||
// the converted range of data is determined automatically because setAutoImageRange(true)
|
||||
graph=new JKQTPColumnMathImage(ui->plot);
|
||||
graph->setTitle("");
|
||||
// image column with the data
|
||||
graph->setImageColumn(mandelbrot_col_display);
|
||||
// image color range is calculated manually!
|
||||
graph->setAutoImageRange(false);
|
||||
graph->setImageMin(0);
|
||||
graph->setImageMax(ui->spinMaxIterations->value());
|
||||
// set image size
|
||||
graph->setX(ui->plot->getXMin());
|
||||
graph->setY(ui->plot->getYMin());
|
||||
graph->setWidth(ui->plot->getXMax()-ui->plot->getXMin());
|
||||
graph->setHeight(ui->plot->getYMax()-ui->plot->getYMin());
|
||||
// add graph to plot
|
||||
ui->plot->addGraph(graph);
|
||||
|
||||
|
||||
ui->cmbColorPalette->setCurrentColorPalette(graph->getColorPalette());
|
||||
|
||||
connect(ui->action_Reset_View, &QAction::triggered, this, &MandelbrotMainWindow::resetView);
|
||||
connect(ui->cmbColorPalette, &JKQTPMathImageColorPaletteComboBox::currentPaletteChanged, this, &MandelbrotMainWindow::paletteChanged);
|
||||
connect(ui->spinMaxIterations, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &MandelbrotMainWindow::maxIterationsChanged);
|
||||
connect(ui->chkLogScaling, &QCheckBox::toggled, this, &MandelbrotMainWindow::logScalingChanged);
|
||||
connect(ui->plot, &JKQTPlotter::zoomChangedLocally, this, &MandelbrotMainWindow::plotZoomChangedLocally);
|
||||
connect(ui->plot, &JKQTPlotter::widgetResized, this, &MandelbrotMainWindow::plotResized);
|
||||
}
|
||||
|
||||
MandelbrotMainWindow::~MandelbrotMainWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void MandelbrotMainWindow::paletteChanged(JKQTPMathImageColorPalette pal)
|
||||
{
|
||||
graph->setColorPalette(pal);
|
||||
ui->plot->redrawPlot();
|
||||
}
|
||||
|
||||
void MandelbrotMainWindow::maxIterationsChanged(int/* maxIter*/)
|
||||
{
|
||||
graph->setAutoImageRange(false);
|
||||
graph->setImageMin(0);
|
||||
if (ui->chkLogScaling->isChecked()) {
|
||||
graph->setImageMax(log10(ui->spinMaxIterations->value()));
|
||||
} else {
|
||||
graph->setImageMax(ui->spinMaxIterations->value());
|
||||
}
|
||||
plotZoomChangedLocally(ui->plot->getXMin(), ui->plot->getXMax(), ui->plot->getYMin(), ui->plot->getYMax(), ui->plot);
|
||||
}
|
||||
|
||||
void MandelbrotMainWindow::logScalingChanged(bool en)
|
||||
{
|
||||
if (en) {
|
||||
std::transform(ui->plot->getDatastore()->begin(mandelbrot_col), ui->plot->getDatastore()->end(mandelbrot_col), ui->plot->getDatastore()->begin(mandelbrot_col_display), &log10);
|
||||
} else {
|
||||
std::copy(ui->plot->getDatastore()->begin(mandelbrot_col), ui->plot->getDatastore()->end(mandelbrot_col), ui->plot->getDatastore()->begin(mandelbrot_col_display));
|
||||
}
|
||||
graph->setAutoImageRange(false);
|
||||
graph->setImageMin(0);
|
||||
if (ui->chkLogScaling->isChecked()) {
|
||||
graph->setImageMax(log10(ui->spinMaxIterations->value()));
|
||||
} else {
|
||||
graph->setImageMax(ui->spinMaxIterations->value());
|
||||
}
|
||||
ui->plot->redrawPlot();
|
||||
}
|
||||
|
||||
void MandelbrotMainWindow::plotResized(int /*new_width*/, int /*new_height*/, JKQTPlotter */*sender*/)
|
||||
{
|
||||
ui->plot->getPlotter()->setAspectRatio(static_cast<double>(ui->plot->width())/static_cast<double>(ui->plot->height()));
|
||||
ui->plot->getPlotter()->setMaintainAspectRatio(true);
|
||||
qDebug()<<"plotResized: aspect="<<static_cast<double>(ui->plot->width())/static_cast<double>(ui->plot->height());
|
||||
plotZoomChangedLocally(ui->plot->getXMin(), ui->plot->getXMax(), ui->plot->getYMin(), ui->plot->getYMax(), ui->plot);
|
||||
}
|
||||
|
||||
void MandelbrotMainWindow::resetView()
|
||||
{
|
||||
ui->plot->setXY(-2,1,-1,1);
|
||||
ui->plot->redrawPlot();
|
||||
}
|
||||
|
||||
void MandelbrotMainWindow::plotZoomChangedLocally(double newxmin, double newxmax, double newymin, double newymax, JKQTPlotter */*sender*/)
|
||||
{
|
||||
calculateMandelSet(newxmin, newxmax, newymin, newymax, ui->plot->getXAxis()->getParentPlotWidth(), ui->plot->getYAxis()->getParentPlotWidth(), ui->spinMaxIterations->value());
|
||||
ui->plot->getDatastore()->copyColumnData(mandelbrot_col_display, mandelbrot_col);
|
||||
if (ui->chkLogScaling->isChecked()) {
|
||||
std::transform(ui->plot->getDatastore()->begin(mandelbrot_col), ui->plot->getDatastore()->end(mandelbrot_col), ui->plot->getDatastore()->begin(mandelbrot_col), &log10);
|
||||
}
|
||||
graph->setX(newxmin);
|
||||
graph->setY(newymin);
|
||||
graph->setWidth(newxmax-newxmin);
|
||||
graph->setHeight(newymax-newymin);
|
||||
// this call ensures correctly set NX and NY
|
||||
graph->setImageColumn(mandelbrot_col_display);
|
||||
ui->plot->redrawPlot();
|
||||
}
|
||||
|
||||
|
||||
void MandelbrotMainWindow::calculateMandelSet(double rmin, double rmax, double imin, double imax, size_t width, size_t height, unsigned int max_iterations) {
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
auto ds=ui->plot->getDatastore();
|
||||
|
||||
// ensure the image column has the correct size
|
||||
ds->resizeImageColumn(mandelbrot_col, width, height);
|
||||
qDebug()<<"calculating for "<<width<<"x"<<height<<"pixels: real="<<rmin<<"..."<<rmax<<", imaginary="<<imin<<"..."<<imax;
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// iterate over all pixels, parallelized version
|
||||
|
||||
// calculate the block size for parallel processing
|
||||
const size_t blocksize=std::max<size_t>(100,width*height/std::max<size_t>(2, std::thread::hardware_concurrency()-1));
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
for (size_t offset=0; offset<width*height; offset+=blocksize) {
|
||||
threads.push_back(std::thread([=](){
|
||||
// start iterating at begin+offset
|
||||
auto pix=ds->begin(mandelbrot_col)+static_cast<int>(offset);
|
||||
// stop iterating at begin+offset+blocksize, or at the end
|
||||
const auto pix_end=pix+static_cast<int>(blocksize);
|
||||
for (; pix!=pix_end; ++pix) {
|
||||
// calculate the pixels coordinate in the imaginary plane
|
||||
const double r0=static_cast<double>(pix.getImagePositionX())/static_cast<double>(width)*(rmax-rmin)+rmin;
|
||||
const double i0=static_cast<double>(pix.getImagePositionY())/static_cast<double>(height)*(imax-imin)+imin;
|
||||
//qDebug()<<pix.getImagePositionX()<<","<<pix.getImagePositionY()<<": "<<r0<<","<<i0;
|
||||
|
||||
unsigned int iteration=0;
|
||||
double ri=0;
|
||||
double ii=0;
|
||||
// check for Mandelbrot series divergence at r0, i0, i.e. calculate
|
||||
// the series [r(i),i(i)]=fmanelbrot(r(i-1),i(i-1) | r0,i0) for every point in the plane [r0,i0]
|
||||
// starting from r(0)=i(0)=0. The number of iterations until |r(i),i(i)|>=2 gives the color of
|
||||
// the point.
|
||||
while(ri*ri+ii*ii<=2.0*2.0 && iteration<max_iterations) {
|
||||
const double tmp=ri*ri-ii*ii+r0;
|
||||
ii=2.0*ri*ii+i0;
|
||||
ri=tmp;
|
||||
iteration++;
|
||||
}
|
||||
*pix=iteration;
|
||||
}
|
||||
}));
|
||||
}
|
||||
qDebug()<<" using "<<threads.size()<<" threads with blocksize="<<blocksize;
|
||||
for (auto& thread:threads) thread.join();
|
||||
threads.clear();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ALTERNATIVE: iterate over all pixels, serial code
|
||||
/*
|
||||
for (auto pix=ds->begin(mandelbrot_col); pix!= ds->end(mandelbrot_col); ++pix) {
|
||||
// calculate the pixels coordinate in the imaginary plane
|
||||
const double r0=static_cast<double>(pix.getImagePositionX())/static_cast<double>(width)*(rmax-rmin)+rmin;
|
||||
const double i0=static_cast<double>(pix.getImagePositionY())/static_cast<double>(height)*(imax-imin)+imin;
|
||||
//qDebug()<<pix.getImagePositionX()<<","<<pix.getImagePositionY()<<": "<<r0<<","<<i0;
|
||||
|
||||
unsigned int iteration=0;
|
||||
double ri=0;
|
||||
double ii=0;
|
||||
// check for Mandelbrot series divergence at r0, i0, i.e. calculate
|
||||
// the series [r(i),i(i)]=fmanelbrot(r(i-1),i(i-1) | r0,i0) for every point in the plane [r0,i0]
|
||||
// starting from r(0)=i(0)=0. The number of iterations until |r(i),i(i)|>=2 gives the color of
|
||||
// the point.
|
||||
while(ri*ri+ii*ii<=2.0*2.0 && iteration<max_iterations) {
|
||||
const double tmp=ri*ri-ii*ii+r0;
|
||||
ii=2.0*ri*ii+i0;
|
||||
ri=tmp;
|
||||
iteration++;
|
||||
}
|
||||
*pix=iteration;
|
||||
}
|
||||
*/
|
||||
|
||||
qDebug()<<"finished calculating after "<<static_cast<double>(timer.nsecsElapsed())/1000000.0<<"ms";
|
||||
}
|
37
examples/mandelbrot/mandelbrotmainwindow.h
Normal file
37
examples/mandelbrot/mandelbrotmainwindow.h
Normal file
@ -0,0 +1,37 @@
|
||||
#ifndef MANDELBROTMAINWINDOW_H
|
||||
#define MANDELBROTMAINWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
#include "jkqtplotter/graphs/jkqtpimage.h"
|
||||
|
||||
namespace Ui {
|
||||
class MandelbrotMainWindow;
|
||||
}
|
||||
|
||||
class MandelbrotMainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MandelbrotMainWindow(QWidget *parent = nullptr);
|
||||
~MandelbrotMainWindow();
|
||||
protected slots:
|
||||
void paletteChanged(JKQTPMathImageColorPalette pal);
|
||||
void plotZoomChangedLocally(double newxmin, double newxmax, double newymin, double newymax, JKQTPlotter* sender);
|
||||
void maxIterationsChanged(int maxIter);
|
||||
void logScalingChanged(bool en);
|
||||
void plotResized(int new_width, int new_height, JKQTPlotter* sender);
|
||||
void resetView();
|
||||
protected:
|
||||
void calculateMandelSet(double rmin, double rmax, double imin, double imax, size_t width, size_t height, unsigned int max_iterations = 1000);
|
||||
private:
|
||||
Ui::MandelbrotMainWindow *ui;
|
||||
// graph representing Mandelbrot Set
|
||||
JKQTPColumnMathImage* graph;
|
||||
// column for Mandelbrot Set image data, used for calculations (double-buffering with mandelbrot_col_display)
|
||||
size_t mandelbrot_col;
|
||||
// column for Mandelbrot Set image data, used for display (double-buffering with mandelbrot_col)
|
||||
size_t mandelbrot_col_display;
|
||||
};
|
||||
|
||||
#endif // MANDELBROTMAINWINDOW_H
|
145
examples/mandelbrot/mandelbrotmainwindow.ui
Normal file
145
examples/mandelbrot/mandelbrotmainwindow.ui
Normal file
@ -0,0 +1,145 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MandelbrotMainWindow</class>
|
||||
<widget class="QMainWindow" name="MandelbrotMainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Color Palette:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="JKQTPMathImageColorPaletteComboBox" name="cmbColorPalette"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="chkLogScaling">
|
||||
<property name="text">
|
||||
<string>logarithmic color scaling</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>max. iterations:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinMaxIterations">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="JKQTPlotter" name="plot" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menu_Main">
|
||||
<property name="title">
|
||||
<string>&Menu</string>
|
||||
</property>
|
||||
<addaction name="action_Reset_View"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionExit"/>
|
||||
</widget>
|
||||
<addaction name="menu_Main"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
<action name="action_Exit">
|
||||
<property name="text">
|
||||
<string>&Exit</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Reset_View">
|
||||
<property name="text">
|
||||
<string>&Reset View</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExit">
|
||||
<property name="text">
|
||||
<string>&Exit</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>JKQTPlotter</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>jkqtplotter/jkqtplotter.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>JKQTPMathImageColorPaletteComboBox</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>jkqtplotter/gui/jkqtpcomboboxes.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>actionExit</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MandelbrotMainWindow</receiver>
|
||||
<slot>close()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>399</x>
|
||||
<y>299</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
BIN
screenshots/mandelbrot.png
Normal file
BIN
screenshots/mandelbrot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
BIN
screenshots/mandelbrot_small.png
Normal file
BIN
screenshots/mandelbrot_small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
screenshots/mandelbrot_zoom.png
Normal file
BIN
screenshots/mandelbrot_zoom.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 194 KiB |
BIN
screenshots/mandelbrot_zoom_pre.png
Normal file
BIN
screenshots/mandelbrot_zoom_pre.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
Loading…
Reference in New Issue
Block a user