JKQtPlotter/lib/jkqtplotter/graphs/jkqtpcontour.cpp

479 lines
16 KiB
C++
Raw Normal View History

/*
2022-07-19 19:40:43 +08:00
Copyright (c) 2008-2022 Jan W. Krieger & Sebastian Isbaner (contour plot)
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/>.
*/
2019-06-20 22:06:31 +08:00
#include "jkqtplotter/graphs/jkqtpcontour.h"
#include "jkqtplotter/jkqtpbaseplotter.h"
#include "jkqtplotter/jkqtpimagetools.h"
#include "jkqtplotter/jkqtptools.h"
#include "jkqtcommon/jkqtpenhancedpainter.h"
#include "jkqtplotter/jkqtplotter.h"
#include "jkqtcommon/jkqtpgeometrytools.h"
#include "jkqtcommon/jkqttools.h"
#include <QDebug>
#include <QImageWriter>
#include <QFileDialog>
#include <QFileInfo>
#include <QApplication>
#include <QClipboard>
# include <QVector3D>
JKQTPContourPlot::JKQTPContourPlot(JKQTBasePlotter *parent) :
JKQTPMathImage(parent),
ignoreOnPlane(false),
contourColoringMode(ColorContoursFromPaletteByValue),
relativeLevels(false),
contourLinesCachedForChecksum(0)
{
initLineStyle(parent, parentPlotStyle, JKQTPPlotStyleType::Default);
}
JKQTPContourPlot::JKQTPContourPlot(JKQTPlotter *parent) :
JKQTPContourPlot(parent->getPlotter())
{
}
void JKQTPContourPlot::draw(JKQTPEnhancedPainter &painter)
{
//qDebug()<<"JKQTPContourPlot::draw";
ensureImageData();
int numberOfLevels=contourLevels.size();
if (numberOfLevels<=0) return;
int64_t colChecksum=-1;
if (data && Nx*Ny>0) {
colChecksum=static_cast<int64_t>(jkqtp_checksum(reinterpret_cast<const char*>(data), static_cast<int64_t>(Nx)*static_cast<int64_t>(Ny)* static_cast<int64_t>(getSampleSize()/sizeof(char))));
}
/*if (parent && parent->getDatastore() && imageColumn>=0) {
colChecksum=static_cast<int64_t>(parent->getDatastore()->getColumnChecksum(imageColumn));
}*/
if(contourLinesCache.isEmpty() || (contourLinesCachedForChecksum!=colChecksum) || (contourLinesCachedForChecksum<0)) { // contour lines are only calculated once
QList<QVector<QLineF> > lines;
lines.reserve(contourLevels.size());
for(int i =0; i<contourLevels.size();++i) {
lines.append(QVector<QLineF> (0));
}
this->calcContourLines(lines);
contourLinesCache.clear();
contourLinesCachedForChecksum=colChecksum;
for (const QVector<QLineF>& l: lines) {
contourLinesCache.push_back(JKQTPUnifyLinesToPolygons(l, qMin(getWidth()/static_cast<double>(getNx()),getHeight()/static_cast<double>(getNy()))/4.0));
}
}
// draw lines
painter.save(); auto __finalpaint=JKQTPFinally([&painter]() {painter.restore();});
QPen p=getLinePen(painter, parent);
painter.setPen(p);
// calculate an image with one pixel per contour level and fill it with the appropriate colors
QImage colorLevels = getPaletteImage(palette,numberOfLevels); // (contourColoringMode==ContourColoringMode::ColorContoursFromPalette)
if (contourColoringMode==ContourColoringMode::SingleColorContours) {
for (int i=0; i<numberOfLevels; i++) colorLevels.setPixel(i, 0, getLineColor().rgba());
} else if (contourColoringMode==ContourColoringMode::ColorContoursFromPaletteByValue) {
QImage colorDataLevels = getPaletteImage(palette,2000);
for (int i=0; i<numberOfLevels; i++) {
colorLevels.setPixel(i, 0, colorDataLevels.pixel(qBound<int>(0, (internalDataMax-contourLevels.value(i, 0))*static_cast<double>(colorDataLevels.width())/(internalDataMax-internalDataMin), colorDataLevels.width()-1),0));
}
}
// set override colors
for (int i=0; i<numberOfLevels; i++) {
if (contourOverrideColor.contains(contourLevels[i])) {
colorLevels.setPixel(i, 0, contourOverrideColor[contourLevels[i]].rgba());
}
}
getDataMinMax(internalDataMin, internalDataMax);
{
#ifdef JKQTBP_AUTOTIMER
JKQTPAutoOutputTimer jkaat(QString("JKQTPContourPlot::draw(): draw lines (incl. unify)"));
#endif
for(int i =0; i<numberOfLevels;++i) {
//qDebug()<<"============================================================\n== LEVEL "<<i<<"\n============================================================";
QVector<QPolygonF> contourLinesTransformedSingleLevel;
p.setColor(QColor(colorLevels.pixel(i,0)));
painter.setPen(p);
// transform into plot coordinates
for(auto polygon =contourLinesCache.at(i).begin(); polygon!=contourLinesCache.at(i).end();++polygon ) {
contourLinesTransformedSingleLevel.push_back(QPolygonF());
for (auto& poly: *polygon) {
contourLinesTransformedSingleLevel.last().append(transform(x+poly.x()/double(Nx-1)*width, y+poly.y()/double(Ny-1)*height));
}
//qDebug()<<lineTranformed;
}
for (const QPolygonF& poly: contourLinesTransformedSingleLevel) {
painter.drawPolylineFast(poly);
}
}
}
}
void JKQTPContourPlot::createContourLevels(int nLevels)
{
ensureImageData();
clearContourLevel();
if (!data) return;
if (nLevels<1) return;
double min,max;
getDataMinMax(min,max);
double delta=(max-min)/static_cast<double>(nLevels+1);
for(int i=1; i<=nLevels; ++i) {
contourLevels.append(min + i*delta);
}
relativeLevels=false;
clearCachedContours();
}
void JKQTPContourPlot::createContourLevelsLog(int nLevels, int m)
{
ensureImageData();
clearContourLevel();
if (!data) return;
if (nLevels<1) return;
double min,max;
getDataMinMax(min,max);
if(min<=0) min=1; // FIXME get smallest number greater zero
int S=floor((log10(max)-log10(min))/log10(m));
if(S<2) S=1;
int P = floor(static_cast<double>(nLevels)/static_cast<double>(S));
if(P<1) P=1;
double delta=min;
contourLevels.append(2*delta);
for (long s=0; s<S; s++) {
for (long p=0; p<P; p++) {
{
contourLevels.append(contourLevels.last()+delta);
}
}
delta=delta*m;
}
if(nLevels!=contourLevels.size()) {
//qDebug()<<"nLevels="<<nLevels<<"contourLevels.size()="<<contourLevels.size();
//qDebug()<<"adapt m";
}
relativeLevels=false;
clearCachedContours();
}
void JKQTPContourPlot::setIgnoreOnPlane(bool __value)
{
this->ignoreOnPlane = __value;
clearCachedContours();
}
bool JKQTPContourPlot::getIgnoreOnPlane() const
{
return this->ignoreOnPlane;
}
int JKQTPContourPlot::getNumberOfLevels() const
{
return this->contourLevels.size();
}
void JKQTPContourPlot::setContourColoringMode(ContourColoringMode __value)
{
this->contourColoringMode = __value;
}
JKQTPContourPlot::ContourColoringMode JKQTPContourPlot::getContourColoringMode() const
{
return this->contourColoringMode;
}
QVector<double> JKQTPContourPlot::getContourLevels() const
{
return this->contourLevels;
}
void JKQTPContourPlot::setRelativeLevels(bool __value)
{
this->relativeLevels = __value;
}
bool JKQTPContourPlot::getRelativeLevels() const
{
return this->relativeLevels;
}
void JKQTPContourPlot::addContourLevel(double level)
{
contourLevels.append(level);
std::sort(contourLevels.begin(), contourLevels.end());
clearCachedContours();
}
void JKQTPContourPlot::addContourLevel(double level, QColor overrideColor)
{
addContourLevel(level);
setOverrideColor(level, overrideColor);
}
void JKQTPContourPlot::setOverrideColor(double level, QColor overrideColor)
{
contourOverrideColor[level]=overrideColor;
}
QColor JKQTPContourPlot::getOverrideColor(int level) const
{
if (level>=0 && level<contourLevels.size()) {
if (contourOverrideColor.contains(contourLevels.at(level))) {
return contourOverrideColor.value(contourLevels.at(level));
}
}
return getLineColor();
}
bool JKQTPContourPlot::hasOverrideColor(int level) const
{
if (level>=0 && level<contourLevels.size()) {
if (contourOverrideColor.contains(contourLevels.at(level))) {
return true;
}
}
return false;
}
void JKQTPContourPlot::removeOverrideColor(int level)
{
if (level>=0 && level<contourLevels.size()) {
if (contourOverrideColor.contains(contourLevels.at(level))) {
contourOverrideColor.remove(contourLevels.at(level));
}
}
}
void JKQTPContourPlot::clearContourLevel()
{
contourLevels.clear();
contourOverrideColor.clear();
clearCachedContours();
}
void JKQTPContourPlot::clearCachedContours()
{
contourLinesCache.clear();
contourLinesCachedForChecksum=-1;
}
void JKQTPContourPlot::calcContourLines(QList<QVector<QLineF> > &ContourLines)
{
#ifdef JKQTBP_AUTOTIMER
JKQTPAutoOutputTimer jkaat(QString("JKQTPContourPlot::calcContourLines()"));
#else
//qDebug()<<"JKQTPContourPlot::calcContourLines()";
#endif
double scale=1; ///< scale of the contour levels;
if(relativeLevels) {
double min;
double max;
getDataMinMax(min,max);
scale=1/(max-min);
}
enum Position
{
// the positions of points of one box
// vertex 1 +-------------------+ vertex 2
// | \ / |
// | \ m=3 / |
// | \ / |
// | \ / |
// | m=2 X m=2 | the center is vertex 0
// | / \ |
// | / \ |
// | / m=1 \ |
// | / \ |
// vertex 4 +-------------------+ vertex 3
Center=0,
TopLeft=1,
TopRight=2,
BottomRight=3,
BottomLeft=4,
NumPositions=5
};
for ( int yp = 0; yp < (int64_t)getNy() - 1; ++yp ) { // go through image (pixel coordinates) in row major order
QVector<QVector3D> vertices(NumPositions);
for ( int xp = 0; xp < (int64_t)getNx() - 1; ++xp ) {
if ( xp == 0 )
{
vertices[TopRight].setX(xp); // will be used for TopLeft later
vertices[TopRight].setY(yp);
vertices[TopRight].setZ(
getPixelValue( vertices[TopRight].x(), vertices[TopRight].y())*scale
);
vertices[BottomRight].setX(xp);
vertices[BottomRight].setY(yp+1);
vertices[BottomRight].setZ(
getPixelValue(vertices[BottomRight].x(), vertices[BottomRight].y())*scale
);
}
vertices[TopLeft] = vertices[TopRight]; // use right vertices of the last box as new left vertices
vertices[BottomLeft] = vertices[BottomRight];
vertices[TopRight].setX(xp + 1);
vertices[TopRight].setY(yp); // <----
vertices[TopRight].setZ(
getPixelValue(vertices[TopRight].x(), vertices[TopRight].y())*scale
);
vertices[BottomRight].setX(xp + 1);
vertices[BottomRight].setY(yp + 1);
vertices[BottomRight].setZ(
getPixelValue(vertices[BottomRight].x(), vertices[BottomRight].y())*scale
);
double zMin = vertices[TopLeft].z();
double zMax = zMin;
double zSum = zMin;
for ( int i = TopRight; i <= BottomLeft; ++i ) {
const double z = vertices[i].z();
zSum += z;
if ( z < zMin )
zMin = z;
if ( z > zMax )
zMax = z;
}
if ( zMax >= contourLevels.first() && zMin <= contourLevels.last() ) {
vertices[Center].setX(xp + 0.5); // pseudo pixel coordinates
vertices[Center].setY(yp + 0.5);
vertices[Center].setZ(0.25 * zSum);
for (int levelIdx=0; levelIdx<contourLevels.size(); ++levelIdx) {
if ( contourLevels.at(levelIdx) >= zMin && contourLevels.at(levelIdx) <= zMax ) {
QLineF line;
QVector<QVector3D> triangle(3);
/* triangle[1]
X
/ \
/ \
/ m \
/ \
triangle[2] +-------------------+ triangle[0]
*/
for (int m = TopLeft; m < NumPositions; m++) { // construct triangles
triangle[0] = vertices[m];
triangle[1] = vertices[Center];
triangle[2] = vertices[(m!=BottomLeft)?(m + 1):TopLeft];
const bool intersects =intersect(line, triangle.at(0),triangle.at(1),triangle.at(2),
contourLevels.at(levelIdx));
if ( intersects ) {
ContourLines[levelIdx]<<line;
}
}
}
}
}
}
}
}
JKQTPColumnContourPlot::JKQTPColumnContourPlot(JKQTBasePlotter *parent):
JKQTPContourPlot(parent),
imageColumn(-1)
{
this->datatype=JKQTPMathImageDataType::DoubleArray;
}
JKQTPColumnContourPlot::JKQTPColumnContourPlot(JKQTPlotter *parent):
JKQTPColumnContourPlot(parent->getPlotter())
{
}
void JKQTPColumnContourPlot::setImageColumn(int __value)
{
this->imageColumn = __value;
if (parent && __value >= 0 && parent->getDatastore()) {
setNx(parent->getDatastore()->getColumnImageWidth(__value));
setNy(parent->getDatastore()->getColumnImageHeight(__value));
}
}
void JKQTPColumnContourPlot::setImageColumn(size_t __value)
{
setImageColumn(static_cast<int>(__value));
}
int JKQTPColumnContourPlot::getImageColumn() const
{
return this->imageColumn;
}
bool JKQTPColumnContourPlot::usesColumn(int c) const
{
return (c==imageColumn);
}
void JKQTPColumnContourPlot::ensureImageData()
{
if (this->Nx==0 || imageColumn<0 || !parent->getDatastore()->getColumnPointer(imageColumn,0)) {
this->Ny=0;
this->data=nullptr;
this->datatype=JKQTPMathImageDataType::DoubleArray;
} else {
this->datatype=JKQTPMathImageDataType::DoubleArray;
this->data=parent->getDatastore()->getColumnPointer(imageColumn,0);
this->Ny=static_cast<int>(parent->getDatastore()->getRows(imageColumn)/this->Nx);
}
}