mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 16:18:42 +00:00
.. deprecate 3D chart as QwtPlot3d has very questionable support and the chart is not widely used.
1475 lines
53 KiB
C++
1475 lines
53 KiB
C++
/*
|
|
* Copyright (c) 2009 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 "ModelPlot.h"
|
|
#include "ModelWindow.h"
|
|
#include "MainWindow.h"
|
|
#include "IntervalItem.h"
|
|
#include "RideItem.h"
|
|
#include "Context.h"
|
|
#include "Context.h"
|
|
#include "Athlete.h"
|
|
#include "Settings.h"
|
|
#include "Zones.h"
|
|
#include "Colors.h"
|
|
#include "RideFile.h"
|
|
#include "Units.h" // for MILES_PER_KM
|
|
|
|
#include <QWidget>
|
|
|
|
using namespace Qwt3D; // namespace ref is only visible in this file (is not in any headers)
|
|
|
|
/*----------------------------------------------------------------------
|
|
* MODEL DATA PROVIDER
|
|
*
|
|
* This function is called by the surface plot to get z values for an
|
|
* xy pair. The data is set by a call from ModelPlot (from ModelWindow)
|
|
* when a user either selects a new ridefile or changes the x/y/z/color
|
|
* comboboxes.
|
|
*
|
|
* setData - sets the model data from a ridefile using the axis passed
|
|
* and returns the mesh values as an xy to set the surface
|
|
* plot. Will clear existing data first.
|
|
*
|
|
* operator () - called by the surface plot to set z for a given x and y
|
|
*
|
|
*----------------------------------------------------------------------*/
|
|
|
|
// util function to create an x/y key for QHash, effective and quick enough
|
|
static QString xystring(double x, double y) { return QString("%1:%2").arg((int)x).arg((int)y); }
|
|
|
|
// returns the color for an xyz point
|
|
class ModelDataColor : public Color
|
|
{
|
|
// return RGBA color for x,y,z
|
|
|
|
// qwtplot3d api changes between 0.2.x and 0.3.x
|
|
#if QWT3D_MINOR_VERSION > 2
|
|
Qwt3D::RGBA rgba (double x, double y, double) const
|
|
#else
|
|
Qwt3D::RGBA operator () (double x, double y, double) const
|
|
#endif
|
|
{
|
|
QColor cHSV, cRGB;
|
|
double val = color.value(xystring(x,y), 0.0);
|
|
RGBA colour;
|
|
if (!val) {
|
|
return RGBA(255,255,255,0); // see thru
|
|
} if (iszones == true) { // zone 0 is set to zone 1 to distinguish from no value
|
|
cHSV = zonecolor.value(val-1, QColor(0,0,0));
|
|
} else if (val) {
|
|
if (max <= min) return RGBA(255,255,255,0); // see thru
|
|
if (val < min) val = min;
|
|
if (val > max) val = max;
|
|
int hue = (double)255 - (double)255 * (double)((val-min)/(max-min));
|
|
cHSV.setHsv(hue, 255, 255);
|
|
}
|
|
cRGB = cHSV.convertTo(QColor::Rgb);
|
|
colour.r = (double) cRGB.red()/(double)255;
|
|
colour.g = (double) cRGB.green()/(double)255;
|
|
colour.b = (double) cRGB.blue()/(double)255;
|
|
colour.a = 1.0;
|
|
return colour;
|
|
}
|
|
|
|
// qwtplot3d api changes between 0.2.x and 0.3.x
|
|
#if QWT3D_MINOR_VERSION > 2
|
|
Color *clone() const { ModelDataColor *x = new ModelDataColor; *x = *this; return x; }
|
|
#endif
|
|
|
|
Qwt3D::ColorVector &createVector(Qwt3D::ColorVector &vec)
|
|
{
|
|
RGBA colour;
|
|
QColor cHSV, cRGB;
|
|
Qwt3D::ColorVector::iterator it;
|
|
|
|
// clear current
|
|
vec.clear();
|
|
|
|
// update the color vector to use our color scheme
|
|
if (iszones) {
|
|
for (int i=0; i< 7; i++) {
|
|
cRGB = zonecolor.value(i, QColor(0,0,0)).convertTo(QColor::Rgb);
|
|
colour.r = (double) cRGB.red()/(double)255;
|
|
colour.g = (double) cRGB.green()/(double)255;
|
|
colour.b = (double) cRGB.blue()/(double)255;
|
|
colour.a = 1.0;
|
|
|
|
it = vec.end();
|
|
vec.insert(it, 1, colour);
|
|
}
|
|
} else {
|
|
if (max <= min) return vec;
|
|
|
|
double val;
|
|
int i;
|
|
// just add 100 colors graded from min to max
|
|
for (i=0, val=min; i<100 && val<=max; i++, val += ((max-min)/100)) {
|
|
|
|
int hue = (double)255 - (double)255 * (double)((val-min)/(max-min));
|
|
cHSV.setHsv(hue, 255, 255);
|
|
cRGB = cHSV.convertTo(QColor::Rgb);
|
|
colour.r = (double) cRGB.red()/(double)255;
|
|
colour.g = (double) cRGB.green()/(double)255;
|
|
colour.b = (double) cRGB.blue()/(double)255;
|
|
colour.a = 1.0;
|
|
it = vec.end();
|
|
vec.insert(it, 1, colour);
|
|
}
|
|
}
|
|
return vec;
|
|
}
|
|
|
|
public:
|
|
QHash<QString, double> color;
|
|
QHash<QString, int> num; // xy map with count of values for averaging
|
|
double min, max;
|
|
|
|
bool iszones; // if the color value is a zone number
|
|
QMap<double, QColor> zonecolor;
|
|
|
|
};
|
|
|
|
|
|
class ModelDataProvider : public Function
|
|
{
|
|
Q_DECLARE_TR_FUNCTIONS(ModelDataProvider)
|
|
|
|
public:
|
|
|
|
ModelDataProvider (ModelPlot &plot, ModelSettings *settings);
|
|
void setData(RideFile *ride, int x, int y, int z, int col); // set the maps and return mesh dimension
|
|
|
|
// return z value for x,y - std qwt3plot method
|
|
double operator () (double x, double y)
|
|
{
|
|
// return the z value for x and y
|
|
return mz.value(xystring(x,y), 0.0);
|
|
}
|
|
double intervals (double x, double y) // return value for selected intervals
|
|
{
|
|
return plot.iz.value(xystring(x,y), 0.0)-minz;
|
|
}
|
|
double getMinz() { return minz; }
|
|
double getMaxz() { return maxz; }
|
|
|
|
~ModelDataProvider()
|
|
{
|
|
mz.clear();
|
|
mnum.clear();
|
|
plot.iz.clear();
|
|
plot.inum.clear();
|
|
}
|
|
|
|
QHash<QString, double> mz; // xy map with max z values;
|
|
QHash<QString, int> mnum; // xy map with count of values for averaging
|
|
|
|
private:
|
|
|
|
double pointType(const RideFilePoint *, int);
|
|
QString describeType(int, bool);
|
|
double maxz, minz;
|
|
double cranklength; // used for CPV/AEPF calculation
|
|
bool useMetricUnits;
|
|
ModelPlot &plot;
|
|
};
|
|
|
|
double
|
|
ModelDataProvider::pointType(const RideFilePoint *point, int type)
|
|
{
|
|
// return the point value for the given type
|
|
switch(type) {
|
|
|
|
case MODEL_POWER : return point->watts;
|
|
case MODEL_CADENCE : return point->cad;
|
|
case MODEL_HEARTRATE : return point->hr;
|
|
case MODEL_SPEED :
|
|
if (useMetricUnits == true){
|
|
return point->kph;
|
|
}else {
|
|
return point->kph * MILES_PER_KM;
|
|
}
|
|
case MODEL_ALT :
|
|
if (useMetricUnits == true){
|
|
return point->alt;
|
|
}else {
|
|
return point->alt * FEET_PER_METER;
|
|
}
|
|
case MODEL_TORQUE : return point->nm;
|
|
case MODEL_TIME : return point->secs;
|
|
case MODEL_DISTANCE :
|
|
if (useMetricUnits == true){
|
|
return point->km;
|
|
}else {
|
|
return point->km * MILES_PER_KM;
|
|
}
|
|
case MODEL_INTERVAL : return point->interval;
|
|
case MODEL_LAT :
|
|
{
|
|
if (ceil(point->lat) > 90 || floor(ceil(point->lat)) < -90)
|
|
return 0;
|
|
else
|
|
return point->lat *1000;
|
|
}
|
|
break;
|
|
case MODEL_LONG :
|
|
{
|
|
if (ceil(point->lon) > 180 || floor(point->lon) < -180)
|
|
return 0;
|
|
else
|
|
return point->lon *1000;
|
|
}
|
|
break;
|
|
case MODEL_AEPF :
|
|
if (point->watts == 0 || point->cad == 0) return 0;
|
|
else return ((point->watts * 60.0) / (point->cad * cranklength * 2.0 * PI));
|
|
case MODEL_CPV :
|
|
return 100 * ((point->cad * cranklength * 2.0 * PI) / 60.0);
|
|
// these you need to do yourself cause there is some
|
|
// logic needed and I'm just lookup table!
|
|
case MODEL_XYTIME : return 1;
|
|
case MODEL_POWERZONE : return point->watts;
|
|
case MODEL_LRBALANCE : return point->lrbalance;
|
|
case MODEL_RV : return point->rvert;
|
|
case MODEL_RGCT : return point->rcontact;
|
|
case MODEL_RCAD : return point->rcad;
|
|
case MODEL_GEAR : return point->gear;
|
|
case MODEL_SMO2 : return point->smo2;
|
|
case MODEL_THB : return point->thb;
|
|
case MODEL_SLOPE : return point->slope;
|
|
}
|
|
return 0; // ? unknown channel ?
|
|
}
|
|
|
|
QString
|
|
ModelDataProvider::describeType(int type, bool longer)
|
|
{
|
|
// return the point value for the given type
|
|
if (longer == true) {
|
|
switch(type) {
|
|
|
|
case MODEL_POWER : return tr("Power (watts)");
|
|
case MODEL_CADENCE : return tr("Cadence (rpm)");
|
|
case MODEL_HEARTRATE : return tr("Heartrate (bpm)");
|
|
case MODEL_SPEED :
|
|
if (useMetricUnits == true){
|
|
return tr("Speed (kph)");
|
|
}else {
|
|
return tr("Speed (mph)");
|
|
}
|
|
case MODEL_ALT :
|
|
if (useMetricUnits == true){
|
|
return tr("Altitude (meters)");
|
|
}else {
|
|
return tr("Altitude (feet)");
|
|
}
|
|
case MODEL_TORQUE : return tr("Torque (N)");
|
|
case MODEL_TIME : return tr("Elapsed Time (secs)");
|
|
case MODEL_DISTANCE :
|
|
if (useMetricUnits == true){
|
|
return tr("Elapsed Distance (km)");
|
|
}else {
|
|
return tr("Elapsed Distance (mi)");
|
|
}
|
|
case MODEL_INTERVAL : return tr("Interval Number");
|
|
case MODEL_LAT : return tr("Latitude (degree x 1000)");
|
|
case MODEL_LONG : return tr("Longitude (degree x 1000)");
|
|
case MODEL_CPV : return tr("Circumferential Pedal Velocity (cm/s)");
|
|
case MODEL_AEPF : return tr("Average Effective Pedal Force (N)");
|
|
|
|
// these you need to do yourself cause there is some
|
|
// logic needed and I'm just lookup table!
|
|
case MODEL_XYTIME : return tr("Time at X/Y (%)");
|
|
case MODEL_POWERZONE : return tr("Power Zone");
|
|
case MODEL_LRBALANCE : return (tr("L/R Balance"));
|
|
case MODEL_RV : return (tr("Running Vertical Oscillation"));
|
|
case MODEL_RGCT : return (tr("Running Ground Contact Time"));
|
|
case MODEL_RCAD : return (tr("Running Cadence"));
|
|
case MODEL_GEAR : return (tr("Gear Ratio"));
|
|
case MODEL_SMO2 : return (tr("Muscle Oxygen"));
|
|
case MODEL_THB : return (tr("Haemoglobin Mass"));
|
|
case MODEL_SLOPE : return (tr("Slope (gradient)"));
|
|
}
|
|
return tr("Unknown");; // ? unknown channel ?
|
|
} else {
|
|
switch(type) {
|
|
|
|
case MODEL_POWER : return tr("Power");
|
|
case MODEL_CADENCE : return tr("Cadence");
|
|
case MODEL_HEARTRATE : return tr("Heartrate");
|
|
case MODEL_SPEED : return tr("Speed");
|
|
case MODEL_ALT : return tr("Altitude");
|
|
case MODEL_TORQUE : return tr("Pedal Force");
|
|
case MODEL_TIME : return tr("Time");
|
|
case MODEL_DISTANCE : return tr("Distance");
|
|
case MODEL_INTERVAL : return tr("Interval");
|
|
case MODEL_LAT : return tr("Latitude");
|
|
case MODEL_LONG : return tr("Longitude");
|
|
case MODEL_XYTIME : return tr("Time at X/Y");
|
|
case MODEL_POWERZONE : return tr("Zone");
|
|
case MODEL_CPV : return tr("CPV");
|
|
case MODEL_AEPF : return tr("AEPF");
|
|
case MODEL_LRBALANCE : return (tr("Balance"));
|
|
case MODEL_RV : return (tr("RV"));
|
|
case MODEL_RGCT : return (tr("GCT"));
|
|
case MODEL_RCAD : return (tr("Run Cad"));
|
|
case MODEL_GEAR : return (tr("Gear"));
|
|
case MODEL_SMO2 : return (tr("SmO2"));
|
|
case MODEL_THB : return (tr("tHb"));
|
|
case MODEL_SLOPE : return (tr("Slope"));
|
|
}
|
|
return tr("None");; // ? unknown channel ?
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Setup the data model and plot according to the settings passed from
|
|
* mainwindow.
|
|
*
|
|
* Truthfully, this is where all the hard work is done!
|
|
* Edit it with caution, there be dragons here.
|
|
*
|
|
*----------------------------------------------------------------------*/
|
|
ModelDataProvider::ModelDataProvider (ModelPlot &plot, ModelSettings *settings) : Function(plot), plot(plot)
|
|
{
|
|
// get application settings
|
|
cranklength = appsettings->cvalue(plot.context->athlete->cyclist, GC_CRANKLENGTH, 0.0).toDouble() / 1000.0;
|
|
useMetricUnits = plot.context->athlete->useMetricUnits;
|
|
|
|
// if there are no settings or incomplete settings
|
|
// create a null data plot
|
|
if (settings == NULL || settings->ride == NULL || settings->ride->ride() == NULL ||
|
|
settings->x == 0 || settings->y == 0 || settings->z == 0) {
|
|
// initialise a null plot
|
|
setDomain(0,0,0,0);
|
|
setMinZ(0);
|
|
create();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
// if its not setup or no settings exist default to 175mm cranks
|
|
if (cranklength == 0.0) cranklength = 0.175;
|
|
|
|
// Run through the ridefile points putting the selected
|
|
// values into the appropriate bins
|
|
settings->colorProvider->color.clear();
|
|
settings->colorProvider->num.clear();
|
|
settings->colorProvider->zonecolor.clear();
|
|
|
|
//plot.makeCurrent();
|
|
|
|
double maxbinx =-180000, maxbiny =-180000; // was 65535
|
|
double minbinx =180000, minbiny =180000; // 180000 is the max value (for longitude)
|
|
double mincol =180000, maxcol =-180000;
|
|
|
|
//
|
|
// Create Plot dataset, filter on values and calculate averages etc
|
|
//
|
|
foreach(const RideFilePoint *point, settings->ride->ride()->dataPoints()) {
|
|
|
|
// get x and z bin values - round to nearest bin
|
|
double dx = pointType(point, settings->x);
|
|
int binx = settings->xbin * floor(dx / settings->xbin);
|
|
|
|
double dy = pointType(point, settings->y);
|
|
int biny = settings->ybin * floor(dy / settings->ybin);
|
|
|
|
// ignore zero points
|
|
if (settings->ignore && (dx==0 || dy==0)) continue;
|
|
|
|
// even further ignore 0 for lat/lon
|
|
if ((settings->y == MODEL_LAT || settings->y == MODEL_LONG) && dy == 0) continue;
|
|
if ((settings->x == MODEL_LAT || settings->x == MODEL_LONG) && dx == 0) continue;
|
|
|
|
// get z value
|
|
double zed=0;
|
|
if (settings->z == MODEL_XYTIME) zed = settings->ride->ride()->recIntSecs(); // time at
|
|
else zed = pointType(point, settings->z); // raw data
|
|
|
|
// get color value
|
|
double color=0;
|
|
if (settings->color == MODEL_XYTIME) color = settings->ride->ride()->recIntSecs(); // time at
|
|
else color = pointType(point, settings->color); // raw data
|
|
|
|
// min max
|
|
if (color > maxcol) maxcol = color;
|
|
if (color < mincol) mincol = color;
|
|
if (binx > maxbinx) maxbinx = binx;
|
|
if (binx < minbinx) minbinx = binx;
|
|
if (biny > maxbiny) maxbiny = biny;
|
|
if (biny < minbiny) minbiny = biny;
|
|
|
|
// How many & Curent Value
|
|
// ZED
|
|
QString lookup = xystring(binx,biny);
|
|
int count = mnum.value(lookup, 0.0);
|
|
double currentz = mz.value(lookup, 0.0);
|
|
|
|
if (settings->z == MODEL_XYTIME) {
|
|
count++;
|
|
zed += currentz;
|
|
} else {
|
|
if (count) zed = ((currentz*count)+zed)/(count+1); // average
|
|
}
|
|
|
|
// always update the max values
|
|
mz.insert(lookup, zed);
|
|
mnum.insert(lookup, count);
|
|
|
|
// NO INTERVALS COLOR IS FOR ALL SAMPLES
|
|
if (settings->intervals.count() == 0 ) {
|
|
plot.intervals_ = 0;
|
|
|
|
int colcount = settings->colorProvider->num.value(lookup, 0.0);
|
|
double currentcol = settings->colorProvider->color.value(lookup,0.0);
|
|
|
|
if (settings->color == MODEL_XYTIME) { // color in time
|
|
colcount++;
|
|
color += currentcol;
|
|
} else { // color in average of
|
|
if (colcount) color = ((currentcol*colcount)+color)/(colcount+1); // average
|
|
}
|
|
settings->colorProvider->color.insert(lookup, color);
|
|
settings->colorProvider->num.insert(lookup, colcount);
|
|
}
|
|
|
|
// WE HAVE INTERVALS! COLOR AND INTERVAL Z VALUES NEED TO BE TREATED
|
|
// DIFFERENTLY NOW - COLOR IS FOR SELECTED INTERVALS AND WE MAINTAIN
|
|
// A SECOND SET OF Z VALUES SO WE HAVE MAX + INTERVALS
|
|
if (settings->intervals.count() > 0) {
|
|
plot.intervals_ = SHOW_INTERVALS;
|
|
if (settings->frame == true) plot.intervals_ |= SHOW_FRAME;
|
|
|
|
QString lookup = xystring(binx, biny);
|
|
|
|
// filter for interval
|
|
for(int i=0; i<settings->intervals.count(); i++) {
|
|
IntervalItem *curr = settings->intervals.at(i);
|
|
if ((point->secs + settings->ride->ride()->recIntSecs()) > curr->start
|
|
&& point->secs < curr->stop) {
|
|
// update colors
|
|
int colcount = settings->colorProvider->num.value(lookup,0.0);
|
|
double currentcol = settings->colorProvider->color.value(lookup, 0.0);
|
|
|
|
if (settings->color == MODEL_XYTIME) { // color in time
|
|
colcount++;
|
|
color += currentcol;
|
|
} else { // color in average of
|
|
if (colcount) color = ((currentcol*colcount)+color)/(colcount+1); // average
|
|
}
|
|
settings->colorProvider->color.insert(lookup, color);
|
|
settings->colorProvider->num.insert(lookup, colcount);
|
|
|
|
double ized=0;
|
|
if (settings->z == MODEL_XYTIME) ized = settings->ride->ride()->recIntSecs(); // time at
|
|
else ized = pointType(point, settings->z); // raw data
|
|
// update interval values
|
|
int count = plot.inum.value(lookup, 0.0);
|
|
double currentz = plot.iz.value(lookup, 0.0);
|
|
|
|
if (settings->z == MODEL_XYTIME) {
|
|
count++;
|
|
ized += currentz;
|
|
} else {
|
|
if (count) ized = ((currentz*count)+ized)/(count+1); // average
|
|
}
|
|
|
|
plot.iz.insert(lookup, ized);
|
|
plot.inum.insert(lookup, count);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mz.count() == 0) {
|
|
|
|
// create a null plot -- bin too large!
|
|
plot.setTitle(tr("No data or bin size too large"));
|
|
// initialise a null plot
|
|
setDomain(0,0,0,0);
|
|
setMinZ(0);
|
|
setMesh(0,0);
|
|
mz.clear();
|
|
settings->colorProvider->color.clear();
|
|
create();
|
|
return;
|
|
}
|
|
|
|
// POST PROCESS DATA SET
|
|
|
|
// COLOR
|
|
// if needed convert average power to the related power zone
|
|
// but only if ranges are defined i.e. user has set CP
|
|
const Zones *zones = plot.context->athlete->zones(settings->ride->isRun);
|
|
int zone_range = -1;
|
|
if (zones) zone_range = zones->whichRange(settings->ride->dateTime.date());
|
|
if (settings->color == MODEL_POWERZONE && zone_range >= 0) {
|
|
// if we need to color by power zones
|
|
// zones are setup and we want to color by them
|
|
maxcol = settings->colorProvider->max = zones->numZones(zone_range);
|
|
mincol = settings->colorProvider->min = 1;
|
|
|
|
// add the zone colors to the color map
|
|
for (int i=0; i<maxcol; i++) {
|
|
settings->colorProvider->zonecolor.insert
|
|
(i, zoneColor(i, zones->numZones(zone_range)));
|
|
}
|
|
|
|
// iterate over the existing power values converting to a power zone
|
|
QHashIterator<QString, double> coli(settings->colorProvider->color);
|
|
while (coli.hasNext()) {
|
|
coli.next();
|
|
QString lookup = coli.key();
|
|
double color = coli.value();
|
|
// turn into power zone
|
|
color = zones->whichZone(zone_range, color);
|
|
// overwrite existing power average with zone number
|
|
// BUT! the zone numbers start at 1 not 0 here to distinguish
|
|
// between zone 0 and no value at all
|
|
settings->colorProvider->color.insert(lookup, color+1);
|
|
|
|
}
|
|
settings->colorProvider->iszones = true;
|
|
} else if (settings->color == MODEL_NONE) {
|
|
settings->colorProvider->iszones = false;
|
|
settings->colorProvider->color.clear();
|
|
} else {
|
|
// otherwise just turn off zoning
|
|
settings->colorProvider->iszones = false;
|
|
}
|
|
|
|
// TIME
|
|
//
|
|
// Convert from absolute values to %age of the entire ride
|
|
|
|
double duration = settings->ride->ride()->dataPoints().last()->secs +
|
|
settings->ride->ride()->recIntSecs();
|
|
|
|
// Multis...
|
|
if (settings->z == MODEL_XYTIME) {
|
|
// time on Z axis
|
|
QHashIterator<QString, double> zi(mz);
|
|
while (duration && settings->z == MODEL_XYTIME && zi.hasNext()) {
|
|
zi.next();
|
|
double timePercent = (zi.value()/duration) * 100;
|
|
mz.insert(zi.key(), timePercent);
|
|
}
|
|
}
|
|
// Intervals
|
|
if (settings->z == MODEL_XYTIME) {
|
|
// time on Z axis
|
|
QHashIterator<QString, double> ii(plot.iz);
|
|
while (duration && settings->z == MODEL_XYTIME && ii.hasNext()) {
|
|
ii.next();
|
|
double timePercent = (ii.value()/duration) * 100;
|
|
plot.iz.insert(ii.key(), timePercent);
|
|
}
|
|
}
|
|
|
|
// time on Color
|
|
if (settings->color == MODEL_XYTIME) {
|
|
mincol=65535; maxcol=0;
|
|
QHashIterator<QString, double> ci(settings->colorProvider->color);
|
|
while (duration && settings->color == MODEL_XYTIME && ci.hasNext()) {
|
|
ci.next();
|
|
double timePercent = (ci.value()/duration) * 100;
|
|
if (timePercent > maxcol) maxcol = timePercent;
|
|
if (timePercent < mincol) mincol = timePercent;
|
|
settings->colorProvider->color.insert(ci.key(), timePercent);
|
|
}
|
|
}
|
|
|
|
// MIN and MAX Z (impacts chart geometry)
|
|
// We DO NOT do the same for color since they represent
|
|
// the entire data set and not just the intervals selected (if any)
|
|
bool first = true;
|
|
QHashIterator <QString, double> iz(mz);
|
|
while (iz.hasNext()) {
|
|
double z;
|
|
iz.next();
|
|
|
|
z = iz.value();
|
|
|
|
if (first == true) {
|
|
minz = 0;
|
|
maxz = iz.value();
|
|
} else {
|
|
if (z > maxz) maxz = z;
|
|
if (z < minz) minz = z;
|
|
}
|
|
first = false;
|
|
}
|
|
|
|
settings->colorProvider->min = mincol;
|
|
settings->colorProvider->max = maxcol;
|
|
|
|
//
|
|
// Adjust the plot settings to reflect the data set
|
|
//
|
|
setMinZ(minz);
|
|
setMaxZ(maxz);
|
|
|
|
QFont font; // has all the defaults in it
|
|
|
|
// qwtplot3d api changes between 0.2.x and 0.3.x
|
|
#if QWT3D_MINOR_VERSION > 2
|
|
plot.setDataColor(*settings->colorProvider);
|
|
#endif
|
|
|
|
//
|
|
// Setup the color legend
|
|
//
|
|
if (settings->legend == true && settings->color != MODEL_NONE) {
|
|
plot.showColorLegend(true);
|
|
plot.legend()->setTitleFont(font.family(), 8, QFont::Normal);
|
|
plot.legend()->setOrientation(Qwt3D::ColorLegend::BottomTop, Qwt3D::ColorLegend::Left);
|
|
plot.legend()->setLimits(mincol, maxcol);
|
|
if (settings->colorProvider->iszones == true) {
|
|
plot.legend()->setMajors(maxcol);
|
|
plot.legend()->setMinors(0);
|
|
} else {
|
|
plot.legend()->setMajors(10);
|
|
plot.legend()->setMinors(0);
|
|
}
|
|
plot.legend()->setTitleString(describeType(settings->color, false));
|
|
} else {
|
|
plot.showColorLegend(false);
|
|
}
|
|
|
|
// mesh size
|
|
int mx = (maxbinx-minbinx) / settings->xbin;
|
|
int my = (maxbiny-minbiny) / settings->ybin;
|
|
|
|
// mesh MUST be at least 2x2 including 0,0
|
|
while (mx < 2) {
|
|
maxbinx += settings->xbin;
|
|
mx++;
|
|
}
|
|
|
|
while(my < 2) {
|
|
maxbiny += settings->ybin;
|
|
my++;
|
|
}
|
|
|
|
// add some graph paper so the plot is pretty
|
|
if (mx < 4) {
|
|
minbinx -= settings->xbin;
|
|
mx++;
|
|
}
|
|
|
|
if (my < 4) {
|
|
minbiny -= settings->ybin;
|
|
my++;
|
|
}
|
|
|
|
// mesh is number of bins PLUS ONE (bug in qwtplot3d)
|
|
setMesh(mx + 1, my + 1);
|
|
|
|
// domain is max values in each axis
|
|
setDomain(maxbinx,minbinx,minbiny,maxbiny); // max x/y vals
|
|
|
|
// set the barsize to a sensible radius (20% space)
|
|
// the +setting->xbin bit is to offset the additional mx/my bug
|
|
double xr = 0.8 * (((double)settings->xbin/((double)(maxbinx-minbinx)+(settings->xbin))) / 2.0);
|
|
double yr = 0.8 * (((double)settings->ybin/((double)(maxbiny-minbiny)+(settings->ybin))) / 2.0);
|
|
|
|
plot.diag_ = xr < yr ? (xr*(maxbinx-minbinx)) : (yr*(maxbiny-minbiny));
|
|
|
|
// now update the model - before the chart axis/legends
|
|
create();
|
|
|
|
double xscale, yscale, zscale;
|
|
if ((maxbinx-minbinx) >= (maxbiny-minbiny) && (maxbinx-minbinx) >= (maxz-minz)) {
|
|
// scale is set off the x-axis
|
|
xscale = 1;
|
|
yscale = (maxbinx-minbinx)/(maxbiny-minbiny);
|
|
zscale = (maxbinx-minbinx)/(maxz-minz);
|
|
} else if ((maxbiny-minbiny) >= (maxbinx-minbinx) && (maxbiny-minbiny) >= (maxz-minz)) {
|
|
// scale is set off the y-axis
|
|
xscale = (maxbiny-minbiny)/(maxbinx-minbinx);
|
|
yscale = 1;
|
|
zscale = (maxbiny-minbiny)/(maxz-minz);
|
|
} else {
|
|
// scale is set off the z-axis
|
|
xscale = (maxz-minz)/(maxbinx-minbinx);
|
|
yscale = (maxz-minz)/(maxbiny-minbiny);
|
|
zscale = 1;
|
|
}
|
|
|
|
// must be integers!?
|
|
if (xscale < 1) {
|
|
double factor = 1 / xscale;
|
|
xscale = 1;
|
|
yscale *= factor;
|
|
zscale *= factor;
|
|
}
|
|
if (yscale < 1) {
|
|
double factor = 1/ yscale;
|
|
yscale = 1;
|
|
xscale *= factor;
|
|
zscale *= factor;
|
|
}
|
|
if (zscale < 1) {
|
|
double factor = 1/ zscale;
|
|
zscale = 1;
|
|
yscale *= factor;
|
|
xscale *=factor;
|
|
}
|
|
|
|
// general plot settings
|
|
plot.setScale(xscale, yscale, zscale);
|
|
plot.setTitle("");
|
|
plot.setTitleFont(font.family(),font.pointSize(), QFont::Bold);
|
|
plot.setCoordinateStyle(FRAME);
|
|
plot.setMeshLineWidth(1);
|
|
plot.setIsolines(10);
|
|
plot.setOrtho(false);
|
|
plot.setSmoothMesh(true);
|
|
|
|
// coordinates - labels, gridlines, tic markers etc
|
|
if (settings->gridlines == true)
|
|
plot.coordinates()->setGridLines(true, true, Qwt3D::BACK | Qwt3D::LEFT | Qwt3D::FLOOR);
|
|
else
|
|
plot.coordinates()->setGridLines(true, true, 0);
|
|
plot.coordinates()->setLineWidth(1);
|
|
plot.coordinates()->setNumberFont(font.family(),font.pointSize());
|
|
plot.coordinates()->adjustLabels(25);
|
|
plot.coordinates()->adjustNumbers(10);
|
|
|
|
for (unsigned int i=0; i < plot.coordinates()->axes.size(); i++) {
|
|
plot.coordinates()->axes[i].setMajors(7);
|
|
plot.coordinates()->axes[i].setMinors(5);
|
|
plot.coordinates()->axes[i].recalculateTics();
|
|
}
|
|
plot.coordinates()->setLabelFont(font.family(), font.pointSize(), QFont::Bold);
|
|
|
|
plot.coordinates()->axes[Z1].setLabelString(describeType(settings->z, true));
|
|
plot.coordinates()->axes[Z2].setLabelString(describeType(settings->z, true));
|
|
plot.coordinates()->axes[Z3].setLabelString(describeType(settings->z, true));
|
|
plot.coordinates()->axes[Z4].setLabelString(describeType(settings->z, true));
|
|
plot.coordinates()->axes[X1].setLabelString(describeType(settings->x, true));
|
|
plot.coordinates()->axes[X2].setLabelString(describeType(settings->x, true));
|
|
plot.coordinates()->axes[X3].setLabelString(describeType(settings->x, true));
|
|
plot.coordinates()->axes[X4].setLabelString(describeType(settings->x, true));
|
|
plot.coordinates()->axes[Y1].setLabelString(describeType(settings->y, true));
|
|
plot.coordinates()->axes[Y2].setLabelString(describeType(settings->y, true));
|
|
plot.coordinates()->axes[Y3].setLabelString(describeType(settings->y, true));
|
|
plot.coordinates()->axes[Y4].setLabelString(describeType(settings->y, true));
|
|
|
|
plot.coordinates()->axes[Z1].setTicLength(25.0/xscale, 10.0/xscale);
|
|
plot.coordinates()->axes[Z2].setTicLength(25.0/xscale, 10.0/xscale);
|
|
plot.coordinates()->axes[Z3].setTicLength(25.0/xscale, 10.0/xscale);
|
|
plot.coordinates()->axes[Z4].setTicLength(25.0/xscale, 10.0/xscale);
|
|
plot.coordinates()->axes[X1].setTicLength(25.0/yscale, 10.0/yscale);
|
|
plot.coordinates()->axes[X2].setTicLength(25.0/yscale, 10.0/yscale);
|
|
plot.coordinates()->axes[X3].setTicLength(25.0/yscale, 10.0/yscale);
|
|
plot.coordinates()->axes[X4].setTicLength(25.0/yscale, 10.0/yscale);
|
|
plot.coordinates()->axes[Y1].setTicLength(25.0/xscale, 10.0/xscale);
|
|
plot.coordinates()->axes[Y2].setTicLength(25.0/xscale, 10.0/xscale);
|
|
plot.coordinates()->axes[Y3].setTicLength(25.0/xscale, 10.0/xscale);
|
|
plot.coordinates()->axes[Y4].setTicLength(25.0/xscale, 10.0/xscale);
|
|
|
|
// reset the flippin tic orientation, really this library
|
|
// is a bit of a pain now!
|
|
plot.coordinates()->axes[X1].setTicOrientation(0, -1, 0);
|
|
plot.coordinates()->axes[X2].setTicOrientation(0, -1, 0);
|
|
plot.coordinates()->axes[X3].setTicOrientation(0, 1, 0);
|
|
plot.coordinates()->axes[X4].setTicOrientation(0, 1, 0);
|
|
|
|
plot.coordinates()->axes[Y1].setTicOrientation( 1, 0, 0);
|
|
plot.coordinates()->axes[Y2].setTicOrientation(-1, 0, 0);
|
|
plot.coordinates()->axes[Y3].setTicOrientation(-1, 0, 0);
|
|
plot.coordinates()->axes[Y4].setTicOrientation( 1, 0, 0);
|
|
|
|
plot.coordinates()->axes[Z1].setTicOrientation( 1, 0, 0);
|
|
plot.coordinates()->axes[Z2].setTicOrientation( 1, 0, 0);
|
|
plot.coordinates()->axes[Z3].setTicOrientation(-1, 0, 0);
|
|
plot.coordinates()->axes[Z4].setTicOrientation(-1, 0, 0);
|
|
|
|
// use the cplotmarker colors for the ticks etc
|
|
QColor p = GColor(CPLOTMARKER);
|
|
plot.coordinates()->axes[Z1].setColor(p.red(), p.green(), p.blue());
|
|
plot.coordinates()->axes[Z2].setColor(p.red(), p.green(), p.blue());
|
|
plot.coordinates()->axes[Z3].setColor(p.red(), p.green(), p.blue());
|
|
plot.coordinates()->axes[Z4].setColor(p.red(), p.green(), p.blue());
|
|
plot.coordinates()->axes[X1].setColor(p.red(), p.green(), p.blue());
|
|
plot.coordinates()->axes[X2].setColor(p.red(), p.green(), p.blue());
|
|
plot.coordinates()->axes[X3].setColor(p.red(), p.green(), p.blue());
|
|
plot.coordinates()->axes[X4].setColor(p.red(), p.green(), p.blue());
|
|
plot.coordinates()->axes[Y1].setColor(p.red(), p.green(), p.blue());
|
|
plot.coordinates()->axes[Y2].setColor(p.red(), p.green(), p.blue());
|
|
plot.coordinates()->axes[Y3].setColor(p.red(), p.green(), p.blue());
|
|
plot.coordinates()->axes[Y4].setColor(p.red(), p.green(), p.blue());
|
|
|
|
// now, at last we can draw the axes markers. phew.
|
|
plot.coordinates()->axes[Z1].draw();
|
|
plot.coordinates()->axes[Z2].draw();
|
|
plot.coordinates()->axes[Z3].draw();
|
|
plot.coordinates()->axes[X1].draw();
|
|
plot.coordinates()->axes[X2].draw();
|
|
plot.coordinates()->axes[X3].draw();
|
|
plot.coordinates()->axes[Y1].draw();
|
|
plot.coordinates()->axes[Y2].draw();
|
|
plot.coordinates()->axes[Y3].draw();
|
|
|
|
// turn off zpane -- causes nasty flashing when left on between plots
|
|
plot.zpane = 0;
|
|
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* BASIC MODEL PLOT
|
|
* This is the qwt3d plot object
|
|
*
|
|
* Constructor - initialises an empty plot
|
|
* setData - calls the model data provider to setup data
|
|
* and then replots via updateData, updateGL.
|
|
*
|
|
*----------------------------------------------------------------------*/
|
|
|
|
ModelPlot::ModelPlot(Context *context, QWidget *parent, ModelSettings *settings) :
|
|
#if QWT3D_MINOR_VERSION > 2
|
|
GridPlot(parent),
|
|
#else
|
|
SurfacePlot(parent),
|
|
#endif
|
|
context(context)
|
|
{
|
|
diag_=0;
|
|
currentStyle = STYLE_BAR;
|
|
|
|
// the color provider returns a color for an xyz
|
|
modelDataColor = new ModelDataColor;
|
|
if (settings) settings->colorProvider = modelDataColor;
|
|
|
|
// the data provider returns a z for an x,y
|
|
modelDataProvider = new ModelDataProvider(*this, settings);
|
|
|
|
|
|
// qwtplot3d api changes between 0.2.x and 0.3.x
|
|
#if QWT3D_MINOR_VERSION < 3
|
|
setDataColor(modelDataColor);
|
|
#endif
|
|
|
|
// box style x/y/z
|
|
setCoordinateStyle(FRAME);
|
|
|
|
// start with bar chart
|
|
bar = (Bar *) this->setPlotStyle(Bar(this));
|
|
|
|
//coordinates()->setAutoScale(true);
|
|
|
|
// add grid lines and antialias them for smoother lines
|
|
//coordinates()->setGridLines(true, true);
|
|
//coordinates()->setLineSmooth(true);
|
|
//setSmoothMesh(true);
|
|
|
|
// some detailed axes points
|
|
for (unsigned int i=0; i < coordinates()->axes.size(); i++) {
|
|
coordinates()->axes[i].setMajors(7);
|
|
coordinates()->axes[i].setMinors(5);
|
|
}
|
|
setMeshLineWidth(1);
|
|
|
|
// put some space between the axes tic labels and the plot
|
|
// to make it easier to read
|
|
coordinates()->adjustNumbers(25);
|
|
|
|
// orthogonal view
|
|
setOrtho(false);
|
|
|
|
// no lighting it makes it tricky to read
|
|
// when there are LOTS of bars
|
|
//blowout();
|
|
|
|
// set shift zoom etc
|
|
resetViewPoint();
|
|
|
|
// set colors
|
|
configChanged(CONFIG_APPEARANCE);
|
|
|
|
updateData();
|
|
updateGL();
|
|
}
|
|
|
|
void
|
|
ModelPlot::configChanged(qint32)
|
|
{
|
|
// setColors bg
|
|
QColor rgba = GColor(CPLOTBACKGROUND);
|
|
RGBA bg(rgba.red()/255.0, rgba.green()/255.0, rgba.blue()/255.0, 0);
|
|
setBackgroundColor(bg);
|
|
|
|
// labels
|
|
QColor irgba = GCColor::invertColor(GColor(CPLOTBACKGROUND));
|
|
RGBA fg(irgba.red()/255.0, irgba.green()/255.0, irgba.blue()/255.0, 0);
|
|
coordinates()->setLabelColor(fg);
|
|
coordinates()->setNumberColor(fg);
|
|
|
|
// set grid lines
|
|
QColor grid = GColor(CPLOTGRID);
|
|
RGBA gr(grid.red()/255.0, grid.green()/255.0, grid.blue()/255.0, 255);
|
|
coordinates()->setGridLinesColor(gr);
|
|
coordinates()->setLineWidth(1);
|
|
}
|
|
|
|
void
|
|
ModelPlot::setStyle(int index)
|
|
{
|
|
if (currentStyle == STYLE_BAR) degrade(bar);
|
|
else degrade(water);
|
|
|
|
switch (index) {
|
|
|
|
case 0 : // BAR
|
|
bar = (Bar*)this->setPlotStyle(Bar(this));
|
|
showNormals(false);
|
|
updateNormals();
|
|
currentStyle = STYLE_BAR;
|
|
break;
|
|
case 1 : // SURFACE GRID
|
|
setPlotStyle(FILLEDMESH);
|
|
water = (Water *)addEnrichment(Water(this));
|
|
showNormals(false);
|
|
updateNormals();
|
|
currentStyle = STYLE_GRID;
|
|
break;
|
|
case 2 : // SURFACE SMOOTH
|
|
setPlotStyle(FILLED);
|
|
water = (Water *)addEnrichment(Water(this));
|
|
showNormals(false);
|
|
updateNormals();
|
|
currentStyle = STYLE_SURFACE;
|
|
break;
|
|
case 3 : // DOTS
|
|
setPlotStyle(Dot(10,10));
|
|
water = (Water *)addEnrichment(Water(this));
|
|
showNormals(false);
|
|
updateNormals();
|
|
currentStyle = STYLE_DOTS;
|
|
break;
|
|
}
|
|
updateData();
|
|
updateGL();
|
|
}
|
|
|
|
void
|
|
ModelPlot::setData(ModelSettings *settings)
|
|
{
|
|
delete modelDataProvider;
|
|
settings->colorProvider = modelDataColor;
|
|
modelDataProvider = new ModelDataProvider(*this, settings);
|
|
|
|
if (modelDataProvider->mz.count() == 0) {
|
|
hide();
|
|
} else {
|
|
show();
|
|
}
|
|
//modelDataProvider->assign(this);
|
|
//create();
|
|
//resetViewPoint();
|
|
//updateNormals();
|
|
updateData();
|
|
updateGL();
|
|
}
|
|
|
|
void
|
|
ModelPlot::setFrame(bool frame)
|
|
{
|
|
if (intervals_ && frame == true) {
|
|
intervals_ |= SHOW_FRAME;
|
|
} else if (frame== false) {
|
|
intervals_ &= ~SHOW_FRAME;
|
|
}
|
|
updateData();
|
|
updateGL();
|
|
}
|
|
|
|
void
|
|
ModelPlot::setLegend(bool legend, int coltype)
|
|
{
|
|
if (legend == true && coltype != MODEL_NONE) {
|
|
showColorLegend(true);
|
|
} else {
|
|
showColorLegend(false);
|
|
}
|
|
}
|
|
|
|
void
|
|
ModelPlot::setGrid(bool grid)
|
|
{
|
|
if (grid == true)
|
|
coordinates()->setGridLines(true, true, Qwt3D::BACK | Qwt3D::LEFT | Qwt3D::FLOOR);
|
|
else
|
|
coordinates()->setGridLines(true, true, 0);
|
|
updateData();
|
|
updateGL();
|
|
}
|
|
|
|
void
|
|
ModelPlot::setZPane(int z)
|
|
{
|
|
//zpane = (modelDataProvider->maxz-modelDataProvider->minz) / 100 * z;
|
|
zpane = (modelDataProvider->getMaxz()-modelDataProvider->getMinz()) / 100 * z;
|
|
updateData();
|
|
updateGL();
|
|
}
|
|
|
|
void
|
|
ModelPlot::resetViewPoint()
|
|
{
|
|
setRotation(45, 0, 30); // seems most pleasing
|
|
setShift(0,0,0); // centre so movement feels natural
|
|
setViewportShift(0,0);
|
|
setZoom(0.8); // zoom in close but leave space for the axis labels
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------
|
|
* WATER VERTEX ENRICHMENT
|
|
*
|
|
* THIS IS *NOT* USED IN BAR STYLE. The water enrichment is done by the
|
|
* Bar ENRICHMENT INSTEAD. (this is because the alpha values are honored
|
|
* amd I cannot work out why).
|
|
*
|
|
* IF we decide to get rid of surface/grid plots then this enrichment can
|
|
* be removed. But maybe someone likes this plot styles. I hate them.
|
|
*
|
|
*----------------------------------------------------------------------*/
|
|
Water::Water()
|
|
{
|
|
}
|
|
|
|
Water::Water(ModelPlot *model) : model(model) {}
|
|
|
|
void Water::drawBegin()
|
|
{
|
|
// diag has been moved to a global variable set by the data model
|
|
// this is because the reference variable below is cached
|
|
// and is unreliable!! (i.e. bug).
|
|
//diag_ = (plot->hull().maxVertex-plot->hull().minVertex).length() * barsize;
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glLineWidth( 0 );
|
|
glEnable(GL_POLYGON_OFFSET_FILL);
|
|
glPolygonOffset(0,1);
|
|
}
|
|
|
|
void Water::drawEnd()
|
|
{
|
|
// plot all the nonsense now -- could shade if
|
|
// power zones selected on plot (?)
|
|
|
|
if (model->zpane) {
|
|
|
|
// qwtplot3d api changes between 0.2.x and 0.3.x
|
|
#if QWT3D_MINOR_VERSION > 2
|
|
double minx = plot_p->hull().minVertex.x;
|
|
double miny = plot_p->hull().minVertex.y;
|
|
double maxx = plot_p->hull().maxVertex.x;
|
|
double maxy = plot_p->hull().maxVertex.y;
|
|
double minz = plot_p->hull().minVertex.z;
|
|
#else
|
|
double minx = plot->hull().minVertex.x;
|
|
double miny = plot->hull().minVertex.y;
|
|
double maxx = plot->hull().maxVertex.x;
|
|
double maxy = plot->hull().maxVertex.y;
|
|
double minz = plot->hull().minVertex.z;
|
|
#endif
|
|
|
|
double z = model->zpane + minz;
|
|
|
|
// ZPANE SHADING
|
|
glColor4f(0.7,0,0,0.4);
|
|
glBegin(GL_QUADS);
|
|
|
|
// top
|
|
glColor4d(0.5,0.5,1,1.0);
|
|
glVertex3d(minx,miny,z);
|
|
glVertex3d(minx,maxy,z);
|
|
glVertex3d(maxx,maxy,z);
|
|
glVertex3d(maxx,miny,z);
|
|
|
|
// bottom
|
|
glColor4d(0.5,0.5,1,1.0);
|
|
glVertex3d(minx,miny,minz);
|
|
glVertex3d(minx,maxy,minz);
|
|
glVertex3d(maxx,maxy,minz);
|
|
glVertex3d(maxx,miny,minz);
|
|
|
|
// front
|
|
glColor4d(0.5,0.5,1,1.0);
|
|
glVertex3d(minx,miny,minz);
|
|
glVertex3d(minx,miny,z);
|
|
glVertex3d(maxx,miny,z);
|
|
glVertex3d(maxx,miny,minz);
|
|
|
|
// back
|
|
glColor4d(0.5,0.5,1,1.0);
|
|
glVertex3d(minx,maxy,minz);
|
|
glVertex3d(minx,maxy,z);
|
|
glVertex3d(maxx,maxy,z);
|
|
glVertex3d(maxx,maxy,minz);
|
|
|
|
// left
|
|
glColor4d(0.5,0.5,1,1.0);
|
|
glVertex3d(minx,miny,minz);
|
|
glVertex3d(minx,miny,z);
|
|
glVertex3d(minx,maxy,z);
|
|
glVertex3d(minx,maxy,minz);
|
|
|
|
// right
|
|
glColor4d(0.5,0.5,1,1.0);
|
|
glVertex3d(maxx,miny,minz);
|
|
glVertex3d(maxx,miny,z);
|
|
glVertex3d(maxx,maxy,z);
|
|
glVertex3d(maxx,maxy,minz);
|
|
glEnd();
|
|
|
|
QColor x = GColor(CPLOTMARKER);
|
|
glColor3d(x.red(),x.green(),x.blue());
|
|
glBegin(GL_LINES);
|
|
glVertex3d(minx,miny,z); glVertex3d(minx,maxy, z);
|
|
glVertex3d(minx,maxy,z); glVertex3d(maxx,maxy, z);
|
|
glVertex3d(maxx,maxy,z); glVertex3d(maxx,miny, z);
|
|
glVertex3d(maxx,miny,z); glVertex3d(minx,miny, z);
|
|
glEnd();
|
|
}
|
|
}
|
|
|
|
// draw the enrichment, called for each xyz triple
|
|
void Water::draw(Qwt3D::Triple const& )
|
|
{}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* BAR VERTEX ENRICHMENT (From here to bottom of source file)
|
|
*
|
|
* A vertex enrichment to show bars not a surface
|
|
* Code courtesy of the enrichment example in the qwtplot3d library and
|
|
* hacked to fixup reloading plots etc (the original was a demo example)
|
|
*
|
|
* CHANGED TO DRAW TWO BARS - wireframe for MAX and shaded for interval
|
|
* or wireframe and shaded MAX depending upon
|
|
* whether any intervals have been selected.
|
|
*
|
|
*----------------------------------------------------------------------*/
|
|
Bar::Bar()
|
|
{
|
|
}
|
|
|
|
Bar::Bar(ModelPlot *model) : model(model) {}
|
|
|
|
void Bar::drawBegin()
|
|
{
|
|
// diag has been moved to a global variable set by the data model
|
|
// this is because the reference variable below is cached
|
|
// and is unreliable!! (i.e. bug).
|
|
//diag_ = (plot->hull().maxVertex-plot->hull().minVertex).length() * barsize;
|
|
glLineWidth( 0 );
|
|
glEnable(GL_POLYGON_OFFSET_FILL);
|
|
glPolygonOffset(1,1);
|
|
|
|
}
|
|
|
|
// enrichment pure virtual - we do nothing
|
|
void Bar::drawEnd()
|
|
{
|
|
// plot all the nonsense now -- could shade if
|
|
// power zones selected on plot (?)
|
|
if (model->zpane) {
|
|
|
|
|
|
// qwtplot3d api changes between 0.2.x and 0.3.x
|
|
#if QWT3D_MINOR_VERSION > 2
|
|
double minx = plot_p->hull().minVertex.x;
|
|
double miny = plot_p->hull().minVertex.y;
|
|
double maxx = plot_p->hull().maxVertex.x;
|
|
double maxy = plot_p->hull().maxVertex.y;
|
|
double minz = plot_p->hull().minVertex.z;
|
|
#else
|
|
double minx = plot->hull().minVertex.x;
|
|
double miny = plot->hull().minVertex.y;
|
|
double maxx = plot->hull().maxVertex.x;
|
|
double maxy = plot->hull().maxVertex.y;
|
|
double minz = plot->hull().minVertex.z;
|
|
#endif
|
|
double z = model->zpane + minz;
|
|
|
|
// ZPANE SHADING
|
|
glColor3d(0.7,0,0);
|
|
glBegin(GL_QUADS);
|
|
|
|
// top
|
|
glColor4d(0.5,0.5,1,0.7);
|
|
glVertex3d(minx,miny,z);
|
|
glVertex3d(minx,maxy,z);
|
|
glVertex3d(maxx,maxy,z);
|
|
glVertex3d(maxx,miny,z);
|
|
|
|
// bottom
|
|
glColor4d(0.5,0.5,1,0.7);
|
|
glVertex3d(minx,miny,minz);
|
|
glVertex3d(minx,maxy,minz);
|
|
glVertex3d(maxx,maxy,minz);
|
|
glVertex3d(maxx,miny,minz);
|
|
|
|
// front
|
|
glColor4d(0.5,0.5,1,0.7);
|
|
glVertex3d(minx,miny,minz);
|
|
glVertex3d(minx,miny,z);
|
|
glVertex3d(maxx,miny,z);
|
|
glVertex3d(maxx,miny,minz);
|
|
|
|
// back
|
|
glColor4d(0.5,0.5,1,0.7);
|
|
glVertex3d(minx,maxy,minz);
|
|
glVertex3d(minx,maxy,z);
|
|
glVertex3d(maxx,maxy,z);
|
|
glVertex3d(maxx,maxy,minz);
|
|
|
|
// left
|
|
glColor4d(0.5,0.5,1,0.7);
|
|
glVertex3d(minx,miny,minz);
|
|
glVertex3d(minx,miny,z);
|
|
glVertex3d(minx,maxy,z);
|
|
glVertex3d(minx,maxy,minz);
|
|
|
|
// left
|
|
glColor4d(0.5,0.5,1,0.7);
|
|
glVertex3d(maxx,miny,minz);
|
|
glVertex3d(maxx,miny,z);
|
|
glVertex3d(maxx,maxy,z);
|
|
glVertex3d(maxx,maxy,minz);
|
|
glEnd();
|
|
|
|
QColor x = GColor(CPLOTMARKER);
|
|
glColor3d(x.red(),x.green(),x.blue());
|
|
glBegin(GL_LINES);
|
|
glVertex3d(minx,miny,z); glVertex3d(minx,maxy, z);
|
|
glVertex3d(minx,maxy,z); glVertex3d(maxx,maxy, z);
|
|
glVertex3d(maxx,maxy,z); glVertex3d(maxx,miny, z);
|
|
glVertex3d(maxx,miny,z); glVertex3d(minx,miny, z);
|
|
glEnd();
|
|
}
|
|
}
|
|
|
|
// draw the enrichment, called for each xyz triple
|
|
void Bar::draw(Qwt3D::Triple const& pos)
|
|
{
|
|
|
|
// GLStateBewarer sb(GL_LINE_SMOOTH, true);
|
|
// sb.turnOn();
|
|
|
|
// qwtplot3d api changes between 0.2.x and 0.3.x
|
|
#if QWT3D_MINOR_VERSION > 2
|
|
double interval = plot_p->hull().maxVertex.z-plot_p->hull().minVertex.z;
|
|
double numlevel = plot_p->hull().minVertex.z + 1 * interval;
|
|
GLdouble gminz = plot_p->hull().minVertex.z;
|
|
#else
|
|
double interval = plot->hull().maxVertex.z-plot->hull().minVertex.z;
|
|
double numlevel = plot->hull().minVertex.z + 1 * interval;
|
|
GLdouble gminz = plot->hull().minVertex.z;
|
|
#endif
|
|
|
|
interval /=100;
|
|
|
|
// get the colour for this bar from the plot colour provider (defined above)
|
|
RGBA rgbat, rgbab;
|
|
|
|
// don't plot a thing if it don't mean a thing
|
|
if (pos.z == gminz) return;
|
|
if (model->intervals_ == 0) {
|
|
// just plot using normal colours (one set of bars per x/y)
|
|
|
|
// qwtplot3d api changes between 0.2.x and 0.3.x
|
|
#if QWT3D_MINOR_VERSION > 2
|
|
rgbat = (plot_p->dataColor())->rgba(pos);
|
|
rgbab = (plot_p->dataColor())->rgba(pos.x, pos.y, gminz);
|
|
#else
|
|
rgbat = (*plot->dataColor())(pos);
|
|
rgbab = (*plot->dataColor())(pos.x, pos.y, gminz);
|
|
#endif
|
|
|
|
} else {
|
|
// first bars use max and are see-through
|
|
QColor irgba = GCColor::invertColor(GColor(CPLOTBACKGROUND));
|
|
RGBA t(irgba.red()/255.0, irgba.green()/255.0, irgba.blue()/255.0, 1);
|
|
RGBA b(irgba.red()/255.0, irgba.green()/255.0, irgba.blue()/255.0, 0);
|
|
rgbat = t;
|
|
rgbab = b;
|
|
}
|
|
|
|
if (model->intervals_ == 0) {
|
|
// shade the max bars if not doing intervals
|
|
glBegin(GL_QUADS);
|
|
glColor4d(rgbab.r,rgbab.g,rgbab.b,rgbab.a);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,gminz);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,gminz);
|
|
glVertex3d(pos.x+model->diag_,pos.y+model->diag_,gminz);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,gminz);
|
|
|
|
if (pos.z > numlevel - interval && pos.z < numlevel + interval )
|
|
glColor3d(0.7,0,0);
|
|
else
|
|
glColor4d(rgbat.r,rgbat.g,rgbat.b,rgbat.a);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,pos.z);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,pos.z);
|
|
glVertex3d(pos.x+model->diag_,pos.y+model->diag_,pos.z);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,pos.z);
|
|
|
|
glColor4d(rgbab.r,rgbab.g,rgbat.b,rgbab.a);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,gminz);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,gminz);
|
|
glColor4d(rgbat.r,rgbat.g,rgbat.b,rgbat.a);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,pos.z);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,pos.z);
|
|
|
|
glColor4d(rgbab.r,rgbab.g,rgbat.b,rgbab.a);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,gminz);
|
|
glVertex3d(pos.x+model->diag_,pos.y+model->diag_,gminz);
|
|
glColor4d(rgbat.r,rgbat.g,rgbat.b,rgbat.a);
|
|
glVertex3d(pos.x+model->diag_,pos.y+model->diag_,pos.z);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,pos.z);
|
|
|
|
glColor4d(rgbab.r,rgbab.g,rgbat.b,rgbab.a);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,gminz);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,gminz);
|
|
glColor4d(rgbat.r,rgbat.g,rgbat.b,rgbat.a);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,pos.z);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,pos.z);
|
|
|
|
glColor4d(rgbab.r,rgbab.g,rgbat.b,rgbab.a);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,gminz);
|
|
glVertex3d(pos.x+model->diag_,pos.y+model->diag_,gminz);
|
|
glColor4d(rgbat.r,rgbat.g,rgbat.b,rgbat.a);
|
|
glVertex3d(pos.x+model->diag_,pos.y+model->diag_,pos.z);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,pos.z);
|
|
glEnd();
|
|
}
|
|
|
|
if (model->intervals_ == 0 || model->intervals_&SHOW_FRAME) {
|
|
QColor irgba = GCColor::invertColor(GColor(CPLOTBACKGROUND));
|
|
glColor3d(irgba.red()/255.0, irgba.green()/255.0,irgba.blue()/255);
|
|
glBegin(GL_LINES);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,gminz); glVertex3d(pos.x+model->diag_,pos.y-model->diag_,gminz);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,pos.z); glVertex3d(pos.x+model->diag_,pos.y-model->diag_,pos.z);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,pos.z); glVertex3d(pos.x+model->diag_,pos.y+model->diag_,pos.z);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,gminz); glVertex3d(pos.x+model->diag_,pos.y+model->diag_,gminz);
|
|
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,gminz); glVertex3d(pos.x-model->diag_,pos.y+model->diag_,gminz);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,gminz); glVertex3d(pos.x+model->diag_,pos.y+model->diag_,gminz);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,pos.z); glVertex3d(pos.x+model->diag_,pos.y+model->diag_,pos.z);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,pos.z); glVertex3d(pos.x-model->diag_,pos.y+model->diag_,pos.z);
|
|
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,gminz); glVertex3d(pos.x-model->diag_,pos.y-model->diag_,pos.z);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,gminz); glVertex3d(pos.x+model->diag_,pos.y-model->diag_,pos.z);
|
|
glVertex3d(pos.x+model->diag_,pos.y+model->diag_,gminz); glVertex3d(pos.x+model->diag_,pos.y+model->diag_,pos.z);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,gminz); glVertex3d(pos.x-model->diag_,pos.y+model->diag_,pos.z);
|
|
glEnd();
|
|
}
|
|
|
|
// if we have don't have intervals we're done
|
|
if (model->intervals_ == 0) return;
|
|
|
|
// get the intervals drawn
|
|
// just plot using normal colours (one set of bars per x/y)
|
|
|
|
// qwtplot3d api changes between 0.2.x and 0.3.x
|
|
#if QWT3D_MINOR_VERSION > 2
|
|
rgbat = (plot_p->dataColor())->rgba(pos);
|
|
rgbab = (plot_p->dataColor())->rgba(pos.x, pos.y, gminz);
|
|
#else
|
|
rgbat = (*plot->dataColor())(pos);
|
|
rgbab = (*plot->dataColor())(pos.x, pos.y, gminz);
|
|
#endif
|
|
|
|
// get pos for the interval data
|
|
// call the current data provider
|
|
// which is a global
|
|
double z = model->iz.value(xystring(pos.x,pos.y));
|
|
if (z == 0) return;
|
|
|
|
// do the max bars
|
|
glBegin(GL_QUADS);
|
|
glColor4d(rgbab.r,rgbab.g,rgbab.b,rgbab.a);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,gminz);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,gminz);
|
|
glVertex3d(pos.x+model->diag_,pos.y+model->diag_,gminz);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,gminz);
|
|
|
|
if (z > numlevel - interval && z < numlevel + interval )
|
|
glColor3d(0.7,0,0);
|
|
else
|
|
glColor4d(rgbat.r,rgbat.g,rgbat.b,rgbat.a);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,z);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,z);
|
|
glVertex3d(pos.x+model->diag_,pos.y+model->diag_,z);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,z);
|
|
|
|
glColor4d(rgbab.r,rgbab.g,rgbat.b,rgbab.a);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,gminz);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,gminz);
|
|
glColor4d(rgbat.r,rgbat.g,rgbat.b,rgbat.a);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,z);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,z);
|
|
|
|
glColor4d(rgbab.r,rgbab.g,rgbat.b,rgbab.a);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,gminz);
|
|
glVertex3d(pos.x+model->diag_,pos.y+model->diag_,gminz);
|
|
glColor4d(rgbat.r,rgbat.g,rgbat.b,rgbat.a);
|
|
glVertex3d(pos.x+model->diag_,pos.y+model->diag_,z);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,z);
|
|
|
|
glColor4d(rgbab.r,rgbab.g,rgbat.b,rgbab.a);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,gminz);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,gminz);
|
|
glColor4d(rgbat.r,rgbat.g,rgbat.b,rgbat.a);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,z);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,z);
|
|
|
|
glColor4d(rgbab.r,rgbab.g,rgbat.b,rgbab.a);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,gminz);
|
|
glVertex3d(pos.x+model->diag_,pos.y+model->diag_,gminz);
|
|
glColor4d(rgbat.r,rgbat.g,rgbat.b,rgbat.a);
|
|
glVertex3d(pos.x+model->diag_,pos.y+model->diag_,z);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,z);
|
|
glEnd();
|
|
|
|
QColor x = GColor(CPLOTMARKER);
|
|
glColor3d(x.red(),x.green(),x.blue());
|
|
glBegin(GL_LINES);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,gminz); glVertex3d(pos.x+model->diag_,pos.y-model->diag_,gminz);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,z); glVertex3d(pos.x+model->diag_,pos.y-model->diag_,z);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,z); glVertex3d(pos.x+model->diag_,pos.y+model->diag_,z);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,gminz); glVertex3d(pos.x+model->diag_,pos.y+model->diag_,gminz);
|
|
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,gminz); glVertex3d(pos.x-model->diag_,pos.y+model->diag_,gminz);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,gminz); glVertex3d(pos.x+model->diag_,pos.y+model->diag_,gminz);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,z); glVertex3d(pos.x+model->diag_,pos.y+model->diag_,z);
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,z); glVertex3d(pos.x-model->diag_,pos.y+model->diag_,z);
|
|
|
|
glVertex3d(pos.x-model->diag_,pos.y-model->diag_,gminz); glVertex3d(pos.x-model->diag_,pos.y-model->diag_,z);
|
|
glVertex3d(pos.x+model->diag_,pos.y-model->diag_,gminz); glVertex3d(pos.x+model->diag_,pos.y-model->diag_,z);
|
|
glVertex3d(pos.x+model->diag_,pos.y+model->diag_,gminz); glVertex3d(pos.x+model->diag_,pos.y+model->diag_,z);
|
|
glVertex3d(pos.x-model->diag_,pos.y+model->diag_,gminz); glVertex3d(pos.x-model->diag_,pos.y+model->diag_,z);
|
|
glEnd();
|
|
}
|