OSCAR-code/oscar/Graphs/gSegmentChart.cpp
2024-01-31 19:14:19 -05:00

322 lines
9.3 KiB
C++

/* gSegmentChart Implementation
*
* Copyright (c) 2011-2018 Mark Watkins
* Copyright (c) 2019-2024 The OSCAR Team
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of the source code
* for more details. */
#include <cmath>
#include "gSegmentChart.h"
gSegmentChart::gSegmentChart(GraphSegmentType type, QColor gradient_color, QColor outline_color)
: Layer(NoChannel), m_graph_type(type), m_gradient_color(gradient_color),
m_outline_color(outline_color)
{
m_empty = true;
}
gSegmentChart::~gSegmentChart()
{
}
void gSegmentChart::AddSlice(ChannelID code, QColor color, QString name)
{
m_codes.push_back(code);
m_values.push_back(0);
m_colors.push_back(color);
m_names.push_back(name);
m_total = 0;
}
void gSegmentChart::SetDay(Day *d)
{
Layer::SetDay(d);
m_total = 0;
if (!m_day) { return; }
for (int c = 0; c < m_codes.size(); c++) {
auto & mval = m_values[c];
auto & mcode = m_codes[c];
mval = 0;
for (const auto & sess : m_day->sessions) {
if (sess->enabled() && sess->m_cnt.contains(mcode)) {
EventDataType cnt = sess->count(mcode);
mval += cnt;
m_total += cnt;
}
}
}
m_empty = true;
for (const auto & mc : m_codes) {
if (m_day->count(mc) > 0) {
m_empty = false;
break;
}
}
}
bool gSegmentChart::isEmpty()
{
return m_empty;
}
void gSegmentChart::paint(QPainter &painter, gGraph &w, const QRegion &region)
{
QRect rect = region.boundingRect();
int height = qMin(rect.height(), rect.width());
int width = qMin(rect.height(), rect.width());
int left = rect.left();
int top = rect.top();
if (rect.width() > rect.height()) {
left = rect.left() + (rect.width() - rect.height());
}
//left --;
if (!m_visible) { return; }
if (!m_day) { return; }
int start_px = left;
int start_py = top;
width--;
float diameter = MIN(width, height);
diameter -= 8;
float radius = diameter / 2.0;
float xmult = float(width) / float(m_total);
float ymult = float(height) / float(m_total);
float xp = left;
int xoffset = width / 2;
int yoffset = height / 2;
if (m_total == 0) {
QColor col = Qt::green;
QString a = ":-)";
int x, y;
GetTextExtent(a, x, y, bigfont);
w.renderText(a, start_px + xoffset - x / 2, (start_py + yoffset + y / 2), 0, col, bigfont);
return;
}
EventDataType data;
unsigned size = m_values.size();
float line_step = float(width) / float(size - 1);
bool line_first = true;
int line_last;
float sum = -90.0;
painter.setFont(*defaultfont);
for (unsigned m = 0; m < size; m++) {
data = m_values[m];
if (data == 0) { continue; }
/////////////////////////////////////////////////////////////////////////////////////
// Pie Chart
/////////////////////////////////////////////////////////////////////////////////////
if (m_graph_type == GST_Pie) {
QColor col = schema::channel[m_codes[m]].defaultColor();
if (w.printing() && AppSetting->monochromePrinting()) {
col = Qt::white;
}
// length of this segment in degrees
float len = 360.0 / m_total * data;
// Setup the shiny radial gradient
painter.setRenderHint(QPainter::Antialiasing);
QRect pierect(start_px+1, start_py+1, width-2, height-2);
painter.setPen(QPen(col, 0));
QRadialGradient gradient(pierect.center(), pierect.width()/2, pierect.center());
gradient.setColorAt(0, Qt::white);
gradient.setColorAt(1, col);
// draw filled pie
painter.setBrush(gradient);
painter.setBackgroundMode(Qt::OpaqueMode);
if (m_total == data) {
painter.drawEllipse(pierect);
} else {
painter.drawPie(pierect, -sum * 16.0, -len * 16.0);
}
// draw outline
painter.setBackgroundMode(Qt::TransparentMode);
painter.setBrush(QBrush(col,Qt::NoBrush));
painter.setPen(QPen(QColor(Qt::black),1.5));
if (m_total == data) {
painter.drawEllipse(pierect);
} else {
painter.drawPie(pierect, -sum * 16.0, -len * 16.0);
}
// Draw text labels if they fit
if (len > 20) {
float angle = (sum+90.0) + len / 2.0;
double tpx = (pierect.x() + pierect.width()/2) + (sin((180 - angle) * (M_PI / 180.0)) * (radius / 1.65));
double tpy = (pierect.y() + pierect.height()/2) + (cos((180 - angle) * (M_PI / 180.0)) * (radius / 1.65));
QString txt = schema::channel[m_codes[m]].label(); //QString::number(floor(100.0/m_total*data),'f',0)+"%";
int x, y;
GetTextExtent(txt, x, y);
// antialiasing looks like crap here..
painter.setPen(QColor(Qt::black));
painter.drawText(tpx - (x / 2.0), tpy+3, txt);
}
sum += len;
} else if (m_graph_type == GST_CandleStick) {
/////////////////////////////////////////////////////////////////////////////////////
// CandleStick Chart
/////////////////////////////////////////////////////////////////////////////////////
QColor &col = m_colors[m % m_colors.size()];
float bw = xmult * float(data);
QLinearGradient linearGrad(QPointF(0, 0), QPointF(bw, 0));
linearGrad.setColorAt(0, col);
linearGrad.setColorAt(1, Qt::white);
painter.fillRect(xp, start_py, bw, height, QBrush(linearGrad));
painter.setPen(m_outline_color);
painter.drawLine(xp, start_py, xp + bw, start_py);
painter.drawLine(xp + bw, start_py + height, xp, start_py + height);
if (!m_names[m].isEmpty()) {
int px, py;
GetTextExtent(m_names[m], px, py);
if (px + 5 < bw) {
w.renderText(m_names[m], (xp + bw / 2) - (px / 2), top + ((height / 2) - (py / 2)), 0, Qt::black);
}
}
xp += bw;
/////////////////////////////////////////////////////////////////////////////////////
// Line Chart
/////////////////////////////////////////////////////////////////////////////////////
} else if (m_graph_type == GST_Line) {
QColor col = Qt::black; //m_colors[m % m_colors.size()];
painter.setPen(col);
float h = (top + height) - (float(data) * ymult);
if (line_first) {
line_first = false;
} else {
painter.drawLine(xp, line_last, xp + line_step, h);
xp += line_step;
}
line_last = h;
}
}
}
gTAPGraph::gTAPGraph(ChannelID code, GraphSegmentType gt, QColor gradient_color,
QColor outline_color)
: gSegmentChart(gt, gradient_color, outline_color), m_code(code)
{
m_colors.push_back(Qt::red);
m_colors.push_back(Qt::green);
}
gTAPGraph::~gTAPGraph()
{
}
void gTAPGraph::SetDay(Day *d)
{
Layer::SetDay(d);
m_total = 0;
if (!m_day) { return; }
QMap<EventStoreType, qint64> tap;
EventStoreType data = 0, lastval = 0;
qint64 time = 0, lasttime = 0;
//bool first;
bool rfirst = true;
//bool changed;
EventDataType gain = 1, offset = 0;
QHash<ChannelID, QVector<EventList *> >::iterator ei;
for (const auto & sess : m_day->sessions) {
if (!sess->enabled()) { continue; }
ei = sess->eventlist.find(m_code);
if (ei == sess->eventlist.end()) { continue; }
for (const auto & el : ei.value()) {
lasttime = el->time(0);
lastval = el->raw(0);
if (rfirst) {
gain = el->gain();
offset = el->offset();
rfirst = false;
}
for (quint32 i=1, end=el->count(); i < end; i++) {
data = el->raw(i);
time = el->time(i);
if (lastval != data) {
qint64 v = (time - lasttime);
if (tap.find(lastval) != tap.end()) {
tap[lastval] += v;
} else {
tap[lastval] = v;
}
//changed=true;
lasttime = time;
lastval = data;
}
}
if (time != lasttime) {
qint64 v = (time - lasttime);
if (tap.find(lastval) != tap.end()) {
tap[data] += v;
} else {
tap[data] = v;
}
}
}
}
m_values.clear();
m_names.clear();
m_total = 0;
EventDataType val;
for (auto i=tap.begin(), end=tap.end(); i != end; i++) {
val = float(i.key()) * gain + offset;
m_values.push_back(i.value() / 1000L);
m_total += i.value() / 1000L;
m_names.push_back(QString::number(val, 'f', 2));
}
m_empty = m_values.size() == 0;
}