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 <QMouseEvent>
14#include <QPainter>
15#include <QTime>
16#include <QLinearGradient>
17
18#include <KLocalizedString>
19#include <kplotobject.h>
20#include <kplotpoint.h>
21#include <QDebug>
22
23#include "kplotaxis.h"
24#include "ksalmanac.h"
25
26AVTPlotWidget::AVTPlotWidget(QWidget *parent) : KPlotWidget(parent)
27{
28 setAntialiasing(true);
29
30 MousePoint = QPoint(-1, -1);
31}
32
37
39{
40 MousePoint = QPoint(-1, -1);
41 update();
42}
43
45{
46 QRect checkRect(leftPadding(), topPadding(), pixRect().width(), pixRect().height());
47 int Xcursor = e->x();
48 int Ycursor = e->y();
49
50 if (!checkRect.contains(e->x(), e->y()))
51 {
52 if (e->x() < checkRect.left())
53 Xcursor = checkRect.left();
54 if (e->x() > checkRect.right())
55 Xcursor = checkRect.right();
56 if (e->y() < checkRect.top())
57 Ycursor = checkRect.top();
58 if (e->y() > checkRect.bottom())
59 Ycursor = checkRect.bottom();
60 }
61
62 Xcursor -= leftPadding();
63 Ycursor -= topPadding();
64
65 MousePoint = QPoint(Xcursor, Ycursor);
66 update();
67}
68
69// All the int coordinates (rise, set) need to be converted from hours relative to midnight
70// into graph coordinates before calling this.
71void drawMoon(QPainter &p, int rise, int set, int fade, const QColor &color, int width, int height, double leftPadding)
72{
73 QBrush brush(color, Qt::Dense5Pattern);
74 QBrush dimmerBrush(color, Qt::Dense6Pattern);
75 QBrush dimmestBrush(color, Qt::Dense7Pattern);
76 QRectF r;
77 if (set < rise)
78 {
79 if (set + fade >= leftPadding && set - fade < leftPadding + width)
80 {
81 r = QRectF(leftPadding, 0.0, (set - fade) - leftPadding, height);
82 p.fillRect(r, brush);
83 r = QRectF(set - fade, 0.0, fade, height);
84 p.fillRect(r, dimmerBrush);
85 r = QRectF(set, 0.0, fade, height);
86 p.fillRect(r, dimmestBrush);
87 }
88 if (rise + fade >= leftPadding && rise - fade < leftPadding + width)
89 {
90 r = QRectF(rise - fade, 0.0, fade, height);
91 p.fillRect(r, dimmestBrush);
92 r = QRectF(rise, 0.0, fade, height);
93 p.fillRect(r, dimmerBrush);
94
95 // Since set < rise, we draw to the end of the box
96 r = QRectF(rise + fade, 0.0, width, height);
97 p.fillRect(r, brush);
98 }
99 }
100 else
101 {
102 r = QRectF(rise - fade, 0.0, fade, height);
103 p.fillRect(r, dimmestBrush);
104 r = QRectF(rise, 0.0, fade, height);
105 p.fillRect(r, dimmerBrush);
106 r = QRectF(rise + fade, 0.0, (set - rise) - 2 * fade, height);
107 p.fillRect(r, brush);
108 r = QRectF(set - fade, 0.0, fade, height);
109 p.fillRect(r, dimmerBrush);
110 r = QRectF(set, 0.0, fade, height);
111 p.fillRect(r, dimmestBrush);
112 }
113}
114
115// All the int coordinates (rise, set, da, du) need to be converted from hours relative to midnight
116// into graph coordinates before calling this.
117void drawSun(QPainter &p, int rise, int set, double minAlt, double maxAlt, int da, int du, bool noDawn,
118 const QColor &color, int width, int height)
119{
120 if (maxAlt < 0.0 && minAlt < -18.0)
121 {
122 // The sun never rise but the sky is not completely dark
123 QLinearGradient grad = QLinearGradient(QPointF(0.0, 0.0), QPointF(du, 0.0));
124
125 QColor gradStartColor = color;
126 gradStartColor.setAlpha((1 - (maxAlt / -18.0)) * 255);
127
128 grad.setColorAt(0, gradStartColor);
130 p.fillRect(QRectF(0.0, 0.0, du, height), grad);
131 grad.setStart(QPointF(width, 0.0));
132 grad.setFinalStop(QPointF(da, 0.0));
133 p.fillRect(QRectF(da, 0.0, width, height), grad);
134 }
135 else if (maxAlt < 0.0 && minAlt > -18.0)
136 {
137 // The sun never rise but the sky is NEVER completely dark
138 QLinearGradient grad = QLinearGradient(QPointF(0.0, 0.0), QPointF(width, 0.0));
139
140 QColor gradStartEndColor = color;
141 gradStartEndColor.setAlpha((1 - (maxAlt / -18.0)) * 255);
142 QColor gradMidColor = color;
143 gradMidColor.setAlpha((1 - (minAlt / -18.0)) * 255);
144
145 grad.setColorAt(0, gradStartEndColor);
146 grad.setColorAt(0.5, gradMidColor);
147 grad.setColorAt(1, gradStartEndColor);
148 p.fillRect(QRectF(0.0, 0.0, width, height), grad);
149 }
150 else if (noDawn)
151 {
152 // The sun sets and rises but the sky is never completely dark
153 p.fillRect(0, 0, set, int(0.5 * height), color);
154 p.fillRect(rise, 0, width, int(0.5 * height), color);
155
156 QLinearGradient grad = QLinearGradient(QPointF(set, 0.0), QPointF(rise, 0.0));
157
158 QColor gradMidColor = color;
159 gradMidColor.setAlpha((1 - (minAlt / -18.0)) * 255);
160
161 grad.setColorAt(0, color);
162 grad.setColorAt(0.5, gradMidColor);
163 grad.setColorAt(1, color);
164 p.fillRect(QRectF(set, 0.0, rise - set, height), grad);
165 }
166 else
167 {
168 if (set > 0)
169 p.fillRect(0, 0, set, height, color);
170 if (rise < width)
171 p.fillRect(rise, 0, width, height, color);
172
173 QLinearGradient grad = QLinearGradient(QPointF(set, 0.0), QPointF(du, 0.0));
174 grad.setColorAt(0, color);
176 p.fillRect(QRectF(set, 0.0, du - set, height), grad);
177
178 grad.setStart(QPointF(rise, 0.0));
179 grad.setFinalStop(QPointF(da, 0.0));
180 p.fillRect(QRectF(da, 0.0, rise - da, height), grad);
181 }
182}
183
184// This legacy code always plotted from noon to noon (24 hours starting at noon).
185// To generalize this code, we still compute noon-to-noon coords, but then convert
186// them to more general plot coordinates where the plot length isn't 24 hours and
187// the plot doesn't begin at noon.
188int AVTPlotWidget::convertCoords(double xCoord)
189{
190 const double plotWidth = pixRect().width();
191 const double pixelsPerHour = plotWidth / plotDuration;
192 const double newPosition = pixelsPerHour * ((xCoord * 24.0 / plotWidth) - noonOffset);
193 return newPosition;
194}
195
196namespace
197{
198double findYValue(const KPlotObject *po, double x)
199{
200 const auto points = po->points();
201 const int size = points.size();
202 if (size == 0) return 0;
203 if (x < points[0]->x()) return points[0]->y();
204 if (x > points[size - 1]->x()) return points[size - 1]->y();
205 for (int i = 0; i < size - 1; ++i)
206 {
207 const double ix = points[i]->x();
208 const double iy = points[i]->y();
209 const double nextIx = points[i + 1]->x();
210 const double nextIy = points[i + 1]->y();
211 if (x == ix) return iy;
212 if (x == nextIx) return nextIy;
213 if (x > ix && x < nextIx)
214 return iy + (nextIy - iy) * (x - ix) / (nextIx - ix);
215 }
216 return points[size - 1]->y();
217}
218} // namespace
219
221{
222 Q_UNUSED(e)
223
224 QPainter p;
225
226 p.begin(this);
230
231 setPixRect();
232 p.setClipRect(pixRect());
233 p.setClipping(true);
234
235 int pW = pixRect().width();
236 int pH = pixRect().height();
237
238 QColor SkyColor(0, 100, 200);
239 /*
240 if (Options::darkAppColors())
241 SkyColor = QColor(200, 0, 0); // use something red, visible through a red filter
242 */
243
244 // Draw gradient representing lunar interference in the sky
245 if (MoonIllum > 0.01) // do this only if Moon illumination is reasonable so it's important
246 {
247 double moonrise = pW * (0.5 + MoonRise);
248 double moonset = pW * (MoonSet - 0.5);
249 if (moonset < 0)
250 moonset += pW;
251 if (moonrise > pW)
252 moonrise -= pW;
253 moonrise = convertCoords(moonrise);
254 moonset = convertCoords(moonset);
255
256 if (moonrise > pW)
257 {
258 const double pixelsPerHour = pW * 1.0 / plotDuration;
259 moonrise -= 24 * pixelsPerHour;
260 }
261 const int mooncolor = int(10 + MoonIllum * 130);
262 const QColor MoonColor(mooncolor, mooncolor, mooncolor);
263 int fadewidth =
264 pW *
265 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.
266
267 drawMoon(p, int(moonrise), int(moonset), fadewidth, MoonColor, pW, pH, leftPadding());
268
269 }
270 //draw daytime sky if the Sun rises for the current date/location
271 if (SunMaxAlt > -18.0)
272 {
273 // Initially compute centered on midnight, so modulate dawn/dusk by 0.5
274 // Then convert to general coordinates.
275 int rise = convertCoords(pW * (0.5 + SunRise));
276 int set = convertCoords(pW * (SunSet - 0.5));
277 int dawn = convertCoords(pW * (0.5 + Dawn));
278 double dusk = int(pW * (Dusk - 0.5));
279 if (dusk < 0) dusk = pW + dusk;
280 dusk = convertCoords(dusk);
281
282 if (SunMinAlt > 0.0)
283 {
284 // The sun never set and the sky is always blue
285 p.fillRect(rect(), SkyColor);
286 }
287 else drawSun(p, rise, set, SunMinAlt, SunMaxAlt, dawn, int(dusk), Dawn < 0.0, SkyColor, pW, pH);
288 }
289
290 //draw ground
291 if (altitudeAxisMin < 0)
292 {
293 const int groundYValue = pH + altitudeAxisMin * pH / (altitudeAxisMax - altitudeAxisMin);
294 p.fillRect(0, groundYValue, pW, groundYValue,
295 KStarsData::Instance()->colorScheme()->colorNamed(
296 "HorzColor")); // asimha changed to use color from scheme. Formerly was QColor( "#002200" )
297 }
298
299 foreach (KPlotObject *po, plotObjects())
300 {
301 po->draw(&p, this);
302 }
303
304 p.setClipping(false);
305 drawAxes(&p);
306
307 //Add vertical line indicating "now"
308 QFont smallFont = p.font();
309 smallFont.setPointSize(smallFont.pointSize()); // wat?
310 if (geo)
311 {
313 .time(); // convert the current system clock time to the TZ corresponding to geo
314 double x = 12.0 + t.hour() + t.minute() / 60.0 + t.second() / 3600.0;
315 while (x > 24.0)
316 x -= 24.0;
317 double ix = x * pW / 24.0; //convert to screen pixel coords
318 ix = convertCoords(ix);
319 p.setPen(QPen(QBrush("white"), 2.0, Qt::DotLine));
320 p.drawLine(int(ix), 0, ix, pH);
321
322 //Label this vertical line with the current time
323 p.save();
324 p.setFont(smallFont);
325 p.translate(int(ix) + 10, pH - 20);
326 p.rotate(-90);
327 p.drawText(
328 0, 0,
329 QLocale().toString(t, QLocale::ShortFormat)); // short format necessary to avoid false time-zone labeling
330 p.restore();
331 }
332
333 //Draw crosshairs at clicked position
334 if (MousePoint.x() > 0)
335 {
336 p.setPen(QPen(QBrush("gold"), 1.0, Qt::SolidLine));
337 p.drawLine(QLineF(MousePoint.x() + 0.5, 0.5, MousePoint.x() + 0.5, pixRect().height() - 0.5));
338
339 //Label each crosshair line (time and altitude)
340 p.setFont(smallFont);
341
342 double h = (MousePoint.x() * plotDuration) / pW - (12.0 - noonOffset);
343 double a = 0;
344 if (currentLine >= 0 && currentLine < plotObjects().size())
345 a = findYValue(plotObjects()[currentLine], h);
346 p.drawText(15, 15, QString::number(a, 'f', 1) + QChar(176));
347
348 if (h < 0.0)
349 h += 24.0;
350 QTime t = QTime(int(h), int(60. * (h - int(h))));
351 p.save();
352 p.translate(MousePoint.x() + 10, pH - 20);
353 p.rotate(-90);
354 p.drawText(
355 0, 0,
356 QLocale().toString(t, QLocale::ShortFormat)); // short format necessary to avoid false time-zone labeling
357 p.restore();
358 }
359
360 p.end();
361}
362
364{
365 if (index >= 0 && index < plotObjects().size())
366 currentLine = index;
367}
368
369void AVTPlotWidget::setDawnDuskTimes(double da, double du)
370{
371 Dawn = da;
372 Dusk = du;
373 update(); // fixme: should we always be calling update? It's probably cheap enough that we can.
374}
375
376void AVTPlotWidget::setMinMaxSunAlt(double min, double max)
377{
378 SunMinAlt = min;
379 SunMaxAlt = max;
380 update();
381}
382
383void AVTPlotWidget::setSunRiseSetTimes(double sr, double ss)
384{
385 SunRise = sr;
386 SunSet = ss;
387 update();
388}
389
390void AVTPlotWidget::setMoonRiseSetTimes(double mr, double ms)
391{
392 MoonRise = mr;
393 MoonSet = ms;
394 update();
395}
396
398{
399 MoonIllum = mi;
400 update();
401}
402
403void AVTPlotWidget::setPlotExtent(double offset, double duration)
404{
405 noonOffset = offset;
406 plotDuration = duration;
407}
408
409void AVTPlotWidget::disableAxis(KPlotWidget::Axis axisToDisable)
410{
411 axis(axisToDisable)->setVisible(false);
412}
413
414void AVTPlotWidget::plot(const GeoLocation *geo, KSAlmanac *ksal, const QVector<double> &times,
415 const QVector<double> &alts, int lineWidth, Qt::GlobalColor color)
416{
418 QPen pen;
419 pen.setWidth(lineWidth);
420 pen.setColor(color);
421 po->setLinePen(pen);
422
423 currentLine = 0;
424 setLimits(times[0], times.last(), altitudeAxisMin, altitudeAxisMax);
425 setSecondaryLimits(times[0], times.last(), altitudeAxisMin, altitudeAxisMax);
429 setGeoLocation(geo);
430
431 setSunRiseSetTimes(ksal->getSunRise(), ksal->getSunSet());
432 setDawnDuskTimes(ksal->getDawnAstronomicalTwilight(), ksal->getDuskAstronomicalTwilight());
433 setMinMaxSunAlt(ksal->getSunMinAlt(), ksal->getSunMaxAlt());
434 setMoonRiseSetTimes(ksal->getMoonRise(), ksal->getMoonSet());
435 setMoonIllum(ksal->getMoonIllum());
436
437 const double noonOffset = times[0] - -12;
438 const double plotDuration = times.last() - times[0];
439 setPlotExtent(noonOffset, plotDuration);
441
442 for (int i = 0; i < times.size(); ++i)
443 po->addPoint(times[i], alts[i]);
444 addPlotObject(po);
445
446 update();
447}
448
449void AVTPlotWidget::plotOverlay(const QVector<double> &times, const QVector<double> &alts, int lineWidth,
450 Qt::GlobalColor color)
451{
453 QPen pen;
454 pen.setWidth(lineWidth);
455 pen.setColor(color);
456 po->setLinePen(pen);
457
458 for (int i = 0; i < times.size(); ++i)
459 po->addPoint(times[i], alts[i]);
460 addPlotObject(po);
461
462 update();
463}
464
465void AVTPlotWidget::setAltitudeAxis(double min, double max)
466{
467 if (min < max)
468 {
469 altitudeAxisMin = min;
470 altitudeAxisMax = max;
471 }
472}
473
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....
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 plot(const GeoLocation *geo, KSAlmanac *ksal, const QVector< double > &times, const QVector< double > &alts, int lineWidth=2, Qt::GlobalColor color=Qt::white)
Higher level method to plot.
void setCurrentLine(int lineIndex)
Sets the plot index which whose altitude is displayed when clicking on the graph.
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()
void setAlpha(int alpha)
int pointSize() const const
void setPointSize(int pointSize)
void setColorAt(qreal position, const QColor &color)
void setFinalStop(const QPointF &stop)
void setStart(const QPointF &start)
T & last()
qsizetype size() 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
QString number(double n, char format, int precision)
Dense5Pattern
transparent
int hour() const const
int minute() const const
int second() const const
void update()
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Mar 7 2025 11:55:45 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.