custom_function.cpp
Go to the documentation of this file.
00001 #include "custom_function.h"
00002 
00003 #include <limits>
00004 #include <QFile>
00005 #include <QMessageBox>
00006 #include <QElapsedTimer>
00007 
00008 CustomFunction::CustomFunction(const std::string &linkedPlot,
00009                                const SnippetData &snippet):
00010     CustomFunction(linkedPlot,
00011                    snippet.name.toStdString(),
00012                    snippet.globalVars,
00013                    snippet.equation )
00014 {
00015 }
00016 
00017 QStringList CustomFunction::getChannelsFromFuntion(const QString& function)
00018 {
00019     QStringList output;
00020     int offset = 0;
00021     while(true)
00022     {
00023         int pos1 = function.indexOf("$$", offset);
00024         if(pos1 == -1){
00025             break;
00026         }
00027 
00028         int pos2 = function.indexOf("$$", pos1+2);
00029         if(pos2 == -1)
00030         {
00031             return {};
00032         }
00033         output.push_back( function.mid(pos1+2, pos2-pos1-2) );
00034         offset = pos2+2;
00035     }
00036     return output;
00037 }
00038 
00039 CustomFunction::CustomFunction(const std::string &linkedPlot,
00040                                const std::string &plotName,
00041                                const QString &globalVars,
00042                                const QString &function):
00043     _linked_plot_name(linkedPlot),
00044     _plot_name(plotName),
00045     _global_vars(globalVars),
00046     _function(function),
00047     _last_updated_timestamp( - std::numeric_limits<double>::max() )
00048 {
00049 
00050     QString qLinkedPlot = QString::fromStdString(_linked_plot_name);
00051 
00052     QString replaced_equation = _function;
00053     while(true)
00054     {
00055         int pos1=replaced_equation.indexOf("$$");
00056         if(pos1 == -1){
00057             break;
00058         }
00059 
00060         int pos2 = replaced_equation.indexOf("$$", pos1+2);
00061         if(pos2 == -1)
00062         {
00063             throw std::runtime_error("syntax error : invalid use of $$ macro");
00064         }
00065 
00066         QString channel_name = replaced_equation.mid(pos1+2, pos2-pos1-2);
00067 
00068         if(channel_name == qLinkedPlot)
00069         {
00070             // special case : user entered linkedPlot ; no need to add another channel
00071             replaced_equation.replace(QStringLiteral("$$%1$$").arg(channel_name), QStringLiteral("value"));
00072         }
00073         else
00074         {
00075             QString jsExpression = QString("CHANNEL_VALUES[%1]").arg(_used_channels.size());
00076             replaced_equation.replace(QStringLiteral("$$%1$$").arg(channel_name), jsExpression);
00077             _used_channels.push_back(channel_name.toStdString());
00078         }
00079     }
00080     _function_replaced = replaced_equation;
00081 
00082     //qDebug() << "final equation string : " << replaced_equation;
00083     initJsEngine();
00084 }
00085 
00086 void CustomFunction::calculateAndAdd(PlotDataMapRef &plotData)
00087 {
00088     bool newly_added = false;
00089 
00090     auto dst_data_it = plotData.numeric.find(_plot_name);
00091     if(dst_data_it == plotData.numeric.end())
00092     {
00093         dst_data_it = plotData.addNumeric(_plot_name);
00094         newly_added = true;
00095     }
00096 
00097     PlotData& dst_data = dst_data_it->second;
00098     dst_data.clear();
00099 
00100     try{
00101         calculate(plotData, &dst_data);
00102     }
00103     catch(...)
00104     {
00105         if( newly_added )
00106         {
00107             plotData.numeric.erase( dst_data_it );
00108         }
00109         std::rethrow_exception( std::current_exception() );
00110     }
00111 }
00112 
00113 void CustomFunction::initJsEngine()
00114 {
00115     _jsEngine = std::unique_ptr<QJSEngine>( new QJSEngine() );
00116 
00117     QJSValue globalVarResult = _jsEngine->evaluate(_global_vars);
00118     if(globalVarResult.isError())
00119     {
00120         throw std::runtime_error("JS Engine : " + globalVarResult.toString().toStdString());
00121     }
00122     QString calcMethodStr = QString("function calc(time, value, CHANNEL_VALUES){with (Math){\n%1\n}}").arg(_function_replaced);
00123     _jsEngine->evaluate(calcMethodStr);
00124 }
00125 
00126 PlotData::Point CustomFunction::calculatePoint(QJSValue& calcFct,
00127                                 const PlotData& src_data,
00128                                 const std::vector<const PlotData*>& channels_data,
00129                                 QJSValue& chan_values,
00130                                 size_t point_index)
00131 {
00132     const PlotData::Point &old_point = src_data.at(point_index);
00133 
00134     int chan_index = 0;
00135     for(const PlotData* chan_data: channels_data)
00136     {
00137         double value;
00138         int index = chan_data->getIndexFromX(old_point.x);
00139         if(index != -1){
00140             value = chan_data->at(index).y;
00141         }
00142         else{
00143             value = std::numeric_limits<double>::quiet_NaN();
00144         }
00145         chan_values.setProperty(static_cast<quint32>(chan_index++), QJSValue(value));
00146     }
00147 
00148     PlotData::Point new_point;
00149     new_point.x = old_point.x;
00150 
00151     QJSValue jsData = calcFct.call({QJSValue(old_point.x), QJSValue(old_point.y), chan_values});
00152     if(jsData.isError())
00153     {
00154         throw std::runtime_error("JS Engine : " + jsData.toString().toStdString());
00155     }
00156     new_point.y = jsData.toNumber();
00157 
00158     return new_point;
00159 }
00160 
00161 void CustomFunction::calculate(const PlotDataMapRef &plotData, PlotData* dst_data)
00162 {
00163     QJSValue calcFct = _jsEngine->evaluate("calc");
00164 
00165     if(calcFct.isError())
00166     {
00167         throw std::runtime_error("JS Engine : " + calcFct.toString().toStdString());
00168     }
00169 
00170     auto src_data_it = plotData.numeric.find(_linked_plot_name);
00171     if(src_data_it == plotData.numeric.end())
00172     {
00173         // failed! keep it empty
00174         return;
00175     }
00176     const PlotData& src_data = src_data_it->second;
00177 
00178     // clean up old data
00179     dst_data->setMaximumRangeX( src_data.maximumRangeX() );
00180 
00181     std::vector<const PlotData*> channel_data;
00182     channel_data.reserve(_used_channels.size());
00183 
00184     for(const auto& channel: _used_channels)
00185     {
00186         auto it = plotData.numeric.find(channel);
00187         if(it == plotData.numeric.end())
00188         {
00189             throw std::runtime_error("Invalid channel name");
00190         }
00191         const PlotData* chan_data = &(it->second);
00192         channel_data.push_back(chan_data);
00193     }
00194 
00195     QJSValue chan_values = _jsEngine->newArray(static_cast<quint32>(_used_channels.size()));
00196 
00197     for(size_t i=0; i < src_data.size(); ++i)
00198     {
00199         if( src_data.at(i).x > _last_updated_timestamp)
00200         {
00201             dst_data->pushBack( calculatePoint(calcFct, src_data, channel_data, chan_values, i ) );
00202         }
00203     }
00204     if (dst_data->size() != 0){
00205       _last_updated_timestamp = dst_data->back().x;
00206     }
00207 }
00208 
00209 const std::string &CustomFunction::name() const
00210 {
00211     return _plot_name;
00212 }
00213 
00214 const std::string &CustomFunction::linkedPlotName() const
00215 {
00216     return _linked_plot_name;
00217 }
00218 
00219 const QString &CustomFunction::globalVars() const
00220 {
00221     return _global_vars;
00222 }
00223 
00224 const QString &CustomFunction::function() const
00225 {
00226     return _function;
00227 }
00228 
00229 
00230 
00231 QDomElement CustomFunction::xmlSaveState(QDomDocument &doc) const
00232 {
00233     QDomElement snippet = doc.createElement("snippet");
00234     snippet.setAttribute("name", QString::fromStdString(_plot_name) );
00235 
00236     QDomElement linked = doc.createElement("linkedPlot");
00237     linked.appendChild( doc.createTextNode( QString::fromStdString(_linked_plot_name)) );
00238     snippet.appendChild(linked);
00239 
00240     QDomElement global = doc.createElement("global");
00241     global.appendChild( doc.createTextNode(_global_vars) );
00242     snippet.appendChild(global);
00243 
00244     QDomElement equation = doc.createElement("equation");
00245     equation.appendChild( doc.createTextNode(_function) );
00246     snippet.appendChild(equation);
00247 
00248     return snippet;
00249 }
00250 
00251 CustomPlotPtr CustomFunction::createFromXML(QDomElement &element)
00252 {
00253     auto name   = element.attribute("name").toStdString();
00254     auto linkedPlot = element.firstChildElement("linkedPlot").text().trimmed().toStdString();
00255     auto globalVars = element.firstChildElement("global").text().trimmed();
00256     auto calcEquation = element.firstChildElement("equation").text().trimmed();
00257 
00258     return std::make_shared<CustomFunction>(linkedPlot, name, globalVars, calcEquation );
00259 }
00260 
00261 SnippetsMap GetSnippetsFromXML(const QString& xml_text)
00262 {
00263     if( xml_text.isEmpty() ) return {};
00264 
00265     QDomDocument doc;
00266     QString parseErrorMsg;
00267     int parseErrorLine;
00268     if(!doc.setContent(xml_text, &parseErrorMsg, &parseErrorLine))
00269     {
00270         QMessageBox::critical(nullptr, "Error",
00271                               QString("Failed to parse snippets (xml), error %1 at line %2")
00272                               .arg(parseErrorMsg).arg(parseErrorLine));
00273         return {};
00274     }
00275     else
00276     {
00277         QDomElement snippets_element = doc.documentElement();
00278         return GetSnippetsFromXML(snippets_element);
00279     }
00280 }
00281 
00282 SnippetsMap GetSnippetsFromXML(const QDomElement &snippets_element)
00283 {
00284     SnippetsMap snippets;
00285 
00286     for (auto elem = snippets_element.firstChildElement("snippet");
00287          !elem.isNull();
00288          elem = elem.nextSiblingElement("snippet"))
00289     {
00290         SnippetData snippet;
00291         snippet.name = elem.attribute("name");
00292         snippet.globalVars = elem.firstChildElement("global").text().trimmed();
00293         snippet.equation = elem.firstChildElement("equation").text().trimmed();
00294         snippets.insert( {snippet.name, snippet } );
00295     }
00296     return snippets;
00297 }
00298 
00299 QDomElement ExportSnippets(const SnippetsMap &snippets, QDomDocument &doc)
00300 {
00301     auto snippets_root = doc.createElement("snippets");
00302 
00303     for (const auto& it: snippets )
00304     {
00305         const auto& snippet = it.second;
00306 
00307         auto element = doc.createElement("snippet");
00308         element.setAttribute("name", it.first);
00309 
00310         auto global_el = doc.createElement("global");
00311         global_el.appendChild( doc.createTextNode( snippet.globalVars ) );
00312 
00313         auto equation_el = doc.createElement("equation");
00314         equation_el.appendChild( doc.createTextNode( snippet.equation ) );
00315 
00316         element.appendChild( global_el );
00317         element.appendChild( equation_el );
00318         snippets_root.appendChild( element );
00319     }
00320     return snippets_root;
00321 }
00322 


plotjuggler
Author(s): Davide Faconti
autogenerated on Wed Jul 3 2019 19:28:04