graph.cpp - description
-------------------
begin : Thu Oct 2 2003
copyright : (C) 2003 by Michael Margraf
email : michael.margraf@alumni.tu-berlin.de
***************************************************************************/
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "graph.h"
#include "misc.h"
#include <cstdlib>
#include <iostream>
#include <cmath>
#include <QPainter>
#include <QDebug>
#include <QPainterPath>
#include <QtAlgorithms>
class Diagram;
Graph::Graph(Diagram const* d, const QString& _Line) :
Element(),
Style(GRAPHSTYLE_SOLID),
diagram(d)
{
Type = isGraph;
Var = _Line;
countY = 0;
Thick = numMode = 0;
Color = 0x0000ff;
Precision = 3;
isSelected = false;
yAxisNo = 0;
cPointsY = 0;
}
Graph::~Graph()
{
if (cPointsY != nullptr) {
delete[] cPointsY;
cPointsY = nullptr;
}
qDeleteAll(cPointsX);
}
void Graph::createMarkerText() const
{
for(auto pm : Markers) {
pm->createText();
}
}
void Graph::paint(QPainter* painter) {
if (ScrPoints.empty()) {
return;
}
painter->save();
if (isSelected) {
painter->setPen(QPen(Qt::darkGray,Thick + 4));
paintLines(painter);
painter->setPen(QPen(Qt::white, Thick, Qt::SolidLine));
paintLines(painter);
painter->restore();
return;
}
painter->setPen(QPen(QColor(Color), Thick, Qt::SolidLine));
paintLines(painter);
painter->restore();
}
void Graph::paintLines(QPainter* painter) {
switch(Style) {
case GRAPHSTYLE_STAR:
drawStarSymbols(painter);
break;
case GRAPHSTYLE_CIRCLE:
drawCircleSymbols(painter);
break;
case GRAPHSTYLE_ARROW:
drawArrowSymbols(painter);
break;
default:
drawLines(painter);
}
}
QString Graph::save()
{
QString s = "\t<\""+Var+"\" "+Color.name()+
" "+QString::number(Thick)+" "+QString::number(Precision)+
" "+QString::number(numMode)+" "+QString::number(Style)+
" "+QString::number(yAxisNo)+">";
for (Marker *pm : Markers)
s += "\n\t "+pm->save();
return s;
}
bool Graph::load(const QString& _s)
{
bool ok;
QString s = _s;
if(s.at(0) != '<') return false;
if(s.at(s.length()-1) != '>') return false;
s = s.mid(1, s.length()-2);
Var = s.section('"',1,1);
QString n;
n = s.section(' ',1,1);
Color = misc::ColorFromString(n);
if(!Color.isValid()) return false;
n = s.section(' ',2,2);
Thick = n.toInt(&ok);
if(!ok) return false;
n = s.section(' ',3,3);
Precision = n.toInt(&ok);
if(!ok) return false;
n = s.section(' ',4,4);
numMode = n.toInt(&ok);
if(!ok) return false;
n = s.section(' ',5,5);
int st = n.toInt(&ok);
if(!ok) return false;
Style = toGraphStyle(st);
if(Style==GRAPHSTYLE_INVALID) return false;
n = s.section(' ',6,6);
if(n.isEmpty()) return true;
yAxisNo = n.toInt(&ok);
if(!ok) return false;
return true;
}
* Checks if the coordinates x/y point to the graph. returns the number of the
* branch of the graph, -1 upon a miss.
*
* x/y are relative to diagram cx/cy. 5 is the precision the user must point
* onto the graph.
*
* FIXME: should return reference to hit sample point or some context.
*/
int Graph::getSelected(int x, int y)
{
auto pp = ScrPoints.begin();
if(pp == ScrPoints.end()) return -1;
int A, z=0;
int dx, dx2, x1;
int dy, dy2, y1;
int countX = cPointsX.at(0)->count;
if(pp->isStrokeEnd()) {
if(pp->isBranchEnd()) z++;
pp++;
if(pp->isBranchEnd()) {
if(pp->isGraphEnd()) return -1;
z++;
pp++;
if(pp->isGraphEnd()) return -1;
}
}
if(Style >= GRAPHSTYLE_STAR) {
while(!pp->isGraphEnd()) {
if(!pp->isStrokeEnd()) {
dx = x - int((pp)->getScrX());
dy = y - int((pp++)->getScrY());
if(dx < -5) continue;
if(dx > 5) continue;
if(dy < -5) continue;
if(dy > 5) continue;
return z*countX;
}
else {
z++;
pp++;
}
}
return -1;
}
while(!pp->isGraphEnd()) {
while(!pp->isBranchEnd()) {
x1 = int(pp->getScrX());
y1 = int((pp++)->getScrY());
dx = x - x1;
dy = y - y1;
if(pp->isPt()){
dx2 = int(pp->getScrX());
}else if(pp->isBranchEnd()) {
break;
}else if(pp->isStrokeEnd()) {
pp++;
dx2 = int(pp->getScrX());
if(pp->isBranchEnd()) break;
}
if(dx < -5) { if(x < dx2-5) continue; }
else { if(x > 5) if(x > dx2+5) continue; }
dy2 = int(pp->getScrY());
if(dy < -5) { if(y < dy2-5) continue; }
else { if(y > 5) if(y > dy2+5) continue; }
dx2 -= x1;
dy2 -= y1;
A = dx2*dy - dx*dy2;
A *= A;
A -= 25*(dx2*dx2 + dy2*dy2);
if(A <= 0) return z*countX;
}
pp++;
z++;
}
return -1;
}
Graph* Graph::sameNewOne()
{
Graph *pg = new Graph(diagram, Var);
pg->Color = Color;
pg->Thick = Thick;
pg->Style = Style;
pg->Precision = Precision;
pg->numMode = numMode;
pg->yAxisNo = yAxisNo;
for (Marker *pm : Markers)
pg->Markers.append(pm->sameNewOne(pg));
return pg;
}
* find a sample point close to VarPos, snap to it, and return data at VarPos
*/
std::pair<double,double> Graph::findSample(std::vector<double>& VarPos) const
{
DataX const* pD;
unsigned nVarPos=0;
unsigned n=0;
unsigned m=1;
for(unsigned ii=0; (pD=axis(ii)); ++ii) {
double* pp = pD->Points;
double v = VarPos[nVarPos];
for(unsigned i=pD->count; i>1; i--) {
if(fabs(v-(*pp)) < fabs(v-(*(pp+1)))) break;
pp++;
n += m;
}
m *= pD->count;
VarPos[nVarPos++] = *pp;
}
return std::pair<double,double>(cPointsY[2*n], cPointsY[2*n+1]);
}
void Graph::ScrPt::setStrokeEnd()
{
type = STROKEEND;
}
void Graph::ScrPt::setBranchEnd()
{
type = BRANCHEND;
}
void Graph::ScrPt::setGraphEnd()
{
type = GRAPHEND;
}
bool Graph::ScrPt::isPt() const{return (ScrX >= 0. && type == DataPt);}
bool Graph::ScrPt::isStrokeEnd() const{return (type >= STROKEEND);}
bool Graph::ScrPt::isBranchEnd() const{return (type >= BRANCHEND);}
bool Graph::ScrPt::isGraphEnd() const{return (type >= GRAPHEND);}
* set screen coordinate for graph sampling point
* these must be nonnegative, but sometimes aren't,
* eg. between calcCoordinate and clip.
* (negative values are reserved for control.)
*/
void Graph::ScrPt::setScrX(float x) {
if (x < 0) {
qDebug() << "dangerous: negative screen coordinate" << x;
} else {
type = DataPt;
}
if (ScrX < 0) {
qDebug() << "dangerous: (maybe) overwriting control token" << x;
}
ScrX = x;
}
void Graph::ScrPt::setScrY(float y) {
if (y < 0) {
qDebug() << "setting negative screen coordinate" << y << "at" << ScrY;
} else {
type = DataPt;
}
ScrY = y;
}
void Graph::ScrPt::setScr(float x, float y) {
x = fmaxf(x, 0);
y = fmaxf(y, 0);
setScrX(x);
setScrY(y);
}
void Graph::ScrPt::setIndep(double x)
{
assert(ScrX>=0);
indep = x;
}
void Graph::ScrPt::setDep(double x)
{
assert(ScrX>=0);
dep = x;
}
float Graph::ScrPt::getScrX() const
{
if(ScrX<0){
std::cerr << "dangerous: returning negative screen coordinate" << ScrX;
}
return ScrX;
}
float Graph::ScrPt::getScrY() const
{
return ScrY;
}
double Graph::ScrPt::getIndep() const
{
assert(ScrX>=0);
return indep;
}
double Graph::ScrPt::getDep() const
{
assert(ScrX>=0);
return dep;
}
void Graph::drawCircleSymbols(QPainter* painter) const {
constexpr double radius = 4.0;
for (auto point : *this) {
if (!point.isPt()) {
continue;
}
painter->drawEllipse({point.getScrX(), point.getScrY()}, radius, radius);
}
}
void Graph::drawArrowSymbols(QPainter* painter) const {
constexpr double head_height = 7.0;
constexpr double head_half_width = 4.0;
for (auto point : *this) {
if (point.isGraphEnd()) {
break;
}
if (!point.isPt()) {
continue;
}
painter->drawLine(QLineF{point.getScrX(), point.getScrY(), point.getScrX(), static_cast<qreal>(cy)});
painter->drawLine(QLineF{point.getScrX() - head_half_width, point.getScrY() - head_height, point.getScrX(), point.getScrY()});
painter->drawLine(QLineF{point.getScrX() + head_half_width, point.getScrY() - head_height, point.getScrX(), point.getScrY()});
}
}
void Graph::drawStarSymbols(QPainter* painter) const {
for (auto point : *this) {
if (!point.isPt()) {
continue;
}
painter->save();
painter->translate(point.getScrX(), point.getScrY());
painter->drawLine(-5, 0, 5, 0);
painter->rotate(60);
painter->drawLine(-5, 0, 5, 0);
painter->rotate(-120);
painter->drawLine(-5, 0, 5, 0);
painter->restore();
}
}
void Graph::drawLines(QPainter* painter) const {
painter->save();
QPen pen = painter->pen();
pen.setJoinStyle(Qt::RoundJoin);
pen.setCapStyle(Qt::RoundCap);
switch(Style) {
case GRAPHSTYLE_DASH:
pen.setDashPattern({10.0, 6.0});
break;
case GRAPHSTYLE_DOT:
pen.setDashPattern({2.0, 4.0});
break;
case GRAPHSTYLE_LONGDASH:
pen.setDashPattern({24.0, 8.0});
break;
default:
pen.setStyle(Qt::SolidLine);
}
painter->setPen(pen);
constexpr double min_pixels = 1.0;
const double x_threshold = std::abs(min_pixels / painter->transform().m11());
const double y_threshold = std::abs(min_pixels / painter->transform().m22());
const auto is_too_short = [x_threshold, y_threshold](const QPointF& a, const QPointF& b) {
return std::abs(a.x() - b.x()) < x_threshold && std::abs(a.y() - b.y()) < y_threshold;
};
bool drawing_started = false;
QPointF segment_start;
QPointF segment_end;
QPainterPath path;
for (const auto& point : *this) {
if (point.isGraphEnd()) {
break;
}
if (point.isStrokeEnd()) {
painter->drawPath(path);
path.clear();
drawing_started = false;
continue;
}
if (!point.isPt()) {
continue;
}
if (!drawing_started) {
segment_start.setX(point.getScrX());
segment_start.setY(point.getScrY());
path.moveTo(segment_start);
drawing_started = true;
continue;
}
segment_end.setX(point.getScrX());
segment_end.setY(point.getScrY());
if (is_too_short(segment_start, segment_end)) {
continue;
}
path.lineTo(segment_end);
segment_start = segment_end;
}
painter->restore();
}