82a6d74d创建于 2025年4月30日历史提交
/***************************************************************************
                              rectdiagram.cpp
                             -----------------
    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.                                   *
 *                                                                         *
 ***************************************************************************/

/*!
  \class RectDiagram
  \brief The RectDiagram class implements the Cartesian diagram
*/

#include <QFontMetrics>

#if HAVE_CONFIG_H
# include <config.h>
#endif
#include <cmath>
#include <float.h>
#if HAVE_IEEEFP_H
# include <ieeefp.h>
#endif

#include "rectdiagram.h"
#include "main.h"
#include "misc.h"


RectDiagram::RectDiagram(int _cx, int _cy) : Diagram(_cx, _cy)
{
  x1 = 10;      // position of label text
  y1 = y3 = 33;
  x2 = 240;    // initial size of diagram
  y2 = 160;
  x3 = 247;    // with some distance for right axes text

  Name = "Rect"; // BUG
  calcDiagram();
}

RectDiagram::~RectDiagram()
{
}

// ------------------------------------------------------------
void RectDiagram::calcCoordinate(const double* xD, const double* yD, const double*,
                                 float *px, float *py, Axis const *pa) const
{
  double x  = *xD;
  double yr = yD[0];
  double yi = yD[1];
  if(xAxis.log) {
    x /= xAxis.low;
    if(x <= 0.0)  *px = -1e5;   // "negative infinity"
    else  *px = float(log10(x)/log10(xAxis.up / xAxis.low) * double(x2));
  }
  else  *px = float((x-xAxis.low)/(xAxis.up-xAxis.low)*double(x2));

  if(pa->log) {
    yr = sqrt(yr*yr + yi*yi);
    if(yr <= 0.0)  *py = -1e5;   // "negative infinity"
    else *py = float(log10(yr/fabs(pa->low)) /
                     log10(pa->up/pa->low) * double(y2));
  }
  else {
    if(fabs(yi) > 1e-250)  // preserve negative values if not complex number
      yr = sqrt(yr*yr + yi*yi);
    *py = float((yr-pa->low)/(pa->up-pa->low)*double(y2));
  }

  if(!std::isfinite(*px))  *px = 0.0;
  if(!std::isfinite(*py))  *py = 0.0;
}

// --------------------------------------------------------------
// Convert a point on the diagram to the value represented by that point.
MappedPoint RectDiagram::pointToValue(const QPointF& point)
{
  MappedPoint result{};
      // Update the value transform. TODO: This only needs to be called when the limits change.
  valueTransform.reset();
  qreal xMin = xAxis.log ? log10(xAxis.low) : xAxis.low;
  qreal yMin = yAxis.log ? log10(yAxis.low) : yAxis.low;
  qreal xMax = xAxis.log ? log10(xAxis.up) : xAxis.up;
  qreal yMax = yAxis.log ? log10(yAxis.up) : yAxis.up;
  valueTransform.translate(xMin, yMin);
  valueTransform.scale((xMax - xMin) / x2, (yMax - yMin) / y2);

  // Obtain the value at point.
  QPointF value = valueTransform.map(point);

  result.x = xAxis.log ? pow(10, value.x()) : value.x();
  result.y1 = yAxis.log ? pow(10, value.y()) : value.y();

  valueTransform.reset();
  yMin = zAxis.log ? log10(zAxis.low) : zAxis.low;
  yMax = zAxis.log ? log10(zAxis.up) : zAxis.up;
  valueTransform.translate(xMin, yMin);
  valueTransform.scale((xMax - xMin) / x2, (yMax - yMin) / y2);
  // Obtain the value at point.
  value = valueTransform.map(point);

  result.y2 = zAxis.log ? pow(10, value.y()) : value.y();

  // qDebug() << "Transform yields: " << value;
  // Convert to exponential if needed.
  return result;
}

void RectDiagram::setLimitsBySelectionRect(QRectF select) {
  double a, b, c;
  // Set the diagram limits.
  auto minValue = pointToValue(select.bottomLeft());
  auto maxValue = pointToValue(select.topRight());

  xAxis.limit_min = minValue.x;
  xAxis.limit_max = maxValue.x;
  calcAxisScale(&xAxis, a, b, c, xAxis.step, double(x2)); // calculate step X
  xAxis.autoScale = false;

  yAxis.limit_min = maxValue.y1;
  yAxis.limit_max = minValue.y1;
  calcAxisScale(&yAxis, a, b, c, yAxis.step, double(y2)); // calculate step Y1
  yAxis.autoScale = false;

  zAxis.limit_min = maxValue.y2;
  zAxis.limit_max = minValue.y2;
  calcAxisScale(&zAxis, a, b, c, zAxis.step, double(y2)); // calculate step Y2
  zAxis.autoScale = false;
}

// --------------------------------------------------------------
void RectDiagram::finishMarkerCoordinates(float& fCX, float& fCY) const
{
  if(!insideDiagram(fCX, fCY)) {
	  fCX = fCY = 0.0;
  }
}

// --------------------------------------------------------------
void RectDiagram::calcLimits()
{
  int i;
  double a, b, c;

  if(xAxis.autoScale) {// check before, to preserve limit exchange (max < min)
    if(xAxis.log) {
      calcAxisLogScale(&xAxis, i, a, b, c, x2);
      xAxis.step = 1.0;
    }
    else  calcAxisScale(&xAxis, a, b, c, xAxis.step, double(x2));
    xAxis.limit_min = xAxis.low;
    xAxis.limit_max = xAxis.up;
  }

  if(yAxis.autoScale) {// check before, to preserve limit exchange (max < min)
    if(yAxis.log) {
      calcAxisLogScale(&yAxis, i, a, b, c, y2);
      yAxis.step = 1.0;
    }
    else  calcAxisScale(&yAxis, a, b, c, yAxis.step, double(y2));
    yAxis.limit_min = yAxis.low;
    yAxis.limit_max = yAxis.up;
  }

  if(zAxis.autoScale) {// check before, to preserve limit exchange (max < min)
    if(zAxis.log) {
      calcAxisLogScale(&zAxis, i, a, b, c, y2);
      zAxis.step = 1.0;
    }
    else  calcAxisScale(&zAxis, a, b, c, zAxis.step, double(y2));
    zAxis.limit_min = zAxis.low;
    zAxis.limit_max = zAxis.up;
  }
}

// --------------------------------------------------------------
int RectDiagram::calcDiagram()
{
  Lines.clear();
  Texts.clear();
  Arcs.clear();

  double GridStep, corr, zD, zDstep, GridNum;
  // get size of text using the screen-compatible metric
  QFontMetrics metrics(QucsSettings.font, 0);
  y1 = QucsSettings.font.pointSize() + 6;

  x1 = 10;      // position of label text
  x3 = x2 + 7;
  QString tmp;
  bool back = false;
  int  z, w, valid = 0;

  // =====  give "step" the right sign (if user made it wrong)  ==============
  xAxis.step = fabs(xAxis.step);
  if(xAxis.limit_min > xAxis.limit_max)
    xAxis.step *= -1.0;

  yAxis.step = fabs(yAxis.step);
  if(yAxis.limit_min > yAxis.limit_max)
    yAxis.step *= -1.0;

  zAxis.step = fabs(zAxis.step);
  if(zAxis.limit_min > zAxis.limit_max)
    zAxis.step *= -1.0;


  // ====  x grid  =======================================================
if(xAxis.log) {
  if(xAxis.autoScale) {
    if(xAxis.max*xAxis.min < 1e-200)  goto Frame;  // invalid
  }
  else  if(xAxis.limit_min*xAxis.limit_max < 1e-200)  goto Frame;  // invalid

  back = calcAxisLogScale(&xAxis, z, zD, zDstep, corr, x2);

  if(back) z = x2;
  while((z <= x2) && (z >= 0)) {    // create all grid lines
    if(xAxis.GridOn)  if(z < x2)  if(z > 0)
      Lines.prepend(new qucs::Line(z, y2, z, 0, GridPen));  // x grid

    if((zD < 1.5*zDstep) || (z == 0) || (z == x2)) {
      if (engineeringNotation) tmp = misc::num2str(zD);
      else tmp = misc::StringNiceNum(zD);
      if(xAxis.up < 0.0)  tmp = '-'+tmp;
      w = metrics.boundingRect(tmp).width();  // width of text
      // center text horizontally under the x tick mark
      Texts.append(new Text(z-(w>>1), -y1, tmp));
      Lines.append(new qucs::Line(z, 5, z, -5, QPen(Qt::black,0)));  // x tick marks
    }

    zD += zDstep;
    if(zD > 9.5*zDstep)  zDstep *= 10.0;
    if(back) {
      z = lround(corr*log10(zD / fabs(xAxis.up)));
      z = x2 - z;
    }
    else
      z = lround(corr*log10(zD / fabs(xAxis.low)));
  }
}
else {  // not logarithmical
  calcAxisScale(&xAxis, GridNum, zD, zDstep, GridStep, double(x2));

  double Expo;
  if(xAxis.up == 0.0)  Expo = log10(fabs(xAxis.up-xAxis.low));
  else  Expo = log10(fabs(xAxis.up));

  zD += 0.5;     // perform rounding
  z = int(zD);   //  "int(...)" implies "floor(...)"
  while((z <= x2) && (z >= 0)) {    // create all grid lines
    if(fabs(GridNum) < 0.01*pow(10.0, Expo)) GridNum = 0.0;// make 0 really 0
    if (engineeringNotation) tmp = misc::num2str(GridNum);
    else tmp = misc::StringNiceNum(GridNum);
    w = metrics.boundingRect(tmp).width();  // width of text
    // center text horizontally under the x tick mark
    Texts.append(new Text(z-(w>>1), -y1, tmp)); // Text(x, y, str, ...)
    GridNum += GridStep;

    if(xAxis.GridOn)  if(z < x2)  if(z > 0)
      Lines.prepend(new qucs::Line(z, y2, z, 0, GridPen)); // x grid
    Lines.append(new qucs::Line(z, 5, z, -5, QPen(Qt::black,0)));   // x tick marks
    zD += zDstep;
    z = int(zD);
  }
} // of "if(xlog) ... else ..."


  // ====  y grid  =======================================================
  if(zAxis.numGraphs > 0) if(calcYAxis(&zAxis, x2)) valid |= 2;
  if(yAxis.numGraphs > 0) if(calcYAxis(&yAxis, 0))  valid |= 1;


Frame:
  // outer frame
  Lines.append(new qucs::Line(0,  y2, x2, y2, QPen(Qt::black,0)));
  Lines.append(new qucs::Line(x2, y2, x2,  0, QPen(Qt::black,0)));
  Lines.append(new qucs::Line(0,   0, x2,  0, QPen(Qt::black,0)));
  Lines.append(new qucs::Line(0,  y2,  0,  0, QPen(Qt::black,0)));
  return valid;
}

// ------------------------------------------------------------
bool RectDiagram::insideDiagram(float x, float y) const
{
  return (regionCode(x, y) == 0);
}

// ------------------------------------------------------------
void RectDiagram::clip(Graph::iterator &p) const
{
  rectClip(p);
}

// ------------------------------------------------------------
Diagram* RectDiagram::newOne()
{
  return new RectDiagram();
}

// ------------------------------------------------------------
Element* RectDiagram::info(QString& Name, char* &BitmapFile, bool getNewOne)
{
  Name = QObject::tr("Cartesian");
  BitmapFile = (char *) "rect";

  if(getNewOne)  return new RectDiagram();
  return 0;
}