Kstars

avtplotwidget.cpp
1/*
2 SPDX-FileCopyrightText: 2007 Jason Harris <kstars@30doradus.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "avtplotwidget.h"
8
9#include "kstarsdata.h"
10#include "Options.h"
11
12#include <QWidget>
13#include <QDebug>
14#include <QLinearGradient>
15#include <QMouseEvent>
16#include <QPainter>
17#include <QTime>
18#include <QToolTip>
19
20#include <KLocalizedString>
21#include <kplotobject.h>
22#include <kplotpoint.h>
23
24#include "kplotaxis.h"
25#include "ksalmanac.h"
26
27AVTPlotWidget::AVTPlotWidget(QWidget *parent) : KPlotWidget(parent)
28{
29 setAntialiasing(true);
30
31 MousePoint = QPoint(-1, -1);
32}
33
38
40{
41 Q_UNUSED(e);
42 MousePoint = QPoint(-1, -1);
43 update();
44}
45
47{
48 QRect checkRect(leftPadding(), topPadding(), pixRect().width(), pixRect().height());
49 int Xcursor = e->x();
50 int Ycursor = e->y();
51
52 if (!checkRect.contains(e->x(), e->y()))
53 {
54 if (e->x() < checkRect.left())
55 Xcursor = checkRect.left();
56 if (e->x() > checkRect.right())
57 Xcursor = checkRect.right();
58 if (e->y() < checkRect.top())
59 Ycursor = checkRect.top();
60 if (e->y() > checkRect.bottom())
61 Ycursor = checkRect.bottom();
62 }
63
64 Xcursor -= leftPadding();
65 Ycursor -= topPadding();
66
67 MousePoint = QPoint(Xcursor, Ycursor);
68 displayToolTip(e->pos(), e->globalPos());
69 update();
70}
71
73{
74 if (event->type() == QEvent::ToolTip)
75 {
76 QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
77 displayToolTip(helpEvent->pos(), helpEvent->globalPos());
78 return true;
79 }
80 return QWidget::event(event);
81}
82
83// Map logical data coordinates to mouse coordinates.
84QPointF AVTPlotWidget::toXY(double vx, double vy)
85{
86 QRectF plotArea = pixRect();
87 double px = leftPadding() + ((vx - xMin) * plotArea.width()) / (xMax - xMin);
88 // Top of the plot is y=0
89 double py = topPadding() + ((altitudeAxisMax - vy) * plotArea.height()) / (altitudeAxisMax - altitudeAxisMin);
90 return QPointF(px, py);
91}
92
93void AVTPlotWidget::displayToolTip(const QPoint &pos, const QPoint &globalPos)
94{
95 int bestDistSq = 1e6;
96 QString label;
97 for (const auto &tip : tips)
98 {
99 for (const auto &pt : tip.points)
100 {
101 const int dx = qAbs(pt.x() - pos.x());
102 const int dy = qAbs(pt.y() - pos.y());
103 if (dx < 5 && dy < 5)
104 {
105 const int distSq = dx * dx + dy * dy;
106 if (distSq < bestDistSq)
107 {
108 bestDistSq = distSq;
109 label = tip.label;
110 }
111 }
112 }
113 }
114 if (label.isEmpty())
116 else
117 QToolTip::showText(globalPos, label, this, QRect(), 3000);
118}
119
120
121// All the int coordinates (rise, set) need to be converted from hours relative to midnight
122// into graph coordinates before calling this.
123void drawMoon(QPainter &p, int rise, int set, int fade, const QColor &color, int width, int height, double leftPadding)
124{
125 QBrush brush(color, Qt::Dense5Pattern);
126 QBrush dimmerBrush(color, Qt::Dense6Pattern);
127 QBrush dimmestBrush(color, Qt::Dense7Pattern);
128 QRectF r;
129 if (set < rise)
130 {
131 if (set + fade >= leftPadding && set - fade < leftPadding + width)
132 {
133 r = QRectF(leftPadding, 0.0, (set - fade) - leftPadding, height);
134 p.fillRect(r, brush);
135 r = QRectF(set - fade, 0.0, fade, height);
136 p.fillRect(r, dimmerBrush);
137 r = QRectF(set, 0.0, fade, height);
138 p.fillRect(r, dimmestBrush);
139 }
140 if (rise + fade >= leftPadding && rise - fade < leftPadding + width)
141 {
142 r = QRectF(rise - fade, 0.0, fade, height);
143 p.fillRect(r, dimmestBrush);
144 r = QRectF(rise, 0.0, fade, height);
145 p.fillRect(r, dimmerBrush);
146
147 // Since set < rise, we draw to the end of the box
148 r = QRectF(rise + fade, 0.0, width, height);
149 p.fillRect(r, brush);
150 }
151 }
152 else
153 {
154 r = QRectF(rise - fade, 0.0, fade, height);
155 p.fillRect(r, dimmestBrush);
156 r = QRectF(rise, 0.0, fade, height);
157 p.fillRect(r, dimmerBrush);
158 r = QRectF(rise + fade, 0.0, (set - rise) - 2 * fade, height);
159 p.fillRect(r, brush);
160 r = QRectF(set - fade, 0.0, fade, height);
161 p.fillRect(r, dimmerBrush);
162 r = QRectF(set, 0.0, fade, height);
163 p.fillRect(r, dimmestBrush);
164 }
165}
166
167// All the int coordinates (rise, set, da, du) need to be converted from hours relative to midnight
168// into graph coordinates before calling this.
169void drawSun(QPainter &p, int rise, int set, double minAlt, double maxAlt, int da, int du, bool noDawn,
170 const QColor &color, int width, int height)
171{
172 if (maxAlt < 0.0 && minAlt < -18.0)
173 {
174 // The sun never rise but the sky is not completely dark
175 QLinearGradient grad = QLinearGradient(QPointF(0.0, 0.0), QPointF(du, 0.0));
176
177 QColor gradStartColor = color;
178 gradStartColor.setAlpha((1 - (maxAlt / -18.0)) * 255);
179
180 grad.setColorAt(0, gradStartColor);
182 p.fillRect(QRectF(0.0, 0.0, du, height), grad);
183 grad.setStart(QPointF(width, 0.0));
184 grad.setFinalStop(QPointF(da, 0.0));
185 p.fillRect(QRectF(da, 0.0, width, height), grad);
186 }
187 else if (maxAlt < 0.0 && minAlt > -18.0)
188 {
189 // The sun never rise but the sky is NEVER completely dark
190 QLinearGradient grad = QLinearGradient(QPointF(0.0, 0.0), QPointF(width, 0.0));
191
192 QColor gradStartEndColor = color;
193 gradStartEndColor.setAlpha((1 - (maxAlt / -18.0)) * 255);
194 QColor gradMidColor = color;
195 gradMidColor.setAlpha((1 - (minAlt / -18.0)) * 255);
196
197 grad.setColorAt(0, gradStartEndColor);
198 grad.setColorAt(0.5, gradMidColor);
199 grad.setColorAt(1, gradStartEndColor);
200 p.fillRect(QRectF(0.0, 0.0, width, height), grad);
201 }
202 else if (noDawn)
203 {
204 // The sun sets and rises but the sky is never completely dark
205 p.fillRect(0, 0, set, int(0.5 * height), color);
206 p.fillRect(rise, 0, width, int(0.5 * height), color);
207
208 QLinearGradient grad = QLinearGradient(QPointF(set, 0.0), QPointF(rise, 0.0));
209
210 QColor gradMidColor = color;
211 gradMidColor.setAlpha((1 - (minAlt / -18.0)) * 255);
212
213 grad.setColorAt(0, color);
214 grad.setColorAt(0.5, gradMidColor);
215 grad.setColorAt(1, color);
216 p.fillRect(QRectF(set, 0.0, rise - set, height), grad);
217 }
218 else
219 {
220 if (set > 0)
221 p.fillRect(0, 0, set, height, color);
222 if (rise < width)
223 p.fillRect(rise, 0, width, height, color);
224
225 QLinearGradient grad = QLinearGradient(QPointF(set, 0.0), QPointF(du, 0.0));
226 grad.setColorAt(0, color);
228 p.fillRect(QRectF(set, 0.0, du - set, height), grad);
229
230 grad.setStart(QPointF(rise, 0.0));
231 grad.setFinalStop(QPointF(da, 0.0));
232 p.fillRect(QRectF(da, 0.0, rise - da, height), grad);
233 }
234}
235
236// This legacy code always plotted from noon to noon (24 hours starting at noon).
237// To generalize this code, we still compute noon-to-noon coords, but then convert
238// them to more general plot coordinates where the plot length isn't 24 hours and
239// the plot doesn't begin at noon.
240int AVTPlotWidget::convertCoords(double xCoord)
241{
242 const double plotWidth = pixRect().width();
243 const double pixelsPerHour = plotWidth / plotDuration;
244 const double newPosition = pixelsPerHour * ((xCoord * 24.0 / plotWidth) - noonOffset);
245 return newPosition;
246}
247
248namespace
249{
250double findYValue(const KPlotObject *po, double x)
251{
252 const auto points = po->points();
253 const int size = points.size();
254 if (size == 0) return 0;
255 if (x < points[0]->x()) return points[0]->y();
256 if (x > points[size - 1]->x()) return points[size - 1]->y();
257 for (int i = 0; i < size - 1; ++i)
258 {
259 const double ix = points[i]->x();
260 const double iy = points[i]->y();
261 const double nextIx = points[i + 1]->x();
262 const double nextIy = points[i + 1]->y();
263 if (x == ix) return iy;
264 if (x == nextIx) return nextIy;
265 if (x > ix && x < nextIx)
266 return iy + (nextIy - iy) * (x - ix) / (nextIx - ix);
267 }
268 return points[size - 1]->y();
269}
270} // namespace
271
273{
274 Q_UNUSED(e)
275
276 QPainter p;
277
278 p.begin(this);
282
283 setPixRect();
284 p.setClipRect(pixRect());
285 p.setClipping(true);
286
287 int pW = pixRect().width();
288 int pH = pixRect().height();
289
290 QColor SkyColor(0, 100, 200);
291 /*
292 if (Options::darkAppColors())
293 SkyColor = QColor(200, 0, 0); // use something red, visible through a red filter
294 */
295
296 // Draw gradient representing lunar interference in the sky
297 if (MoonIllum > 0.01) // do this only if Moon illumination is reasonable so it's important
298 {
299 double moonrise = pW * (0.5 + MoonRise);
300 double moonset = pW * (MoonSet - 0.5);
301 if (moonset < 0)
302 moonset += pW;
303 if (moonrise > pW)
304 moonrise -= pW;
305 moonrise = convertCoords(moonrise);
306 moonset = convertCoords(moonset);
307
308 if (moonrise > pW)
309 {
310 const double pixelsPerHour = pW * 1.0 / plotDuration;
311 moonrise -= 24 * pixelsPerHour;
312 }
313 const int mooncolor = int(10 + MoonIllum * 130);
314 const QColor MoonColor(mooncolor, mooncolor, mooncolor);
315 int fadewidth =
316 pW *
317 0.01; // pW * fraction of day to fade the moon brightness over (0.01 corresponds to roughly 15 minutes, 0.007 to 10 minutes), both before and after actual set.
318
319 drawMoon(p, int(moonrise), int(moonset), fadewidth, MoonColor, pW, pH, leftPadding());
320
321 }
322 //draw daytime sky if the Sun rises for the current date/location
323 if (SunMaxAlt > -18.0)
324 {
325 // Initially compute centered on midnight, so modulate dawn/dusk by 0.5
326 // Then convert to general coordinates.
327 int rise = convertCoords(pW * (0.5 + SunRise));
328 int set = convertCoords(pW * (SunSet - 0.5));
329 int dawn = convertCoords(pW * (0.5 + Dawn));
330 double dusk = int(pW * (Dusk - 0.5));
331 if (dusk < 0) dusk = pW + dusk;
332 dusk = convertCoords(dusk);
333
334 if (SunMinAlt > 0.0)
335 {
336 // The sun never set and the sky is always blue
337 p.fillRect(rect(), SkyColor);
338 }
339 else drawSun(p, rise, set, SunMinAlt, SunMaxAlt, dawn, int(dusk), Dawn < 0.0, SkyColor, pW, pH);
340 }
341
342 //draw ground
343 if (altitudeAxisMin < 0)
344 {
345 const int groundYValue = pH + altitudeAxisMin * pH / (altitudeAxisMax - altitudeAxisMin);
346 p.fillRect(0, groundYValue, pW, groundYValue,
347 KStarsData::Instance()->colorScheme()->colorNamed(
348 "HorzColor")); // asimha changed to use color from scheme. Formerly was QColor( "#002200" )
349 }
350
351 foreach (KPlotObject *po, plotObjects())
352 {
353 po->draw(&p, this);
354 }
355
356 p.setClipping(false);
357 drawAxes(&p);
358
359 //Add vertical line indicating "now"
360 QFont smallFont = p.font();
361 smallFont.setPointSize(smallFont.pointSize()); // wat?
362 if (geo)
363 {
365 .time(); // convert the current system clock time to the TZ corresponding to geo
366 double x = 12.0 + t.hour() + t.minute() / 60.0 + t.second() / 3600.0;
367 while (x > 24.0)
368 x -= 24.0;
369 double ix = x * pW / 24.0; //convert to screen pixel coords
370 ix = convertCoords(ix);
371 p.setPen(QPen(QBrush("white"), 2.0, Qt::DotLine));
372 p.drawLine(int(ix), 0, ix, pH);
373
374 //Label this vertical line with the current time
375 p.save();
376 p.setFont(smallFont);
377 p.translate(int(ix) + 10, pH - 20);
378 p.rotate(-90);
379 p.drawText(
380 0, 0,
381 QLocale().toString(t, QLocale::ShortFormat)); // short format necessary to avoid false time-zone labeling
382 p.restore();
383 }
384
385 //Draw crosshairs at clicked position
386 if (MousePoint.x() > 0)
387 {
388 p.setPen(QPen(QBrush("gold"), 1.0, Qt::SolidLine));
389 p.drawLine(QLineF(MousePoint.x() + 0.5, 0.5, MousePoint.x() + 0.5, pixRect().height() - 0.5));
390
391 //Label each crosshair line (time and altitude)
392 p.setFont(smallFont);
393
394 double h = (MousePoint.x() * plotDuration) / pW - (12.0 - noonOffset);
395 double a = 0;
396 if (currentLine >= 0 && currentLine < plotObjects().size())
397 a = findYValue(plotObjects()[currentLine], h);
398 p.drawText(15, 15, QString::number(a, 'f', 1) + QChar(176));
399
400 if (h < 0.0)
401 h += 24.0;
402 QTime t = QTime(int(h), int(60. * (h - int(h))));
403 p.save();
404 p.translate(MousePoint.x() + 10, pH - 20);
405 p.rotate(-90);
406 p.drawText(
407 0, 0,
408 QLocale().toString(t, QLocale::ShortFormat)); // short format necessary to avoid false time-zone labeling
409 p.restore();
410 }
411
412 p.end();
413}
414
416{
417 if (index >= 0 && index < plotObjects().size())
418 currentLine = index;
419}
420
421void AVTPlotWidget::setDawnDuskTimes(double da, double du)
422{
423 Dawn = da;
424 Dusk = du;
425 update(); // fixme: should we always be calling update? It's probably cheap enough that we can.
426}
427
428void AVTPlotWidget::setMinMaxSunAlt(double min, double max)
429{
430 SunMinAlt = min;
431 SunMaxAlt = max;
432 update();
433}
434
435void AVTPlotWidget::setSunRiseSetTimes(double sr, double ss)
436{
437 SunRise = sr;
438 SunSet = ss;
439 update();
440}
441
442void AVTPlotWidget::setMoonRiseSetTimes(double mr, double ms)
443{
444 MoonRise = mr;
445 MoonSet = ms;
446 update();
447}
448
450{
451 MoonIllum = mi;
452 update();
453}
454
455void AVTPlotWidget::setPlotExtent(double offset, double duration)
456{
457 noonOffset = offset;
458 plotDuration = duration;
459}
460
461void AVTPlotWidget::disableAxis(KPlotWidget::Axis axisToDisable)
462{
463 axis(axisToDisable)->setVisible(false);
464}
465
466void AVTPlotWidget::plot(const GeoLocation *geo, KSAlmanac *ksal, const QVector<double> &times,
467 const QVector<double> &alts, int lineWidth, Qt::GlobalColor color, const QString &label)
468{
469 currentLine = 0;
470 xMin = times[0];
471 xMax = times.last();
472 setLimits(xMin, xMax, altitudeAxisMin, altitudeAxisMax);
473
474 setSecondaryLimits(times[0], times.last(), altitudeAxisMin, altitudeAxisMax);
478 setGeoLocation(geo);
479
480 setSunRiseSetTimes(ksal->getSunRise(), ksal->getSunSet());
481 setDawnDuskTimes(ksal->getDawnAstronomicalTwilight(), ksal->getDuskAstronomicalTwilight());
482 setMinMaxSunAlt(ksal->getSunMinAlt(), ksal->getSunMaxAlt());
483 setMoonRiseSetTimes(ksal->getMoonRise(), ksal->getMoonSet());
484 setMoonIllum(ksal->getMoonIllum());
485
486 const double noonOffset = times[0] - -12;
487 const double plotDuration = times.last() - times[0];
488 setPlotExtent(noonOffset, plotDuration);
490
491 tips.clear();
492 plotOverlay(times, alts, lineWidth, color, label);
493}
494
495void AVTPlotWidget::plotOverlay(const QVector<double> &times, const QVector<double> &alts, int lineWidth,
496 Qt::GlobalColor color, const QString &label)
497{
499 QPen pen;
500 pen.setWidth(lineWidth);
501 pen.setColor(color);
502 po->setLinePen(pen);
503
504 Tip tip;
505 tip.label = label;
506 for (int i = 0; i < times.size(); ++i)
507 {
508 po->addPoint(times[i], alts[i]);
509 if (!label.isEmpty())
510 {
511 QPointF p = toXY(times[i], alts[i]);
512 tip.points.append(p);
513 }
514 }
515 addPlotObject(po);
516 if (!label.isEmpty())
517 tips.append(tip);
518 update();
519}
520
521void AVTPlotWidget::setAltitudeAxis(double min, double max)
522{
523 if (min < max)
524 {
525 altitudeAxisMin = min;
526 altitudeAxisMax = max;
527 }
528}
529
void setGeoLocation(const GeoLocation *geo_)
Set the GeoLocation.
void setSunRiseSetTimes(double sr, double ss)
Set the fractional positions of the Sunrise and Sunset positions, in units where last midnight was 0....
bool event(QEvent *event) override
Used to catch tooltip event, otherwise calls parent.
void setMoonRiseSetTimes(double mr, double ms)
Set the fractional positions of moonrise and moon set in units where last midnight was 0....
void paintEvent(QPaintEvent *e) override
Redraw the plot.
void setPlotExtent(double noonOffset, double plotDuration)
This is needed when not plotting from noon to noon.
void setCurrentLine(int lineIndex)
Sets the plot index which whose altitude is displayed when clicking on the graph.
void plot(const GeoLocation *geo, KSAlmanac *ksal, const QVector< double > &times, const QVector< double > &alts, int lineWidth=2, Qt::GlobalColor color=Qt::white, const QString &label="")
Higher level method to plot.
void mouseDoubleClickEvent(QMouseEvent *e) override
Reset the MousePoint to a null value, to erase the crosshairs.
void setMoonIllum(double mi)
Set the moon illumination.
void mousePressEvent(QMouseEvent *e) override
Simply calls mouseMoveEvent().
void mouseMoveEvent(QMouseEvent *e) override
Handle mouse move events.
void setAltitudeAxis(double min, double max)
Sets the Y-axis min and max values.
Contains all relevant information for specifying a location on Earth: City Name, State/Province name,...
Definition geolocation.h:28
void setTickLabelFormat(char format='g', int fieldWidth=0, int precision=-1)
void setTickLabelsShown(bool b)
void setVisible(bool visible)
void draw(QPainter *p, KPlotWidget *pw)
void addPoint(const QPointF &p, const QString &label=QString(), double barWidth=0.0)
void setLinePen(const QPen &p)
QList< KPlotPoint * > points() const
QList< KPlotObject * > plotObjects() const
void setPixRect()
void setSecondaryLimits(double x1, double x2, double y1, double y2)
int leftPadding() const
int topPadding() const
KPlotAxis * axis(Axis type)
virtual void drawAxes(QPainter *p)
void setLimits(double x1, double x2, double y1, double y2)
void addPlotObject(KPlotObject *object)
QRect pixRect() const
QColor backgroundColor() const
bool antialiasing() const
void removeAllPlotObjects()
A class that implements methods to find sun rise, sun set, twilight begin / end times,...
Definition ksalmanac.h:27
double getSunMaxAlt() const
These functions return the max and min altitude of the sun during the course of the day in degrees.
Definition ksalmanac.h:75
double getMoonIllum() const
Definition ksalmanac.h:86
double getSunRise() const
All the functions returns the fraction of the day given by getDate() as their return value.
Definition ksalmanac.h:65
static KStarsDateTime currentDateTimeUtc()
QString label(StandardShortcut id)
void setAlpha(int alpha)
int pointSize() const const
void setPointSize(int pointSize)
void setColorAt(qreal position, const QColor &color)
const QPoint & globalPos() const const
const QPoint & pos() const const
void setFinalStop(const QPointF &stop)
void setStart(const QPointF &start)
T & last()
qsizetype size() const const
QPoint globalPos() const const
QPoint pos() const const
int x() const const
int y() const const
bool begin(QPaintDevice *device)
void drawLine(const QLine &line)
void drawText(const QPoint &position, const QString &text)
bool end()
void fillRect(const QRect &rectangle, QGradient::Preset preset)
const QFont & font() const const
void restore()
void rotate(qreal angle)
void save()
void setClipRect(const QRect &rectangle, Qt::ClipOperation operation)
void setClipping(bool enable)
void setFont(const QFont &font)
void setPen(Qt::PenStyle style)
void setRenderHint(RenderHint hint, bool on)
void translate(const QPoint &offset)
void setColor(const QColor &color)
void setWidth(int width)
int bottom() const const
bool contains(const QPoint &point, bool proper) const const
int height() const const
int left() const const
int right() const const
int top() const const
int width() const const
qreal height() const const
qreal width() const const
bool isEmpty() const const
QString number(double n, char format, int precision)
Dense5Pattern
transparent
int hour() const const
int minute() const const
int second() const const
void hideText()
void showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
virtual bool event(QEvent *event) override
void update()
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 11:58:40 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.