apache / apache/echarts

Export image of the charts using CORS resources

Open
#15,117 7 comments 0 reactions 0 assignees View on GitHub
en new-feature
Dominant language
TypeScript
Stars
67.3k
Forks
19.8k
Avg merge
11d 14h
Merged PRs (30d)
8

Description

**Background**

In most cases, one chart may be referring to some external resources, some of them allow requests from any origin with the response header like `Access-Control-Allow-Origin: '*'` but some of them don't.

For those resources with CORS header, we hope they can be exported. See this simple example,

```js
option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
toolbox: {
feature: {
saveAsImage: {}
}
},
series: [{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line',
symbolSize: [95, 22],
symbol: 'image://https://cdn.jsdelivr.net/gh/apache/echarts-website@asf-site/zh/images/logo.png'
}]
};
```

If we hope to get a screenshot through the current API `getDataURL`, we would get an unexpected error like this,

```
Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
```

To avoid this, we will have to create some `Image` objects and specify their own `crossOrigin` property. Like this,

```js
const logoImg = new Image();
logoImg.src = 'image://https://cdn.jsdelivr.net/gh/apache/echarts-website@asf-site/zh/images/logo.png';
logoImg.crossOrigin = ''; // or 'anonymous'
logoImg.onload = () => { // xxx };
```

But here is another limit, our `series.symbol` doesn't support the `Image` object yet! So we can't directly use the created image objects with the `crossOrigin` property. But we can try another approach: convert it to a Base64 URL. The code will be,

```js
const symbolSize = [95, 22];
const logoImg = new Image();
logoImg.src = 'https://cdn.jsdelivr.net/gh/apache/echarts-website@asf-site/zh/images/logo.png';
logoImg.crossOrigin = ''; // or 'anonymous'
logoImg.onload = () => {
const tmpCanvas = echarts.zrUtil.createCanvas()
const tmpCtx = tmpCanvas.getContext('2d')
const dpr = myChart.getDevicePixelRatio()

tmpCanvas.width = symbolSize[0] * dpr
tmpCanvas.height = symbolSize[1] * dpr
tmpCanvas.style.width = symbolSize[0] + 'px'
tmpCanvas.style.height = symbolSize[1] + 'px'
tmpCtx.drawImage(logoImg, 0, 0, tmpCanvas.width, tmpCanvas.height)

myChart.setOption({
series: [{
symbol: 'image://' + tmpCanvas.toDataURL()
}]
})
}

option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
toolbox: {
feature: {
saveAsImage: {}
}
},
series: [{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line',
symbolSize: symbolSize,
symbol: 'none'
}]
};
```

Now let's export again, we would found it can be exported and downloaded successfully.

But everything was going to be complex when we hope to set a different image for each symbol item.
It would be simpler and easier if there could be an option to specify whether the image URL is cross-origin.

This feature will involve some required changes in zrender, but that's underlying. Let's leave it alone now and just discuss the things related to ECharts.

**Proposed Usage**

For [`symbol` with `image://`](https://echarts.apache.org/en/option.html#series-scatter.symbol), I prefer specifying the `crossOrigin` in the URL,
either `image://crossOrigin/URL` or `image://URL?crossOrigin`.

For the [common pattern object](https://echarts.apache.org/en/option.html#legend.itemStyle.color),

```
{
image: imageDom, // Image, HTMLImageElement, and HTMLCanvasElement are supported, while string path is not supported
repeat: 'repeat' // whether to repeat texture, whose value can be repeat-x, repeat-y, or no-repeat
}
```

The `crossOrigin ` can be set in `imageDom` in advance. So it should be OK. We can also provide a new option `crossOrigin` if needed.

### Demo

https://www.makeapie.com/editor.html?c=xntd1QC3kI&v=1

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.