dataload_csv.cpp
Go to the documentation of this file.
1 #include "dataload_csv.h"
2 #include <QTextStream>
3 #include <QFile>
4 #include <QMessageBox>
5 #include <QDebug>
6 #include <QSettings>
7 #include <QProgressDialog>
8 #include <QDateTime>
9 #include <QInputDialog>
10 #include <QPushButton>
11 #include "QSyntaxStyle"
12 #include "datetimehelp.h"
13 
14 
15 #include <QStandardItemModel>
16 
17 const int TIME_INDEX_NOT_DEFINED = -2;
18 const int TIME_INDEX_GENERATED = -1;
19 
20 void SplitLine(const QString& line, QChar separator, QStringList& parts)
21 {
22  parts.clear();
23  bool inside_quotes = false;
24  bool quoted_word = false;
25  int start_pos = 0;
26 
27  int quote_start = 0;
28  int quote_end = 0;
29 
30  for (int pos = 0; pos < line.size(); pos++)
31  {
32  if (line[pos] == '"')
33  {
34  if (inside_quotes)
35  {
36  quoted_word = true;
37  quote_end = pos - 1;
38  }
39  else
40  {
41  quote_start = pos + 1;
42  }
43  inside_quotes = !inside_quotes;
44  }
45 
46  bool part_completed = false;
47  bool add_empty = false;
48  int end_pos = pos;
49 
50  if ((!inside_quotes && line[pos] == separator))
51  {
52  part_completed = true;
53  }
54  if (pos + 1 == line.size())
55  {
56  part_completed = true;
57  end_pos = pos + 1;
58  // special case
59  if (line[pos] == separator)
60  {
61  end_pos = pos;
62  add_empty = true;
63  }
64  }
65 
66  if (part_completed)
67  {
68  QString part;
69  if (quoted_word)
70  {
71  part = line.mid(quote_start, quote_end - quote_start + 1);
72  }
73  else
74  {
75  part = line.mid(start_pos, end_pos - start_pos);
76  }
77 
78  parts.push_back(part.trimmed());
79  start_pos = pos + 1;
80  quoted_word = false;
81  inside_quotes = false;
82  }
83  if (add_empty)
84  {
85  parts.push_back(QString());
86  }
87  }
88 }
89 
91 {
92  _extensions.push_back("csv");
93  _delimiter = ',';
95  // setup the dialog
96 
97  _dialog = new QDialog();
98  _ui = new Ui::DialogCSV();
99  _ui->setupUi(_dialog);
100 
102 
103 
104  _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
105 
106  connect(_ui->radioButtonSelect, &QRadioButton::toggled, this, [this](bool checked) {
107  _ui->listWidgetSeries->setEnabled(checked);
108  auto selected = _ui->listWidgetSeries->selectionModel()->selectedIndexes();
109  bool box_enabled = !checked || selected.size() == 1;
110  _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(box_enabled);
111  });
112  connect(_ui->listWidgetSeries, &QListWidget::itemSelectionChanged, this, [this]() {
113  auto selected = _ui->listWidgetSeries->selectionModel()->selectedIndexes();
114  bool box_enabled = _ui->radioButtonIndex->isChecked() || selected.size() == 1;
115  _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(box_enabled);
116  });
117 
118  connect(_ui->listWidgetSeries, &QListWidget::itemDoubleClicked, this,
119  [this]() { emit _ui->buttonBox->accepted(); });
120 
121  connect(_ui->checkBoxDateFormat, &QCheckBox::toggled, this,
122  [this](bool checked) { _ui->lineEditDateFormat->setEnabled(checked); });
123 
124  connect(_ui->dateTimeHelpButton,&QPushButton::clicked, this, [this](){
125  _dateTime_dialog->show();
126  });
127  _ui->rawText->setHighlighter(&_csvHighlighter);
128 
129  QSizePolicy sp_retain = _ui->tableView->sizePolicy();
130  sp_retain.setRetainSizeWhenHidden(true);
131  _ui->tableView->setSizePolicy(sp_retain);
132 
133  _ui->splitter->setStretchFactor(0, 1);
134  _ui->splitter->setStretchFactor(1, 2);
135 
136  _model = new QStandardItemModel;
137  _ui->tableView->setModel(_model);
138 }
139 
141 {
142  delete _ui;
143  delete _dateTime_dialog;
144  delete _dialog;
145 }
146 
147 const std::vector<const char*>& DataLoadCSV::compatibleFileExtensions() const
148 {
149  return _extensions;
150 }
151 
152 void DataLoadCSV::parseHeader(QFile& file, std::vector<std::string>& column_names)
153 {
154  file.open(QFile::ReadOnly);
155 
157 
158  column_names.clear();
159  _ui->listWidgetSeries->clear();
160 
161  QTextStream inA(&file);
162  // The first line should contain the header. If it contains a number, we will
163  // apply a name ourselves
164  QString first_line = inA.readLine();
165 
166  QString preview_lines = first_line + "\n";
167 
168  QStringList firstline_items;
169  SplitLine(first_line, _delimiter, firstline_items);
170 
171  int is_number_count = 0;
172 
173  std::set<std::string> different_columns;
174 
175  // check if all the elements in first row are numbers
176  for (int i = 0; i < firstline_items.size(); i++)
177  {
178  bool isNum;
179  firstline_items[i].trimmed().toDouble(&isNum);
180  if (isNum)
181  {
182  is_number_count++;
183  }
184  }
185 
186  if (is_number_count == firstline_items.size())
187  {
188  for (int i = 0; i < firstline_items.size(); i++)
189  {
190  auto field_name = QString("_Column_%1").arg(i);
191  auto column_name = field_name.toStdString();
192  column_names.push_back(column_name);
193  different_columns.insert(column_name);
194  }
195  }
196  else
197  {
198  for (int i = 0; i < firstline_items.size(); i++)
199  {
200  // remove annoying prefix
201  QString field_name(firstline_items[i].trimmed());
202 
203  if (field_name.isEmpty())
204  {
205  field_name = QString("_Column_%1").arg(i);
206  }
207  auto column_name = field_name.toStdString();
208  column_names.push_back(column_name);
209  different_columns.insert(column_name);
210  }
211  }
212 
213  if (different_columns.size() < column_names.size())
214  {
216  {
217  QMessageBox::warning(nullptr, "Duplicate Column Name",
218  "Multiple Columns have the same name.\n"
219  "The column number will be added (as suffix) to the name.");
221  }
222 
223  std::vector<size_t> repeated_columns;
224  for (size_t i = 0; i < column_names.size(); i++)
225  {
226  repeated_columns.clear();
227  repeated_columns.push_back(i);
228 
229  for (size_t j = i + 1; j < column_names.size(); j++)
230  {
231  if (column_names[i] == column_names[j])
232  {
233  repeated_columns.push_back(j);
234  }
235  }
236  if (repeated_columns.size() > 1)
237  {
238  for (size_t index : repeated_columns)
239  {
240  QString suffix = "_";
241  suffix += QString::number(index).rightJustified(2, '0');
242  column_names[index] += suffix.toStdString();
243  }
244  }
245  }
246  }
247 
248  QStringList column_labels;
249  for (const auto& name : column_names)
250  {
251  auto qname = QString::fromStdString( name );
252  _ui->listWidgetSeries->addItem( qname );
253  column_labels.push_back( qname );
254  }
255  _model->setColumnCount(column_labels.size());
256  _model->setHorizontalHeaderLabels(column_labels);
257 
258  QStringList lines;
259 
260  for( int row = 0; row <= 100 && !inA.atEnd(); row ++)
261  {
262  auto line = inA.readLine();
263  preview_lines += line + "\n";
264  lines.push_back( line );
265  }
266 
267  _model->setRowCount( lines.count() );
268  for(int row = 0; row < lines.count(); row ++)
269  {
270  QVector<QStringRef> lineToken = lines[row].splitRef(_delimiter);
271  for (int j = 0; j < lineToken.size(); j++)
272  {
273  QString value = lineToken[j].toString();
274  if( auto item = _model->item(row, j) )
275  {
276  item->setText(value);
277  }
278  else{
279  _model->setItem(row, j, new QStandardItem(value));
280  }
281  }
282  }
283 
284  _ui->rawText->setPlainText(preview_lines);
285  _ui->tableView->resizeColumnsToContents();
286 
287  file.close();
288 }
289 
290 int DataLoadCSV::launchDialog(QFile& file, std::vector<std::string>* column_names)
291 {
292  column_names->clear();
293  _ui->tabWidget->setCurrentIndex(0);
294 
295  QSettings settings;
296  _dialog->restoreGeometry(settings.value("DataLoadCSV.geometry").toByteArray());
297 
298  _ui->radioButtonIndex->setChecked(
299  settings.value("DataLoadCSV.useIndex", false).toBool());
300  _ui->checkBoxDateFormat->setChecked(
301  settings.value("DataLoadCSV.useDateFormat", false).toBool());
302  _ui->lineEditDateFormat->setText(
303  settings.value("DataLoadCSV.dateFormat", "yyyy-MM-dd hh:mm:ss").toString());
304 
305  // suggest separator
306  {
307  file.open(QFile::ReadOnly);
308  QTextStream in(&file);
309 
310  QString first_line = in.readLine();
311  int comma_count = first_line.count(QLatin1Char(','));
312  int semicolon_count = first_line.count(QLatin1Char(';'));
313  int space_count = first_line.count(QLatin1Char(' '));
314  int tab_count = first_line.count(QLatin1Char('\t'));
315 
316  if (comma_count > 3 && comma_count > semicolon_count)
317  {
318  _ui->comboBox->setCurrentIndex(0);
319  _delimiter = ',';
320  }
321  if (semicolon_count > 3 && semicolon_count > comma_count)
322  {
323  _ui->comboBox->setCurrentIndex(1);
324  _delimiter = ';';
325  }
326  if (space_count > 3 && comma_count == 0 && semicolon_count == 0)
327  {
328  _ui->comboBox->setCurrentIndex(2);
329  _delimiter = ' ';
330  }
331  if (tab_count > 3 && comma_count == 0 && semicolon_count == 0)
332  {
333  _ui->comboBox->setCurrentIndex(3);
334  _delimiter = '\t';
335  } file.close();
336  }
337 
338 
339  QString theme = settings.value("StyleSheet::theme", "light").toString();
340  auto style_path = (theme == "light" ) ? ":/resources/lua_style_light.xml" :
341  ":/resources/lua_style_dark.xml";
342 
343  QFile fl(style_path);
344  if (fl.open(QIODevice::ReadOnly))
345  {
346  auto style = new QSyntaxStyle(this);
347  if (style->load(fl.readAll()))
348  {
349  _ui->rawText->setSyntaxStyle( style );
350  }
351  }
352 
353  // temporary connection
354  std::unique_ptr<QObject> pcontext(new QObject);
355  QObject* context = pcontext.get();
356  QObject::connect(_ui->comboBox, qOverload<int>(&QComboBox::currentIndexChanged),
357  context, [&](int index) {
358  switch (index)
359  {
360  case 0:
361  _delimiter = ',';
362  break;
363  case 1:
364  _delimiter = ';';
365  break;
366  case 2:
367  _delimiter = ' ';
368  break;
369  case 3:
370  _delimiter = '\t';
371  break;
372  }
374  parseHeader(file, *column_names);
375  });
376 
377  // parse the header once and launch the dialog
378  parseHeader(file, *column_names);
379 
380  QString previous_index = settings.value("DataLoadCSV.timeIndex", "").toString();
381  if (previous_index.isEmpty() == false)
382  {
383  auto items = _ui->listWidgetSeries->findItems(previous_index, Qt::MatchExactly);
384  if( items.size() > 0 )
385  {
386  _ui->listWidgetSeries->setCurrentItem(items.front());
387  }
388  }
389 
390  int res = _dialog->exec();
391 
392  settings.setValue("DataLoadCSV.geometry", _dialog->saveGeometry());
393  settings.setValue("DataLoadCSV.useIndex", _ui->radioButtonIndex->isChecked());
394  settings.setValue("DataLoadCSV.useDateFormat", _ui->checkBoxDateFormat->isChecked());
395  settings.setValue("DataLoadCSV.dateFormat", _ui->lineEditDateFormat->text());
396 
397  if (res == QDialog::Rejected)
398  {
399  return TIME_INDEX_NOT_DEFINED;
400  }
401 
402  if (_ui->radioButtonIndex->isChecked())
403  {
404  return TIME_INDEX_GENERATED;
405  }
406 
407  QModelIndexList indexes = _ui->listWidgetSeries->selectionModel()->selectedRows();
408  if (indexes.size() == 1)
409  {
410  int row = indexes.front().row();
411  auto item = _ui->listWidgetSeries->item(row);
412  settings.setValue("DataLoadCSV.timeIndex", item->text());
413  return row;
414  }
415 
416  return TIME_INDEX_NOT_DEFINED;
417 }
418 
420 {
421  bool use_provided_configuration = false;
423 
424  _fileInfo = info;
425  _default_time_axis.clear();
426 
427  if (info->plugin_config.hasChildNodes())
428  {
429  use_provided_configuration = true;
430  xmlLoadState(info->plugin_config.firstChildElement());
431  }
432 
433  QFile file(info->filename);
434  std::vector<std::string> column_names;
435 
436  int time_index = TIME_INDEX_NOT_DEFINED;
437 
438  if (!use_provided_configuration)
439  {
440  time_index = launchDialog(file, &column_names);
441  }
442  else
443  {
444  parseHeader(file, column_names);
445  if (_default_time_axis == "__TIME_INDEX_GENERATED__")
446  {
447  time_index = TIME_INDEX_GENERATED;
448  }
449  else
450  {
451  for (size_t i = 0; i < column_names.size(); i++)
452  {
453  if (column_names[i] == _default_time_axis)
454  {
455  time_index = i;
456  break;
457  }
458  }
459  }
460  }
461 
462  if (time_index == TIME_INDEX_NOT_DEFINED)
463  {
464  return false;
465  }
466 
467  //-----------------------------------
468  bool interrupted = false;
469  int linecount = 0;
470 
471  // count the number of lines first
472  int tot_lines = 0;
473  {
474  file.open(QFile::ReadOnly);
475  QTextStream in(&file);
476  while (!in.atEnd())
477  {
478  in.readLine();
479  tot_lines++;
480  }
481  file.close();
482  }
483 
484  QProgressDialog progress_dialog;
485  progress_dialog.setLabelText("Loading... please wait");
486  progress_dialog.setWindowModality(Qt::ApplicationModal);
487  progress_dialog.setRange(0, tot_lines - 1);
488  progress_dialog.setAutoClose(true);
489  progress_dialog.setAutoReset(true);
490  progress_dialog.show();
491 
492  //---- build plots_vector from header ------
493 
494  std::vector<PlotData*> plots_vector;
495  std::vector<StringSeries*> string_vector;
496  bool sortRequired = false;
497 
498  for (unsigned i = 0; i < column_names.size(); i++)
499  {
500  const std::string& field_name = (column_names[i]);
501  auto num_it = plot_data.addNumeric(field_name);
502  plots_vector.push_back(&(num_it->second));
503 
504  auto str_it = plot_data.addStringSeries(field_name);
505  string_vector.push_back(&(str_it->second));
506  }
507 
508  //-----------------
509  double prev_time = std::numeric_limits<double>::lowest();
510  bool parse_date_format = _ui->checkBoxDateFormat->isChecked();
511  QString format_string = _ui->lineEditDateFormat->text();
512 
513  auto ParseNumber = [&](QString str, bool& is_number) {
514  QString str_trimmed = str.trimmed();
515  double val = str_trimmed.toDouble(&is_number);
516  // handle numbers with comma instead of point as decimal separator
517  if(!is_number)
518  {
519  static QLocale locale_with_comma(QLocale::German);
520  val = locale_with_comma.toDouble(str_trimmed, &is_number);
521  }
522  if (!is_number && parse_date_format && !format_string.isEmpty())
523  {
524  QDateTime ts = QDateTime::fromString(str_trimmed, format_string);
525  is_number = ts.isValid();
526  if (is_number)
527  {
528  val = ts.toMSecsSinceEpoch() / 1000.0;
529  }
530  }
531  return val;
532  };
533 
534  file.open(QFile::ReadOnly);
535  QTextStream in(&file);
536  // remove first line (header)
537  QString header_str = in.readLine();
538  QStringList string_items;
539  QStringList header_string_items;
540 
541  SplitLine(header_str, _delimiter, header_string_items);
542  QString time_header_str;
543  QString t_str;
544  QString prev_t_str;
545 
546  while (!in.atEnd())
547  {
548  QString line = in.readLine();
549  SplitLine(line, _delimiter, string_items);
550 
551  // empty line? just try skipping
552  if(string_items.size() == 0)
553  {
554  continue;
555  }
556 
557  if (string_items.size() != column_names.size())
558  {
559  QMessageBox msgBox;
560  msgBox.setWindowTitle(tr("Error reading file"));
561  msgBox.setText(tr("The number of values at line %1 is %2,\n"
562  "but the expected number of columns is %3.\n"
563  "Aborting...")
564  .arg(linecount + 2)
565  .arg(string_items.size())
566  .arg(column_names.size()));
567 
568  msgBox.setDetailedText(tr("File: \"%1\" \n\n"
569  "Error reading file | Mismatched field count\n"
570  "Delimiter: [%2]\n"
571  "Header fields: %6\n"
572  "Fields on line [%4]: %7\n\n"
573  "File Preview:\n"
574  "[1]%3\n"
575  "[...]\n"
576  "[%4]%5\n").arg(_fileInfo->filename).arg(_delimiter).arg(header_str).arg(linecount+2).arg(line).arg(column_names.size()).arg(string_items.size()));
577 
578  QPushButton *abortButton = msgBox.addButton(QMessageBox::Ok);
579 
580  msgBox.setIcon(QMessageBox::Warning);
581 
582  msgBox.exec();
583 
584  return false;
585  }
586 
587  double t = linecount;
588 
589  if (time_index >= 0)
590  {
591  bool is_number = false;
592  t_str = string_items[time_index];
593  t = ParseNumber(t_str, is_number);
594  time_header_str = header_string_items[time_index];
595 
596  if (!is_number)
597  {
598  QMessageBox msgBox;
599  msgBox.setWindowTitle(tr("Error reading file"));
600  msgBox.setText(tr("Couldn't parse timestamp on line %1 with string \"%2\" . Aborting.\n").arg(linecount+1).arg(t_str));
601 
602  msgBox.setDetailedText(tr("File: \"%1\" \n\n"
603  "Error reading file | Couldn't parse timestamp\n"
604  "Parsing format: [%4]\n"
605  "Time at line %2 : [%3]\n").arg(_fileInfo->filename).arg(linecount + 1).arg(t_str).arg((parse_date_format && !format_string.isEmpty())? format_string: "None"));
606 
607  QPushButton *abortButton = msgBox.addButton(QMessageBox::Ok);
608 
609  msgBox.setIcon(QMessageBox::Warning);
610 
611  msgBox.exec();
612 
613  return false;
614  }
615 
616  if (prev_time > t && !sortRequired)
617  {
618  QMessageBox msgBox;
619  QString timeName;
620  timeName = time_header_str;
621 
622  msgBox.setWindowTitle(tr("Selected time is not monotonic"));
623  msgBox.setText(tr("PlotJuggler detected that the time in this file is non-monotonic. This may indicate an issue with the input data. Continue? (Input file will not be modified but data will be sorted by PlotJuggler)"));
624  msgBox.setDetailedText(tr("File: \"%1\" \n\n"
625  "Selected time is not monotonic\n"
626  "Time Index: %6 [%7]\n"
627  "Time at line %2 : %3\n"
628  "Time at line %4 : %5").arg(_fileInfo->filename).arg(linecount + 1).arg(prev_t_str).arg(linecount + 2).arg(t_str).arg(time_index).arg(timeName));
629 
630  QPushButton *sortButton = msgBox.addButton(tr("Continue"), QMessageBox::ActionRole);
631  QPushButton *abortButton = msgBox.addButton(QMessageBox::Abort);
632  msgBox.setIcon(QMessageBox::Warning);
633  msgBox.exec();
634 
635  if(msgBox.clickedButton() == abortButton)
636  {
637  return false;
638  }
639  else if (msgBox.clickedButton() == sortButton)
640  {
641  sortRequired = true;
642  }
643  else
644  {
645  return false;
646  }
647 
648  }
649 
650  prev_time = t;
651  prev_t_str = t_str;
652 
653  }
654 
655  for (unsigned i = 0; i < string_items.size(); i++)
656  {
657  bool is_number = false;
658  const auto& str = string_items[i];
659  double y = ParseNumber(str, is_number);
660  if (is_number)
661  {
662  plots_vector[i]->pushBack({ t, y });
663  }
664  else
665  {
666  string_vector[i]->pushBack({ t, str.toStdString() });
667  }
668  }
669 
670  if (linecount++ % 100 == 0)
671  {
672  progress_dialog.setValue(linecount);
673  QApplication::processEvents();
674  if (progress_dialog.wasCanceled())
675  {
676  interrupted = true;
677  break;
678  }
679  }
680  }
681 
682  if (interrupted)
683  {
684  progress_dialog.cancel();
685  plot_data.clear();
686  return false;
687  }
688 
689  if (time_index >= 0)
690  {
691  _default_time_axis = column_names[time_index];
692  } else if (time_index == TIME_INDEX_GENERATED)
693  {
694  _default_time_axis = "__TIME_INDEX_GENERATED__";
695  }
696 
697  // cleanups
698  for (unsigned i = 0; i < column_names.size(); i++)
699  {
700  const auto& name = column_names[i];
701  bool is_numeric = true;
702  if (plots_vector[i]->size() == 0 && string_vector[i]->size() > 0)
703  {
704  is_numeric = false;
705  }
706  if (is_numeric)
707  {
708  plot_data.strings.erase(plot_data.strings.find(name));
709  }
710  else
711  {
712  plot_data.numeric.erase(plot_data.numeric.find(name));
713  }
714  }
715  return true;
716 }
717 
718 bool DataLoadCSV::xmlSaveState(QDomDocument& doc, QDomElement& parent_element) const
719 {
720  QDomElement elem = doc.createElement("default");
721  elem.setAttribute("time_axis", _default_time_axis.c_str());
722  elem.setAttribute("delimiter", _ui->comboBox->currentIndex());
723 
724  QString date_format;
725  if (_ui->checkBoxDateFormat->isChecked())
726  {
727  elem.setAttribute("date_format", _ui->lineEditDateFormat->text());
728  }
729 
730  parent_element.appendChild(elem);
731  return true;
732 }
733 
734 bool DataLoadCSV::xmlLoadState(const QDomElement& parent_element)
735 {
736  QDomElement elem = parent_element.firstChildElement("default");
737  if (!elem.isNull())
738  {
739  if (elem.hasAttribute("time_axis"))
740  {
741  _default_time_axis = elem.attribute("time_axis").toStdString();
742  }
743  if (elem.hasAttribute("delimiter"))
744  {
745  int separator_index = elem.attribute("delimiter").toInt();
746  _ui->comboBox->setCurrentIndex(separator_index);
747  switch (separator_index)
748  {
749  case 0:
750  _delimiter = ',';
751  break;
752  case 1:
753  _delimiter = ';';
754  break;
755  case 2:
756  _delimiter = ' ';
757  break;
758  }
759  }
760  if (elem.hasAttribute("date_format"))
761  {
762  _ui->checkBoxDateFormat->setChecked(true);
763  _ui->lineEditDateFormat->setText(elem.attribute("date_format"));
764  }
765  }
766  return true;
767 }
void SplitLine(const QString &line, QChar separator, QStringList &parts)
virtual bool xmlSaveState(QDomDocument &doc, QDomElement &parent_element) const override
Override this method to save the status of the plugin to XML.
void parseHeader(QFile &file, std::vector< std::string > &ordered_names)
QStandardItemModel * _model
Definition: dataload_csv.h:59
virtual bool readDataFromFile(PJ::FileLoadInfo *fileload_info, PlotDataMapRef &destination) override
FileLoadInfo * _fileInfo
Definition: dataload_csv.h:53
Ui::DialogCSV * _ui
Definition: dataload_csv.h:56
std::vector< const char * > _extensions
Definition: dataload_csv.h:45
auto arg(const Char *name, const T &arg) -> detail::named_arg< Char, T >
Definition: core.h:1736
QString filename
name of the file to open
virtual ~DataLoadCSV()
QDialog * _dialog
Definition: dataload_csv.h:55
DateTimeHelp * _dateTime_dialog
Definition: dataload_csv.h:57
virtual const char * name() const override
Name of the plugin type, NOT the particular instance.
Definition: dataload_csv.h:29
TimeseriesMap numeric
Numerical timeseries.
Definition: plotdata.h:38
int launchDialog(QFile &file, std::vector< std::string > *ordered_names)
StringSeriesMap strings
Series of strings.
Definition: plotdata.h:45
bool multiple_columns_warning_
Definition: dataload_csv.h:61
TimeseriesMap::iterator addNumeric(const std::string &name, PlotGroup::Ptr group={})
Definition: plotdata.cpp:51
Class, that describes Qt style parser for QCodeEditor.
const int TIME_INDEX_GENERATED
QCSVHighlighter _csvHighlighter
Definition: dataload_csv.h:51
QChar _delimiter
Definition: dataload_csv.h:49
StringSeriesMap::iterator addStringSeries(const std::string &name, PlotGroup::Ptr group={})
Definition: plotdata.cpp:63
virtual bool xmlLoadState(const QDomElement &parent_element) override
Override this method to load the status of the plugin from XML.
std::string _default_time_axis
Definition: dataload_csv.h:47
span_constexpr std::size_t size(span< T, Extent > const &spn)
Definition: span.hpp:1485
Definition: core.h:1131
QDomDocument plugin_config
Saved configuration from a previous run or a Layout file.
virtual const std::vector< const char * > & compatibleFileExtensions() const override
Provide a list of file extensions that this plugin can open.
const int TIME_INDEX_NOT_DEFINED


plotjuggler
Author(s): Davide Faconti
autogenerated on Mon Jun 19 2023 03:01:01