QCustomPlot Discussion and Comments

Consistent placing of text in a QCPItemTextReturn to overview

I have files of data from an electronic instrument that I need to plot, and then be able to insert the plots in high quality into reports in a word processor. I'm using QCustomPlot to produce a graph in a promoted widget, then inserting the graph into a QTextEdit, and finally rendering the graph into an SVG file using QSvgGenerator. The vector nature of the data is preserved the whole way, and the SVG file can be inserted into the word processor document with the required high quality.

All bar one of the elements of the graph seem to be working fine. However, I'm having trouble with some text in a QCPItemText. I'm aiming to locate this close to the top of the plot, with its centre aligned to be above the centre of the x-axis. In the widget, this seems to be the case, but in the QTextEdit the rest of the plot has shrunk slightly, while the QCPItemText has not, resulting in it being a little to the right of where I want it. (Both the widget and the QTextEdit are set graphically in the form mainwindow.ui to be 471 x 271). Even though the effect is relatively small, why do the QCPItemText and the rest of the graph appear to scale differently?

In both the above cases, the rectangle around the text is close in to the text. However, in the SVG file, the rectangle is extended to the right of the text, typically by about a quarter of the length of the text itself. The appearance is quite different from how it is in the widget and the QCPItemText: neither the text nor the rectangle around the text is centred above the centre of the x-axis.

Here's my code:

QCPDocumentObject *plotObjectHandler = new QCPDocumentObject(this);
    ui->textEdit->document()->documentLayout()->registerHandler(QCPDocumentObject::PlotTextFormat, plotObjectHandler);

    int width = 450;
    int height = 250;

    QSvgGenerator generator;
    generator.setFileName(QCoreApplication::applicationDirPath() + "/test.svg");
    generator.setSize(QSize(width, height));
    generator.setViewBox(QRect(2, 2, width-2, height-2));  // come in from the edge to avoid a border that is added somewhere...
    generator.setTitle("SVG Generator Graph");
    generator.setDescription("A test SVG drawing of a QCustomPlot");

    ui->plot->addGraph();
    ui->plot->replot();
    ui->plot->plotLayout()->insertRow(0);  //Make space for a title/caption (QCPItemText)
    ui->plot->plotLayout()->setRowStretchFactor(0, 0.1);    //set height of the new row
    ui->plot->xAxis->setRange(0, 10);
    ui->plot->yAxis->setRange(0, 12);
    ui->plot->xAxis->setLabel("X-axis label");
    ui->plot->yAxis->setLabel("Y-axis label");

    ui->plot->replot(); //need to do this before getting the coordinates of the axisRect

    int left = ui->plot->axisRect()->left();
    int right = ui->plot->axisRect()->right();

    QCPItemText *caption = new QCPItemText(ui->plot);
    caption->setPen(Qt::SolidLine);  // draw the box round the text
    caption->setFont(QFont("Sans", 12, QFont::Bold));
    caption->setText("ABLE WAS I ERE I SAW ELBA");
    caption->setTextAlignment(Qt::AlignCenter);    //centre text within its rectangle
    caption->setPositionAlignment(Qt::AlignHCenter | Qt::AlignTop);    // use the centre top of the rectangle to position it
    caption->position->setType(QCPItemPosition::ptAbsolute);
    caption->position->setCoords((left+right)/2, 5);
    caption->setClipToAxisRect(false);

    ui->plot->replot();

    QTextCursor cursor = ui->textEdit->textCursor();
    cursor.insertText(QString(QChar::ObjectReplacementCharacter), QCPDocumentObject::generatePlotFormat(ui->plot, width, height));

    QPainter painter;
    painter.begin(&generator);
    ui->textEdit->render(&painter);
    painter.end();

Am I doing something wrong? Is it a problem with my code, with QCustomPlot or with QSvgGenerator? Any help will be gratefully received!

Best wishes and thanks,

Peter

Hi Peter,

From a quick look, I'd suspect that your absolute positioning is the issue here:

    caption->position->setType(QCPItemPosition::ptAbsolute);
    caption->position->setCoords((left+right)/2, 5);

This makes your text item have a fixed position relative to the top left of the QCustomPlot viewport, irrespective of any size changes of the axis rect or the viewport width/height.

I recommend doing this instead:

    caption->position->setType(QCPItemPosition::ptAxisRectRatio);
    caption->position->setCoords(0.5, 0.02);

This way your text is always centered horizontally in the axis rect (coordinate 0.5 in axis rect ratio coordinates), and 2% from the top edge.

Does this fix your issue?

DerManu,

Many thanks for this. I can see that your code is a much better approach. I have tweaked it slightly in order to put the caption above the graph rather than in the AxisRect:

caption->position->setCoords(0.5, -0.2);

but unfortunately there remains a problem. The textEdit now displays what I want (see https://www.dropbox.com/s/tu29bbykqunhoys/Screenshot_of_textEdit.png?dl=0), but in the SVG file that it goes on to create, while the text rectangle ends are in the same position relative to the horizontal axis (i.e. at about 1.5 and 8.5), the text now does not fill the rectangle, and is justified to the left (see https://www.dropbox.com/s/64l47zg0vozbz9t/test.svg?dl=0).

The question in my mind is whether this remaining problem is due to QCustomPlot or to rendering the textEdit with QSvgGenerator. Any guidance on this will be greatly appreciated!

Peter

I have found another difference between an SVG file and the image on either a promoted widget or a textEdit. To the code above I added the following at line 22:

    QVector<double> xData, yData;
    xData << 2 << 8;
    yData << -2 << 8;
    ui->plot->graph(0)->setData(xData, yData);

These two points represent a straight line that goes below the horizontal axis at its left end. In the promoted widget and the textEdit, the data line below the horizontal axis is hidden, but in the SVG file it is visible.

Any clues, please?

Thanks!

Peter

Hi Peter,

I've experimented and searched around in the last few days and I'm convinced it's an issue with QSvgGenerator. There are many bug reports on the Qt bug tracker reporting the problems that you're encountering when trying to render the plot. For example, the line clipping and text box problem:
QTBUG-28636
QTBUG-1865
QTBUG-52538
Unfortunately, all those bugs have been around for a long time and still haven't been addressed by the Qt team. That's very unfortunate, as it makes very essential parts of SVG functionality unavailable to Qt developers. They mention these limitations by only claiming "support for SVG 1.2 Tiny", which I guess is pretty tiny if it doesn't even support clipping. Even then I'd expect the QPainter backend for SVG to take that limitation into account and implement a clipped output to the SVG. But that's not how it is.

Anyhow, here are my further observations:
I can reproduce your font issues with SVG. I see them as two separate issues: For one, the SVG painter reports font metrics incorrectly, which leads to the wrong size of the text box (that's QTBUG-52538). I don't have a solution for this, since it's an issue with QSvgGenerator. The second issue is that SVG viewers generally mess up text if it uses fonts that are not available on the system, or that the generator associated to different fonts than the viewer. This is an oversight of the SVG standard, that font object dimensions aren't embedded and that font files themselves can't (yet) be embedded. So for unknown fonts, the viewer makes a wild guess and uses any seemingly similar font, which typically messes up spacing etc.
I could observe this behavior when, as in your example, keeping default fonts or using generic font family descriptors such as "Sans". If I change every font (i.e. axis tick label, axis tick, text element,...) to a specific font that was available on the system, such as "DejaVu Sans", the spacing issues were gone and the text elements appeared correctly when viewing the SVG file in Inkscape.

I can reproduce your clipping issue with SVG. But unfortunately this is also out of my influence since it's a limitation of the SVG 1.2 Tiny standard which Qt only supports. Additionally, If I inspect or "take apart" the SVG file in inkscape that QSvgGenerator creates, it's pretty clear that many strange things are going on. For example there are many layers of differently colored filled rectangles below the plot, which QCustomPlot definitely doesn't draw in its code.

All in all, I believe the QtSvg module is unsuitable for any slightly sophisticated output. Quite a sad situation that I didn't expect myself, since SVG is becoming more and more important for various applications.

However, there is an alternative that works very well, and is explicitly supported by and tested with QCustomPlot: PDF output. If you have a look at the plot when generated as follows:

  QPrinter printer(QPrinter::ScreenResolution);
  printer.setOutputFormat(QPrinter::PdfFormat);
  printer.setPaperSize(QPrinter::A4);
  printer.setOutputFileName(QCoreApplication::applicationDirPath() + "/test.pdf");
  ui->textEdit->document()->print(&printer);

(or also just the plot->savePdf() method)

You will see that everything looks as expected, text box, clipping, and all. (Another sign that the issue is not in QCustomPlot.)

PDF is more important especially for report generation than SVG – which is why I only ever test PDF output and not SVG, which again is why I never noticed how limited QSvgGenerator is. PDF output works great, so in light of QSvgGenerator not being an option for production, I'd recommend a rather hacky setup if you definitely need SVG: export to PDF, and call an external PDF-to-SVG-program (such as inkscape, or pdf2svg, or any of the other tools that can be called via QProcess). I know that's a pain, but in light of Qt's limited native SVG support, I'm afraid the only currently viable option.