src/qvgui/qvplot.cpp

Go to the documentation of this file.
00001 /*
00002  *      Copyright (C) 2007, 2008. PARP Research Group.
00003  *      <http://perception.inf.um.es>
00004  *      University of Murcia, Spain.
00005  *
00006  *      This file is part of the QVision library.
00007  *
00008  *      QVision is free software: you can redistribute it and/or modify
00009  *      it under the terms of the GNU Lesser General Public License as
00010  *      published by the Free Software Foundation, version 3 of the License.
00011  *
00012  *      QVision is distributed in the hope that it will be useful,
00013  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  *      GNU Lesser General Public License for more details.
00016  *
00017  *      You should have received a copy of the GNU Lesser General Public
00018  *      License along with QVision. If not, see <http://www.gnu.org/licenses/>.
00019  */
00020 
00024 
00025 #include <qvgui/qvplot.h>
00026 #include <qwt_plot_layout.h>
00027 #include <qwt_scale_draw.h>
00028 #include <qwt_scale_widget.h>
00029 #include <qwt_legend.h>
00030 #include <qwt_legend_item.h>
00031 #include <qwt_plot_canvas.h>
00032 #include <qpainter.h>
00033 
00034 QColor QVPlot::colors[] = {
00035                 Qt::red,
00036                 Qt::green,
00037                 Qt::blue,
00038                 Qt::cyan,
00039                 Qt::magenta,
00040                 Qt::darkRed,
00041                 Qt::darkGreen,
00042                 Qt::darkBlue,
00043                 Qt::darkCyan,
00044                 Qt::darkMagenta
00045                 };
00046 
00048 // auxiliar classes functionality
00049 QVPlot::PieMarker::PieMarker(QVPlot *plot): qvplot(plot)
00050         {
00051         setZ(1000);
00052         setRenderHint(QwtPlotItem::RenderAntialiased, true);
00053         }
00054 
00055 int QVPlot::PieMarker::rtti() const
00056         {
00057         return QwtPlotItem::Rtti_PlotUserItem;
00058         }
00059 
00060 void QVPlot::PieMarker::draw(QPainter *p, const QwtScaleMap &, const QwtScaleMap &, const QRect &rect) const
00061         {
00062         const int margin = 5;
00063 
00064         QRect pieRect;
00065         pieRect.setX(rect.x() + margin);
00066         pieRect.setY(rect.y() + margin);
00067 
00068         int diameter = ( (rect.height() > rect.width()) ? rect.width(): rect.height() ) / 4;
00069         pieRect.setHeight(diameter);
00070         pieRect.setWidth(diameter);
00071 
00072         int angle = (int)(5760 * 0.75);
00073 
00074         double sum = 0;
00075         for(int i = 0; i < qvplot->linkCont.size(); i++)
00076                 for(int j = 0; j < qvplot->linkCont[i].properties.size(); j++)
00077                         for(int k = 0; k < qvplot->linkCont[i].properties[j].curves.size(); k++)
00078                                 sum += qvplot->getValue(i, j, k);
00079 
00080 
00081         for(int i = 0; i < qvplot->linkCont.size(); i++)
00082                 for(int j = 0; j < qvplot->linkCont[i].properties.size(); j++)
00083                         for(int k = 0; k < qvplot->linkCont[i].properties[j].curves.size(); k++)
00084                                 {
00085                                 const QwtPlotCurve *curve = qvplot->linkCont[i].properties[j].curves[k].plot;
00086                                 if ( curve->dataSize() > 0 )
00087                                         {
00088                                         const int value = (int)(5760 * qvplot->getValue(i, j, k) / sum);
00089                                         p->save();
00090                                         p->setBrush(QBrush(curve->pen().color(), Qt::SolidPattern));
00091                                         if ( value != 0 )
00092                                                 p->drawPie(pieRect, -angle, -value);
00093                                         p->restore();
00094                 
00095                                         angle += value;
00096                                         }
00097                                 }
00098         }
00099 
00101 
00102 QVPlot::QVPlot(const QString name, bool decorations, bool havePie, bool brush, bool _autoShow, bool time, int step, QWidget *parent): QVPropertyContainer(name),
00103         byTime(time), iterationIndex(1), decorations(decorations), hPie(havePie), doBrush(brush), nStep(step), initied(false), autoShow(_autoShow), timer(0),
00104         haveCurves(FALSE), activeWorkers(0), dataCount(0), usedColors(0)
00105         {
00106         qDebug() << "QVPlot::QVPlot()";
00107         if (qvApp == NULL)
00108                 {
00109                 QString str = "QVPlot::QVPlot(): the QVPlot cannot be created before the QVApplication instance. Aborting now.";
00110                 std::cerr << qPrintable(str) << std::endl;
00111                 exit(1);
00112                 }
00113         // if its a --help call, do nothing
00114         if (qvApp->forHelp()) return;
00115         else QwtPlot(parent);
00116 
00117 
00118         setWindowTitle("QVPlot for " + getName());
00119         resize(400,200);
00120         setAutoReplot(false);
00121 
00122         for ( int i = 0; i < MAX_HISTORY; i++ )
00123                 timeData[MAX_HISTORY - 1 - i] = i;
00124 
00125         plotLayout()->setAlignCanvasToScales(true);
00126 
00127         if (decorations)
00128                 {
00129                 QwtLegend *legend = new QwtLegend;
00130                 legend->setItemMode(QwtLegend::CheckableItem);
00131                 insertLegend(legend, QwtPlot::RightLegend);
00132                 }
00133         else
00134                 {
00135                 enableAxis(0,false);
00136                 enableAxis(1,false);
00137                 enableAxis(2,false);
00138                 }
00139 
00140         class TimeScaleDraw: public QwtScaleDraw
00141                 {
00142                 public:
00143                         TimeScaleDraw(const int stp): baseTime(), step(stp) { }
00144                         virtual QwtText label(double v) const { return baseTime.addSecs( (int)((v-MAX_HISTORY)*step/100)).toString(); }
00145                 private:
00146                         QTime baseTime;
00147                         int step;
00148                 };
00149         class IterScaleDraw: public QwtScaleDraw
00150                 {
00151                 public:
00152                         IterScaleDraw(const int stp): step(stp) { }
00153                         virtual QwtText label(double v) const   { return QString::number((int)(v-MAX_HISTORY)*step); }
00154                 private:
00155                         int step;
00156                 };
00157         if (byTime)     setAxisScaleDraw(QwtPlot::xBottom, new TimeScaleDraw(nStep));
00158         else            setAxisScaleDraw(QwtPlot::xBottom, new IterScaleDraw(nStep));
00159 
00160         setAxisScale(QwtPlot::xBottom, 0, MAX_HISTORY);
00161         setAxisLabelRotation(QwtPlot::xBottom, -50.0);
00162         setAxisLabelAlignment(QwtPlot::xBottom, Qt::AlignLeft | Qt::AlignBottom);
00163 
00164         /* In situations, when there is a label at the most right position of the
00165         scale, additional space is needed to display the overlapping part
00166         of the label would be taken by reducing the width of scale and canvas.
00167         To avoid this "jumping canvas" effect, we add a permanent margin.
00168         We don't need to do the same for the left border, because there
00169         is enough space for the overlapping label below the left scale. */
00170 
00171         QwtScaleWidget *scaleWidget = axisWidget(QwtPlot::xBottom);
00172         const int fmh = QFontMetrics(scaleWidget->font()).height();
00173         scaleWidget->setMinBorderDist(0, fmh / 2);
00174 
00175         setAxisTitle(QwtPlot::yLeft, "Value");
00176         if (byTime)     setAxisTitle(QwtPlot::xBottom, "Time");
00177         else            setAxisTitle(QwtPlot::xBottom, "Iterations");
00178         }
00179 
00180 bool QVPlot::linkProperty(QVWorker &worker, const QString propertyName)
00181         {
00182         if (initied)
00183                 {
00184                 std::cerr << "Warning: a worker can't be linked before plot was initied." << std::endl;
00185                 return false;
00186                 }
00187 
00188         bool newWorker = false;
00189         if (!pcl_Workers.contains(&worker))
00190                 {
00191                 pcl_Workers << &worker;
00192                 if ( (!byTime) && (!QVPropertyContainer::areSynchronized(pcl_Workers)) )
00193                         {
00194                         pcl_Workers.removeLast();
00195                         std::cerr << "Warning: linked property's worker must be synchronized with previous plot's workers." << std::endl;
00196                         return false;
00197                         }
00198                 activeWorkers++;
00199                 newWorker = true;
00200                 }
00201 
00202         QString myPropertyName = QString("%1").arg(worker.getId()) + ": " + propertyName;
00203         addPropertyFromQVariant(myPropertyName, inputFlag, worker.getPropertyQVariantValue(propertyName), worker.getPropertyInfo(propertyName));
00204         bool result = worker.linkProperty(propertyName, this, myPropertyName, QVWorker::AsynchronousLink);
00205 
00206         if (newWorker)
00207                 {
00208                 linkCont << LinkedContainer(worker.getId());
00209 
00210                 // este tipo de bloqueo pos iteraciones puede retardar al worker
00211                 QObject::connect(&worker, SIGNAL(endIteration(uint, int)), this, SLOT(update(uint, int)), Qt::BlockingQueuedConnection);
00212                 QObject::connect(&worker, SIGNAL(statusUpdate(QVWorker::TWorkerStatus)), this, SLOT(workerChange(QVWorker::TWorkerStatus)));
00213                 }
00214 
00215         for (int i = 0; i < linkCont.size(); i++)
00216                 if (linkCont[i].id == worker.getId())
00217                         {
00218                         linkCont[i].properties.append(Property(myPropertyName));
00219                         break;
00220                         }
00221 
00222         return result;
00223         }
00224 
00225 bool QVPlot::unlink(QVWorker *worker, const QString propertyName)
00226         {
00227         if (initied)
00228                 {
00229                 std::cerr << "Warning: a worker can't be linked before plot was initied." << std::endl;
00230                 return false;
00231                 }
00232 
00233         QString myPropertyName = QString("%1").arg(worker->getId()) + ": " + propertyName;
00234         if (worker->unlinkProperty(propertyName, this, myPropertyName))
00235                 {
00236                 for (int i = 0; i < linkCont.size(); i++)
00237                         if (linkCont[i].id == worker->getId())
00238                                 {
00239                                 int pos = -1;
00240                                 for (int j = 0; j < linkCont[i].properties.size(); j++)
00241                                         if (linkCont[i].properties[j].name == myPropertyName)
00242                                                 pos = j;
00243         
00244                                 if (pos >= 0)
00245                                         {
00246                                         linkCont[i].properties.removeAt(pos);
00247                                         }
00248         
00249                                 if (linkCont[i].properties.size() == 0)
00250                                         {
00251                                         linkCont.removeAt(i);
00252                                         pcl_Workers.removeAll(worker);
00253                                         activeWorkers--;
00254                                         QObject::disconnect(worker, SIGNAL(endIteration(uint, int)), this, SLOT(update(uint, int)));
00255                                         QObject::disconnect(worker, SIGNAL(statusUpdate(QVWorker::TWorkerStatus)), this, SLOT(workerChange(QVWorker::TWorkerStatus)));
00256                                         }
00257                                 break;
00258                                 }
00259                 removeProperty(myPropertyName);
00260                 return true;
00261                 }
00262         return false;
00263         }
00264 
00265 void QVPlot::init()
00266         {
00267         if (initied)
00268                 {
00269                 std::cerr << "Warning: a plot can't be initied more than one time." << std::endl;
00270                 return;
00271                 }
00272 
00273         if (hPie)
00274                 {
00275                 pie = new PieMarker(this);
00276                 pie->attach(this);
00277                 }
00278 
00279         readInputProperties();
00280         for(int i = 0; i < linkCont.size(); i++)
00281                 for(int j = 0; j < linkCont[i].properties.size(); j++)
00282                         {
00283                         const QStringList curvNames = getPropertyCurvNames(linkCont[i].properties[j].name);
00284 
00285                         for(int k = curvNames.size()-1; k >= 0; k--)
00286                                 {
00287                                 QwtPlotCurve * qwtpc = new QwtPlotCurve(curvNames.at(k));
00288                                 qwtpc->setRenderHint(QwtPlotItem::RenderAntialiased);
00289                                 QColor color = nextColor();
00290                                 qwtpc->setPen(color);
00291                                 if (doBrush) qwtpc->setBrush(color);
00292                                 qwtpc->setVisible(true);
00293                                 qwtpc->attach(this);
00294 
00295                                 if (byTime) linkCont[i].properties[j].curves.prepend(Curve(curvNames.at(k), qwtpc, 1));
00296                                 else        linkCont[i].properties[j].curves.prepend(Curve(curvNames.at(k), qwtpc, linkCont.size()+1));
00297                                 haveCurves = TRUE;
00298                                 }
00299                         }
00300 
00301         if (byTime) timer = startTimer(nStep * 10); // nStep * 10 ms
00302 
00303         initied = true;
00304         }
00305 
00306 void QVPlot::stop()
00307         {
00308         if (byTime && initied) killTimer(timer);
00309         initied = false;
00310         }
00311 
00312 QColor QVPlot::nextColor()
00313         {
00314         QColor color = colors[usedColors % 10];
00315         usedColors++;
00316         return color;
00317         }
00318 
00319 void QVPlot::timerEvent(QTimerEvent *)
00320         {
00321         if (!initied) 
00322                 {
00323                 std::cerr << "Warning: a plot can't be advanced before it was initied." << std::endl;
00324                 return;
00325                 }
00326 
00327         // if there are data of flags, advance the Plot
00328         if (haveCurves) advancePlot();
00329         for (int i = 0; i < linkCont.size(); i++) linkCont[i].meanItems = 0;
00330         }
00331 
00332 void QVPlot::workerChange(QVWorker::TWorkerStatus status)
00333         {
00334         if ( (status == QVWorker::Finished) && (activeWorkers > 0) ) activeWorkers--;
00335         }
00336 
00337 void QVPlot::update(uint id, int)
00338         {
00339         // First must be initied
00340         if (!initied) 
00341                 {
00342                 std::cerr << "Warning: a plot can't be advanced before it was initied." << std::endl;
00343                 return;
00344                 }
00345 
00346         if (byTime)
00347                 {
00348                 readInputProperties();
00349                 for(int i = 0; i < linkCont.size(); i++)
00350                         if (linkCont[i].id == id)
00351                                 {
00352                                 for(int j = 0; j < linkCont[i].properties.size(); j++)
00353                                         {
00354                                         insertNewFlags(i, j);
00355                 
00356                                         const QList<double> values = getPropertyCurvValues(linkCont[i].properties[j].name);
00357                                         if (values.size() != linkCont[i].properties[j].curves.size())
00358                                                 {
00359                                                 std::cerr << "QVPlot internal error: flags insert." << std::endl;
00360                                                 return;
00361                                                 }
00362                 
00363                                         for (int k = 0; k < linkCont[i].properties[j].curves.size(); k++)
00364                                                 if (linkCont[i].meanItems < 1)
00365                                                         linkCont[i].properties[j].curves[k].temp[0] = values[k];
00366                                                 else
00367                                                         linkCont[i].properties[j].curves[k].temp[0] =
00368                                                         (linkCont[i].properties[j].curves[k].temp[0]*linkCont[i].meanItems+values[k]) / (linkCont[i].meanItems + 1);
00369                                         linkCont[i].meanItems++;
00370                                         }
00371                                 break;
00372                                 }
00373                 }
00374         else
00375                 {
00376                 readInputProperties();
00377                 for(int i = 0; i < linkCont.size(); i++)
00378                         if (linkCont[i].id == id)
00379                                 {
00380                                 linkCont[i].iter++;
00381                                 for(int j = 0; j < linkCont[i].properties.size(); j++)
00382                                         {
00383                                         insertNewFlags(i, j);
00384                 
00385                                         const QList<double> values = getPropertyCurvValues(linkCont[i].properties[j].name);
00386                                         if (values.size() != linkCont[i].properties[j].curves.size())
00387                                                 {
00388                                                 std::cerr << "QVPlot internal error: flags insert." << std::endl;
00389                                                 return;
00390                                                 }
00391 
00392                                         // update worker's properties values for its iteration in the "temp" buffer (add the new value)
00393                                         for (int k = 0; k < linkCont[i].properties[j].curves.size(); k++)
00394                                                 linkCont[i].properties[j].curves[k].temp[(linkCont[i].iter % (linkCont.size()+1))] += values[k];
00395                                         }
00396                                 break;
00397                                 }
00398 
00399                 int updateWorkers = 0;
00400                 for (int i = 0; i < linkCont.size(); i++)
00401                         if (linkCont[i].iter >= iterationIndex) updateWorkers++;
00402 
00403                 // If has completed the current iteration column
00404                 if (updateWorkers >= activeWorkers)
00405                         {
00406                         // If has completed nStep iterations
00407                         if ((iterationIndex % nStep) == 0)
00408                                 {
00409                                 // obtain the mean to it
00410                                 for(int i = 0; i < linkCont.size(); i++)
00411                                         {
00412                                         linkCont[i].meanItems++;
00413                                         for(int j = 0; j < linkCont[i].properties.size(); j++)
00414                                                 for (int k = 0; k < linkCont[i].properties[j].curves.size(); k++)
00415                                                         linkCont[i].properties[j].curves[k].temp[(iterationIndex % (linkCont.size()+1))] /= linkCont[i].meanItems;
00416                                         linkCont[i].meanItems = 0;
00417                                         }
00418 
00419                                 // Advance the plot
00420                                 if (haveCurves) advancePlot();
00421                                 }
00422                         else
00423                                 {
00424                                 for(int i = 0; i < linkCont.size(); i++)
00425                                         {
00426                                         linkCont[i].meanItems++;
00427                                         for(int j = 0; j < linkCont[i].properties.size(); j++)
00428                                                 for (int k = 0; k < linkCont[i].properties[j].curves.size(); k++)
00429                                                         linkCont[i].properties[j].curves[k].temp[((iterationIndex+1) % (linkCont.size()+1))] += linkCont[i].properties[j].curves[k].temp[(iterationIndex % (linkCont.size()+1))];
00430                                         }
00431                                 }
00432                         // And reset the column
00433                         for(int i = 0; i < linkCont.size(); i++)
00434                                 for(int j = 0; j < linkCont[i].properties.size(); j++)
00435                                         for (int k = 0; k < linkCont[i].properties[j].curves.size(); k++)
00436                                                 linkCont[i].properties[j].curves[k].temp[(iterationIndex % (linkCont.size()+1))] = 0;
00437                         iterationIndex++;
00438                         }
00439                 }
00440         }
00441 
00442 void QVPlot::insertNewFlags(int cont, int prop)
00443         {
00444         const QStringList curvNames = getPropertyCurvNames(linkCont[cont].properties[prop].name);
00445         if ( (linkCont.size() > cont) && (linkCont[cont].properties.size() > prop) && (curvNames.size() > linkCont[cont].properties[prop].curves.size()) )
00446                 {
00447                 const QList<int> curvOrders = getPropertyCurvOrders(linkCont[cont].properties[prop].name);
00448                 for (int i = 0; i < curvOrders.size(); i++)
00449                         if (curvOrders.at(i) > linkCont[cont].properties[prop].curves.size())
00450                                 {
00451                                 QwtPlotCurve * qwtpc = new QwtPlotCurve(curvNames.at(i));
00452                                 qwtpc->setRenderHint(QwtPlotItem::RenderAntialiased);
00453                                 QColor color = nextColor();
00454                                 qwtpc->setPen(color);
00455                                 if (doBrush) qwtpc->setBrush(color);
00456                                 qwtpc->setVisible(true);
00457 
00458                                 if (byTime) linkCont[cont].properties[prop].curves.insert(i, Curve(curvNames.at(i), qwtpc, 1));
00459                                 else        linkCont[cont].properties[prop].curves.insert(i, Curve(curvNames.at(i), qwtpc, linkCont.size()+1));
00460                                 haveCurves = TRUE;
00461                                 }
00462 
00463                 for(int i = linkCont.size()-1; i >= 0; i--)
00464                         for(int j = linkCont[i].properties.size()-1; j >= 0; j--)
00465                                 for (int k = linkCont[i].properties[j].curves.size()-1; k >= 0; k--)
00466                                         {
00467                                         linkCont[i].properties[j].curves[k].plot->detach();
00468                                         linkCont[i].properties[j].curves[k].plot->attach(this);
00469                                         }
00470                 }
00471         }
00472 
00473 void QVPlot::advancePlot()
00474         {
00475         if (!haveCurves)
00476                 {
00477                 std::cerr << "QVPlot internal error: early call to advancePlot." << std::endl;
00478                 return;
00479                 }
00480 
00481         // update data
00482         for(int i = 0; i < linkCont.size(); i++)
00483                 for(int j = 0; j < linkCont[i].properties.size(); j++)
00484                         for(int k = 0; k < linkCont[i].properties[j].curves.size(); k++)
00485                                 {
00486                                 for (int l = dataCount; l > 0; l--) // scroll the row
00487                                         if (l < MAX_HISTORY) linkCont[i].properties[j].curves[k].history[l] = linkCont[i].properties[j].curves[k].history[l-1];
00488                                 updateValue(i, j, k); // and puss the new value
00489                                 }
00490 
00491         // increments the values to scroll the window
00492         if ( dataCount < MAX_HISTORY ) dataCount++;
00493         for ( int j = 0; j < MAX_HISTORY; j++ )
00494                 timeData[j]++;
00495 
00496         // scroll bottom axis
00497         setAxisScale(QwtPlot::xBottom, timeData[MAX_HISTORY - 1], timeData[0]);
00498 
00499         // curve update
00500         for(int i = 0; i < linkCont.size(); i++)
00501                 for(int j = 0; j < linkCont[i].properties.size(); j++)
00502                         for(int k = 0; k < linkCont[i].properties[j].curves.size(); k++)
00503                                 linkCont[i].properties[j].curves[k].plot->setRawData(timeData, linkCont[i].properties[j].curves[k].history, dataCount);
00504 
00505         // adjust left axis (using the visible values's maximum)
00506         double max = 0;
00507 
00508         for(int i = 0; i < linkCont.size(); i++)
00509                 for(int j = 0; j < linkCont[i].properties.size(); j++)
00510                         for(int k = 0; k < linkCont[i].properties[j].curves.size(); k++)
00511                                 for (int l = dataCount-1; l >= 0; l--)
00512                                         if (max < linkCont[i].properties[j].curves[k].history[l]) max = linkCont[i].properties[j].curves[k].history[l];
00513         max = 1.1 *max;
00514         setAxisScale(QwtPlot::yLeft, 0, max);
00515 
00516         // and replot
00517         replot();
00518         }
00519 
00520 void QVPlot::updateValue(const int cont, const int prop, const int curv)
00521         {
00522         double prevValue = 0;
00523         if (doBrush)
00524                 {
00525                 if (curv > 0)
00526                         prevValue = linkCont[cont].properties[prop].curves[curv-1].history[0];
00527                 else if (prop > 0)
00528                         {
00529                         int prevCurves = linkCont[cont].properties[prop-1].curves.size();
00530                         if (prevCurves > 0) prevValue = linkCont[cont].properties[prop-1].curves[prevCurves-1].history[0];
00531                         }
00532                 else if (cont > 0)
00533                         {
00534                         int prevProps = linkCont[cont-1].properties.size();
00535                         if (prevProps > 0)
00536                                 {
00537                                 int prevCurves = linkCont[cont-1].properties[prevProps-1].curves.size();
00538                                 if (prevCurves > 0) prevValue = linkCont[cont-1].properties[prevProps-1].curves[prevCurves-1].history[0];
00539                                 }
00540                         }
00541                 }
00542 
00543         if (byTime) linkCont[cont].properties[prop].curves[curv].history[0] = linkCont[cont].properties[prop].curves[curv].temp[0] + prevValue;
00544         else        linkCont[cont].properties[prop].curves[curv].history[0] =
00545                     linkCont[cont].properties[prop].curves[curv].temp[(iterationIndex % (linkCont.size()+1))] + prevValue;
00546         }
00547 
00548 double QVPlot::getValue(const int cont, const int prop, const int curv) const
00549         {
00550         double prevValue = 0;
00551         if (doBrush)
00552                 {
00553                 if (curv > 0)
00554                         prevValue = linkCont[cont].properties[prop].curves[curv-1].history[0];
00555                 else if (prop > 0)
00556                         {
00557                         int prevCurves = linkCont[cont].properties[prop-1].curves.size();
00558                         if (prevCurves > 0) prevValue = linkCont[cont].properties[prop-1].curves[prevCurves-1].history[0];
00559                         }
00560                 else if (cont > 0)
00561                         {
00562                         int prevProps = linkCont[cont-1].properties.size();
00563                         if (prevProps > 0)
00564                                 {
00565                                 int prevCurves = linkCont[cont-1].properties[prevProps-1].curves.size();
00566                                 if (prevCurves > 0) prevValue = linkCont[cont-1].properties[prevProps-1].curves[prevCurves-1].history[0];
00567                                 }
00568                         }
00569                 }
00570 
00571         return (linkCont[cont].properties[prop].curves[curv].history[0] - prevValue);
00572         }
00573