admin管理员组

文章数量:1289637

I am using EvoPdf version 7.5. We have this html that has been used to generate a pdf report for quite some time with no issues, but its just straight up HTML and css, there was not javascript being used. We want to add charts using chart.js, but when I add it to the existing report to test if it will show, nothing appears.

Here is my HTML

@using System.Linq
@using RazorEngine
@using System.Text.RegularExpressions
<!DOCTYPE html>


<html>
<head>
    <meta charset="utf-8" />
    <link href="/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel='stylesheet' href='/[email protected]/font/bootstrap-icons.css'>
</head>

<body id="Body">

    <div class="section">
        <div class="mx-auto" style="height: 50px;"></div>
        <div class="lblTitle">
            <h1>This is where the chart should be</h1>
            <div style="width: 80%; margin: auto;">
                <canvas id="myChart"></canvas>
            </div>
            <h1>This is where the chart should end</h1>
        </div>
    </div>

    <script src=".js"></script>
    <script src="~/Scripts/local_chart.js"></script>

</body>


</html>

Here is a sample of the javascript

const ctx = document.getElementById('myChart');

const startPoint = 58, progressPoint = 65, maxPoint = 100;
const xAxisLabel = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'];

const expected = (start, end, steps) => {
    const stepSize = (end.y - start.y) / steps;
    return Array.from({ length: steps + 1 }, (_, i) => ({ x: i < steps ? start.x : end.x, y: start.y + stepSize * i }));
};

const customPointCircleCanvas = (ctx, size = 10) => ({
    draw: ({ point: { x, y } }) => {
        ctx.beginPath();
        ctx.arc(x, y, size, 0, 2 * Math.PI);
        ctx.fillStyle = '#3498db';
        ctx.fill();
        ctx.lineWidth = 3;
        ctx.strokeStyle = '#FFFFFF';
        ctx.stroke();
    }
});

const htmlPlanTimelineLegendPlugin = {
    id: 'htmlLegend',
    afterUpdate(chart, _, { containerID }) {
        const container = document.getElementById(containerID);
        if (!container) return;
        container.innerHTML = '';
        chart.legend.legendItems.forEach(({ fillStyle, text }, index) => {
            const legendDiv = document.createElement('div');
            legendDiv.innerHTML = `<span style="background:${fillStyle};width:10px;height:10px;display:inline-block;margin-right:5px;"></span>${text}`;
            legendDiv.style.cursor = 'pointer';
            legendDiv.onclick = () => {
                chart.setDatasetVisibility(index, !chart.isDatasetVisible(index));
                chart.update();
            };
            container.appendChild(legendDiv);
        });
    }
};

const getGradient = (ctx, { left, right, bottom, top }, colorStart, colorEnd, isVertical = false) => {
    if (!left) return;
    const gradient = ctx.createLinearGradient(isVertical ? 0 : left, isVertical ? bottom : 0, isVertical ? 0 : right, isVertical ? top : 0);
    gradient.addColorStop(0, colorStart);
    gradient.addColorStop(1, colorEnd);
    return gradient;
};

new Chart(ctx, {
    type: 'line',
    data: {
        labels: xAxisLabel,
        datasets: [
            { label: 'Label 1', data: expected({ x: 'Jan', y: startPoint }, { x: 'Apr', y: maxPoint }, 7), borderColor: 'transparent', pointRadius: 0, spanGaps: true },
            { label: 'Label 2', data: expected({ x: 'Jan', y: startPoint }, { x: 'Jan', y: startPoint }, 1), pointStyle: 'circle', pointRadius: 7, borderColor: '#FFF', backgroundColor: '#3498db' },
            { label: 'Label 3', data: expected({ x: 'Mar', y: progressPoint }, { x: 'Mar', y: progressPoint }, 1), pointStyle: 'circle', pointRadius: 30, borderColor: '#FFF', backgroundColor: '#3498db' },
            {
                label: 'Label 4',
                data: [ { x: 'Jan', y: startPoint }, { x: 'Mar', y: progressPoint }, { x: 'Jul', y: maxPoint } ],
                segment: {
                    borderDash: (ctx) => ctx.p0.skip || ctx.p1.skip ? [6, 6] : undefined,
                    backgroundColor: ({ chart: { ctx, chartArea } }) => getGradient(ctx, chartArea, 'rgba(205, 205, 205, 0.4)', 'rgba(255, 99, 132, 0.4)', true) || 'transparent'
                },
                spanGaps: true, radius: 0,
                borderColor: ({ chart: { ctx, chartArea } }) => getGradient(ctx, chartArea, 'rgba(52, 152, 219, 1)', 'rgba(255, 99, 132, 0.4)') || 'transparent',
                borderWidth: 3, backgroundColor: 'rgba(255, 99, 132, 0.4)', fill: 'stack'
            }
        ]
    },
    options: {
        responsive: true,
        events: [],
        scales: {
            y: { grid: { display: false }, title: { display: true, text: 'Y Scale', font: { family: 'Arial', size: 20 } }, ticks: { maxTicksLimit: 5, callback: (value) => `${value}%` } },
            x: { grid: { display: false }, title: { display: true, text: 'X Scale', font: { family: 'Arial', size: 20 } } }
        },
        plugins: {
            htmlLegend: { containerID: 'legend-container' },
            legend: { display: true, labels: { color: 'black' } }
        }
    },
    plugins: [htmlPlanTimelineLegendPlugin]
});

This is all I see

I know the code works because I added it to one of our web pages (we use Asp.Net) and it renders just fine there:

I tried setting the JavaScriptEnabled to true when setting up the converter, but that didnt really seem to make a difference.

var htmlToPdfConverter = new HtmlToPdfConverter { LicenseKey = _evoLicenceKey };

htmlToPdfConverter.PdfDocumentOptions.PdfPageSize = GetPageSize(_principal.Profile.CountryCode);
htmlToPdfConverter.PdfDocumentOptions.TopMargin = 20f;
htmlToPdfConverter.PdfDocumentOptions.RightMargin = 20f;
htmlToPdfConverter.PdfDocumentOptions.BottomMargin = 20f;
htmlToPdfConverter.PdfDocumentOptions.LeftMargin = 20f;
htmlToPdfConverter.PdfDocumentOptions.FitWidth = true;
htmlToPdfConverter.PdfDocumentOptions.EmbedFonts = true;
htmlToPdfConverter.JavaScriptEnabled = true; // Enable JavaScript execution

Am I missing a setting? Does version 7.5 allow this?

I am using EvoPdf version 7.5. We have this html that has been used to generate a pdf report for quite some time with no issues, but its just straight up HTML and css, there was not javascript being used. We want to add charts using chart.js, but when I add it to the existing report to test if it will show, nothing appears.

Here is my HTML

@using System.Linq
@using RazorEngine
@using System.Text.RegularExpressions
<!DOCTYPE html>


<html>
<head>
    <meta charset="utf-8" />
    <link href="https://cdn.jsdelivr/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel='stylesheet' href='https://cdn.jsdelivr/npm/[email protected]/font/bootstrap-icons.css'>
</head>

<body id="Body">

    <div class="section">
        <div class="mx-auto" style="height: 50px;"></div>
        <div class="lblTitle">
            <h1>This is where the chart should be</h1>
            <div style="width: 80%; margin: auto;">
                <canvas id="myChart"></canvas>
            </div>
            <h1>This is where the chart should end</h1>
        </div>
    </div>

    <script src="https://cdn.jsdelivr/npm/chart.js"></script>
    <script src="~/Scripts/local_chart.js"></script>

</body>


</html>

Here is a sample of the javascript

const ctx = document.getElementById('myChart');

const startPoint = 58, progressPoint = 65, maxPoint = 100;
const xAxisLabel = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'];

const expected = (start, end, steps) => {
    const stepSize = (end.y - start.y) / steps;
    return Array.from({ length: steps + 1 }, (_, i) => ({ x: i < steps ? start.x : end.x, y: start.y + stepSize * i }));
};

const customPointCircleCanvas = (ctx, size = 10) => ({
    draw: ({ point: { x, y } }) => {
        ctx.beginPath();
        ctx.arc(x, y, size, 0, 2 * Math.PI);
        ctx.fillStyle = '#3498db';
        ctx.fill();
        ctx.lineWidth = 3;
        ctx.strokeStyle = '#FFFFFF';
        ctx.stroke();
    }
});

const htmlPlanTimelineLegendPlugin = {
    id: 'htmlLegend',
    afterUpdate(chart, _, { containerID }) {
        const container = document.getElementById(containerID);
        if (!container) return;
        container.innerHTML = '';
        chart.legend.legendItems.forEach(({ fillStyle, text }, index) => {
            const legendDiv = document.createElement('div');
            legendDiv.innerHTML = `<span style="background:${fillStyle};width:10px;height:10px;display:inline-block;margin-right:5px;"></span>${text}`;
            legendDiv.style.cursor = 'pointer';
            legendDiv.onclick = () => {
                chart.setDatasetVisibility(index, !chart.isDatasetVisible(index));
                chart.update();
            };
            container.appendChild(legendDiv);
        });
    }
};

const getGradient = (ctx, { left, right, bottom, top }, colorStart, colorEnd, isVertical = false) => {
    if (!left) return;
    const gradient = ctx.createLinearGradient(isVertical ? 0 : left, isVertical ? bottom : 0, isVertical ? 0 : right, isVertical ? top : 0);
    gradient.addColorStop(0, colorStart);
    gradient.addColorStop(1, colorEnd);
    return gradient;
};

new Chart(ctx, {
    type: 'line',
    data: {
        labels: xAxisLabel,
        datasets: [
            { label: 'Label 1', data: expected({ x: 'Jan', y: startPoint }, { x: 'Apr', y: maxPoint }, 7), borderColor: 'transparent', pointRadius: 0, spanGaps: true },
            { label: 'Label 2', data: expected({ x: 'Jan', y: startPoint }, { x: 'Jan', y: startPoint }, 1), pointStyle: 'circle', pointRadius: 7, borderColor: '#FFF', backgroundColor: '#3498db' },
            { label: 'Label 3', data: expected({ x: 'Mar', y: progressPoint }, { x: 'Mar', y: progressPoint }, 1), pointStyle: 'circle', pointRadius: 30, borderColor: '#FFF', backgroundColor: '#3498db' },
            {
                label: 'Label 4',
                data: [ { x: 'Jan', y: startPoint }, { x: 'Mar', y: progressPoint }, { x: 'Jul', y: maxPoint } ],
                segment: {
                    borderDash: (ctx) => ctx.p0.skip || ctx.p1.skip ? [6, 6] : undefined,
                    backgroundColor: ({ chart: { ctx, chartArea } }) => getGradient(ctx, chartArea, 'rgba(205, 205, 205, 0.4)', 'rgba(255, 99, 132, 0.4)', true) || 'transparent'
                },
                spanGaps: true, radius: 0,
                borderColor: ({ chart: { ctx, chartArea } }) => getGradient(ctx, chartArea, 'rgba(52, 152, 219, 1)', 'rgba(255, 99, 132, 0.4)') || 'transparent',
                borderWidth: 3, backgroundColor: 'rgba(255, 99, 132, 0.4)', fill: 'stack'
            }
        ]
    },
    options: {
        responsive: true,
        events: [],
        scales: {
            y: { grid: { display: false }, title: { display: true, text: 'Y Scale', font: { family: 'Arial', size: 20 } }, ticks: { maxTicksLimit: 5, callback: (value) => `${value}%` } },
            x: { grid: { display: false }, title: { display: true, text: 'X Scale', font: { family: 'Arial', size: 20 } } }
        },
        plugins: {
            htmlLegend: { containerID: 'legend-container' },
            legend: { display: true, labels: { color: 'black' } }
        }
    },
    plugins: [htmlPlanTimelineLegendPlugin]
});

This is all I see

I know the code works because I added it to one of our web pages (we use Asp.Net) and it renders just fine there:

I tried setting the JavaScriptEnabled to true when setting up the converter, but that didnt really seem to make a difference.

var htmlToPdfConverter = new HtmlToPdfConverter { LicenseKey = _evoLicenceKey };

htmlToPdfConverter.PdfDocumentOptions.PdfPageSize = GetPageSize(_principal.Profile.CountryCode);
htmlToPdfConverter.PdfDocumentOptions.TopMargin = 20f;
htmlToPdfConverter.PdfDocumentOptions.RightMargin = 20f;
htmlToPdfConverter.PdfDocumentOptions.BottomMargin = 20f;
htmlToPdfConverter.PdfDocumentOptions.LeftMargin = 20f;
htmlToPdfConverter.PdfDocumentOptions.FitWidth = true;
htmlToPdfConverter.PdfDocumentOptions.EmbedFonts = true;
htmlToPdfConverter.JavaScriptEnabled = true; // Enable JavaScript execution

Am I missing a setting? Does version 7.5 allow this?

Share Improve this question edited Feb 19 at 21:36 Ian asked Feb 19 at 17:35 IanIan 114 bronze badges 5
  • Your chart.js options don't seem to have disabled animations. Does your htmlToPdfConverter run with a delay allowing the animations to complete? – kikon Commented Feb 19 at 17:40
  • Then, we have to know what kind of object is chart.js receiving as the canvas, what exactly are its capabilities. Since the converter seems to be under a licence key, we'll have to debug this together, try to extract some information about ctx out of the javascript (possibly as the content of a new div) - things like ctx.toString() or ctx.getContext("2d").toString() will do for starters. – kikon Commented Feb 19 at 17:43
  • Yes I tried adding a delay of 3000 and 5000 ms. EvoPdf can be used without a license key with their free evaluation version. This renders in the browser, but not in the pdf. I don't quite understand what you mean by getting information about the ctx for when its rendering into the pdf. – Ian Commented Feb 20 at 19:30
  • I tested your chart with evoPDF online demo and it seems to work. There was an issue with the getGradient function that doesn't work with the latest chart.js; after correction, the chart shows identically in browser and in evoPDF online demo. On the Online Demo I set the radiobutton "Convert HTML String" and pasted the html+javascript, as in this jsFiddle, where everything is the html tab. Here's a screen capture of the evoPDF generated pdf. – kikon Commented Feb 21 at 23:02
  • 1 That looks good, wow. Thank you! – Ian Commented Feb 25 at 0:08
Add a comment  | 

1 Answer 1

Reset to default 0

The javascript code for chart.js has an issue at the function:

const getGradient = (ctx, { left, right, bottom, top }, colorStart, colorEnd, isVertical = false) => {
     if (!left) return;
    //......
}

When the function is called, the second argument provided is chart.chartArea; but the first calls to the function occur before the chartArea is computed, so for those first calls the second argument is null, and the attempt to destructure it results in an error.

To make it run with a null second argument one might delay destructuring to after a guard against null is run:

const getGradient = (ctx, chartArea, colorStart, colorEnd, isVertical = false) => {
    if (!charArea) return;
    const { left, right, bottom, top } = chartArea;
    if (!left) return;
    //......
}

Then the first calls with null argument pass without event and the later calls, after chartArea was computed will produce the useful results.

The original code should not run in browser either because of that issue, and there's not enough information for a certain explanation of why it worked in OP's tests. The most probable explanation seems to me that the code was run through a bundler stack that included a transpiler, which transformed the destructuring in the argument to something more tame and resilient to null arguments.

Here's a runnable version of the full code in a jsFiddle, that has everything set in the html tab, so it can easily copied and pasted to evoPDF's online demo (with the "Convert HTML String" option checked) - here's a screen capture of the generated pdf.

本文标签: aspnetEvoPdf not rendering chart from chartjsStack Overflow