ASE Home Page Products Download Purchase Support About ASE
ChartDirector Support
Forum HomeForum Home   SearchSearch

Message ListMessage List     Post MessagePost Message

  Creating PDF Report
Posted by Medic89 on Sep-25-2021 19:54
Attachments:
Hello Peter,

I implemented the create PDF report code into my app, it works so far, but when I try to print the pdf report, my Adobe DC reports a graphic error. Is there anything I missed to implement? Please see the created pdf attached to this post.
chartdirector_report.pdf
chartdirector_report.pdf

48.77 Kb

  Re: Creating PDF Report
Posted by Peter Kwan on Sep-28-2021 01:23
Hi Medic89,

I have just tried myself. The Adobe DC can view the PDF normally, but when I printed it using Adobe DC on Windows, it reports a graphics error as you mentioned. I can print it successful using Adobe DC only if I choose "Print as Image" in Adobe DC print dialog.

I have tried to display and print using other viewers, like using various browsers, and the "Preview" app on macOS (the default viewer on macOS). They all work normally.

I also found some unusual features in your PDF report. There are two pages. In the second page, there is the "unregistered" yellow bar, but in the first page there is no such bar. Normally, the "unregistered" message will either not display at all, or it will display on every page.

Is it possible to create a simple source code example that can create a PDF with this problem? You can use hard coded data like in most of our sample code. I suspect there is some configuration in the report which may cause the interaction between Adobe DC and the printer driver to malfunction. If I can reproduce the problem from the source code, I can determine exactly what is inside the PDF and this would help to find out the cause the problem. I can also be a bug in Adobe DC (since it can display it, it should be able to print it, and all other programs can print and display it), in which case I can find out which feature triggers the problem.

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Sep-30-2021 19:44
Hi Peter,

here is my code:

void CMFCApplication2Dlg::createPdfReport(const char* filename)
{
MultiPagePDF doc;
const char* pageConfig = "pagewidth = 1123; pageHeight = 794";

PieChart firstPage(720, 960);
CDMLTable* Tabelle = firstPage.addTable(200, 200, Chart::TopLeft, 1, 2);
Tabelle->setCell(0, 0, 1, 1, "Diagnosen");
Tabelle->getCell(0, 0)->setFontStyle("Arial Bold");
Tabelle->getCell(0, 0)->setFontSize(10);
Tabelle->getCell(0, 0)->setHeight(16);
Tabelle->getStyle()->setWidth(668);
Tabelle->getStyle()->setBackground(Chart::Transparent, RGB(0, 0, 0), 0);
Tabelle->getCell(0, 0)->setAlignment(Chart::Left);
Tabelle->setCell(0, 1, 1, 1, "Keine");
Tabelle->getCell(0, 1)->setFontStyle("Arial");
Tabelle->getCell(0, 1)->setFontSize(9);
Tabelle->getCell(0, 1)->setHeight(16);
Tabelle->getCell(0, 1)->setAlignment(Chart::Left);
firstPage.setOutputOptions(pageConfig);
doc.addPage(&firstPage);

int startYear = (2021);
int endYear = 2021;
int pageNumber = 0;

for (int yyyy = startYear; yyyy <= endYear; yyyy += 2)
{
MultiChart m(1123, 794);

XYChart* c = drawXYChart(Chart::chartTime(yyyy, 1, 1), Chart::chartTime(yyyy + 1, 1, 1));
m.addChart(0, 120, c);

XYChart* c2 = 0;
if (yyyy < endYear)
{
c2 = drawXYChart(Chart::chartTime(yyyy + 1, 1, 1), Chart::chartTime(yyyy + 2, 1, 1));
m.addChart(0, 120, c2);
}

++pageNumber;

m.setOutputOptions(pageConfig);
doc.addPage(&m);

delete c;
delete c2;
}

// Output the PDF report
doc.outPDF(filename);
TCHAR szFilters[] = _T("PNG (*.png)|*.png|JPG (*.jpg)|*.jpg|GIF (*.gif)|*.gif|")
_T("BMP (*.bmp)|*.bmp|SVG (*.svg)|*.svg|PDF (*.pdf)|*.pdf||");


}

XYChart* CMFCApplication2Dlg::drawXYChart(double startX, double endX)
{
int startIndex = 0;
int endIndex = 164;
int noOfPoints = endIndex - startIndex + 1;

DoubleArray viewPortTimeStamps = DoubleArray(&(myRow1y[0]), 164);
XYChart* c = new XYChart(1100, 390 + 46);

c->setPlotArea(250, 5, 780, 350, RGB(255, 255, 255), RGB(255, 255, 255), 0xD9D9D9, RGB(255, 255, 255), RGB(255, 255, 255))->setGridWidth(1, 2, 0, 0);
c->setPlotArea(250, 5, 780, 350, RGB(255, 255, 255), RGB(255, 255, 255), 0xD9D9D9, RGB(255, 255, 255), RGB(255, 255, 255))->setGridColor(0xD9D9D9, 0xD9D9D9, 0xD9D9D9, RGB(255, 255, 255));
c->yAxis()->setWidth(2);
c->yAxis()->setColors(0xD9D9D9, RGB(0, 0, 0));
c->xAxis()->setDateScale(Startzeit(), Startzeit() + 9899, 900, 300);
c->xAxis()->setLabelFormat("{value|hh:nn}");
c->yAxis()->setLabelStyle("Arial", 8, RGB(0, 0, 0));
c->xAxis()->setLabelStyle("Arial", 8, RGB(0, 0, 0));
c->xAxis()->setLabelGap(3);
c->yAxis()->setTickDensity(40);
c->setAntiAlias(TRUE, 1);
ScatterLayer* systole = c->addScatterLayer(DoubleArray(Systolex, Systolex_size), DoubleArray(Systoley, Systoley_size), "Systole [mmHg]", Chart::InvertedTriangleShape, 11, 0x4472C4, 0x4472C4);
systole->setLineWidth(0);
systole->getDataSet(0)->setSymbolOffset(0, -6);
ScatterLayer* diastole = c->addScatterLayer(DoubleArray(Diastolex, Diastolex_size), DoubleArray(Diastoley, Diastoley_size), "Diastole [mmHg]", Chart::TriangleShape, 11, 0x4472C4, 0x4472C4);
diastole->setLineWidth(0);
diastole->getDataSet(0)->setSymbolOffset(0, 6);
ScatterLayer* mittel = c->addScatterLayer(DoubleArray(Mittelx, Mittelx_size), DoubleArray(Mittely, Mittely_size), "Mitteldruck [mmHg]", Chart::Cross2Shape(0.1), 11, 0x4472C4, 0x4472C4);
mittel->setLineWidth(0);
ScatterLayer* HF = c->addScatterLayer(DoubleArray(HFx, GroesseHF), DoubleArray(HFy, GroesseHF), "Herzfrequenz [/min.]", Chart::CircleShape, 7, 0xFF0000, 0xFF0000);
HF->setLineWidth(1);
c->yAxis()->setLinearScale(0, 130, 10);

Mark* yMark1 = c->yAxis()->addMark(100, RGB(0, 0, 0), "");
yMark1->setLineWidth(1);
yMark1->setZOrder(Chart::GridLinesZ);
// Add a line layer for the lines, using a line width of 2 pixels
LineLayer* layer = c->addLineLayer();
layer->setLineWidth(1);
c->layoutAxes();

for (int w = 0;w < 164;++w)
{
int X1 = systole->getXCoor(Systolex[w]);
int X2 = diastole->getXCoor(Diastolex[w]);
int Y1 = systole->getYCoor(Systoley[w]) - 2;
int Y2 = diastole->getYCoor(Diastoley[w]) + 2;
Line* Linie = c->addLine(X1, Y1, X2, Y2, 0x4472C4, 2);
Linie->setZOrder(Chart::GridLinesZ);
}
HF->moveFront();

// 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);
return c;
}

void CMFCApplication2Dlg::OnClickedButton1()
{
// The standard CFileDialog
CFileDialog fileDlg(FALSE, _T("pdf"), _T("chartdirector_report"), OFN_HIDEREADONLY |
OFN_OVERWRITEPROMPT, _T("PDF (*.pdf)|*.pdf||"));
if (fileDlg.DoModal() == IDOK)
createPdfReport(TCHARtoUTF8(fileDlg.GetPathName()));
}

  Re: Creating PDF Report
Posted by Peter Kwan on Oct-01-2021 17:45
Attachments:
Hi Medic89,

I do not have your data arrays, so I modified your "drawXYChart" routine to assign random data to your arrays. It works normally. Below is my modified code. I have attached the PDF report generated. It can print normally. Please try it to see if you can print in your printer. (Sometimes it may be printer driver related.)


XYChart* CZoomScrollPdfDlg::drawXYChart(double startX, double endX)
{
    RanSeries r(127);
    DoubleArray m_timeStamps = r.getDateSeries(1827, Chart::chartTime(2015, 1, 1), 300);
    m_dataSeriesA = r.getSeries(1827, 150, -10, 10);
    m_dataSeriesB = r.getSeries(1827, 200, -10, 10);
    m_dataSeriesC = r.getSeries(1827, 250, -8, 8);

    double Startzeit = *m_timeStamps.data;
    const double* myRow1y = m_timeStamps.data + 1;

    const double* Systolex = myRow1y;
    int Systolex_size = 32;

    const double* Systoley = m_dataSeriesA.data;
    int Systoley_size = Systolex_size;

    const double* Diastolex = myRow1y;
    int Diastolex_size = Systolex_size;

    const double* Diastoley = m_dataSeriesB.data;
    int Diastoley_size = Diastolex_size;

    const double* Mittelx = myRow1y;
    int Mittelx_size = Systolex_size;

    const double* Mittely = m_dataSeriesC.data;
    int Mittely_size = Mittelx_size;

    const double* HFx = myRow1y;
    const double* HFy = m_dataSeriesA.data;
    int GroesseHF = Systolex_size;

    int startIndex = 0;
    int endIndex = 164;
    int noOfPoints = endIndex - startIndex + 1;

    DoubleArray viewPortTimeStamps = DoubleArray(&(myRow1y[0]), 164);
    XYChart* c = new XYChart(1100, 390 + 46);

c->setPlotArea(250, 5, 780, 350, RGB(255, 255, 255), RGB(255, 255, 255), 0xD9D9D9, RGB(255, 255, 255), RGB(255, 255, 255))->setGridWidth(1, 2, 0, 0);
c->setPlotArea(250, 5, 780, 350, RGB(255, 255, 255), RGB(255, 255, 255), 0xD9D9D9, RGB(255, 255, 255), RGB(255, 255, 255))->setGridColor(0xD9D9D9, 0xD9D9D9, 0xD9D9D9, RGB(255, 255, 255));
c->yAxis()->setWidth(2);
c->yAxis()->setColors(0xD9D9D9, RGB(0, 0, 0));
c->xAxis()->setDateScale(Startzeit, Startzeit + 9899, 900, 300);
c->xAxis()->setLabelFormat("{value|hh:nn}");
c->yAxis()->setLabelStyle("Arial", 8, RGB(0, 0, 0));
c->xAxis()->setLabelStyle("Arial", 8, RGB(0, 0, 0));
c->xAxis()->setLabelGap(3);
c->yAxis()->setTickDensity(40);
c->setAntiAlias(TRUE, 1);
ScatterLayer* systole = c->addScatterLayer(DoubleArray(Systolex, Systolex_size), DoubleArray(Systoley, Systoley_size), "Systole [mmHg]", Chart::InvertedTriangleShape, 11, 0x4472C4, 0x4472C4);
systole->setLineWidth(0);
systole->getDataSet(0)->setSymbolOffset(0, -6);
ScatterLayer* diastole = c->addScatterLayer(DoubleArray(Diastolex, Diastolex_size), DoubleArray(Diastoley, Diastoley_size), "Diastole [mmHg]", Chart::TriangleShape, 11, 0x4472C4, 0x4472C4);
diastole->setLineWidth(0);
diastole->getDataSet(0)->setSymbolOffset(0, 6);
ScatterLayer* mittel = c->addScatterLayer(DoubleArray(Mittelx, Mittelx_size), DoubleArray(Mittely, Mittely_size), "Mitteldruck [mmHg]", Chart::Cross2Shape(0.1), 11, 0x4472C4, 0x4472C4);
mittel->setLineWidth(0);
ScatterLayer* HF = c->addScatterLayer(DoubleArray(HFx, GroesseHF), DoubleArray(HFy, GroesseHF), "Herzfrequenz [/min.]", Chart::CircleShape, 7, 0xFF0000, 0xFF0000);
HF->setLineWidth(1);
c->yAxis()->setLinearScale(0, 300, 20);

Mark* yMark1 = c->yAxis()->addMark(100, RGB(0, 0, 0), "");
yMark1->setLineWidth(1);
yMark1->setZOrder(Chart::GridLinesZ);
// Add a line layer for the lines, using a line width of 2 pixels
LineLayer* layer = c->addLineLayer();
layer->setLineWidth(1);
c->layoutAxes();

for (int w = 0; w < 30; ++w)
{
    int X1 = systole->getXCoor(Systolex[w]);
    int X2 = diastole->getXCoor(Diastolex[w]);
    int Y1 = systole->getYCoor(Systoley[w]) - 2;
    int Y2 = diastole->getYCoor(Diastoley[w]) + 2;
    Line* Linie = c->addLine(X1, Y1, X2, Y2, 0x4472C4, 2);
    Linie->setZOrder(Chart::GridLinesZ);
}
HF->moveFront();

// 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);
return c;
}


If the above code works in your computer, but your real code does not work, is it possible to use hard coded data to create some code that I can try?


Regards
Peter Kwan
chartdirector_report.pdf
chartdirector_report.pdf

40.52 Kb

  Re: Creating PDF Report
Posted by Medic89 on Oct-01-2021 21:27
Hi Peter,

your attached PDF report works fine, I can print it without problems. I tried to implement your code, but I don't have m_dataSeries defined. What are those for? In the actual code I have a function, that retrieves the beginning of the diagram and since the diagramm has always the same width, the end point is 9899 seconds greater than the start point (Startzeit() and Startzeit() + 9899).

Another thing that bothers me: The lines of the diagramm do look very thick (with density set to 2), I tried to set it to 1, but then they are too thin, is there a way to get a density in between 1 and 2? Since the value is defined as integer, I cannot enter 1.5.

  Re: Creating PDF Report
Posted by Peter Kwan on Oct-02-2021 03:16
Hi Medic89,

The m_dataSeries are just random numbers. As I do not have you data, I need to some data to plot the chart, so I generate some random numbers right at the top few lines of the code:

RanSeries r(127);
DoubleArray m_timeStamps = r.getDateSeries(1827, Chart::chartTime(2015, 1, 1), 300);
m_dataSeriesA = r.getSeries(1827, 150, -10, 10);
m_dataSeriesB = r.getSeries(1827, 200, -10, 10);
m_dataSeriesC = r.getSeries(1827, 250, -8, 8);

In your own code, you will use your real data, not random numbers. If the chart works with random numbers, but not your real data, please inform me exact what are your real data. It is best if you can hard coded the real data in your code for testing, so that I can run your code with your real data.

For the line width, are you referring to the grid lines?

It is technically impossible to draw a horizontal or vertical line that is 1.5 pixels thick, as it is not allowed by the display hardware. (The hardware does not allow filling half a pixel.) In practice, anti-alias method is used, which means a 1.5 pixel line is still drawn  as a 2 pixel line but with a semi-transparent color. That means drawing a 2 pixel line with a semi-transparent color is equivalent to a 1.5 pixel line. For example, instead of using 0xD9D9D9 as the grid line color, you can use 0x7fD9D9D9 as the grid line color to make it thinner.

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Oct-02-2021 18:39
Attachments:
Hi Peter,

the code works, even with my data, however, when I open the pdf report in Adobe DC and start to zoom in or out, the rectangular symbols dissapear and they are missing on the printout as well. (see attached report)

Regarding the line with:

Is it possible to increase the resolution of the page? If I increase the resolution by 50% a 1 point line appears to be 1.5 points thick.
chartdirector_report.pdf
chartdirector_report.pdf

48.03 Kb

  Re: Creating PDF Report
Posted by Peter Kwan on Oct-05-2021 00:07
Attachments:
Hi Medic89,

I investigate your PDF in detail. I suspect there are some issues with your data. In the PDF, it seems there are shapes that are far outside the page boundary. How it is handled is up to the PDF viewer. In all the PDF viewers (varous browsers, Apple Preview, etc), they just ignore those points. I suspect the Adobe DC will attempt to draw the point anyway, and this results in various strange problem as the points are too far outside the page.

As mentioned in my previous message, for testing, it is useful to use hard coded data. In this way, you can see the data and make sure they are correct. For your existing code, it is hard to know if all your arrays are correct. For example, there may be uninitialized elements and they may result in random values that are way outside the axis range and so it invisible on the chart, or the memory may be overwritten by other parts of the program. (In C/C++, it is easy for code to write outside the array bounds.) As your chart does not have too many points, it should be feasible to test using hard coded data, like:

double myData[] = { ..... some data values ... };

If the hard coded data always work, but your actual data do not work, at least we know the problem is triggered by the data.

You also mention about a line being 1.5 points thick. Do you mean 1.5 pixels thick?

You can certainly increase the resolution by 50%. But as explained in my last message, it is technically impossible to have anything 1.5 pixels thick due to hardware limitations. Your monitor cannot display anything 1.5 pixels thick.

From experience, if the line is specified as 1.5 pixels thick, the Adobe PDF viewer will probably draw the line segments randomly as 1 or 2 pixels with various shades of colors. I have attached a screen shot for your PDF as rendered by Adobe DC in my computer. You can clearly see that the vertical lines are of different widths. It is the way Adobe choose to draw lines with an "impossible width", so that "on average" the line width is 1.5 pixels.

For Google Chrome, it will probably draw the 1.5 pixel line as 2 pixels. So the line will be thicker than expected, but all vertical lines has the same width.

If your draw the chart directly on screen using the CChartViewer, ChartDirector will behave similar to Chrome, but will round down to 1 pixel instead of round up to 2 pixels.

To change the display resolution, you can use BaseChart.setOutputOption:

https://www.advsofteng.com/doc/cdcpp.htm#BaseChart.setOutputOptions.htm

For display resolution (rendered on screen with CChartViewer), the option is bmpScale. For example:

c->setOutputOptions("bmpScale=1.5");

If it normally not necessary to set the resolution for PDF, because the PDF viewer allows the user to interactively set the resolution (the user can zoom in/out, which is the same as changing the resolution). If you must set the resolution, you can use:

// The industry standard is that dpi=96 corresponds to 100% resolution
c->setOutputOptions("dpi=144");

Regards
Peter Kwan
AcroRd32_20211004234647.png

  Re: Creating PDF Report
Posted by Medic89 on Oct-06-2021 22:29
Hello Peter,

isn't there a way to just "copy" the original Chart (which is a bitmap as far as I understand) on screen and put it on a paper and convert that to a PDF?

Another question:

I have a lot of data, that is supposed to be displayed in tables (they don't belong to the chart), is there a way to copy those data and the chart into word?

  Re: Creating PDF Report
Posted by Peter Kwan on Oct-06-2021 23:14
Hi Medic89,

You can always output the chart as a PDF by using Basechart.makeChart. For example:

c->makeChart("c:/path/to/aaa.pdf");

If the chart has already been displayed to the screen using the MFC CChartViewer, the chart can be obtained from the CChartViewer like:

BaseChart *myChart = myChartViewer->getChart();

Then you can save it using myChart->makeChart("c:/path/to/filename.pdf");

There are several examples included in ChartDirector that demonstrates saving the chart as PDF. One of them is:

https://www.advsofteng.com/doc/cdcpp.htm#xyzoomscroll.htm

ChartDirector cannot generate MS Word document. Instead, MS Word should import the chart and your data. MS Word can import the chart because the chart can be generated in a standard image format (like PNG or BMP), and then MS Word can import images in standard format. MS Word also has tables and can import your data. You may refer to MS Word documentation on how to create a word document and insert images and tables into it programmatically.

https://docs.microsoft.com/en-us/office/vba/api/overview/word

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Oct-07-2021 16:49
Hi Peter,

I found the following function to save the chart as an image:

void CXYZoomScrollDlg::OnSavePB()
{
    // Supported formats = PNG, JPG, GIF, BMP, SVG and PDF
    TCHAR szFilters[]= _T("PNG (*.png)|*.png|JPG (*.jpg)|*.jpg|GIF (*.gif)|*.gif|")
        _T("BMP (*.bmp)|*.bmp|SVG (*.svg)|*.svg|PDF (*.pdf)|*.pdf||");

    // The standard CFileDialog
    CFileDialog fileDlg(FALSE, _T("png"), _T("chartdirector_demo"), OFN_HIDEREADONLY |
        OFN_OVERWRITEPROMPT, szFilters);
    if(fileDlg.DoModal() != IDOK)
        return;

    // Save the chart
    CString path = fileDlg.GetPathName();
    BaseChart *c = m_ChartViewer.getChart();
    if (0 != c)
        c->makeChart(TCHARtoUTF8(path));
}

Is it possible to so only parts of the chart? In my case, my chart is always 1500 pixels high, but the diagram itself is sometimes smaller, Can I just get an image of the lets say top 500 pixels?

  Re: Creating PDF Report
Posted by Medic89 on Oct-08-2021 19:57
Attachments:
Or is there a way in general to increase the resolution of the output image? When I zoom in, the chart looks very "rough"
chartdirector_demo.png

  Re: Creating PDF Report
Posted by Peter Kwan on Oct-08-2021 23:37
Hi Medic89,

The chart displayed by ChartDirector on the screen should stay sharp when you zoom in. PDF also can stay sharp when you zoom in. For your case, I assume you are referring to the PNG image output with the intention of importing into MS Word.

Of the standard image types, MS Word only supports raster images, which are like digital photographs. If you zoom in a photograph, at some stage, the photograph will look blurred. The blurring can be reduced by using a photograph with more pixels (higher resolution).

The API to increase the number of pixels is BaseChart.setOutputOptions. For example:

// Increase the pixel size to 150% x 150% of the original
c->setOutputOptions("bmpScale=1.5");

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Oct-09-2021 00:32
Hi Peter,

perfect, now it's sharp when I zoom in. What about eporting just parts of the chart as image? On my example above, there is a large white space underneath the chart, so I'd like to export lets say just the first 1000 pixels of the chart area.

  Re: Creating PDF Report
Posted by Peter Kwan on Oct-09-2021 01:12
Hi Medic89,

May be you can try the following way:

(a) Create another empty chart with the desired size (only 1000 pixels in height).

(b) Copy the original chart to the chart in (a). In this way, you obtain a chart the same as the original chart but only 1000 pixels in height. The original chart is unchanged so it can  be displayed as normal.

(c) Output the chart in (a)

I think it is like:

BaseChart *originalChart = myChartViewer->getChart();

// Any chart is OK for the empty chart. I use a pie chart as
// it is the simplest chart type.
BaseChart *newChart = new PieChart(originalChart->getWidth(), 1000);

// There are several ways to copy one chart to another chart. I will use "DrawArea.merge" here.
// Another alternatives is to use the original chart as the background image of the empty chart,
newChart->getDrawArea()->merge(originalChart->makeChart(), 0. 0. Chart::TopLeft, 0);

// Output the new chart
newChart->setOutputOptions(....);
newChart->makeChart(......);

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Oct-10-2021 18:25
Hi Peter,

I got another solution, I just resized the multichart to be 50 pixels higher than the actual chart with the setsize() method.

I have another problem/question:

With my app I want to create a complex report with a chart and a few pages of data in various tables. My original plan was to copy the chart in a word template and fill the tables in that word template with data from the app and finally create a pdf. The down side is, during export from word to pdf the chart quality dreceases very much, do you have an idea how to solve that better? Would you create the hole report from my app? The problem is, my report contains some tables with formatted text.

  Re: Creating PDF Report
Posted by Peter Kwan on Oct-11-2021 21:53
Hi Medic89,

You can use ChartDirector to create a page with just text and tables. For text, you can use BaseChart.addText, and for tables, you can use BaseChart.addTable.

See:

https://www.advsofteng.com/doc/cdcpp.htm#BaseChart.addText.htm
https://www.advsofteng.com/doc/cdcpp.htm#BaseChart.addTable.htm

For text, basically can add the text at any location you like. For tables, you can add the table at any location you like too, and then add text to the cells. The format of each piece of text and each cell is configurable. Even within a single line of text, you can specify different colors and fonts for different parts of the text, and you can insert icons too. See:

https://www.advsofteng.com/doc/cdcpp.htm#cdml.htm

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Oct-14-2021 17:41
Hi Peter,

I tried to set up a table for the data on my report, but when I add Text like this:

PieChart firstPage(794, 1123);
TextBox* TextBox1 = firstPage.addText(50, 50, "Diagnosen:", "Arial Bold", 11);
TextBox* TextBox1k = firstPage.addText(50, 50, buf, "Arial", 10);
TextBox1->setWidth(694);
int H1 = TextBox1k->getHeight();
TextBox1k->setBackground(Chart::Transparent, RGB(0, 0, 0), 0);

But on the printout, the lines around the Boxes do look very bold, is there a way to get them thinner?

  Re: Creating PDF Report
Posted by Peter Kwan on Oct-15-2021 03:06
Hi Medic89.

I cannot try your code because I do not know what is the text in "buf" in your code. Anyway, I try it with some random text. The line width of the box is exactly 1 pixel thick when displaying the PDF at 100% size on my machine. If you think this is not the case in your machine, is it possible to attach the output so I can analyze it. Please include two things:

(a) The PDF file you obtained.

(b) A screen shot that shows the PDF as appears on your screen.

Note that for PDF, you can view it at any resolution you like. For example, you can ask the PDF viewer to display the PDF at 300% size, in this case the line width will be 3 pixels as appeared on the screen.

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Oct-15-2021 17:31
Attachments:
Hi Peter,

please see the PDF report attached to this post. I also attached two screenshots, One from that pdf report and another one from a report created in word and exported as pdf, both screenshots are made at a zoom of 800%, as you can see, on the word pdf, the line are much thinner than on the chartviewer pdf.
Screenshot 2021-10-15 112823.png
Screenshot 2021-10-15 112801.png
chartdirector_report.pdf
chartdirector_report.pdf

50.93 Kb

  Re: Creating PDF Report
Posted by Peter Kwan on Oct-18-2021 23:28
Hi Medic89,

I have just tried your PDF. At 100% zoom, the line width is exactly 1 pixel. So the PDF correctly reflect the chart.

ChartDirector is basically a graphics program. It aims to create an accurate display on screen. Like other screen oriented graphics programs, such as Photoshop and Windows Paint, the minimum line width is 1 pixel, which matches with the screen.

MS Word aims to print accurately on paper. It uses physical length units, which is appropriate for paper. Traditionally the "point" (pt) is used as unit, where 1 pt = 1/72 inch. Physical units are continuous, so MS Word can use fractional units.

As MS Word (and PDF) prioritize paper over screen, so the screen display often has distortions. For example, lines of the same widths and have rendered at different widths of screen, as illustrated in the following post:

https://www.chartdir.com/forum/download_thread.php?site=chartdir&bn=chartdir_support&thread=1632570844#N1633363677

Also, line width may not zoom proportionally on screen for MS Word / PDF. The line may appear to have the same line width at 100% and 200% zoom. It is even possible for a line at 400% zoom to become thinner than a line at 300% zoom.

In brief, for your case, the 1px is already the minimum line width. I suspect the MS Word PDF also displays the line as 1px at 100% zoom. If you want the line width to be smaller at 800% zoom, the only method is to configure the ChartDirector PDF to use a higher dpi (dot per inch is a scale factor to change the pixel unit to inch unit used in PDF. The industry standard is 96dpi). By default, 1 pixel will be 1/96 inch, or 0.75pt in PDF. If you use a higher dpi, it will become thin in pt unit. However, it also make everything (such as the text) smaller, so it is equivalent to using a smaller zoom factor. To make everything the same size except the line, you have to make everything bigger (eg, use larger text), which keeping the line width unchanged, and apply a larger dpi when creating the PDF.

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Oct-28-2021 17:38
Hello Peter,

which of the following lines are the best way to increase DPI?

const char* pageConfig = "pagewidth = 1123; pageHeight = 794";
firstPage.setOutputOptions("dpi=192");
firstPage.setOutputOptions("pagewidth = 1157; pageHeight = 1637");
firstPage.setOutputOptions("bmpScale=4");

  Re: Creating PDF Report
Posted by Peter Kwan on Oct-29-2021 01:32
Hi Medic89,

The dpi determines the DPI. The pageWidth and pageHeight determines the paper size. The default DPI is 96. Suppose you want to double the DPI, you can use dpi=192.

For the page size, suppose you want to use the A4 paper size (210mm x 297mm). So the number of dots for 210mm must be 210 / 25.4 x 192 = 1587 dots, (The 25.4 factor is to convert mm to inch.) Similarly, 297mm equais to 297 / 25.4 x 192 =  2245 dots. So the  pageWidth should be 2245 and pageHeight should be 1587,

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Oct-29-2021 16:54
Hello Peter,

so I set those two lines?

firstPage.setOutputOptions("pagewidth = 1587; pageHeight = 2245");
firstPage.setOutputOptions("dpi=192");


If I just add the first line to my project, the pdf page is twice as big as an A4 size.

  Re: Creating PDF Report
Posted by Peter Kwan on Oct-29-2021 21:43
Hi Medic89,

You can use:

// A4 in portrait orientation
firstPage.setOutputOptions("pageWidth = 1587; pageHeight = 2245; dpi=192");

or

// A4 in landscape orientation
firstPage.setOutputOptions("pageHeight = 1587; pageWidth = 2245; dpi=192");

Hope this can help.

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Oct-31-2021 20:56
Hi Peter,

it works perfect, thank you:)


Another problem:

I have a subdialog with a DateTimePicker. When I hit the save button, I want to save the time as double. But how do I get the time from my DTP into a double variable? I used to convert it by using the following lines:

CTime Zeit1;
DWORD dwResult1;
dwResult1 = AlarmDatum.GetTime(Zeit1);
((CMFCApplication2Dlg*)m_pDialog2)->AZ = Chart::chartTime(Zeit1.GetYear() - 1969, Zeit1.GetMonth(), Zeit1.GetDay(), Zeit1.GetHour() - 1, Zeit1.GetMinute(), Zeit1.GetSecond());

However, after switching to Daylight saving time today, there is always a difference of one Hour, when the DTP is at 00:00 and I hit save, the timestamp is at 01:00...

  Re: Creating PDF Report
Posted by Medic89 on Oct-31-2021 21:09
However, If I enter 12:00 for example, the time string is correct.

  Re: Creating PDF Report
Posted by Peter Kwan on Nov-01-2021 20:37
Hi Medic89,

ChartDirector is time zone independent. That means it always display the same time that you provides in terms of yyyy, mm, dd, hh, nn, ss. If you enter 00:00, and the chart is displayed as 01:00, the most likely reason is that your code provides 01:00 to ChartDirector.

I am also thinking, the year should be Zeit1.GetYear(), not Zeit1.GetYear() - 1969, and the hour should be Zeit1.GetHour() as opposed to Zeit1.GetHour() - 1.

Please check what your code actually provides the ChartDirector. May be you can try something like:

int yyyy = Zeit1.GetYear();
int mm = Zeit1.GetMonth();
int dd = Zeit1.GetDay();
int hh = Zeit1.GetHour();
int nn= Zeit1.GetMinute();
int ss= Zeit1.GetSecond();
double tt = Chart::chartTime(yyyy, mm, dd, hh, nn, ss);

// Displays in the Visual Studio debugger window what is the yyyy-mm-dd hh:nn:ss as
// reported by CTime, what is the double value tt, and how ChartDirector interprets this
// value.
TRACE("%04d-%02d-%02d %02d:%02d:%02d => %f\n", yyyy, mm, dd, hh, nn, ss);
XYChart c;
TRACE("%s\n", c.formatValue(tt, "{value|yyyy-mm-dd hh:nn:ss}");

((CMFCApplication2Dlg*)m_pDialog2)->AZ = tt;

If the above still cannot solve the problem, please let me know what is display on the debugger window. (You can copy and paste the lines generated by TRACE and include it in your message. I can then try the same date in my machine.) Note that the TRACE macro only works for code that is complied in Debug mode.

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Nov-02-2021 17:49
Hi Peter,

my problem is, I select a certain date/time a datetimepicker and I use the Chart::time function to store them in a double variable. But when I want to put the double value back into the DTP, the time displayed on the DTP is different than the one I saved. Thats why I had to put in some modifications like .getYear()-1969

  Re: Creating PDF Report
Posted by Peter Kwan on Nov-03-2021 00:18
Hi Medic89,

Would you mind to clarify how do you put the double value back to the DTP? As far as I know, DTP does not support the double value representation used by ChartDirector. The suggested way to putting the date/time back to DTP from ChartDirector is to get the yyyy-mm-dd hh:nn:ss back and put them to the DTP. To get the yyyy-mm-dd hh:nn:ss back, the code is:

// t = double value created by chartTime
int yyyymmdd = Chart::getChartYMD(t);
int yyyy = yyyymmdd / 10000;
int mm = yyyymmdd % 10000 / 100;
int dd = yyyymmdd % 100;
int secondsOfDay = t % (24 * 60 * 60);
int hh = secondOfDay / 3600;
int nn = secondOfDay % 3600 / 60;
int ss = secondOfDay % 60;

You can then use the above date/time parts to obtain the CTime to be used by DTP.

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Nov-03-2021 20:13
Hi Peter,

when I open my Dialog, I set the time in the DTP as following:

time_t timestamp;
tm* now;
int correction;
// a change greater 0 indicates, that the default time value has already been modified, otherwise, the current time will be displayed in the DTP (with the value for seconds of 0)
if (change > 0)
{
timestamp = change;
        correction = 0;
}
else
{
timestamp = time(0);
        correction = 0;
}
now = localtime(&Zeitstempel);
struct tm
{
int tm_sec;
};

if (change > 0)
{
        correction = 0;
}
else
{
        correction = now->tm_sec;
}

v_DTP1.SetTime(timestamp - correction);

  Re: Creating PDF Report
Posted by Medic89 on Nov-04-2021 19:25
Hi Peter,

I tried to make a more detailed pdf report, which is working great so far. However, is there a way to continue a table which starts on page one on a second page? For example, my pages are 2245 pixels high, and I have a margin of 60 pixels at the bottom of every page. So if my the bottom border of my table overruns 2185 pixels, I want to continue the rest of the table on the second page. Is there an easy way to do this?

  Re: Creating PDF Report
Posted by Peter Kwan on Nov-05-2021 02:11
Hi Medic89,

For the PDF report, you would need to use your own code to split the table two multiple pages. The ChartDirector API can tell you the height of a row and the height of the table. The APIs are CDMLTable.getRowHeight and CDMLTable.getHeight. If your code feel that it is too close to the bottom margin, it can start a new page (by adding a new empty chart to the MultiChart) with a new CDMLTable object for the rest of the rows.

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Nov-08-2021 17:35
Attachments:
Hi Peter,

my problem is, the text inside the textbox is one block of data. So what can I do, if lets say just have of the block fits on page one, in that case I'd have to split the block into two parts (part one from line 1 to 4 for example and part two from line 5 to end), is there a way to split the data block by lines? I attached an example of a textblock.
Screenshot 2021-11-08 103443.png

  Re: Creating PDF Report
Posted by Peter Kwan on Nov-09-2021 16:05
Hi Medic89,

ChartDirector is a charting library. It does not have built-in features to layout text in multiple pages. So for your case, you would need to layout the text with your own code.

You can split a block of text and put then on two different pages by splitting them to two blocks of text instead of one block of text. You would need to use your own code to determine where the split the text.

ChartDirector can tell you how tall is a block of the text. You may try to remove the last word of the text and check if it is still too tall. You can repeat this procedure until you obtain a block of text that can fit on the page. The removed text can then be added to the next page.

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Peter Kwan on Nov-05-2021 02:00
Hi Medic89,

For the issue related to the Date Time Picker, would you mind to clarify which variable in your code is the "double value that comes from Chart::chartTime"? Is it the "change" variable?

The Date Time Picker supports several date/time representations, but the "double value" is not one of them. So if change > 0, you would need to use the code as mentioned in my last message to convert it to the MFC CTime and assign it to the Date Time Picker. (The Date Time Picker can support CTime value.)

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Nov-05-2021 17:51
Hello Peter,

yes, when I open the subdialog I transfer the time value to the change variable, which is then put into the DTP.

  Re: Creating PDF Report
Posted by Medic89 on Nov-10-2021 17:47
Hello Peter,

is it possible, to create a draft page which is as wide as an A4 page, but as long as necessary to hold all the tables. In a second step, I would take everthing that doesn't fit on page one and "transfer" it to a second page. Can I do this without drawing everything again, I'd rather take some sort of "screenshot" from all parts that belong to page 2 and put them on a new page.

  Re: Creating PDF Report
Posted by Medic89 on Nov-10-2021 17:53
One more question:

I'm really struggling to put an image on my pdf report, I added a PNG file as a ressource and tried to add it to my report using the following code, but without sucess:

char resource_path[256];
sprintf(resource_path, "@/PNG/%d", IDB_PNG1);
firstPage.setSearchPath(resource_path);
TextBox* Kopfzeile = firstPage.addText(189, 60, resource_path, "Arial Bold", 16);

  Re: Creating PDF Report
Posted by Peter Kwan on Nov-12-2021 00:24
Hi Medic89,

If you ask the textbox to display "@/PNG/123", then it will display "@/PNG/123". It will not display an image. To display an mage, you have to use "<*img=@/PNG/123*>". Many ChartDirector examples include images on the chart. For example, you may refer to the MFC version of the "Spline Line Chart" to see how the logo is added to the chart.

https://www.advsofteng.com/doc/cdcpp.htm#splineline.htm

(Note: The code listed above is the command line example, which uses file paths instead of resources. The MFC version of the sample code in "mfcdemo.cpp" uses resources.)

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Peter Kwan on Nov-12-2021 00:56
Hi Medic89,

When you create a table on a A4 page, the table can grow to as long as necessary. If the table is longer than the page, then the overflow part of the table wiil not be displayed. You can resize the page by using BaseChart.setSize to make the page long enough to contain the table.

You can take an equivalent of a screenshot programmatically, and copy the part that extends beyond the A4 size to another page. However, I think it is not a good idea. First, you may cut the text in the incorrect place. To cut correctly, it must be cut in the empty space between two lines, not just the part that is overflow to the next page. That means your code must know the height of each line, so that it knows where is the space between two lines. Also, the "screenshot" is a bitmap image, and it will become pixelated when zoom in.

I think the approach mentioned in my previous message is the easiest method. In the simplest case, you may try to add the text block by block in a loop, until the table is too long. Then your code can move the last block to the next page and restart the layout there.

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Nov-12-2021 18:32
Attachments:
Hello Peter,

i attached a screenshot of an example. The textblock is "one piece" of text, which is being added via the addText function to the page. I can determinde how many lines this block has and I know the height of each line. So with a loop its not problem to determine, how many lines of the block fit on page one, and with part needs to be shifted on page 2.

In my example lines 1 to 3 fit on page one, but line 4 doen't. So how can I cut the fourth line and put it on another page using a different textbox? The only way I think is making a "screenshot" of the 4th line and place that on page two. Do you have another idea?
Screenshot 2021-11-12 113106.png

  Re: Creating PDF Report
Posted by Peter Kwan on Nov-12-2021 22:59
Hi Medic89,

I am not sure what is the "textblock" in your code. I assume it is just a text string (something like char* or std::string in C++) that your code can copy or manipulate. Since your code knows the page can only fit lines 1 to 3, it should remove the 4th line from the "textblock", and then use BaseChart::addText to add the remaining textblock that just contains 3 lines. You can then add the page to the PDF using MultiPagePDF::addPage.

Your code can create another empty chart of the proper size, add the fourth line to it using BaseChart::addText, and add the chart as a page using MultiPagePDF::addPage.

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Nov-13-2021 17:50
Hello Peter:

my textblock is a CString, which is being transformed to char* and then out into a textbox, the problem is, the texblock doen't have any returns in it, so I don't know, which part of the text is in lines 1 to 3 from my previous example.

  Re: Creating PDF Report
Posted by Peter Kwan on Nov-15-2021 19:10
Hi Medic89,

If you do not know how many lines in your text or which part of the text is in lines 1 to 3, then you can use my earlier suggestion - just try to add text in a loop until you find out how which part of the text fit the page.

Since your code specifies the page size, and also the starting position of the text block,
it certain knows how much space are available on the page. So your code can try in a loop until finds out on to fit the text on the page.

Suppose your TextBox is:

const char* text = .... some text ....;
TextBox* tb = c->addText(100, 100, text, "Arial Bold", 8, 0x0000ff);
tb->setMaxWidth(400);

// Use a loop for reduce the text if it is too tall
std::string str = text;
while ((str.size() > 0) && (tb->getHeight() > maxHeight)) {
     // Assume we can split the text at a white space character
     int i = str.size();
     while ((i >= 0) && (str[i] != ' ')) --i;

     // No space character in the text, so the whole text must be put on the next page
     if (i < 0)
        str.resize(0);

     if (i >= 0) {
         // Split the text at the white space and try again
         str.resize(i);
tb->setText(str.c_str());
     }
}

// The following are the text on the next page
const char* textOnNextPage = text + str.size();
if (*textOnNextPage)
{
      // create another page and put the text there
}

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Nov-15-2021 20:45
Hi Peter,

thank you a lot for your code, I will try that in the following days. I just had a look at the ChartDirector reference and I found the truncate function, wouldn't it be easer to use this function?

  Re: Creating PDF Report
Posted by Peter Kwan on Nov-15-2021 21:43
Hi Medic89,

The truncate function will instruct the TextBox to limit the number of lines. When the chart is rendered, if the text turns out to exceed a certain number of lines, the rest of the text will be omitted. However, it will not tell which part is being omitted, so your code will not know which part of the text should be moved to the next page.

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Medic89 on Nov-20-2021 23:08
Hi Peter,

do you know, if it's possible to use an already existing pdf for creating a pdf report? On another project I want to 'fill out' a form using ChartDirector, so basically I would add some text to an already existing pdf and create a new one out of the template, is that even possible?

  Re: Creating PDF Report
Posted by Peter Kwan on Nov-22-2021 20:56
Hi Medic89,

It should be possible if you have a suitable PDF reporting library.

Note that ChartDirector is a charting library, not a PDF reporting library. ChartDirector can save the chart as a PDF, SVG, PNG or JPG. You can always use your own PDF library to insert these content into a PDF. All PDF reporting libraries should support at least one of these formats as input.

The enhancement in ChartDirector 7.0 is that it can save multiple charts as a PDF with multiple pages. Still, it is not a PDF reporting library. Many PDF features, such as PDF Forms, signatures, etc., are not supported in ChartDirector. You will need to use a library specifically designed to generate and manipulate PDF for these features.

Regards
Peter Kwan

  Re: Creating PDF Report
Posted by Daniel on Nov-24-2021 12:52
"On another project I want to 'fill out' a form using ChartDirector, so basically I would add some text to an already existing pdf and create a new one out of the template, is that even possible?"

But I can fully recommend using Quickpdf to incorporate both CD output material (both as raster ie png-s or even vectorial pdf output from CD itself).

I have been using CD in tandem with the Quickpdf library quite effectively over the last 10 years or so. The lib is not always easy but it it extensive and really powerful: you can indeed use pdf forms as templates for feeding arbitrary text and/or CD image material. And more.

There are certainly alternatives of course in this field!

Daniel

  Re: Creating PDF Report
Posted by Medic89 on Nov-29-2021 18:09
Hello Peter,

your code for splitting the text is working great, thank you!

For another page I tried to use the advanceTo CDML atribute, I start in Line 1 with some text, then I add advanceTo=669 and add some more text, but when I add n I can't use the advanceTo atribute anymore (in the second line I want to move to position 170).

  Re: Creating PDF Report
Posted by Peter Kwan on Nov-30-2021 03:39
Hi Medic89,

For advanceTo, ChartDirector assumes the text segment flows from left to right, although the characters and words inside the text segment can flow from right to left for some text.

You can think of "advanceTo" to be like the "Tab" key. When you hit the "Tab" key, it moves the cursor to the next tap stop to the right. It cannot move backwards. The ChartDirector documentation mentions that the usage of "advanceTo" is:

Move the cursor forward (to the right) to the position as specified by the value this attribute. The position is specified as the number of pixels to the right of the left border of the block. If the cursor has already passed through the specified position, the cursor is not moved.

Is it possible to rearrange your text segments so that it is in left to right order, that is, adding the text at 170 first, followed by 669.

Regards
Peter Kwan