ChartDirector 7.1 (C++ Edition)

Zoom/Scroll with PDF Report (Qt)




NOTE: This section describes Zoom/Scroll with PDF Report for Qt only. For MFC, please refer to Zoom/Scroll with PDF Report (MFC).

NOTE: For conciseness, some of the following descriptions only mention QChartViewer (for Qt Widgets applications). Those descriptions apply to QmlChartViewer (for QML/Qt Quick applications) as well.

This example extends Zooming and Scrolling with Track Line (1) (Qt) to demonstrate creating a PDF report with multiple charts covering the entire data range.

This example adds two buttons to the Zooming and Scrolling with Track Line (1) (Qt) example. The "Save Chart Image" button uses BaseChart.makeChart to save the visible chart in PNG, JPG, GIF, PDF or SVG format. The "Create PDF Report" button uses MultiPagePDF to create a PDF Report with multiple charts to cover the entire data range.

MultiPagePDF works by generating a PDF page for chart added to it. The chart can be a MultiChart, and can contain free form CDML text, tables, shape and images. In this way, MultiPagePDF can create complete PDF reports.

In this example, the PDF report is generated as follows:

Source Code Listing

[Qt Widgets version] qtdemo/zoomscrollpdf.cpp
#include <QApplication> #include <QPushButton> #include <QMouseEvent> #include <QFileDialog> #include <math.h> #include <sstream> #include <algorithm> #include "zoomscrollpdf.h" ZoomScrollPdf::ZoomScrollPdf(QWidget *parent) : QDialog(parent) { // // Set up the GUI // setFixedSize(790, 376); setWindowTitle("PDF Report Demonstration"); // The frame on the left side QFrame *frame = new QFrame(this); frame->setGeometry(4, 4, 128, 370); frame->setFrameShape(QFrame::StyledPanel); // Pointer push button QPushButton *pointerPB = new QPushButton(QIcon(":/icons/scroll_icon.png"), "Scroll", frame); pointerPB->setGeometry(4, 8, 120, 28); pointerPB->setStyleSheet("QPushButton { text-align:left; padding:5px 8px}"); pointerPB->setCheckable(true); // Zoom In push button QPushButton *zoomInPB = new QPushButton(QIcon(":/icons/zoomin_icon.png"), "Zoom In", frame); zoomInPB->setGeometry(4, 36, 120, 28); zoomInPB->setStyleSheet("QPushButton { text-align:left; padding:5px 8px}"); zoomInPB->setCheckable(true); // Zoom Out push button QPushButton *zoomOutPB = new QPushButton(QIcon(":/icons/zoomout_icon.png"), "Zoom Out", frame); zoomOutPB->setStyleSheet("QPushButton { text-align:left; padding:5px 8px}"); zoomOutPB->setGeometry(4, 64, 120, 28); zoomOutPB->setCheckable(true); // The Pointer/Zoom In/Zoom Out buttons form a button group mouseUsage = new QButtonGroup(frame); mouseUsage->addButton(pointerPB, Chart::MouseUsageScroll); mouseUsage->addButton(zoomInPB, Chart::MouseUsageZoomIn); mouseUsage->addButton(zoomOutPB, Chart::MouseUsageZoomOut); connect(mouseUsage, SIGNAL(buttonPressed(QAbstractButton*)), SLOT(onMouseUsageChanged(QAbstractButton*))); // Zoom Out push button QPushButton *savePB = new QPushButton("Save Chart Image", frame); savePB->setGeometry(4, 128, 120, 28); savePB->setCheckable(true); connect(savePB, SIGNAL(clicked(bool)), SLOT(onSaveChart(bool))); QPushButton *savePdfPB = new QPushButton("Create PDF Report", frame); savePdfPB->setGeometry(4, 158, 120, 28); savePdfPB->setCheckable(true); connect(savePdfPB, SIGNAL(clicked(bool)), SLOT(onSaveReport(bool))); // Chart Viewer m_ChartViewer = new QChartViewer(this); m_ChartViewer->setGeometry(136, 4, 650, 350); connect(m_ChartViewer, SIGNAL(viewPortChanged()), SLOT(onViewPortChanged())); connect(m_ChartViewer, SIGNAL(mouseMovePlotArea(QMouseEvent*)), SLOT(onMouseMovePlotArea(QMouseEvent*))); // Horizontal scroll bar m_HScrollBar = new QScrollBar(Qt::Horizontal, this); m_HScrollBar->setGeometry(136, 356, 650, 17); connect(m_HScrollBar, SIGNAL(valueChanged(int)), SLOT(onHScrollBarChanged(int))); // // Initialize the chart // // Load the data loadData(); // Initialize the QChartViewer initChartViewer(m_ChartViewer); // Initially set the mouse to drag to scroll mode pointerPB->click(); // Trigger the ViewPortChanged event to draw the chart m_ChartViewer->updateViewPort(true, true); } ZoomScrollPdf::~ZoomScrollPdf() { delete m_ranSeries; delete m_ChartViewer->getChart(); } // // Load the data // void ZoomScrollPdf::loadData() { // In this example, we just use random numbers as data. m_ranSeries = new RanSeries(127); m_timeStamps = m_ranSeries->getDateSeries(1827, Chart::chartTime(2015, 1, 1), 86400); m_dataSeriesA = m_ranSeries->getSeries(1827, 150, -10, 10); m_dataSeriesB = m_ranSeries->getSeries(1827, 200, -10, 10); m_dataSeriesC = m_ranSeries->getSeries(1827, 250, -8, 8); } // // Initialize the QChartViewer // void ZoomScrollPdf::initChartViewer(QChartViewer *viewer) { // Set the full x range to be the duration of the data viewer->setFullRange("x", m_timeStamps[0], m_timeStamps[m_timeStamps.len - 1]); // Initialize the view port to show the latest 20% of the time range viewer->setViewPortWidth(0.2); viewer->setViewPortLeft(1 - viewer->getViewPortWidth()); // Enable mouse wheel zooming by setting the zoom ratio to 1.1 per wheel event viewer->setMouseWheelZoomRatio(1.1); // Set the maximum zoom to 10 points viewer->setZoomInWidthLimit(10.0 / m_timeStamps.len); } // // The ViewPortChanged event handler. This event occurs if the user scrolls or zooms in // or out the chart by dragging or clicking on the chart. It can also be triggered by // calling WinChartViewer.updateViewPort. // void ZoomScrollPdf::onViewPortChanged() { // In addition to updating the chart, we may also need to update other controls that // changes based on the view port. updateControls(m_ChartViewer); // Update chart if necessary if (m_ChartViewer->needUpdateChart()) drawChart(m_ChartViewer); } // // Update controls in the user interface when the view port changed // void ZoomScrollPdf::updateControls(QChartViewer *viewer) { // The logical length of the scrollbar. It can be any large value. The actual value does // not matter. const int scrollBarLen = 1000000000; // Update the horizontal scroll bar m_HScrollBar->setEnabled(viewer->getViewPortWidth() < 1); m_HScrollBar->setPageStep((int)ceil(viewer->getViewPortWidth() * scrollBarLen)); m_HScrollBar->setSingleStep((std::min)(scrollBarLen / 100, m_HScrollBar->pageStep())); m_HScrollBar->setRange(0, scrollBarLen - m_HScrollBar->pageStep()); m_HScrollBar->setValue((int)(0.5 + viewer->getViewPortLeft() * scrollBarLen)); } // // Draw the chart and display it in the given viewer // void ZoomScrollPdf::drawChart(QChartViewer *viewer) { // Get the start date and end date that are visible on the chart. double viewPortStartDate = viewer->getValueAtViewPort("x", viewer->getViewPortLeft()); double viewPortEndDate = viewer->getValueAtViewPort("x", viewer->getViewPortLeft() + viewer->getViewPortWidth()); // Draw the XYChart XYChart* c = drawXYChart(viewPortStartDate, viewPortEndDate); // Add a title to the chart using 18 pts Times New Roman Bold Italic font c->addTitle(" PDF Report Demonstration", "Times New Roman Bold Italic", 18); // We need to update the track line too. If the mouse is moving on the chart (eg. if // the user drags the mouse on the chart to scroll it), the track line will be updated // in the MouseMovePlotArea event. Otherwise, we need to update the track line here. if ((!viewer->isInMouseMoveEvent()) && viewer->isMouseOnPlotArea()) trackLineLabel(c, viewer->getPlotAreaMouseX()); delete viewer->getChart(); viewer->setChart(c); } // // Draw an XYChart using data from startX to endX // XYChart* ZoomScrollPdf::drawXYChart(double startX, double endX) { // Get the array indexes that corresponds to the visible start and end dates int startIndex = (int)floor(Chart::bSearch(m_timeStamps, startX)); int endIndex = (int)ceil(Chart::bSearch(m_timeStamps, endX)); int noOfPoints = endIndex - startIndex + 1; // Extract the part of the data array that are visible. DoubleArray viewPortTimeStamps = DoubleArray(m_timeStamps.data + startIndex, noOfPoints); DoubleArray viewPortDataSeriesA = DoubleArray(m_dataSeriesA.data + startIndex, noOfPoints); DoubleArray viewPortDataSeriesB = DoubleArray(m_dataSeriesB.data + startIndex, noOfPoints); DoubleArray viewPortDataSeriesC = DoubleArray(m_dataSeriesC.data + startIndex, noOfPoints); // // At this stage, we have extracted the visible data. We can use those data to plot the chart. // /////////////////////////////////////////////////////////////////////////////////////// // Configure overall chart appearance. /////////////////////////////////////////////////////////////////////////////////////// // Create an XYChart object of size 650 x 350 pixels, with a white (ffffff) background and grey // (aaaaaa) border XYChart* c = new XYChart(650, 350, 0xffffff, 0xaaaaaa); // Set the plotarea at (55, 55) with width 90 pixels less than chart width, and height 90 pixels // less than chart height. Use a vertical gradient from light blue (f0f6ff) to sky blue (a0c0ff) // as background. Set border to transparent and grid lines to white (ffffff). c->setPlotArea(55, 55, c->getWidth() - 90, c->getHeight() - 90, c->linearGradientColor(0, 55, 0, c->getHeight() - 35, 0xf0f6ff, 0xa0c0ff), -1, Chart::Transparent, 0xffffff, 0xffffff); // As the data can lie outside the plotarea in a zoomed chart, we need enable clipping. c->setClipping(); // Add a legend box at (55, 30) using horizontal layout. Use 8pts Arial Bold as font. Set the // background and border color to Transparent and use line style legend key. LegendBox* b = c->addLegend(55, 30, false, "Arial Bold", 8); b->setBackground(Chart::Transparent); b->setLineStyleKey(); // Set legend icon style to use line style icon, sized for 8pt font c->getLegend()->setLineStyleKey(); c->getLegend()->setFontSize(8); // Set the axis stem to transparent c->xAxis()->setColors(Chart::Transparent); c->yAxis()->setColors(Chart::Transparent); // Add axis title using 10pts Arial Bold Italic font c->yAxis()->setTitle("Ionic Temperature (C)", "Arial Bold Italic", 10); /////////////////////////////////////////////////////////////////////////////////////// // Add data to chart /////////////////////////////////////////////////////////////////////////////////////// // // In this example, we represent the data by lines. You may modify the code below to use other // representations (areas, scatter plot, etc). // // Add a line layer for the lines, using a line width of 2 pixels LineLayer* layer = c->addLineLayer(); layer->setLineWidth(2); // In this demo, we do not have too many data points. In real code, the chart may contain a lot // of data points when fully zoomed out - much more than the number of horizontal pixels in this // plot area. So it is a good idea to use fast line mode. layer->setFastLineMode(); // Now we add the 3 data series to a line layer, using the color red (ff0000), green // (00cc00) and blue (0000ff) layer->setXData(viewPortTimeStamps); layer->addDataSet(viewPortDataSeriesA, 0xff3333, "Alpha"); layer->addDataSet(viewPortDataSeriesB, 0x008800, "Beta"); layer->addDataSet(viewPortDataSeriesC, 0x3333CC, "Gamma"); /////////////////////////////////////////////////////////////////////////////////////// // Configure axis scale and labelling /////////////////////////////////////////////////////////////////////////////////////// // Set the x-axis as a date/time axis with the scale according to the view port x range. //viewer->syncDateAxisWithViewPort("x", c->xAxis()); c->xAxis()->setDateScale(startX, endX); // // In this demo, the time range can be from a few years to a few days. We demonstrate // how to set up different date/time format based on the time range. // // If all ticks are yearly aligned, then we use "yyyy" as the label format. c->xAxis()->setFormatCondition("align", 360 * 86400); c->xAxis()->setLabelFormat("{value|yyyy}"); // If all ticks are monthly aligned, then we use "mmm yyyy" in bold font as the first // label of a year, and "mmm" for other labels. c->xAxis()->setFormatCondition("align", 30 * 86400); c->xAxis()->setMultiFormat(Chart::StartOfYearFilter(), "<*font=bold*>{value|mmm yyyy}", Chart::AllPassFilter(), "{value|mmm}"); // If all ticks are daily algined, then we use "mmm dd<*br*>yyyy" in bold font as the // first label of a year, and "mmm dd" in bold font as the first label of a month, and // "dd" for other labels. c->xAxis()->setFormatCondition("align", 86400); c->xAxis()->setMultiFormat(Chart::StartOfYearFilter(), "<*block,halign=left*><*font=bold*>{value|mmm dd<*br*>yyyy}", Chart::StartOfMonthFilter(), "<*font=bold*>{value|mmm dd}"); c->xAxis()->setMultiFormat(Chart::AllPassFilter(), "{value|dd}"); // For all other cases (sub-daily ticks), use "hh:nn<*br*>mmm dd" for the first label of // a day, and "hh:nn" for other labels. c->xAxis()->setFormatCondition("else"); c->xAxis()->setMultiFormat(Chart::StartOfDayFilter(), "<*font=bold*>{value|hh:nn<*br*>mmm dd}", Chart::AllPassFilter(), "{value|hh:nn}"); return c; } // // The Pointer, Zoom In or Zoom out button is pressed // void ZoomScrollPdf::onMouseUsageChanged(QAbstractButton *b) { m_ChartViewer->setMouseUsage(mouseUsage->id(b)); } // // User clicks on the the horizontal scroll bar // void ZoomScrollPdf::onHScrollBarChanged(int value) { if (!m_ChartViewer->isInViewPortChangedEvent()) { // Set the view port based on the scroll bar int scrollBarLen = m_HScrollBar->maximum() + m_HScrollBar->pageStep(); m_ChartViewer->setViewPortLeft(value / (double)scrollBarLen); // Update the chart display without updating the image maps. (We can delay updating // the image map until scrolling is completed and the chart display is stable.) m_ChartViewer->updateViewPort(true, false); } } // // Draw track cursor when mouse is moving over plotarea // void ZoomScrollPdf::onMouseMovePlotArea(QMouseEvent *) { trackLineLabel((XYChart *)m_ChartViewer->getChart(), m_ChartViewer->getPlotAreaMouseX()); m_ChartViewer->updateDisplay(); // Hide the track cursor when the mouse leaves the plot area m_ChartViewer->removeDynamicLayer("mouseLeavePlotArea"); } // // Draw track line with data labels // void ZoomScrollPdf::trackLineLabel(XYChart* c, int mouseX) { // Clear the current dynamic layer and get the DrawArea object to draw on it. DrawArea* d = c->initDynamicLayer(); // The plot area object PlotArea* plotArea = c->getPlotArea(); // Get the data x-value that is nearest to the mouse, and find its pixel coordinate. double xValue = c->getNearestXValue(mouseX); int xCoor = c->getXCoor(xValue); // Draw a vertical track line at the x-position d->vline(plotArea->getTopY(), plotArea->getBottomY(), xCoor, d->dashLineColor(0x000000, 0x0101)); // Draw a label on the x-axis to show the track line position. std::ostringstream xlabel; xlabel << "<*font,bgColor=000000*> " << c->xAxis()->getFormattedLabel(xValue, "mmm dd, yyyy") << " <*/font*>"; TTFText* t = d->text(xlabel.str().c_str(), "Arial Bold", 8); // Restrict the x-pixel position of the label to make sure it stays inside the chart image. int xLabelPos = (std::max)(0, (std::min)(xCoor - t->getWidth() / 2, c->getWidth() - t->getWidth())); t->draw(xLabelPos, plotArea->getBottomY() + 6, 0xffffff); t->destroy(); // Iterate through all layers to draw the data labels for (int i = 0; i < c->getLayerCount(); ++i) { Layer* layer = c->getLayerByZ(i); // The data array index of the x-value int xIndex = layer->getXIndexOf(xValue); // Iterate through all the data sets in the layer for (int j = 0; j < layer->getDataSetCount(); ++j) { DataSet* dataSet = layer->getDataSetByZ(j); const char* dataSetName = dataSet->getDataName(); // Get the color, name and position of the data label int color = dataSet->getDataColor(); int yCoor = c->getYCoor(dataSet->getPosition(xIndex), dataSet->getUseYAxis()); // Draw a track dot with a label next to it for visible data points in the plot area if ((yCoor >= plotArea->getTopY()) && (yCoor <= plotArea->getBottomY()) && (color != Chart::Transparent) && dataSetName && *dataSetName) { d->circle(xCoor, yCoor, 4, 4, color, color); std::ostringstream label; label << "<*font,bgColor=" << std::hex << color << "*> " << c->formatValue(dataSet->getValue(xIndex), "{value|P4}") << " <*font*>"; t = d->text(label.str().c_str(), "Arial Bold", 8); // Draw the label on the right side of the dot if the mouse is on the left side the // chart, and vice versa. This ensures the label will not go outside the chart image. if (xCoor <= (plotArea->getLeftX() + plotArea->getRightX()) / 2) t->draw(xCoor + 5, yCoor, 0xffffff, Chart::Left); else t->draw(xCoor - 5, yCoor, 0xffffff, Chart::Right); t->destroy(); } } } } // // User clicks on the Save Chart pushbutton // void ZoomScrollPdf::onSaveChart(bool) { QString fileName = QFileDialog::getSaveFileName(this, "Save", "chartdirector_demo", "PNG (*.png);;JPG (*.jpg);;GIF (*.gif);;BMP (*.bmp);;SVG (*.svg);;PDF (*.pdf)"); if (!fileName.isEmpty()) { // Save the chart BaseChart *c = m_ChartViewer->getChart(); if (0 != c) c->makeChart(fileName.toUtf8().constData()); } } // // User clicks on the Create PDF Report pushbutton // void ZoomScrollPdf::onSaveReport(bool) { QString fileName = QFileDialog::getSaveFileName(this, "Save", "chartdirector_report", "PDF (*.pdf)"); if (!fileName.isEmpty()) createPdfReport(fileName.toUtf8().constData()); } // // Create a multi-page PDF Report // void ZoomScrollPdf::createPdfReport(const char* filename) { // The MultiPagePDF object can create PDF from multiple pages, each with one chart // object. Since a chart object can contain text (eg. using BaseChart.addText) and // other charts (eg. using MultiChart), that means each page can contain text and // multiple charts. MultiPagePDF doc; // Page configuration - A4 = 210 x 297mm. The PDF default is 96 dpi (dot per inch), // so the A4 size is equal to 794 x 1123 dots. const char* pageConfig = "pagewidth = 794; pageHeight = 1123"; // In this example, we include a cover page with only text. This is by creating an // empty pie chart with text only. PieChart firstPage(720, 960); firstPage.addText(360, 320, "<*size=50*>ChartDirector<*br*><*size=30*>PDF Report Demonstration<*/*>", "Arial Bold", 30, 0x000000, Chart::Center); firstPage.setOutputOptions(pageConfig); doc.addPage(&firstPage); // We include 2 charts per page, with each chart showing one year of data. Each page // will also have a header and page number int startYear = Chart::getChartYMD(m_timeStamps[0]) / 10000; int endYear = Chart::getChartYMD(m_timeStamps[m_timeStamps.len - 1] - 1) / 10000; int pageNumber = 0; for (int yyyy = startYear; yyyy <= endYear; yyyy += 2) { // This chart is the page. MultiChart m(760, 920); // Use addTitle to add a header m.addTitle("ChartDirector PDF Report Demonstration", "Arial Bold", 20); // Create the first chart XYChart* c = drawXYChart(Chart::chartTime(yyyy, 1, 1), Chart::chartTime(yyyy + 1, 1, 1)); m.addChart((m.getWidth() - c->getWidth()) / 2, 100, c); c->addTitle(c->formatValue(yyyy, "Year {value}")); XYChart* c2 = 0; if (yyyy < endYear) { // Create the second chart c2 = drawXYChart(Chart::chartTime(yyyy + 1, 1, 1), Chart::chartTime(yyyy + 2, 1, 1)); c2->addTitle(c->formatValue(yyyy + 1, "Year {value}")); m.addChart((m.getWidth() - c2->getWidth()) / 2, 500, c2); } // Add the page number ++pageNumber; m.addTitle(Chart::BottomCenter, c->formatValue(pageNumber, "{value}"), "Arial Bold", 8); m.setOutputOptions(pageConfig); doc.addPage(&m); delete c; delete c2; } // Output the PDF report doc.outPDF(filename); }

[QML/Qt Quick version] qmldemo/zoomscrollpdf.qml
import QtQuick import QtQuick.Window import QtQuick.Controls import QtQuick.Dialogs import advsofteng.com 1.0 Window { title: "PDF Report Demonstration" visible: true modality: Qt.ApplicationModal width: 780 minimumWidth: 780 maximumWidth: 780 height: 375 minimumHeight: 375 maximumHeight: 375 Pane { id: leftPane width: 120 padding: 5 anchors.top: parent.top; anchors.bottom: parent.bottom; Column { Button { width: 110 contentItem: Row { padding: 2; leftPadding: 5 Image { source: "icons/scroll_icon.png"; width:16; height:16; } Text { text: " Pointer"; font.pixelSize: 12; } } checked: viewer.mouseUsage == QmlChartViewer.MouseUsageScroll onClicked: viewer.mouseUsage = QmlChartViewer.MouseUsageScroll } Button { width: 110 contentItem: Row { padding: 2; leftPadding: 5; Image { source: "icons/zoomin_icon.png"; width:16; height:16; } Text { text: " Zoom In"; font.pixelSize: 12; } } checked: viewer.mouseUsage == QmlChartViewer.MouseUsageZoomIn onClicked: viewer.mouseUsage = QmlChartViewer.MouseUsageZoomIn } Button { width: 110 contentItem: Row { padding: 2; leftPadding: 5 Image { source: "icons/zoomout_icon.png"; width:16; height:16; } Text { text: " Zoom Out"; font.pixelSize: 12; } } checked: viewer.mouseUsage == QmlChartViewer.MouseUsageZoomOut onClicked: viewer.mouseUsage = QmlChartViewer.MouseUsageZoomOut } // Spacer Item {width: 1; height: 30} Button { width: 110 height: 28 text: "Save Chart Image" onClicked: saveImageDialog.open() } Button { width: 110 height: 28 text: "Create PDF Report" onClicked: pdfReportDialog.open() } } } FileDialog { id: saveImageDialog title: "Save" currentFile: "chartdirector_demo" fileMode: FileDialog.SaveFile nameFilters: ["PNG (*.png);", "JPG (*.jpg)", "GIF (*.gif)", "BMP (*.bmp)", "SVG (*.svg)", "PDF (*.pdf)"] onAccepted: demo.saveChartImage(viewer, selectedFile) } FileDialog { id: pdfReportDialog title: "Create PDF Report" currentFile: "chartdirector_demo" fileMode: FileDialog.SaveFile nameFilters: ["PDF (*.pdf)"] onAccepted: demo.createPdfReport(selectedFile) } // The backend implementation of this demo. QmlChartViewer { id: viewer anchors.left: leftPane.right anchors.leftMargin: 5 y: 5 // set default mouse usage to scroll and mouse wheel to zoom mouseUsage: QmlChartViewer.MouseUsageScroll mouseWheelZoomRatio: 1.1 // Update track cursor on mouse move onMouseMovePlotArea: demo.drawTrackCursor(this, chartMouseX); // Update chart on viewport change. Update the scrollbar too. onViewPortChanged: { if (needUpdateChart) demo.drawChart(this); hScrollBar.size = Math.min(viewer.viewPortWidth, 0.999999999); hScrollBar.position = viewer.viewPortLeft; } } // Add scrollbar under the chart viewer ScrollBar { id: hScrollBar orientation: Qt.Horizontal height: 20 anchors.bottom: parent.bottom anchors.left: viewer.left anchors.right: viewer.right onPositionChanged: { // The scrollbar and viewport can update each others. To avoid infinite loop, // the scrollbar will update the viewport only if the viewport is not updating // the scrollbar. if (viewer.isInViewPortChangedEvent) return; // update the viewport viewer.viewPortLeft = this.position; viewer.updateViewPort(true, false); } } // The backend implementation of this demo. ZoomScrollPdfDemo { id: demo; } Component.onCompleted: { demo.initChartViewer(viewer); viewer.updateViewPort(true, true); } }

[QML/Qt Quick version] qmldemo/zoomscrollpdf.cpp
#include "zoomscrollpdf.h" #include <sstream> #include <math.h> ZoomScrollPdf::ZoomScrollPdf(QObject *parent) : QObject(parent) { m_currentChart = 0; // Load the data loadData(); } ZoomScrollPdf::~ZoomScrollPdf() { delete m_ranSeries; delete m_currentChart; } // // Load the data // void ZoomScrollPdf::loadData() { // In this example, we just use random numbers as data. m_ranSeries = new RanSeries(127); m_timeStamps = m_ranSeries->getDateSeries(1827, Chart::chartTime(2015, 1, 1), 86400); m_dataSeriesA = m_ranSeries->getSeries(1827, 150, -10, 10); m_dataSeriesB = m_ranSeries->getSeries(1827, 200, -10, 10); m_dataSeriesC = m_ranSeries->getSeries(1827, 250, -8, 8); } // // Initialize the QChartViewer // void ZoomScrollPdf::initChartViewer(QmlChartViewer *viewer) { // Set the full x range to be the duration of the data viewer->setFullRange("x", m_timeStamps[0], m_timeStamps[m_timeStamps.len - 1]); // Initialize the view port to show the latest 20% of the time range viewer->setViewPortWidth(0.2); viewer->setViewPortLeft(1 - viewer->getViewPortWidth()); // Enable mouse wheel zooming by setting the zoom ratio to 1.1 per wheel event viewer->setMouseWheelZoomRatio(1.1); // Set the maximum zoom to 10 points viewer->setZoomInWidthLimit(10.0 / m_timeStamps.len); } // // Draw the chart and display it in the given viewer // void ZoomScrollPdf::drawChart(QmlChartViewer *viewer) { // Get the start date and end date that are visible on the chart. double viewPortStartDate = viewer->getValueAtViewPort("x", viewer->getViewPortLeft()); double viewPortEndDate = viewer->getValueAtViewPort("x", viewer->getViewPortLeft() + viewer->getViewPortWidth()); // Draw the XYChart XYChart* c = drawXYChart(viewPortStartDate, viewPortEndDate); // Add a title to the chart using 18 pts Times New Roman Bold Italic font c->addTitle(" PDF Report Demonstration", "Times New Roman Bold Italic", 18); // We need to update the track line too. If the mouse is moving on the chart (eg. if // the user drags the mouse on the chart to scroll it), the track line will be updated // in the MouseMovePlotArea event. Otherwise, we need to update the track line here. if ((!viewer->isInMouseMoveEvent()) && viewer->isMouseOnPlotArea()) trackLineLabel(c, viewer->getPlotAreaMouseX()); delete viewer->getChart(); viewer->setChart(m_currentChart = c); } // // Draw an XYChart using data from startX to endX // XYChart* ZoomScrollPdf::drawXYChart(double startX, double endX) { // Get the array indexes that corresponds to the visible start and end dates int startIndex = (int)floor(Chart::bSearch(m_timeStamps, startX)); int endIndex = (int)ceil(Chart::bSearch(m_timeStamps, endX)); int noOfPoints = endIndex - startIndex + 1; // Extract the part of the data array that are visible. DoubleArray viewPortTimeStamps = DoubleArray(m_timeStamps.data + startIndex, noOfPoints); DoubleArray viewPortDataSeriesA = DoubleArray(m_dataSeriesA.data + startIndex, noOfPoints); DoubleArray viewPortDataSeriesB = DoubleArray(m_dataSeriesB.data + startIndex, noOfPoints); DoubleArray viewPortDataSeriesC = DoubleArray(m_dataSeriesC.data + startIndex, noOfPoints); // // At this stage, we have extracted the visible data. We can use those data to plot the chart. // /////////////////////////////////////////////////////////////////////////////////////// // Configure overall chart appearance. /////////////////////////////////////////////////////////////////////////////////////// // Create an XYChart object of size 650 x 350 pixels, with a white (ffffff) background and grey // (aaaaaa) border XYChart* c = new XYChart(650, 350, 0xffffff, 0xaaaaaa); // Set the plotarea at (55, 55) with width 90 pixels less than chart width, and height 90 pixels // less than chart height. Use a vertical gradient from light blue (f0f6ff) to sky blue (a0c0ff) // as background. Set border to transparent and grid lines to white (ffffff). c->setPlotArea(55, 55, c->getWidth() - 90, c->getHeight() - 90, c->linearGradientColor(0, 55, 0, c->getHeight() - 35, 0xf0f6ff, 0xa0c0ff), -1, Chart::Transparent, 0xffffff, 0xffffff); // As the data can lie outside the plotarea in a zoomed chart, we need enable clipping. c->setClipping(); // Add a legend box at (55, 30) using horizontal layout. Use 8pts Arial Bold as font. Set the // background and border color to Transparent and use line style legend key. LegendBox* b = c->addLegend(55, 30, false, "Arial Bold", 8); b->setBackground(Chart::Transparent); b->setLineStyleKey(); // Set legend icon style to use line style icon, sized for 8pt font c->getLegend()->setLineStyleKey(); c->getLegend()->setFontSize(8); // Set the axis stem to transparent c->xAxis()->setColors(Chart::Transparent); c->yAxis()->setColors(Chart::Transparent); // Add axis title using 10pts Arial Bold Italic font c->yAxis()->setTitle("Ionic Temperature (C)", "Arial Bold Italic", 10); /////////////////////////////////////////////////////////////////////////////////////// // Add data to chart /////////////////////////////////////////////////////////////////////////////////////// // // In this example, we represent the data by lines. You may modify the code below to use other // representations (areas, scatter plot, etc). // // Add a line layer for the lines, using a line width of 2 pixels LineLayer* layer = c->addLineLayer(); layer->setLineWidth(2); // In this demo, we do not have too many data points. In real code, the chart may contain a lot // of data points when fully zoomed out - much more than the number of horizontal pixels in this // plot area. So it is a good idea to use fast line mode. layer->setFastLineMode(); // Now we add the 3 data series to a line layer, using the color red (ff0000), green // (00cc00) and blue (0000ff) layer->setXData(viewPortTimeStamps); layer->addDataSet(viewPortDataSeriesA, 0xff3333, "Alpha"); layer->addDataSet(viewPortDataSeriesB, 0x008800, "Beta"); layer->addDataSet(viewPortDataSeriesC, 0x3333CC, "Gamma"); /////////////////////////////////////////////////////////////////////////////////////// // Configure axis scale and labelling /////////////////////////////////////////////////////////////////////////////////////// // Set the x-axis as a date/time axis with the scale according to the view port x range. //viewer->syncDateAxisWithViewPort("x", c->xAxis()); c->xAxis()->setDateScale(startX, endX); // // In this demo, the time range can be from a few years to a few days. We demonstrate // how to set up different date/time format based on the time range. // // If all ticks are yearly aligned, then we use "yyyy" as the label format. c->xAxis()->setFormatCondition("align", 360 * 86400); c->xAxis()->setLabelFormat("{value|yyyy}"); // If all ticks are monthly aligned, then we use "mmm yyyy" in bold font as the first // label of a year, and "mmm" for other labels. c->xAxis()->setFormatCondition("align", 30 * 86400); c->xAxis()->setMultiFormat(Chart::StartOfYearFilter(), "<*font=bold*>{value|mmm yyyy}", Chart::AllPassFilter(), "{value|mmm}"); // If all ticks are daily algined, then we use "mmm dd<*br*>yyyy" in bold font as the // first label of a year, and "mmm dd" in bold font as the first label of a month, and // "dd" for other labels. c->xAxis()->setFormatCondition("align", 86400); c->xAxis()->setMultiFormat(Chart::StartOfYearFilter(), "<*block,halign=left*><*font=bold*>{value|mmm dd<*br*>yyyy}", Chart::StartOfMonthFilter(), "<*font=bold*>{value|mmm dd}"); c->xAxis()->setMultiFormat(Chart::AllPassFilter(), "{value|dd}"); // For all other cases (sub-daily ticks), use "hh:nn<*br*>mmm dd" for the first label of // a day, and "hh:nn" for other labels. c->xAxis()->setFormatCondition("else"); c->xAxis()->setMultiFormat(Chart::StartOfDayFilter(), "<*font=bold*>{value|hh:nn<*br*>mmm dd}", Chart::AllPassFilter(), "{value|hh:nn}"); return c; } // // Draw track cursor when mouse is moving over plotarea // void ZoomScrollPdf::drawTrackCursor(QmlChartViewer *viewer, int mouseX) { trackLineLabel((XYChart *)viewer->getChart(), mouseX); viewer->updateDisplay(); // Hide the track cursor when the mouse leaves the plot area viewer->removeDynamicLayer("mouseLeavePlotArea"); } // // Draw track line with data labels // void ZoomScrollPdf::trackLineLabel(XYChart* c, int mouseX) { // Clear the current dynamic layer and get the DrawArea object to draw on it. DrawArea* d = c->initDynamicLayer(); // The plot area object PlotArea* plotArea = c->getPlotArea(); // Get the data x-value that is nearest to the mouse, and find its pixel coordinate. double xValue = c->getNearestXValue(mouseX); int xCoor = c->getXCoor(xValue); // Draw a vertical track line at the x-position d->vline(plotArea->getTopY(), plotArea->getBottomY(), xCoor, d->dashLineColor(0x000000, 0x0101)); // Draw a label on the x-axis to show the track line position. std::ostringstream xlabel; xlabel << "<*font,bgColor=000000*> " << c->xAxis()->getFormattedLabel(xValue, "mmm dd, yyyy") << " <*/font*>"; TTFText* t = d->text(xlabel.str().c_str(), "Arial Bold", 8); // Restrict the x-pixel position of the label to make sure it stays inside the chart image. int xLabelPos = (std::max)(0, (std::min)(xCoor - t->getWidth() / 2, c->getWidth() - t->getWidth())); t->draw(xLabelPos, plotArea->getBottomY() + 6, 0xffffff); t->destroy(); // Iterate through all layers to draw the data labels for (int i = 0; i < c->getLayerCount(); ++i) { Layer* layer = c->getLayerByZ(i); // The data array index of the x-value int xIndex = layer->getXIndexOf(xValue); // Iterate through all the data sets in the layer for (int j = 0; j < layer->getDataSetCount(); ++j) { DataSet* dataSet = layer->getDataSetByZ(j); const char* dataSetName = dataSet->getDataName(); // Get the color, name and position of the data label int color = dataSet->getDataColor(); int yCoor = c->getYCoor(dataSet->getPosition(xIndex), dataSet->getUseYAxis()); // Draw a track dot with a label next to it for visible data points in the plot area if ((yCoor >= plotArea->getTopY()) && (yCoor <= plotArea->getBottomY()) && (color != Chart::Transparent) && dataSetName && *dataSetName) { d->circle(xCoor, yCoor, 4, 4, color, color); std::ostringstream label; label << "<*font,bgColor=" << std::hex << color << "*> " << c->formatValue(dataSet->getValue(xIndex), "{value|P4}") << " <*font*>"; t = d->text(label.str().c_str(), "Arial Bold", 8); // Draw the label on the right side of the dot if the mouse is on the left side the // chart, and vice versa. This ensures the label will not go outside the chart image. if (xCoor <= (plotArea->getLeftX() + plotArea->getRightX()) / 2) t->draw(xCoor + 5, yCoor, 0xffffff, Chart::Left); else t->draw(xCoor - 5, yCoor, 0xffffff, Chart::Right); t->destroy(); } } } } // // User clicks on the Save Chart pushbutton // void ZoomScrollPdf::saveChartImage(QmlChartViewer *viewer, QUrl url) { QString path = url.toLocalFile(); if (!path.isEmpty()) { // Save the chart BaseChart *c = viewer->getChart(); if (0 != c) c->makeChart(path.toUtf8().constData()); } } // // User clicks on the Create PDF Report pushbutton // void ZoomScrollPdf::createPdfReport(QUrl url) { QString path = url.toLocalFile(); // The MultiPagePDF object can create PDF from multiple pages, each with one chart // object. Since a chart object can contain text (eg. using BaseChart.addText) and // other charts (eg. using MultiChart), that means each page can contain text and // multiple charts. MultiPagePDF doc; // Page configuration - A4 = 210 x 297mm. The PDF default is 96 dpi (dot per inch), // so the A4 size is equal to 794 x 1123 dots. const char* pageConfig = "pagewidth = 794; pageHeight = 1123"; // In this example, we include a cover page with only text. This is by creating an // empty pie chart with text only. PieChart firstPage(720, 960); firstPage.addText(360, 320, "<*size=50*>ChartDirector<*br*><*size=30*>PDF Report Demonstration<*/*>", "Arial Bold", 30, 0x000000, Chart::Center); firstPage.setOutputOptions(pageConfig); doc.addPage(&firstPage); // We include 2 charts per page, with each chart showing one year of data. Each page // will also have a header and page number int startYear = Chart::getChartYMD(m_timeStamps[0]) / 10000; int endYear = Chart::getChartYMD(m_timeStamps[m_timeStamps.len - 1] - 1) / 10000; int pageNumber = 0; for (int yyyy = startYear; yyyy <= endYear; yyyy += 2) { // This chart is the page. MultiChart m(760, 920); // Use addTitle to add a header m.addTitle("ChartDirector PDF Report Demonstration", "Arial Bold", 20); // Create the first chart XYChart* c = drawXYChart(Chart::chartTime(yyyy, 1, 1), Chart::chartTime(yyyy + 1, 1, 1)); m.addChart((m.getWidth() - c->getWidth()) / 2, 100, c); c->addTitle(c->formatValue(yyyy, "Year {value}")); XYChart* c2 = 0; if (yyyy < endYear) { // Create the second chart c2 = drawXYChart(Chart::chartTime(yyyy + 1, 1, 1), Chart::chartTime(yyyy + 2, 1, 1)); c2->addTitle(c->formatValue(yyyy + 1, "Year {value}")); m.addChart((m.getWidth() - c2->getWidth()) / 2, 500, c2); } // Add the page number ++pageNumber; m.addTitle(Chart::BottomCenter, c->formatValue(pageNumber, "{value}"), "Arial Bold", 8); m.setOutputOptions(pageConfig); doc.addPage(&m); delete c; delete c2; } // Output the PDF report doc.outPDF(path.toUtf8().constData()); }