virtual void draw(QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &, const QRect &rect) const { RideItem *rideItem = parent->rideItem; if (! rideItem) return; const Zones *zones = rideItem->zones; int zone_range = rideItem->zoneRange(); if (parent->shadeZones() && (zone_range >= 0)) { QList <int> zone_lows = zones->getZoneLows(zone_range); int num_zones = zone_lows.size(); if (num_zones > 0) { for (int z = 0; z < num_zones; z ++) { QRect r = rect; QColor shading_color = zoneColor(z, num_zones); shading_color.setHsv( shading_color.hue(), shading_color.saturation() / 4, shading_color.value() ); r.setLeft(xMap.transform(zone_lows[z])); if (z + 1 < num_zones) r.setRight(xMap.transform(zone_lows[z + 1])); if (r.right() >= r.left()) painter->fillRect(r, shading_color); } } } }
PfPvPlotZoneLabel(PfPvPlot *_parent, int _zone_number) { parent = _parent; zone_number = _zone_number; RideItem *rideItem = parent->rideItem; const Zones *zones = rideItem->zones; int zone_range = rideItem->zoneRange(); setZ(1.0 + zone_number / 100.0); // create new zone labels if we're shading if (zone_range >= 0) { // retrieve zone setup QList <int> zone_lows = zones->getZoneLows(zone_range); QList <QString> zone_names = zones->getZoneNames(zone_range); int num_zones = zone_lows.size(); assert(zone_names.size() == num_zones); if (zone_number < num_zones) { watts = ((zone_number + 1 < num_zones) ? 0.5 * (zone_lows[zone_number] + zone_lows[zone_number + 1]) : ( (zone_number > 0) ? (1.5 * zone_lows[zone_number] - 0.5 * zone_lows[zone_number - 1]) : 2.0 * zone_lows[zone_number])); text = QwtText(zone_names[zone_number]); text.setFont(QFont("Helvetica",24, QFont::Bold)); QColor text_color = zoneColor(zone_number, num_zones); text_color.setAlpha(64); text.setColor(text_color); } } }
PfPvPlotZoneLabel(PfPvPlot *_parent, int _zone_number) { parent = _parent; RideItem *rideItem = parent->rideItem; zone_number = _zone_number; // get zone data from ride or athlete ... const Zones *zones; int zone_range = -1; if (parent->context->isCompareIntervals) { zones = parent->context->athlete->zones(); if (!zones) return; // use first compare interval date if (parent->context->compareIntervals.count()) zone_range = zones->whichRange(parent->context->compareIntervals[0].data->startTime().date()); // still not set if (zone_range == -1) zone_range = zones->whichRange(QDate::currentDate()); } else if (rideItem) { zones = rideItem->zones; zone_range = rideItem->zoneRange(); } else { return; // nulls } setZ(1.0 + zone_number / 100.0); // create new zone labels if we're shading if (zone_range >= 0) { // retrieve zone setup QList <int> zone_lows = zones->getZoneLows(zone_range); QList <QString> zone_names = zones->getZoneNames(zone_range); int num_zones = zone_lows.size(); if(zone_names.size() != num_zones) return; if (zone_number < num_zones) { watts = ((zone_number + 1 < num_zones) ? 0.5 * (zone_lows[zone_number] + zone_lows[zone_number + 1]) : ( (zone_number > 0) ? (1.5 * zone_lows[zone_number] - 0.5 * zone_lows[zone_number - 1]) : 2.0 * zone_lows[zone_number])); text = QwtText(zone_names[zone_number]); text.setFont(QFont("Helvetica",24, QFont::Bold)); QColor text_color = zoneColor(zone_number, num_zones); text_color.setAlpha(64); text.setColor(text_color); } } }
void PfPvPlot::refreshZoneItems() { // clear out any zone curves which are presently defined if (zoneCurves.size()) { QListIterator<QwtPlotCurve *> i(zoneCurves); while (i.hasNext()) { QwtPlotCurve *curve = i.next(); curve->detach(); //delete curve; } } zoneCurves.clear(); // delete any existing power zone labels if (zoneLabels.size()) { QListIterator<PfPvPlotZoneLabel *> i(zoneLabels); while (i.hasNext()) { PfPvPlotZoneLabel *label = i.next(); label->detach(); delete label; } } zoneLabels.clear(); // set zones from ride or athlete and date of // first item in the compare set const Zones *zones; int zone_range = -1; // comparing does zones for items selected not current ride if (context->isCompareIntervals) { zones = context->athlete->zones(); // no athlete zones anyway! if (!zones) return; // use first compare interval date if (context->compareIntervals.count()) { zone_range = zones->whichRange(context->compareIntervals[0].data->startTime().date()); } // still not set if (zone_range == -1) { zone_range = zones->whichRange(QDate::currentDate()); } } else if (rideItem) { zones = rideItem->zones; zone_range = rideItem->zoneRange(); } else { return; // null ride and not compare etc } if (zone_range >= 0) { setCP(zones->getCP(zone_range)); // populate the zone curves QList <int> zone_power = zones->getZoneLows(zone_range); QList <QString> zone_name = zones->getZoneNames(zone_range); int num_zones = zone_power.size(); if (zone_name.size() != num_zones) return; if (num_zones > 0) { QPen *pen = new QPen(); pen->setStyle(Qt::NoPen); QwtArray<double> yvalues; // generate x values for (int z = 0; z < num_zones; z ++) { QwtPlotCurve *curve = new QwtPlotCurve(zone_name[z]); curve->setPen(*pen); QColor brush_color = zoneColor(z, num_zones); brush_color.setHsv(brush_color.hue(), brush_color.saturation() / 4, brush_color.value()); curve->setBrush(brush_color); // fill below the line curve->setZ(1 - 1e-6 * zone_power[z]); // generate data for curve if (z < num_zones - 1) { QwtArray <double> contour_yvalues; int watts = zone_power[z + 1]; int dwatts = (double) watts; for (int i = 0; i < contour_xvalues.size(); i ++) { contour_yvalues.append( (1e6 * contour_xvalues[i] < watts) ? 1e6 : dwatts / contour_xvalues[i]); } curve->setSamples(contour_xvalues, contour_yvalues); } else { // top zone has a curve at "infinite" power QwtArray <double> contour_x; QwtArray <double> contour_y; contour_x.append(contour_xvalues[0]); contour_x.append(contour_xvalues[contour_xvalues.size() - 1]); contour_y.append(1e6); contour_y.append(1e6); curve->setSamples(contour_x, contour_y); } curve->setVisible(shade_zones); curve->attach(this); zoneCurves.append(curve); } delete pen; // generate labels for existing zones for (int z = 0; z < num_zones; z ++) { PfPvPlotZoneLabel *label = new PfPvPlotZoneLabel(this, z); label->setVisible(shade_zones); label->attach(this); zoneLabels.append(label); } } } }
void WWPowerScale::paint(QPainter *painter) { int rnum = -1; // CP etc are not available so draw nothing if (context->athlete->zones(false) == NULL || (rnum = context->athlete->zones(false)->whichRange(QDate::currentDate())) == -1) return; // lets get the zones, CP and PMAX int CP = context->athlete->zones(false)->getCP(rnum); int Pmax = context->athlete->zones(false)->getPmax(rnum); Q_UNUSED(Pmax); // for now ........ int numZones = context->athlete->zones(false)->numZones(rnum); QFontMetrics fontMetrics(workoutWidget()->markerFont); QFontMetrics bfontMetrics(workoutWidget()->bigFont); for(int i=0; i<numZones; i++) { // zoneinfo here QString name, description; int low, high; // get zoneinfo for a given range and zone context->athlete->zones(false)->zoneInfo(rnum, i, name, description, low, high); // draw coordinates int ylow=workoutWidget()->transform(0,low).y(); int yhigh=workoutWidget()->transform(0,high).y(); // stay within left block if (yhigh < workoutWidget()->canvas().y()) yhigh = workoutWidget()->canvas().y(); int ymid = (yhigh + ylow) / 2; // bounding rect for zone color QRect bound(QPoint(workoutWidget()->left().topRight().x()-POWERSCALEWIDTH, yhigh), QPoint(workoutWidget()->left().topRight().x(), ylow)); // draw rect painter->fillRect(bound, QBrush(zoneColor(i,numZones))); // HIGH % LABELS if (i<(numZones-1)) { // percent labels for high only - but skip the last zone its off to infinity.. QString label = QString("%1%").arg(int(double(high) / double(CP) * 100.00f)); QRect textBound = fontMetrics.boundingRect(label); painter->setFont(workoutWidget()->markerFont); painter->setPen(workoutWidget()->markerPen); painter->drawText(QPoint(workoutWidget()->left().right()-SPACING-textBound.width()-POWERSCALEWIDTH, yhigh+(fontMetrics.ascent()/2)), // we use ascent not height to line up numbers label); } // if we have gridlines enabled and don't paint on canvas border if (GRIDLINES && yhigh > workoutWidget()->canvas().y()) { QColor color(zoneColor(i,numZones)); color.setAlpha(128); painter->setPen(QPen(color)); // zone high painter->drawLine(QPoint(workoutWidget()->canvas().x(), yhigh), QPoint(workoutWidget()->canvas().x()+workoutWidget()->canvas().width(), yhigh)); // ZONE LABELS MID CANVAS AND FEINT QRect boundText = bfontMetrics.boundingRect(name); painter->setFont(workoutWidget()->bigFont); painter->drawText(QPoint(workoutWidget()->canvas().center().x() - (boundText.width()/2), ymid+(bfontMetrics.ascent()/2)), // we use ascent not height to line up numbers name); } } // CP ! QPen cppen(GColor(CPLOTMARKER)); cppen.setStyle(Qt::DashLine); painter->setPen(cppen); double CPy = workoutWidget()->transform(0, CP).y(); // zone high painter->drawLine(QPoint(workoutWidget()->canvas().x(), CPy), QPoint(workoutWidget()->canvas().x()+workoutWidget()->canvas().width(), CPy)); }
void CpintPlot::plot_allCurve(CpintPlot *thisPlot, int n_values, const double *power_values) { clear_CP_Curves(); QVector<double> energyBests(n_values); QVector<double> time_values(n_values); // generate an array of time values for (int t = 0; t < n_values; t++) { time_values[t] = (t + 1) / 60.0; energyBests[t] = power_values[t] * time_values[t] * 60.0 / 1000.0; } // generate zones from derived CP value if (cp > 0) { QList <int> power_zone; int n_zones = zones->lowsFromCP(&power_zone, (int) int(cp)); int high = n_values - 1; int zone = 0; while (zone < n_zones && high > 0) { int low = high - 1; int nextZone = zone + 1; if (nextZone >= power_zone.size()) low = 0; else { while ((low > 0) && (power_values[low] < power_zone[nextZone])) --low; } QColor color = zoneColor(zone, n_zones); QString name = zones->getDefaultZoneName(zone); QwtPlotCurve *curve = new QwtPlotCurve(name); if (appsettings->value(this, GC_ANTIALIAS, false).toBool() == true) curve->setRenderHint(QwtPlotItem::RenderAntialiased); QPen pen(color.darker(200)); pen.setWidth(appsettings->value(this, GC_LINEWIDTH, 2.0).toDouble()); curve->setPen(pen); curve->attach(thisPlot); // use a linear gradient color.setAlpha(180); QColor color1 = color; color1.setAlpha(64); QLinearGradient linearGradient(0, 0, 0, height()); linearGradient.setColorAt(0.0, color); linearGradient.setColorAt(1.0, color1); linearGradient.setSpread(QGradient::PadSpread); curve->setBrush(linearGradient); // fill below the line if (series == RideFile::none) { // this is Energy mode curve->setData(time_values.data() + low, energyBests.data() + low, high - low + 1); } else { curve->setData(time_values.data() + low, power_values + low, high - low + 1); } allCurves.append(curve); if (series != RideFile::none || energyBests[high] > 100.0) { QwtText text(name); text.setFont(QFont("Helvetica", 20, QFont::Bold)); color.setAlpha(255); text.setColor(color); QwtPlotMarker *label_mark = new QwtPlotMarker(); // place the text in the geometric mean in time, at a decent power double x, y; if (series == RideFile::none) { x = (time_values[low] + time_values[high]) / 2; y = (energyBests[low] + energyBests[high]) / 5; } else { x = sqrt(time_values[low] * time_values[high]); y = (power_values[low] + power_values[high]) / 5; } label_mark->setValue(x, y); label_mark->setLabel(text); label_mark->attach(thisPlot); allZoneLabels.append(label_mark); } high = low; ++zone; } } // no zones available: just plot the curve without zones else { QwtPlotCurve *curve = new QwtPlotCurve(tr("maximal power")); if (appsettings->value(this, GC_ANTIALIAS, false).toBool() == true) curve->setRenderHint(QwtPlotItem::RenderAntialiased); QPen pen(GColor(CCP)); pen.setWidth(appsettings->value(this, GC_LINEWIDTH, 2.0).toDouble()); curve->setPen(pen); QColor brush_color = GColor(CCP); brush_color.setAlpha(200); curve->setBrush(brush_color); // brush fills below the line if (series == RideFile::none) curve->setData(time_values.data(), energyBests.data(), n_values); else curve->setData(time_values.data(), power_values, n_values); curve->attach(thisPlot); allCurves.append(curve); } // Energy mode is really only interesting in the range where energy is // linear in interval duration--up to about 1 hour. double xmax = (series == RideFile::none) ? 60.0 : time_values[n_values - 1]; if (series == RideFile::vam) thisPlot->setAxisScale(thisPlot->xBottom, (double) 4.993, (double)xmax); else thisPlot->setAxisScale(thisPlot->xBottom, (double) 0.017, (double)xmax); double ymax; if (series == RideFile::none) { int i = std::lower_bound(time_values.begin(), time_values.end(), 60.0) - time_values.begin(); ymax = 10 * ceil(energyBests[i] / 10); } else { ymax = 100 * ceil(power_values[0] / 100); if (ymax == 100) ymax = 5 * ceil(power_values[0] / 5); } thisPlot->setAxisScale(thisPlot->yLeft, 0, ymax); }
void PfPvPlot::refreshZoneItems() { // clear out any zone curves which are presently defined if (zoneCurves.size()) { QListIterator<QwtPlotCurve *> i(zoneCurves); while (i.hasNext()) { QwtPlotCurve *curve = i.next(); curve->detach(); delete curve; } } zoneCurves.clear(); // delete any existing power zone labels if (zoneLabels.size()) { QListIterator<PfPvPlotZoneLabel *> i(zoneLabels); while (i.hasNext()) { PfPvPlotZoneLabel *label = i.next(); label->detach(); delete label; } } zoneLabels.clear(); // give up for a null ride if (! rideItem) return; const Zones *zones = rideItem->zones; int zone_range = rideItem->zoneRange(); if (zone_range >= 0) { setCP(zones->getCP(zone_range)); // populate the zone curves QList <int> zone_power = zones->getZoneLows(zone_range); QList <QString> zone_name = zones->getZoneNames(zone_range); int num_zones = zone_power.size(); assert(zone_name.size() == num_zones); if (num_zones > 0) { QPen *pen = new QPen(); pen->setStyle(Qt::NoPen); QwtArray<double> yvalues; // generate x values for (int z = 0; z < num_zones; z ++) { QwtPlotCurve *curve = new QwtPlotCurve(zone_name[z]); curve->setPen(*pen); QColor brush_color = zoneColor(z, num_zones); brush_color.setHsv(brush_color.hue(), brush_color.saturation() / 4, brush_color.value()); curve->setBrush(brush_color); // fill below the line curve->setZ(1 - 1e-6 * zone_power[z]); // generate data for curve if (z < num_zones - 1) { QwtArray <double> contour_yvalues; int watts = zone_power[z + 1]; int dwatts = (double) watts; for (int i = 0; i < contour_xvalues.size(); i ++) { contour_yvalues.append( (1e6 * contour_xvalues[i] < watts) ? 1e6 : dwatts / contour_xvalues[i]); } curve->setData(contour_xvalues, contour_yvalues); } else { // top zone has a curve at "infinite" power QwtArray <double> contour_x; QwtArray <double> contour_y; contour_x.append(contour_xvalues[0]); contour_x.append(contour_xvalues[contour_xvalues.size() - 1]); contour_y.append(1e6); contour_y.append(1e6); curve->setData(contour_x, contour_y); } curve->setVisible(shade_zones); curve->attach(this); zoneCurves.append(curve); } delete pen; // generate labels for existing zones for (int z = 0; z < num_zones; z ++) { PfPvPlotZoneLabel *label = new PfPvPlotZoneLabel(this, z); label->setVisible(shade_zones); label->attach(this); zoneLabels.append(label); } } } }