1 /******************************************************************************
2 
3   This source file is part of the Avogadro project.
4 
5   Copyright 2018 Kitware, Inc.
6 
7   This source code is released under the New BSD License, (the "License").
8 
9   Unless required by applicable law or agreed to in writing, software
10   distributed under the License is distributed on an "AS IS" BASIS,
11   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   See the License for the specific language governing permissions and
13   limitations under the License.
14 
15 ******************************************************************************/
16 
17 #include <iostream>
18 #include <string>
19 
20 #include <vtkAxis.h>
21 #include <vtkChartXY.h>
22 #include <vtkContextScene.h>
23 #include <vtkContextView.h>
24 #include <vtkDoubleArray.h>
25 #include <vtkFloatArray.h>
26 #include <vtkGenericOpenGLRenderWindow.h>
27 #include <vtkNew.h>
28 #include <vtkPen.h>
29 #include <vtkPlot.h>
30 #include <vtkRenderWindowInteractor.h>
31 #include <vtkRenderer.h>
32 #include <vtkStringArray.h>
33 #include <vtkTable.h>
34 #include <vtkTextProperty.h>
35 
36 #include <QSurfaceFormat>
37 #include <QVTKOpenGLWidget.h>
38 
39 #include "vtkplot.h"
40 
41 using std::array;
42 using std::string;
43 using std::vector;
44 
45 namespace Avogadro {
46 namespace VTK {
47 
VtkPlot()48 VtkPlot::VtkPlot() : m_widget(new QVTKOpenGLWidget)
49 {
50   m_widget->SetRenderWindow(m_renderWindow);
51 
52   // Set up the view
53   m_widget->setFormat(QVTKOpenGLWidget::defaultFormat());
54   m_view->SetRenderWindow(m_renderWindow);
55   m_view->GetRenderer()->SetBackground(1.0, 1.0, 1.0);
56   m_view->GetRenderWindow()->SetSize(600, 600);
57 
58   // Add the chart
59   m_view->GetScene()->AddItem(m_chart);
60 
61   vtkAxis* bottomAxis = m_chart->GetAxis(vtkAxis::BOTTOM);
62   vtkAxis* leftAxis = m_chart->GetAxis(vtkAxis::LEFT);
63 
64   // Increase the title font sizes
65   bottomAxis->GetTitleProperties()->SetFontSize(20);
66   leftAxis->GetTitleProperties()->SetFontSize(20);
67 
68   // Increase the tick font sizes
69   bottomAxis->GetLabelProperties()->SetFontSize(20);
70   leftAxis->GetLabelProperties()->SetFontSize(20);
71 }
72 
73 VtkPlot::~VtkPlot() = default;
74 
setData(const vector<vector<double>> & data)75 void VtkPlot::setData(const vector<vector<double>>& data)
76 {
77   if (data.size() < 2) {
78     std::cerr << "Error in " << __FUNCTION__
79               << ": data must be of size 2 or greater!\n";
80     return;
81   }
82 
83   // All of the rows must be equal in size currently. Otherwise, we get
84   // a garbage plot. We may be able to improve on this in the future.
85   size_t numRows = data[0].size();
86   for (size_t i = 1; i < data.size(); ++i) {
87     if (data[i].size() != numRows) {
88       std::cerr << "Error in " << __FUNCTION__ << ": all of the data must "
89                 << "have the same number of rows!\n";
90       return;
91     }
92   }
93 
94   // Erase the current table
95   while (m_table->GetNumberOfRows() > 0)
96     m_table->RemoveRow(0);
97 
98   for (size_t i = 0; i < data.size(); ++i) {
99     vtkNew<vtkFloatArray> array;
100     // Unique column names are necessary to prevent vtk from crashing.
101     array->SetName(("Column " + std::to_string(i)).c_str());
102     m_table->AddColumn(array);
103   }
104 
105   // Put the data in the table
106   m_table->SetNumberOfRows(numRows);
107   for (size_t i = 0; i < data.size(); ++i) {
108     for (size_t j = 0; j < data[i].size(); ++j) {
109       m_table->SetValue(j, i, data[i][j]);
110     }
111   }
112 }
113 
setWindowName(const char * windowName)114 void VtkPlot::setWindowName(const char* windowName)
115 {
116   m_view->GetRenderWindow()->SetWindowName(windowName);
117 }
118 
setXTitle(const char * xTitle)119 void VtkPlot::setXTitle(const char* xTitle)
120 {
121   vtkAxis* bottomAxis = m_chart->GetAxis(vtkAxis::BOTTOM);
122   bottomAxis->SetTitle(xTitle);
123 }
124 
setYTitle(const char * yTitle)125 void VtkPlot::setYTitle(const char* yTitle)
126 {
127   vtkAxis* leftAxis = m_chart->GetAxis(vtkAxis::LEFT);
128   leftAxis->SetTitle(yTitle);
129 }
130 
setCustomTickLabels(Axis _axis,const vector<double> & customTickPositions,const vector<string> & customTickLabels)131 void VtkPlot::setCustomTickLabels(Axis _axis,
132                                   const vector<double>& customTickPositions,
133                                   const vector<string>& customTickLabels)
134 {
135   vtkAxis* axis = getAxis(_axis);
136   if (!axis) {
137     std::cerr << "Error in " << __FUNCTION__ << ": invalid axis\n";
138     return;
139   }
140 
141   // These must be equal in size
142   if (customTickPositions.size() != customTickLabels.size()) {
143     std::cerr << "Error in " << __FUNCTION__ << ": custom tick labels "
144               << "must be equal in size to custom tick positions!\n";
145     return;
146   }
147 
148   vtkNew<vtkDoubleArray> doubleArray;
149   doubleArray->SetName("Custom Tick Positions");
150   for (const auto& pos : customTickPositions)
151     doubleArray->InsertNextValue(pos);
152 
153   vtkNew<vtkStringArray> stringArray;
154   stringArray->SetName("Custom Tick Labels");
155 
156   for (const auto& label : customTickLabels)
157     stringArray->InsertNextValue(label);
158 
159   axis->SetCustomTickPositions(doubleArray, stringArray);
160 }
161 
convertLineStyleEnum(VTK::VtkPlot::LineStyle style)162 static int convertLineStyleEnum(VTK::VtkPlot::LineStyle style)
163 {
164   using LineStyle = VTK::VtkPlot::LineStyle;
165 
166   if (style == LineStyle::noLine)
167     return vtkPen::NO_PEN;
168   else if (style == LineStyle::solidLine)
169     return vtkPen::SOLID_LINE;
170   else if (style == LineStyle::dashLine)
171     return vtkPen::DASH_LINE;
172   else if (style == LineStyle::dotLine)
173     return vtkPen::DOT_LINE;
174   else if (style == LineStyle::dashDotLine)
175     return vtkPen::DASH_DOT_LINE;
176   else if (style == LineStyle::dashDotDotLine)
177     return vtkPen::DASH_DOT_DOT_LINE;
178 
179   std::cerr << "Error in " << __FUNCTION__ << ": unknown line style.\n";
180   std::cerr << "Defaulting to solid line.\n";
181   return vtkPen::SOLID_LINE;
182 }
183 
show()184 void VtkPlot::show()
185 {
186   // First, clear all previous plots
187   m_chart->ClearPlots();
188 
189   // Add the lines to the chart
190   for (size_t i = 1; i < m_table->GetNumberOfColumns(); ++i) {
191     vtkPlot* line = m_chart->AddPlot(vtkChart::LINE);
192     line->SetInputData(m_table, 0, i);
193 
194     // If we have a label for this line, set it
195     if (i <= m_lineLabels.size())
196       line->SetLabel(m_lineLabels[i - 1]);
197 
198     // If we have a color for this line, set it (rgba)
199     if (i <= m_lineColors.size()) {
200       line->SetColor(m_lineColors[i - 1][0], m_lineColors[i - 1][1],
201                      m_lineColors[i - 1][2], m_lineColors[i - 1][3]);
202     }
203 
204     // If we have a line style for this line, set it
205     if (i <= m_lineStyles.size() && line->GetPen())
206       line->GetPen()->SetLineType(convertLineStyleEnum(m_lineStyles[i - 1]));
207 
208     line->SetWidth(2.0);
209   }
210 
211   m_widget->show();
212 }
213 
setAxisLimits(Axis _axis,double min,double max)214 void VtkPlot::setAxisLimits(Axis _axis, double min, double max)
215 {
216   vtkAxis* axis = getAxis(_axis);
217   if (!axis) {
218     std::cerr << "Error in " << __FUNCTION__ << ": invalid axis\n";
219     return;
220   }
221 
222   axis->SetMinimumLimit(min);
223   axis->SetMaximumLimit(max);
224 }
225 
getAxis(Axis axis)226 vtkAxis* VtkPlot::getAxis(Axis axis)
227 {
228   if (axis == Axis::xAxis) {
229     return m_chart->GetAxis(vtkAxis::BOTTOM);
230   } else if (axis == Axis::yAxis) {
231     return m_chart->GetAxis(vtkAxis::LEFT);
232   }
233 
234   // If we get here, there is an error...
235   std::cerr << "Error in " << __FUNCTION__ << ": unknown axis\n";
236   return nullptr;
237 }
238 
239 } // namespace VTK
240 } // namespace Avogadro
241