Ngx-charts: download the chart as a image or excel spreadsheet

Created on 9 Jun 2017  路  32Comments  路  Source: swimlane/ngx-charts

I'm submitting a ...
[ ] bug report => search github for a similar issue or PR before submitting
[ x ] feature request
[ ] support request => Please do not submit support request here

Current behavior
No download option

Expected behavior
The ability to download the chart as a image or excel spreadsheet, like highcharts
https://www.highcharts.com/docs/export-module/export-module-overview

What is the motivation / use case for changing the behavior?
Allows greater use of the charts package by consumers, allowing them to save data for offline use or putting into a report, such as presentation.

New Feature In Discussion Suggestion

Most helpful comment

I would absolutely love an export as PDF, SVG and PNG option.

All 32 comments

This is not built into the library yet. We have built this feature in our app outside of the library.

You can easily use something like https://github.com/exupero/saveSvgAsPng to add this functionality, but we may want to build it into the library.

Yer i think a built in feature would be much better than depending on a separate library but thanks for letting me know.

Cant wait to see this with a few more features and charts. I was close to choosing this over highcharts but it lacked this feature and a few charts i needed, such as scatter graph (didnt see one on the demos anyway).

Looking forward to future releases tho i will be keeping an eye on this repo

We do have a scatter chart: https://swimlane.github.io/ngx-charts/#/ngx-charts/bubble-chart

I would absolutely love an export as PDF, SVG and PNG option.

Also, this feature is built into a chart builder prototype that @Hypercubed created, you can find it here:
https://swimlane.github.io/ngx-charts-builder/

@marjan-georgiev, thanks for pointing out the availability of the prototype by @Hypercubed.

I've been playing around with the chart builder, and it seems like the svgsaver library only captures the graph but not the legend. For some charts, such as the advanced pie chart, key information is not captured by svgsaver since those are contained as the advanced-pie-legend-wrapper class and not as part of the <svg>...</svg>.

@huiguang-liang that's a good point. The svgsaver library works only on the SVG elements. It will be tricky to include the surrounding HTML.

@huiguang-liang did you manage to download the legend also for the advanced-pie-legend-wrapper ?
*
Edit: I actually found a way to include the legend also in the exported png image

@ErvinLlojku could you share your solution to include the legend in the exported png image? Thx.

Hello averybody,
are there any news on exporting charts as svg?

@marjan-georgiev if you want export to svg you can just view the code in the repo of https://swimlane.github.io/ngx-charts-builder/

Yes, you can look at how that is implemented and use it. One note though, it only exports the SVG part of the chart, so it will not include legends.

@ErvinLlojku Can you elaborate on the edit you made on Nov 10, 2017 where you mentioned that you "found a way to include the legend also in the exported png image"? I see that I'm not the only person curious about this last piece of the puzzle, and it does not appear that the ngx-charts-builder that you reminded @marjan-georgiev of 9 days ago includes this functionality.

Did you customize that export process yourself?

@Londovir yes i practicly used this libraries to handle the process for all the charts in my app:

import * as FileSaver from 'file-saver';
import domtoimage from 'dom-to-image';

I'll try to make a working versions for you guys in a few hours.

@ErvinLlojku Ahh, dom-to-image. Interesting. I wasn't on to that library just yet. I was expecting some combination of html2canvas and using the native .toDataURL() call to persist the canvas after the html2canvas work, combined with file-saver.

This would take a step out of that process, I imagine. Interesting indeed.

@Londovir , I ended up using html2canvas with custom scaling, to avoid blurry images, and export them to pdf using .toDataURL(). It works quite nicely. I chose that because I wanted to export Legend as well. Also, in case of bar chart, I wanted to have x/y axises, which by exporting only SVG part, you won't get. I do have some issues with onrender method in html2canvas, but I saw yesterday, that this was implemented. I cannot provide you jsFiddle or similar, but I can show you an output I got if you are interested. Oh, main reason why I insisted on using .toDataURL() was because pdf is built dynamically, using multiple charts, spreadsheet and some custom text.

Nice work guys! If anyone would be willing to create a stackblitz, or even PR this functionality to the demo page, that would be awesome.

I used html2canvas for my needs too, it worked well.
So easy to use, it might not be worth building this into ngx-charts.

I agree. Let's close this issue. If anyone still wants to add a stackblitz example of it, or PR it to the demo page, please do.

@marjan-georgiev here is a Stackblitz how to use html2canvas on ngx-charts. Even if there's nothing difficult with it, I'd like to know how to really render the correct fonts, backgrounds and so on. @kriscoleman @markokoleznik maybe you can give some insight to your implementations. Thanks!

https://vertical-bar-chart-smoh7c.stackblitz.io/
https://stackblitz.com/edit/vertical-bar-chart-smoh7c

Hello everyone.
First of all, thank you @hnitzsche for creating a sample app, I forked your app and modified it a bit.
So, the initial question was how to generate nice chart and export it into PDF.
For that, I've used ngx-charts (obvious) and pdfmake.
Here is Stackblitz example:
https://stackblitz.com/edit/pie-chart-download-pdf

I deliberately made chart invisible and rendered it "in the background". For this, check what onClone is doing. You might need that if you want to slightly modify your chart for the export. I used that to render much more dense chart (scale=3) so it looks nice even on retina display.

Another example why you want to render charts in the background is when you want to show charts responsive (view is not fixed) but when exporting, you want to use a fixed size, like in the example. Oh my, what a hack 馃槀

If you have any further questions, let me know. But all in all, this should get you started.

Thanks @markokoleznik for the quick answer and your fork!

But the funny thing is, the pie chart you showed is the one I have the fewest problems with (in terms of exporting). Look at this example, when using a grouped bar chart.

https://stackblitz.com/edit/grpd-bar-chart-download-pdf

The background of the bars gets black and I can't manage to change this in a satisfying way. My solution at the moment is really hacky: Changing the HTML tag name of the <g> elements that wrap the bars to another one e.g. <foreignObject>. Then the black background is gone.

One hint regarding your export:
I'm also already using the onclone callback of html2canvas and I look for the correct DOM node where the actual svg is located and then doing this for example:

const svg = _document.getElementsByClassName('ngx-charts')[nodeIndex].childNodes[0].childNodes[0].firstChild;
svg.setAttribute('style', 'font-family: Roboto');

With this DOM manipulation (setting an explicit inline style tag on the svg) you will be able to style for example the axis ticks and labels. (That don't properly inherit the document font in your example, too)

But my question is now: What is a proper way to do this? Wouldn't it be nice for the charts to have style classes where you could override the computed style? Or is there a way to achieve this and I'm just stupid atm?

Ah, I see what you mean. Unfortunately, I do not know how to do this properly.
My feeling was also that there are CSS classes exposed that could be overridden but apparently, they are not.
Check this Gitter chat where I posted a question some time ago:
https://gitter.im/swimlane/ngx-charts?at=5a82a4378c71e5e01d8b6cd6
Does that answer your question, at least partially?

Props for your hack, though 馃槀

OK, finally achieved it. This is my solution for getting a png that includes a correct styled ngx-chart with legend and everything you might have around your chart:

  • calling html2canvas on the element that wraps your chart and legend

Then inside the html2canvas onclone callback:

  • maybe set some things like font explicitly
  • applying all the dynamic/computed styles to the svg element by using @Hypercubed 's cloneSVG method
  • replacing the original svg with the cloned one in the DOM node tree of the html2canvas cloned doc
  • enough cloning for now

@hnitzsche do you have any Stackblitz example? I'm facing some issues exporting charts as they show in screen.

function inlineStyleSVG(_document, nodeIndex) {
    const svg = _document.getElementsByClassName('ngx-charts')[nodeIndex].childNodes[0].childNodes[0].firstChild;

    const allTextElements = svg.querySelectorAll('text');
    for (let i = allTextElements.length - 1; i >= 0; --i) {
        allTextElements[i].style.fontFamily = "Roboto,sans-serif";
    }
    const clonedSVG = this.svgsaver.cloneSVG(svg);
    svg.parentNode.replaceChild(clonedSVG, svg);
}

const options = {
    logging: false, scale: 3, onclone: clonedDoc => {
        inlineStyleSVG(clonedDoc, 0);
    }
};

html2canvas(document.getElementById('barChart'), options)
    .then(canvas => {
        const dataURL = canvas.toDataURL("image/png");
    });

@hnitzsche This might be useful: https://github.com/Hypercubed/computed-styles... does a lot of the dirty work in https://github.com/Hypercubed/svgsaver

@hnitzsche to be more specific, the thing I am having trouble with is showing the GridLines in my generated png. I think is something related with the tag. Did you have this problem before?

I'm not sure where the magic happens, but I think it might be @Hypercubed 's cloneSVG() that is doing the thing. My exported charts with the code I posted above have gridlines.

hello everyone , i am using https://github.com/exupero/saveSvgAsPng to create uri . Uri of All other charts are being created but pie-chart throws error:
ERROR Error: Uncaught (in promise): There was an error loading the data URI as an image on the following SVG

My svg is as below:
<svg class="ngx-charts" width="405" height="233"><g class="pie-chart chart" transform="translate(202.5, 116.5)"><g ngx-charts-pie-series="" ng-reflect-colors="[object Object]" ng-reflect-series="[object Object],[object Object" ng-reflect-inner-radius="0" ng-reflect-outer-radius="57.666666666666664" ng-reflect-explode-slices="false" ng-reflect-show-labels="true" ng-reflect-gradient="false" ng-reflect-active-entries="" ng-reflect-trim-labels="true" ng-reflect-max-label-length="10" ng-reflect-tooltip-disabled="false" ng-reflect-animations="false"><!--bindings={ "ng-reflect-ng-for-of": "[object Object],[object Object", "ng-reflect-ng-for-track-by": "function (index, item) {\r\n " }--><g class="ng-star-inserted" style=""><!--bindings={ "ng-reflect-ng-if": "true" }--><g ngx-charts-pie-label="" ng-reflect-data="[object Object]" ng-reflect-radius="57.666666666666664" ng-reflect-label="Unknown (RMS 0)" ng-reflect-color="#5bc0de" ng-reflect-max="503066774.801616" ng-reflect-value="503066774.801616" ng-reflect-explode-slices="false" ng-reflect-animations="false" ng-reflect-label-trim="true" ng-reflect-label-trim-size="10" class="ng-star-inserted"><title>Unknown (RMS 0)</title><g style="transform: translate3d(86.5px, -14.2239px, 0px);"><text class="pie-label" dy=".35em" style="text-anchor: start; shape-rendering: crispedges;"> Unkno... </text></g><path class="pie-label-line line" fill="none" d="M56.88167497419664,-9.482589128202049L85.32251246129496,-14.223883692303072L86.5,-14.223883692303072" stroke="#5bc0de"></path></g><g ngx-charts-pie-arc="" ngx-tooltip="" ng-reflect-tooltip-title=" <span class=&quot;tooltip-la" ng-reflect-tooltip-disabled="false" ng-reflect-tooltip-placement="top" ng-reflect-tooltip-type="tooltip" ng-reflect-tooltip-context="[object Object]" ng-reflect-fill="#5bc0de" ng-reflect-start-angle="0" ng-reflect-end-angle="2.8112162743813918" ng-reflect-inner-radius="0" ng-reflect-outer-radius="57.666666666666664" ng-reflect-value="503066774.801616" ng-reflect-max="503066774.801616" ng-reflect-data="[object Object]" ng-reflect-explode-slices="false" ng-reflect-gradient="false" ng-reflect-animate="false" ng-reflect-is-active="false"><g class="arc-group"><!--bindings={ "ng-reflect-ng-if": "false" }--><path class="arc" d="M3.531064937541535e-15,-57.666666666666664A57.666666666666664,57.666666666666664,0,0,1,18.707013388586347,54.54807140975467L0,0Z" fill="#5bc0de" style="pointer-events: auto;"></path></g></g></g><g class="ng-star-inserted" style=""><!--bindings={ "ng-reflect-ng-if": "true" }--><g ngx-charts-pie-label="" ng-reflect-data="[object Object]" ng-reflect-radius="57.666666666666664" ng-reflect-label="Light Wood Stud Walls (RMS 1A1" ng-reflect-color="#5cb85c" ng-reflect-max="503066774.801616" ng-reflect-value="321358574.591884" ng-reflect-explode-slices="false" ng-reflect-animations="false" ng-reflect-label-trim="true" ng-reflect-label-trim-size="10" class="ng-star-inserted"><title>Light Wood Stud Walls (RMS 1A1)</title><g style="transform: translate3d(-86.5px, 72.9398px, 0px);"><text class="pie-label" dy=".35em" style="text-anchor: end; shape-rendering: crispedges;"> Light... </text></g><path class="pie-label-line line" fill="none" d="M-30.99851403201157,48.62650071978885L-46.497771048017356,72.93975107968328L-86.5,72.93975107968328" stroke="#5cb85c"></path></g><g ngx-charts-pie-arc="" ngx-tooltip="" ng-reflect-tooltip-title=" <span class=&quot;tooltip-la" ng-reflect-tooltip-disabled="false" ng-reflect-tooltip-placement="top" ng-reflect-tooltip-type="tooltip" ng-reflect-tooltip-context="[object Object]" ng-reflect-fill="#5cb85c" ng-reflect-start-angle="2.8112162743813918" ng-reflect-end-angle="4.607018541706577" ng-reflect-inner-radius="0" ng-reflect-outer-radius="57.666666666666664" ng-reflect-value="321358574.591884" ng-reflect-max="503066774.801616" ng-reflect-data="[object Object]" ng-reflect-explode-slices="false" ng-reflect-gradient="false" ng-reflect-animate="false" ng-reflect-is-active="false"><g class="arc-group"><!--bindings={ "ng-reflect-ng-if": "false" }--><path class="arc" d="M18.707013388586347,54.54807140975467A57.666666666666664,57.666666666666664,0,0,1,-57.34682829674664,6.065123968057302L0,0Z" fill="#5cb85c" style="pointer-events: auto;"></path></g></g></g><g class="ng-star-inserted" style=""><!--bindings={ "ng-reflect-ng-if": "true" }--><g ngx-charts-pie-label="" ng-reflect-data="[object Object]" ng-reflect-radius="57.666666666666664" ng-reflect-label="Unknown (RMS IND 0)" ng-reflect-color="#6a533b" ng-reflect-max="503066774.801616" ng-reflect-value="299949817.600467" ng-reflect-explode-slices="false" ng-reflect-animations="false" ng-reflect-label-trim="true" ng-reflect-label-trim-size="10" class="ng-star-inserted"><title>Unknown (RMS IND 0)</title><g style="transform: translate3d(-86.5px, -57.8589px, 0px);"><text class="pie-label" dy=".35em" style="text-anchor: end; shape-rendering: crispedges;"> Unkno... </text></g><path class="pie-label-line line" fill="none" d="M-42.86723647847163,-38.57258717202198L-64.30085471770745,-57.858880758032974L-86.5,-57.858880758032974" stroke="#6a533b"></path></g><g ngx-charts-pie-arc="" ngx-tooltip="" ng-reflect-tooltip-title=" <span class=&quot;tooltip-la" ng-reflect-tooltip-disabled="false" ng-reflect-tooltip-placement="top" ng-reflect-tooltip-type="tooltip" ng-reflect-tooltip-context="[object Object]" ng-reflect-fill="#6a533b" ng-reflect-start-angle="4.607018541706577" ng-reflect-end-angle="6.283185307179585" ng-reflect-inner-radius="0" ng-reflect-outer-radius="57.666666666666664" ng-reflect-value="299949817.600467" ng-reflect-max="503066774.801616" ng-reflect-data="[object Object]" ng-reflect-explode-slices="false" ng-reflect-gradient="false" ng-reflect-animate="false" ng-reflect-is-active="false"><g class="arc-group"><!--bindings={ "ng-reflect-ng-if": "false" }--><path class="arc" d="M-57.34682829674664,6.065123968057302A57.666666666666664,57.666666666666664,0,0,1,-6.181148368199848e-14,-57.666666666666664L0,0Z" fill="#6a533b" style="pointer-events: auto;"></path></g></g></g></g></g></svg>

Any Ideas what is going wrong here?
You can view the detailed [issue here](https://github.com/exupero/saveSvgAsPng/issues/199#issuecomment-489073871)

Thanks!

This is how I ended up solving it, surprisingly it works:

let rectElements = Array.from(document.getElementsByTagName('rect'));
  if (rectElements.length > 0) {
    rectElements.forEach(rect => {
      rect.setAttribute('fill', '#ffffff');
    });
  }

@Londovir yes i practicly used this libraries to handle the process for all the charts in my app:

import * as FileSaver from 'file-saver';
import domtoimage from 'dom-to-image';

I'll try to make a working versions for you guys in a few hours.

This was the best solutions worked for me as it exports the graph with all customization.

Was this page helpful?
0 / 5 - 0 ratings