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