/* * 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 #include // 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(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(e)->pos(); Qt::MouseButton button = static_cast(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; }