Files
GoldenCheetah/src/TreeMapPlot.h
Mark Liversedge 255f69c1ad ConfigChanged Part 1 of 3
.. introduce concept of configChanged(what) to
   pass details of what config has been changed

.. fixed zones changes to re-read after write
   to correct the save twice to get changes to
   zones bug.

.. next parts need to spot changes (part 2) and
   then action appropriately (part 3)
2014-12-29 13:41:50 +00:00

270 lines
8.2 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 "TreeMapWindow.h"
#include "Context.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 = "tr((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 *, Context *context);
~TreeMapPlot();
void setData(TMSettings *);
public slots:
void configChanged(qint32);
bool eventFilter(QObject *object, QEvent *e);
signals:
void clicked(QString, QString);
protected:
TreeMapWindow *parent;
virtual void paintEvent(QPaintEvent *);
virtual void resizeEvent(QResizeEvent *);
private:
Context *context;
TMSettings *settings;
TreeMap *root; // the tree map data structure
TreeMap *highlight; // currently needs to be highlighted
};
#endif // _GC_TreeMapPlot_h