Жасмин Бланшет - QT 4: программирование GUI на С++
189 void Plotter::refreshPixmap()
190 {
191 pixmap = QPixmap(size());
192 pixmap.fill(this, 0, 0);
193 QPainter painter(&pixmap);
194 painter.initFrom(this);
195 drawGrid(&painter);
196 drawCurves(&painter);
197 update();
198 }
Функция refreshPixmap() перерисовывает график на внеэкранной пиксельной карте и обновляет изображение на экране. Мы изменяем размеры пиксельной карты на размеры виджета и заполняем ее цветом стертого виджета. Этот цвет является «темным» компонентом палитры из-за вызова функции setBackgroundRole() в конструкторе Plotter. Если фон задается неоднородной кистью, в функции QPixmap::fill() необходимо указать смещение в виджете, где будет заканчиваться пиксельная карта, чтобы правильно выравнить образец кисти. Здесь пиксельная карта соответствует всему виджету, поэтому мы задаем позицию (0, 0).
Затем мы создаем QPainter для вычерчивания диаграммы на пиксельной карте. Вызов initFrom() устанавливает в рисовальщике перо, фон и шрифт такими же, как для виджета Plotter. Затем мы вызываем функции drawGrid() и drawCurves(), которые рисуют диаграмму. В конце мы вызываем функцию update() для инициации события рисования всего виджета. Пиксельная карта копируется в виджет функцией paintEvent().
199 void Plotter::drawGrid(QPainter *painter)
200 {
201 QRect rect(Margin, Margin,
202 width() - 2 * Margin, height() - 2 * Margin);
203 if (!rect.isValid())
204 return;
205 PlotSettings settings = zoomStack[curZoom];
206 QPen quiteDark = palette().dark().color().light();
207 QPen light = palette().light().color();
208 for (int i = 0; i <= settings.numXTicks; ++i) {
209 int x = rect.left() + (i * (rect.width() - 1)
210 / settings.numXTicks);
211 double label = settings.minX + (i * settings.spanX()
212 / settings.numXTicks);
213 painter->setPen(quiteDark);
214 painter->drawLine(x, rect.top(), x, rect.bottom());
215 painter->setPen(light);
216 painter->drawLine(x, rect.bottom(), x, rect.bottom() + 5);
217 painter->drawText(x - 50, rect.bottom() + 5, 100, 15,
218 Qt::AlignHCenter | Qt::AlignTop,
219 QString::number(label));
220 }
221 for (int j = 0; j <= settings.numVTicks; ++j) {
222 int y = rect.bottom() - (j * (rect.height() - 1)
223 / settings.numYTicks);
224 double label = settings.minY + (j * settings.spanY()
225 / settings.numYTicks);
226 painter->setPen(quiteDark);
227 painter->drawLine(rect.left(), у, rect.right(), у);
228 painter->setPen(light);
229 painter->drawLine(rect.left() - 5, y, rect.left(), у);
230 painter->drawText(rect.left() - Margin, у - 10, Margin - 5, 20,
231 Qt::AlignRight | Qt::AlignVCenter,
232 QString::number(label));
233 }
234 painter->drawRect(rect.adjusted(0, 0, -1, -1));
235 }
Функция drawGrid() чертит сетку под кривыми и осями. Область для вычерчивания сетки задается прямоугольником rect. Если размеры виджета недостаточны для размещения графика, мы сразу возвращаем управление.
Первый цикл for проводит вертикальные линии сетки и отметки по оси x. Второй цикл for выводит горизонтальные линии и отметки по оси y. В конце мы рисуем прямоугольники по окаймляющей кромке. Функция drawText() применяется для вывода числовых значений для отметок обеиз осей.
Вызовы функции drawText() имеют следующий формат:
painter.drawText(x, у, ширина, высота, смещение, текст);
где (x, у, ширина, высота) определяют прямоугольник, смещение задает позицию текста в этом прямоугольнике и текст представляет собой выводимый текст.
236 void Plotter::drawCurves(QPainter *painter)
237 {
238 static const QColor colorForIds[6] = {
239 Qt::red, Qt::green, Qt::blue, Qt::cyan, Qt::magenta, Qt::yellow };
240 PlotSettings settings = zoomStack[curZoom];
241 QRect rect(Margin, Margin,
242 width() - 2 * Margin, height() - 2 * Margin);
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.