243 if (!rect.isValid())
244 return;
245 painter->setClipRect(rect.adjusted(+1, +1, -1, -1));
246 QMapIterator<int, QVector<QPointF> > i(curveMap);
247 while (i.hasNext()) {
248 i.next();
249 int id = i.key();
250 const QVector<QPointF> &data = i.value();
251 QPolygonF polyline(data.count());
252 for (int j = 0; j < data.count(); ++j) {
253 double dx = data[j].x() - settings.minX;
254 double dy = data[j].y() - settings.minY;
255 double x = rect.left() + (dx * (rect.width() - 1)
256 / settings.spanX());
257 double у = rect.bottom() - (dy * (rect.height() - 1)
258 / settings.spanY());
259 polyline[j] = QPointF(x, у);
260 }
261 painter->setPen(colorForIds[uint(id) % 6]);
262 painter->drawPolyline(polyline);
263 }
264 }
Функция drawCurves() рисует кривые поверх сетки. Мы начинаем с вызова функции setClipRect для ограничения области отображения QPainter прямоугольником, содержащим кривые (без окаймляющей кромки и рамки вокруг графика). После этого QPainter будет игнорировать вывод пикселей вне этой области.
Затем мы выполняем цикл по всем кривым, используя итератор в стиле Java, и для каждой кривой мы выполняем цикл по ее точкам QPointF. Функция key() позволяет получить идентификатор кривой, а функция value() — данные соответствующей кривой в виде вектора QVector<QPointF>. Внутри цикла for производятся преобразование всех точек QPointF из системы координат построителя графика в систему координат виджета и сохранение их в переменной polyline.
После преобразования всех точек кривой в систему координат виджета мы устанавливаем цвет пера для кривой (используя один из наборов заранее определенных цветов) и вызываем drawPolyline() для вычерчивания линии, которая проходит по всем точкам кривой.
Этим мы завершаем построение класса Plotter. Остается только рассмотреть несколько функций настроек графика PlotSettings.
265 PlotSettings::PlotSettings()
266 {
267 minX = 0.0;
268 maxX = 10.0;
269 numXTicks = 5;
270 minY = 0.0;
271 maxY = 10.0;
272 numYTicks = 5;
273 }
Конструктор PlotSettings инициализирует обе оси координат диапазоном от 0 до 10 с пятью отметками.
274 void PlotSettings::scroll(int dx, int dy)
275 {
276 double stepX = spanX() / numXTicks;
277 minX += dx * stepX;
278 maxX += dx * stepX;
279 double stepY = spanY() / numYTicks;
280 minY += dy * stepY;
281 maxY += dy *stepY;
282 }
Функция scroll() увеличивает (или уменьшает) minX, maxX, minY и maxY на интервал между двух отметок, помноженный на заданное число. Данная функция применяется для реализации скроллинга в функции Plotter::keyPressEvent().
283 void PlotSettings::adjust()
284 {
285 adjustAxis(minX, maxX, numXTicks);
286 adjustAxis(minY, maxY, numYTicks);
287 }
Функция adjust() вызывается из mouseReleaseEvent() для округления значений minX, maxX, minY и maxY, чтобы получить «удобные» значения, и определения количества меток на каждой оси. Закрытая фyнкция adjustAxis() выполняет эти действия отдельно для каждой оси.
288 void PlotSettings::adjustAxis(double &min, double &max, int &numTiсks)
289 {
290 const int MinTicks = 4;
291 double grossStep = (max - min) / MinTicks;
292 double step = pow(10.0, floor(log10(grossStep)));
293 if (5 * step < grossStep) {
294 step *= 5;
295 } else if (2* step < grossStep) {
296 step *= 2;
297 }
298 numTicks = int (ceil(max / step) - floor(min / step));
299 if (numTicks < MinTicks)
300 numTicks = MinTicks;
301 min = floor(min / step) * step;
302 max = ceil(max / step) * step;
303 }
Функция adjustAxis() преобразует свои параметры min и max в «удобные» числа и устанавливает свой параметр numTicks на количество меток, которое, по ее расчету, подходит для заданного диапазона [min, max]. Поскольку в функции adjustAxis() фактически требуется модифицировать переменные (minX, maxX, numXTicks и так далее), а не просто копировать их, для этих параметров не используется модификатор const. Большая часть программного кода в adjustAxis() предназначена просто для определения соответствующего значения интервала между двумя метками (переменная step — шаг). Для получения на оси удобных чисел мы должнытщательно выбирать этот шаг. Например, значение шага 3.8 привело бы к появлению на оси чисел, кратных 3.8, что затрудняет восприятие диаграммы человеком. Для осей с десятичной системой обозначения «удобными» значениями шага являются числа вида 10n, 2 • 10n или 5 • 10n.