mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 08:38:45 +00:00
The recent fix for date range selection was not applied to the TreeMap plot and caused a SEGV on restart (when placed on home window). This fixes that. It also fixes a minor cosmetic related to QFrame margins.
271 lines
8.3 KiB
C++
271 lines
8.3 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
|
|
*/
|
|
|
|
#ifndef _GC_TreeMapPlot_h
|
|
#define _GC_TreeMapPlot_h 1
|
|
#include "GoldenCheetah.h"
|
|
|
|
#include <QtGui>
|
|
|
|
#include "LTMTool.h"
|
|
#include "LTMSettings.h"
|
|
#include "MetricAggregator.h"
|
|
|
|
#include "MainWindow.h"
|
|
|
|
// for sorting
|
|
class TreeMap;
|
|
bool TreeMapLessThan(const TreeMap *, const TreeMap *);
|
|
|
|
class TreeMap
|
|
{
|
|
public:
|
|
TreeMap(TreeMap *parent = NULL, QString name = "", double value = 0.0) :
|
|
parent(parent), name(name), value(value) {}
|
|
|
|
// insert into children, if not there then add
|
|
TreeMap *insert(QString name, double value = 0.0) {
|
|
|
|
// accumulate and update my parent too
|
|
this->value += value;
|
|
for (TreeMap *p = parent; p != NULL; p = p->parent) p->value += value;
|
|
|
|
foreach (TreeMap *x, children) {
|
|
if (x->name == name) {
|
|
x->value += value;
|
|
return x;
|
|
}
|
|
}
|
|
|
|
TreeMap *newone = new TreeMap(this, name, value);
|
|
children.append(newone);
|
|
return newone;
|
|
}
|
|
|
|
// find the treemap that under cursor
|
|
TreeMap *findAt(QPoint pos) {
|
|
|
|
// find the child that is under pos!
|
|
foreach (TreeMap *child, children)
|
|
if (child->rect.contains(pos)) return child;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// wipe out value and all children
|
|
void clear() {
|
|
foreach (TreeMap *x, children) {
|
|
x->clear();
|
|
delete x;
|
|
}
|
|
children.clear();
|
|
name = "(unknown)";
|
|
value = 0.00;
|
|
}
|
|
|
|
void sort() {
|
|
// sort the children in descending order
|
|
qSort(children.begin(), children.end(), TreeMapLessThan);
|
|
foreach (TreeMap *child, children) child->sort();
|
|
}
|
|
|
|
// The main user entry point - call this on the root
|
|
// node and it will layout all the children in the
|
|
// rectangle supplied. The children's rectangles can
|
|
// then be passed directly to painter.drawRect etc
|
|
void layout(QRect rect) {
|
|
|
|
// I'll take that
|
|
this->rect = rect;
|
|
|
|
// need to sort in descending order
|
|
sort();
|
|
|
|
// Use the squarified algorithm outlined
|
|
// by Mark Bruls, Kees Huizing, and Jarke J. van Wijk
|
|
// in "http://citeseerx.ist.psu.edu/viewdoc/
|
|
// download?doi=10.1.1.36.6685&rep=rep1&type=pdf"
|
|
// ... will recurse
|
|
squarifyLayout(children, rect);
|
|
}
|
|
|
|
// we use the well-known squarify layout
|
|
// to maintain aspect ratios as near as possible
|
|
// to a square. It in turn uses the original
|
|
// slice layout to split rows or columns
|
|
// we do ourselves and then our children
|
|
// and do it recursively, despite the fact
|
|
// that the Treemap is currently on 2 deep
|
|
// that may change in the future
|
|
void squarifyLayout(QList<TreeMap*> items, QRect bounds) {
|
|
rect = bounds;
|
|
layout(items, 0, items.count()-1, bounds);
|
|
foreach (TreeMap *item, items)
|
|
item->squarifyLayout(item->children, item->rect);
|
|
}
|
|
|
|
// this iterates over sections of the list
|
|
// and calls itself to process mid/left sections
|
|
void layout(QList<TreeMap*> items, int start, int end, QRect bounds) {
|
|
|
|
if (start > end) return;
|
|
|
|
if (end-start < 2) {
|
|
slicelayout(items, start, end, bounds);
|
|
return;
|
|
}
|
|
|
|
// setup, using smaller vars for more
|
|
// concise / readable code (?)
|
|
double x=bounds.x(),
|
|
y=bounds.y(),
|
|
w=bounds.width(),
|
|
h=bounds.height();
|
|
|
|
double total=0;
|
|
for(int i=start; i<=end; i++) total += items[i]->value;
|
|
int mid=start;
|
|
double a=items[start]->value/total;
|
|
double b=a;
|
|
|
|
if (w<h) {
|
|
|
|
// height / width
|
|
while (mid<=end) {
|
|
|
|
double aspect=normAspect(h,w,a,b);
|
|
double q=items[mid]->value/total;
|
|
|
|
if (normAspect(h,w,a,b+q)>aspect) break;
|
|
|
|
mid++;
|
|
b+=q;
|
|
}
|
|
|
|
slicelayout(items,start,mid, QRect(x,y,w,h*b));
|
|
layout(items,mid+1,end, QRect(x,y+h*b,w,h*(1-b)));
|
|
|
|
} else {
|
|
|
|
// width/height
|
|
while (mid<=end) {
|
|
|
|
double aspect=normAspect(w,h,a,b);
|
|
double q=items[mid]->value/total;
|
|
|
|
if (normAspect(w,h,a,b+q)>aspect) break;
|
|
|
|
mid++;
|
|
b+=q;
|
|
}
|
|
|
|
slicelayout(items,start,mid, QRect(x,y,w*b,h));
|
|
layout(items,mid+1,end, QRect(x+w*b,y,w*(1-b),h));
|
|
}
|
|
}
|
|
|
|
double aspect(double _big, double _small, double a, double b) {
|
|
return (_big*b)/(_small*a/b);
|
|
}
|
|
|
|
|
|
double normAspect(double _big, double _small, double a, double b) {
|
|
double x=aspect(_big,_small,a,b);
|
|
if (x<1) return 1/x;
|
|
return x;
|
|
}
|
|
|
|
// slice the items into strips either horizontally
|
|
// or vertically along whichever has the longest side
|
|
void slicelayout(QList<TreeMap*> items, int start, int end, QRect bounds) {
|
|
|
|
// setup
|
|
double total=0, accumulator=0; // total value of items and running total
|
|
for(int i= start; i<= end && i<items.count(); i++) total += items[i]->value;
|
|
Qt::Orientation orientation = (bounds.width() > bounds.height()) ? Qt::Horizontal : Qt::Vertical;
|
|
|
|
// slice em up!
|
|
for (int i=start; i<=end && i<items.count(); i++) {
|
|
|
|
double factor=items[i]->value/total;
|
|
if (orientation == Qt::Vertical) {
|
|
// slice em into a vertical stack
|
|
items[i]->rect.setX(bounds.x());
|
|
items[i]->rect.setWidth(bounds.width());
|
|
items[i]->rect.setY(bounds.y()+bounds.height()*(1-accumulator-factor));
|
|
items[i]->rect.setHeight(bounds.height()*factor);
|
|
|
|
} else {
|
|
|
|
// slice em into a horizontal stack
|
|
items[i]->rect.setX(bounds.x()+bounds.width()*(1-accumulator-factor));
|
|
items[i]->rect.setWidth(bounds.width()*factor);
|
|
items[i]->rect.setY(bounds.y());
|
|
items[i]->rect.setHeight(bounds.height());
|
|
}
|
|
accumulator += factor;
|
|
}
|
|
}
|
|
|
|
|
|
// data
|
|
TreeMap *parent;
|
|
QString name;
|
|
double value;
|
|
QList<TreeMap*> children;
|
|
|
|
// geometry
|
|
QRect rect;
|
|
};
|
|
|
|
|
|
class TreeMapPlot : public QWidget
|
|
{
|
|
Q_OBJECT
|
|
G_OBJECT
|
|
|
|
|
|
public:
|
|
TreeMapPlot(TreeMapWindow *, MainWindow *main, QDir home);
|
|
~TreeMapPlot();
|
|
void setData(LTMSettings *);
|
|
|
|
public slots:
|
|
void configUpdate();
|
|
bool eventFilter(QObject *object, QEvent *e);
|
|
|
|
protected:
|
|
|
|
TreeMapWindow *parent;
|
|
virtual void paintEvent(QPaintEvent *);
|
|
virtual void resizeEvent(QResizeEvent *);
|
|
|
|
private:
|
|
MainWindow *main;
|
|
QDir home;
|
|
bool useMetricUnits;
|
|
struct LTMSettings *settings;
|
|
|
|
TreeMap *root; // the tree map data structure
|
|
TreeMap *highlight; // currently needs to be highlighted
|
|
};
|
|
|
|
|
|
#endif // _GC_TreeMapPlot_h
|
|
|