mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-15 00:49:55 +00:00
... draw Level 1 text at the end to have full contrast (not overlapped by level 2 brush) ... dynamically choose font size for Level 2 texts from 4 options (based on available space in rectangle) - easier readable on big screen - still usable on small screen
244 lines
7.4 KiB
C++
244 lines
7.4 KiB
C++
/*
|
|
* Copyright (c) 2010 Mark Liversedge (liversedge@gmail.com)
|
|
*
|
|
* 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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "Context.h"
|
|
#include "Athlete.h"
|
|
#include "TreeMapPlot.h"
|
|
#include "LTMTool.h"
|
|
#include "TreeMapWindow.h"
|
|
#include "RideCache.h"
|
|
#include "RideMetric.h"
|
|
#include "Settings.h"
|
|
#include "Colors.h"
|
|
|
|
#include <QSettings>
|
|
#include <cmath> // for isinf() isnan()
|
|
|
|
// Treemap sorter - reversed to do descending
|
|
bool TreeMapLessThan(const TreeMap *a, const TreeMap *b) {
|
|
return (a->value) > (b->value);
|
|
}
|
|
|
|
TreeMapPlot::TreeMapPlot(TreeMapWindow *parent, Context *context)
|
|
: QWidget (parent), parent(parent), context(context)
|
|
{
|
|
settings = NULL;
|
|
|
|
root = new TreeMap;
|
|
setMouseTracking(true);
|
|
installEventFilter(this);
|
|
|
|
// no margins
|
|
setContentsMargins(0,0,0,0);
|
|
|
|
configChanged(CONFIG_APPEARANCE); // set basic colors
|
|
connect(context, SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32)));
|
|
}
|
|
|
|
TreeMapPlot::~TreeMapPlot()
|
|
{
|
|
}
|
|
|
|
void
|
|
TreeMapPlot::configChanged(qint32) { }
|
|
|
|
void
|
|
TreeMapPlot::setData(TMSettings *settings)
|
|
{
|
|
root->clear();
|
|
|
|
foreach (RideItem *item, context->athlete->rideCache->rides()) {
|
|
|
|
// don't plot if filtered
|
|
if (!settings->specification.pass(item)) continue;
|
|
|
|
double value = item->getForSymbol(settings->symbol);
|
|
QString text1 = item->getText(settings->field1, tr("(unknown)"));
|
|
QString text2 = item->getText(settings->field2, tr("(unknown)"));
|
|
if (text1 == "") text1 = tr("(unknown)");
|
|
if (text2 == "") text2 = tr("(unknown)");
|
|
|
|
TreeMap *first = root->insert(text1, 0.0);
|
|
first->insert(text2, value);
|
|
}
|
|
|
|
// layout and paint
|
|
resizeEvent(NULL);
|
|
repaint();
|
|
}
|
|
|
|
void
|
|
TreeMapPlot::resizeEvent(QResizeEvent *)
|
|
{
|
|
// layout the map
|
|
if (root) root->layout(QRect(9,9,geometry().width()-18, geometry().height()-18));
|
|
}
|
|
|
|
|
|
void
|
|
TreeMapPlot::paintEvent(QPaintEvent *)
|
|
{
|
|
if (!root) return;
|
|
|
|
// labels
|
|
QFont font;
|
|
|
|
// Init paint settings
|
|
QPainter painter(this);
|
|
QColor color = GColor(CTRENDPLOTBACKGROUND);
|
|
QPen pen(color);
|
|
pen.setWidth(10); // root
|
|
QBrush brush(color);
|
|
painter.setBrush(brush);
|
|
painter.setPen(pen);
|
|
|
|
// draw border and background
|
|
painter.drawRect(root->rect.x()+4,
|
|
root->rect.y()+4,
|
|
root->rect.width()-8,
|
|
root->rect.height()-8);
|
|
|
|
// first level - rectangles, but not text yet
|
|
pen.setWidth(5);
|
|
pen.setColor(color);
|
|
painter.setPen(pen);
|
|
|
|
int n=1;
|
|
QColor cHSV, cRGB;
|
|
double factor = double(1) / double(root->children.count());
|
|
foreach (TreeMap *first, root->children) {
|
|
|
|
cHSV.setHsv((double)255 / (factor*n++), 255, 150);
|
|
cRGB = cHSV.convertTo(QColor::Rgb);
|
|
brush.setColor(cRGB);
|
|
painter.setBrush(brush);
|
|
painter.drawRect(first->rect);
|
|
}
|
|
|
|
// second level - overlay rectangles - with text
|
|
QPen textPen(Qt::white);
|
|
font.setPointSize(8);
|
|
painter.setFont(font);
|
|
|
|
// overlay colors
|
|
color = Qt::darkGray;
|
|
color.setAlpha(127);
|
|
brush.setColor(color);
|
|
QColor hcolor(Qt::lightGray);
|
|
hcolor.setAlpha(127);
|
|
QBrush hbrush(hcolor);
|
|
QRect textRect;
|
|
QRect demandedTextRect;
|
|
foreach (TreeMap *first, root->children) {
|
|
foreach (TreeMap *second, first->children) {
|
|
if (second == highlight) painter.setBrush(hbrush);
|
|
else painter.setBrush(brush);
|
|
|
|
painter.setPen(Qt::NoPen);
|
|
painter.drawRect(second->rect.x()+2,
|
|
second->rect.y()+2,
|
|
second->rect.width()-4,
|
|
second->rect.height()-4);
|
|
|
|
textRect.setRect(second->rect.x()+2, second->rect.y()+2,second->rect.width()-4, second->rect.height()-4 );
|
|
// determine font size based on size of window and length of text / try to apply 3 different font sizes - then fall back to "8"
|
|
font.setPointSize(14);
|
|
painter.setFont(font);
|
|
painter.setPen(textPen);
|
|
demandedTextRect = painter.boundingRect(textRect, Qt::AlignTop|Qt::AlignLeft, second->name);
|
|
if (!textRect.contains(demandedTextRect)) {
|
|
font.setPointSize(12);
|
|
painter.setFont(font);
|
|
demandedTextRect = painter.boundingRect(textRect, Qt::AlignTop|Qt::AlignLeft, second->name);
|
|
if (!textRect.contains(demandedTextRect)) {
|
|
font.setPointSize(10);
|
|
painter.setFont(font);
|
|
demandedTextRect = painter.boundingRect(textRect, Qt::AlignTop|Qt::AlignLeft, second->name);
|
|
if (!textRect.contains(demandedTextRect)) {
|
|
// fall back to use smallest useful font / even if text may be clipped
|
|
font.setPointSize(8);
|
|
painter.setFont(font);
|
|
}
|
|
}
|
|
}
|
|
painter.drawText(textRect, Qt::AlignTop|Qt::AlignLeft, second->name);
|
|
}
|
|
}
|
|
|
|
// paint the text for level 1 now - to not overlap it with gray/alpha coming from level 2 drawing overlay
|
|
textPen.setColor(Qt::black);
|
|
font.setPointSize(18);
|
|
painter.setFont(font);
|
|
painter.setPen(textPen);
|
|
foreach (TreeMap *first, root->children) {
|
|
painter.drawText(first->rect,
|
|
Qt::AlignVCenter|Qt::AlignHCenter,
|
|
first->name);
|
|
}
|
|
}
|
|
|
|
bool
|
|
TreeMapPlot::eventFilter(QObject *, QEvent *e)
|
|
{
|
|
|
|
if (e->type() == QEvent::MouseMove) {
|
|
QPoint pos = static_cast<QMouseEvent*>(e)->pos();
|
|
TreeMap *underMouse = NULL;
|
|
|
|
// look at the bottom rung.
|
|
foreach (TreeMap *first, root->children)
|
|
if ((underMouse = first->findAt(pos)) != NULL)
|
|
break;
|
|
|
|
// if this one isn't the one that is
|
|
// currently highlighted repaint to
|
|
// highlight it!
|
|
if (underMouse && highlight != underMouse) {
|
|
highlight = underMouse;
|
|
repaint();
|
|
return true;
|
|
}
|
|
|
|
} else if (e->type() == QEvent::Leave) {
|
|
highlight = NULL;
|
|
repaint();
|
|
return true;
|
|
|
|
} else if (e->type() == QEvent::MouseButtonPress) {
|
|
|
|
QPoint pos = static_cast<QMouseEvent*>(e)->pos();
|
|
Qt::MouseButton button = static_cast<QMouseEvent*>(e)->button();
|
|
|
|
if (button == Qt::LeftButton) {
|
|
TreeMap *underMouse = NULL;
|
|
|
|
// look at the bottom rung.
|
|
foreach (TreeMap *first, root->children)
|
|
if ((underMouse = first->findAt(pos)) != NULL)
|
|
break;
|
|
|
|
// got one?
|
|
if (underMouse) {
|
|
emit clicked(underMouse->parent->name, underMouse->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|