Skip to content

Instantly share code, notes, and snippets.

@webdevilopers
Last active May 22, 2018 20:23
Show Gist options
  • Save webdevilopers/e0d34a1c04ef2050a450 to your computer and use it in GitHub Desktop.
Save webdevilopers/e0d34a1c04ef2050a450 to your computer and use it in GitHub Desktop.
Rendering Highcharts in wkhtmltopdf generated PDF using the symfony2 bundles ObHighchartsBundle and KnpSnappyBundle
<?php
namespace Plusquam\Bundle\ContractBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Ob\HighchartsBundle\Highcharts\Highchart;
/**
* Bundle controller.
*
*/
class BundleController extends Controller
{
/**
* @Route("/qis/contract/{contractId}/bundle/list")
* @Template()
*/
public function listAction(Request $request)
{
$contractId = $request->get('contractId');
// Chart
$series = array(
array("name" => "Data Serie Name",
"data" => array(1,2,4,5,6,3,8),
// @see http://blog.psyrendust.com/2012/12/03/wkhtmltopdf-setup-guide-with-knplabs-snappy-support-on-debian/
'animation' => false // important?
)
);
$ob = new Highchart(); // uses zend components? json, stdlib
$ob->chart->renderTo('linechart'); // The #id of the div where to render the chart
$ob->title->text('Chart Title');
$ob->xAxis->title(array('text' => "Horizontal axis title"));
$ob->yAxis->title(array('text' => "Vertical axis title"));
$ob->series($series);
$ob->plotOptions->pie(array(
'animation' => false,
'enableMouseTracking' => false,
'shadow' => false
));
$em = $this->getDoctrine()->getManager();
$parttypes = $em->getRepository('Plusquam\Bundle\ContractBundle\Entity\Bundle')
->getGroupedParttypes(array('contract' => $contractId), true, false);
$parttypesGroupedByShippingNumber = $em->getRepository('Plusquam\Bundle\ContractBundle\Entity\Bundle')
->getGroupedParttypes(array('contract' => $contractId), true, true);
$viewParameters = array(
'contract' => $contract,
'parttypes' => $parttypes,
'parttypesGrouped' => $parttypesGrouped,
'parttypesGroupedByShippingNumber' => $parttypesGroupedByShippingNumber,
'chart' => $ob
);
if ($request->get('pdf')) {
$html = $this->renderView('PlusquamContractBundle:Bundle:list.html.twig', $viewParameters);
$snappy = $this->get('knp_snappy.pdf');
return new Response(
$snappy->getOutputFromHtml($html, array(
'orientation' => 'Landscape',
'images' => true,
'enable-javascript' => true
)),
200,
array(
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="report.pdf"'
)
);
}
return $viewParameters;
}
}
framework:
templating:
assets_base_urls: http://localhost:8001 # run on a different port using another single-threaded built-in webserver
<img src="{{ asset('logo.png', absolute=true) }}">
<div id="linechart" style="min-width: 400px; height: 400px; margin: 0 auto"></div>
<script src="{{ asset('/bundles/sonatacore/vendor/jquery/dist/jquery.min.js') }}" type="text/javascript"></script>
<script src="{{ asset('/highcharts/highcharts.js') }}" type="text/javascript"></script>
<script type="text/javascript">
{{ chart(chart) }}
</script>
@webdevilopers
Copy link
Author

Blog by @psyrendust:

and by @claytonlz:

recommend setting all animations to false.
In order for wkhtmltopdf to correctly print the chart, it is advised to remove any animations on hicharts.js objects. You will need to set the {javascript}animation{/javascript} property to false for both the {javascript}Chart{/javascript} object and the {javascript}plotOptions.series{/javascript} object.

It looks like the Highcharts API has changed and you cannot disable animation on the chart object directely. It must now be done on the plotOptions for a single type or all types resp. series:
http://stackoverflow.com/questions/13831501/highcharts-turn-animation-false-for-line-chart

Still the chart won't show up.

@psyrendust
Copy link

@webdevilopers I don't have experience with twig, but you could try to rearrange your scripts so that it's below your div#linechart.

<div id="linechart" style="min-width: 400px; height: 400px; margin: 0 auto"></div>

<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="http://code.highcharts.com/highcharts.js"></script>
<script src="http://code.highcharts.com/modules/exporting.js"></script>
<script type="text/javascript">
(function($){
  $(function() {
    var linechart = new Highcharts.Chart({
      chart: {"renderTo":"linechart"},
      plotOptions: {"pie":{"animation":false,"enableMouseTracking":false,"shadow":false,"images":true,"enable-javascript":true,"javascript-delay":500}},
      series: [{"name":"Data Serie Name","data":[1,2,4,5,6,3,8],"animation":false}], 
      title: {"text":"Chart Title"},
      xAxis: {"title":{"text":"Horizontal axis title"}},
      yAxis: {"title":{"text":"Vertical axis title"}}
    });
  });
})(jQuery);
</script>

Also with snappy I was setting the javascript-delay to 500 milliseconds. This was to ensure that snappy has enough time to render the page before printing it out to echo.

$snappy->setOption('javascript-delay', 500);

I would check to see if your page is even rendering the chart without the print to pdf part. If it is, then your delay might not be long enough.

@webdevilopers
Copy link
Author

Thanks @psyrendust. I think I found the major problem by checking if javascript is correctely executed:

    <script src="/bundles/sonatacore/vendor/jquery/dist/jquery.min.js" type="text/javascript"></script>
    <script src="/highcharts/highcharts.js"></script>
    <script type="text/javascript">
        document.write('X');

        $( document ).ready(function() {
            document.write('JQ');

        });
        {{ chart(chart) }}
    </script>  

While my regular page will load the jQuery part and output "JQ" my PDF only shows "X".
Javascript is executed but not jQuery. I guess it is a problem with the assets path.

I will check that!

@webdevilopers
Copy link
Author

I replaced the path with the official twig assets:

<script src="{{ asset('/bundles/sonatacore/vendor/jquery/dist/jquery.min.js') }}" type="text/javascript"></script>

But still no success. Then I included the CDN version:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>

Which includes jQuery but the chart is still not generated.

So I will have to check the path problem in twig and why Highcharts still won't work even with different delays.

@webdevilopers
Copy link
Author

Regarding the javascript delay I misunderstood you @psyrendust ! I now set it on snappy directely:

$snappy = $this->get('knp_snappy.pdf');
$snappy->setOption('javascript-delay', 500);

Now I get this error:
The exit status code '1' says something went wrong:
stderr: "Unknown long argument --javascript-delay

Well there is an array of options you can pass instead e.g.:

            return new Response(
                $snappy->getOutputFromHtml($html, array(
                    'lowquality' => false,
                    'encoding' => 'utf-8',
                    'images' => true,
                    'enable-javascript' => true,
                    'javascript-delay' => 5000
                )),
                200,
                array(
                    'Content-Type'          => 'application/pdf',
                    'Content-Disposition'   => 'attachment; filename="report.pdf"'
                )
            );

But this time:
The exit status code '1' says something went wrong:
stderr: "Unknown long argument --images

@webdevilopers
Copy link
Author

The mentioned errors really seem to be wkhtmltopdf 0.9.9 related issues. I've updated to 0.12 (with patched qt) and will try again.

@webdevilopers
Copy link
Author

BTW I tried generating a PDF from an URL with charts - I don't know if Javascript is executed for this purpose. But there is a chart - though it looks terrible. :)

$this->get('knp_snappy.pdf')->generate('http://www.highcharts.com/stock/demo/', '/home/foo/snappy.pdf');

@webdevilopers
Copy link
Author

It looks like this is a Highchart issue. I easily created an example using jqPlot.

Following this issue related to Highchart:
wkhtmltopdf/wkhtmltopdf#1964

@webdevilopers
Copy link
Author

There seems to be an issue with wkhtmltopdf and HighCharts using specific ports like 8000 or 8080:

Still checking. Any help would be appreciated!
@barryvdh @docteurklein @MarieMinasyan @skybondsor @colejarczyk

@webdevilopers
Copy link
Author

Indeed there is a port problem but NOT WITH wkhtmltopdf.

My symfony2 app runs on the PHP built-in webserver. This webserver is single-threaded only.
Multiple processes like generating the PDF on the hand while loading assets like images or javascript on the other hand will result in a timeout when using Snappy.

PHP applications will stall if a request is blocked.
http://stackoverflow.com/questions/25062398/max-concurrent-connections-for-phps-built-in-development-server

The embedded web server is for testing purpose and not meant as a full web server. This limitation along with others (like missing vhost support etc.) is by design and we don't have any plans to change that as there are multiple good web servers already which work nicely with PHP.
https://bugs.php.net/bug.php?id=67884

I havn't tested my code on a production server yet but it should work out of the box.

In order to make it working on your local machine when using the built-in server and Symfony2 you can route the assets to a different URL / port:

# app/config/config_dev.yml
framework:
    templating:
        assets_base_urls: http://localhost:8001

as suggested by @docteurklein in issue KnpLabs/snappy#58 (comment).

Now the HighCharts will show up using the code provided in this gist.

Of course you will have to run a second instance of the built-in webserver on the new port 8001:

@webdevilopers
Copy link
Author

Ps.: Paths to assets e.g. images maybe need to be absolute:

<img src="{{ asset('logo.png', absolute=true) }}">

@cassianotartari
Copy link

I've tried all said here without success... The last and stupid shot worked, still don't know why. I'm running my symfony2 app under apache server in my machine. I'm using Ubuntu 14.10 with wkhtmltopdf 0.12.2.1

I've created two routes: one to render the html with the chart and other to create the pdf with the generated absolute url from first route.

Testing with the command line all works fine:

wkhtmltopdf --javascript-delay 10000 http://www.highcharts.com/demo/line-basic test.pdf

The test code:

    public function indexAction()
    {

        $series = array(
            array("name" => "Data Serie Name", "data" => array(1, 2, 4, 5, 6, 3, 8))
        );

        $ob = new Highchart();
        $ob->chart->renderTo('linechart');  // The #id of the div where to render the chart
        $ob->title->text('Chart Title');
        $ob->xAxis->title(array('text' => "Horizontal axis title"));
        $ob->yAxis->title(array('text' => "Vertical axis title"));
        $ob->series($series);
        $ob->plotOptions->series(array(
            'animation' => false,
            'enableMouseTracking' => false,
            'shadow' => false
        ));

        return $this->render('LogBundle:Reports:teste.html.twig', array(
            'chart' => $ob
        ));
    }

    public function testAction() {
        $url = $this->generateUrl('test_index_route', array(), UrlGeneratorInterface::ABSOLUTE_URL);
        return $this->container->get('knp_snappy.pdf')->generate($url, '/home/cassiano/Desktop/file.pdf');
    }

@cassianotartari
Copy link

Found the issue, in my template: LogBundle:Reports:teste.html.twig I was adding the highcharts.js without the url scheme, like this:

<script src="//code.highcharts.com/4.0.1/highcharts.js"></script>

After adding http: to url the chart appeared in the pdf.

Now I understood why just calling asset in twig with dev env leaving default value in assets_base_urls rendered blank chart. Maybe if add assets_base_urls the same vhost name it will rendered too instead of using:

{{ app.request.scheme ~'://' ~ app.request.httpHost ~ asset(...) }}

I access my symfony app by http://myapp/app_dev.php/...

@webdevilopers
Copy link
Author

@AndreyBashuk
Copy link

Thank!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment