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
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
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
00174 return;
00175 }
00176 const PlotData& src_data = src_data_it->second;
00177
00178
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