如题,在写博客的过程中,有时候会需要绘制一些简单的统计图形,数据不多,为了轻量,所以不想用 R 包 echarts4r,连文档也是选用 Markdown 格式。我试了 chart.js (适合极简单的图形)和 Mermaid (画非统计图形合适),最后,发现我的这个使用场景还是 ECharts 比较好,感觉 ECharts 比 plotly 轻量一些。

我是这么捣鼓的

我从 ECharts 官网拉来一个示例

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>ECharts</title>
    <!-- 引入刚刚下载的 ECharts 文件 -->
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.6.0/dist/echarts.min.js"></script>
  </head>
  <body>
    <!-- 为 ECharts 准备一个定义了宽高的 DOM -->
    <div id="main" style="width: 900px;height:400px;"></div>
    <script type="text/javascript">
      // 基于准备好的dom,初始化echarts实例
      var myChart = echarts.init(document.getElementById('main'));

      // 指定图表的配置项和数据
      var option = {
        title: {
            text: 'ECharts Getting Started Example'
        },
        tooltip: {},
        legend: {
            data: ['sales']
        },
        xAxis: {
            data: ['Shirts', 'Cardigans', 'Chiffons', 'Pants', 'Heels', 'Socks']
        },
        yAxis: {},
        series: [
            {
            name: 'sales',
            type: 'bar',
            data: [5, 20, 36, 10, 10, 20]
            }
        ]
        };

      // 使用刚指定的配置项和数据显示图表。
      myChart.setOption(option);
    </script>
  </body>
</html>

效果如下

我期待的样子

在 ECharts 的示例库里,可以找到很多,我的目标是把示例代码复制到我的文档,然后,换上自己的数据就行。

问题现状

目前,我用 shortcodes 引入 ECharts 做的效果如下:

问题一看就还很大,所以,求助各位。

我在我的博客网站加入这个功能的代码,见这个 PR https://github.com/rbind/xiangyun/pull/114

参考材料

我在自己瞎干的过程中,主要参考了两个材料

  1. https://discourse.gohugo.io/t/need-help-with-apache-echarts/45500/4 示例比较简单,主要参考这个。
  2. https://hugoloveit.com/theme-documentation-echarts-shortcode/ 我想要这样的效果,但是,这个项目的代码好复杂的样子,搞不懂。

问了一下 Cursor 中今天最新上市的 claude-3.7-sonnet-thinking,答案就是修改 shortcode 模版如下。

我在本地试了一下,看起来是解决了问题。

{{ $id := printf "echarts-js-%d" .Ordinal }}
{{ $width := default "800px" (.Get "width") }}
{{ $height := default "600px" (.Get "height") }}

<!-- Prepare a DOM container for ECharts -->
<div id="{{ $id }}" style="width: {{ $width }}; height: {{ $height }};"></div>

<script type="text/javascript">
  (function() {
    // Initialize ECharts instance
    var chart = echarts.init(document.getElementById('{{ $id }}'));

    // Specify chart configuration and data
    {{ .Inner | safeJS }}

    // Use the specified configuration and data to display the chart
    if (typeof option !== 'undefined') {
      chart.setOption(option);
    }

    // Handle window resize
    window.addEventListener('resize', function() {
      chart.resize();
    });
  })();
</script>

完整思维链:

===

<think>
Let's examine the issue with the ECharts embedding in the Hugo theme. I'll analyze the three files provided to understand the problem and propose solutions.

First, let's look at the ECharts shortcode template:

// layouts/shortcodes/echarts.html
{{ $id := printf "echarts-js-%d" .Ordinal }}
{{ $width := "800px" }}
{{ $height := "600px" }}

<!-- 为 ECharts 准备一个定义了宽高的 DOM -->
<div><canvas id={{ $id }} style="width: {{ $width }};height: {{ $height }};"></canvas></div>

<script type="text/javascript">
  // 基于准备好的dom,初始化echarts实例
  var chart = echarts.init(document.getElementById({{ $id }}));

  // 指定图表的配置项和数据
  {{ .Inner | safeJS }}

  // 使用刚指定的配置项和数据显示图表。
  chart.setOption(option);
</script>

Next, looking at the head.html partial:

// layouts/partials/head.html
// ... other code ...
{{ if .HasShortcode "echarts" }}
  <script src="https://cdn.jsdelivr.net/npm/echarts@5.6.0/dist/echarts.min.js"></script>
{{ end }}
// ... other code ...

And finally, the example markdown post:

// content/post/2025-02-25-shortcodes.md
// ... front matter ...
{{<echarts width="900px" height="600px">}}
var option = {
  title: {
    text: 'ECharts Getting Started Example'
  },
  tooltip: {},
  legend: {
    data: ['sales']
  },
  xAxis: {
    data: ['Shirts', 'Cardigans', 'Chiffons', 'Pants', 'Heels', 'Socks']
  },
  yAxis: {},
  series: [
    {
      name: 'sales',
      type: 'bar',
      data: [5, 20, 36, 10, 10, 20]
    }
  ]
};
{{</echarts>}}

Problems I see:

  1. The width and height parameters are defined in the shortcode template but not used when passed from the shortcode call in the markdown file.
  2. ECharts requires a div container, not directly a canvas element. The way the DOM is set up could be problematic.
  3. There might be issues with the initialization of ECharts if the DOM isn't ready.

Let's address these issues one by one:

  1. Fix the parameters in the shortcode template:

    • Modify the width and height variables to use the parameters passed from the shortcode
    • If no parameters are provided, use default values
  2. Fix the container for ECharts:

    • ECharts typically uses a div container, not directly a canvas element
    • Ensure proper id attributes and styling
  3. Ensure proper initialization:

    • Make sure ECharts initializes after the DOM is ready

Here's the updated echarts.html shortcode template:

{{ $id := printf "echarts-js-%d" .Ordinal }}
{{ $width := default "800px" (.Get "width") }}
{{ $height := default "600px" (.Get "height") }}

<!-- Prepare a DOM container with defined width and height for ECharts -->
<div id="{{ $id }}" style="width: {{ $width }}; height: {{ $height }};"></div>

<script type="text/javascript">
  document.addEventListener('DOMContentLoaded', function() {
    // Initialize ECharts instance based on the prepared DOM
    var chart = echarts.init(document.getElementById('{{ $id }}'));

    // Specify chart configuration and data
    {{ .Inner | safeJS }}

    // Use the specified configuration and data to display the chart
    chart.setOption(option);

    // Handle window resize
    window.addEventListener('resize', function() {
      chart.resize();
    });
  });
</script>

This updated template should:

  1. Properly use the width and height parameters from the shortcode
  2. Use a proper div container for ECharts
  3. Initialize ECharts after the DOM is ready
  4. Add a resize handler to make the chart responsive

Let me look at the original user request again to ensure I'm addressing the right issue...

The user mentioned:

the JS plot seems to be rendered twice and with incorrect sizing of elements

So there may be a duplication issue as well. Let's consider what might cause the chart to render twice:

  1. The script code might be executing twice
  2. There might be duplicate DOM elements with the same ID
  3. The ECharts library might be loaded multiple times

Looking at the code again, I don't see an obvious cause for duplicate rendering, but let's be thorough in our fix.

Let me revise my solution to address both the sizing issues and potential duplicate rendering:

{{ $id := printf "echarts-%d" .Ordinal }}
{{ $width := default "800px" (.Get "width") }}
{{ $height := default "600px" (.Get "height") }}

<!-- Prepare a DOM container with defined width and height for ECharts -->
<div id="{{ $id }}" style="width: {{ $width }}; height: {{ $height }};"></div>

<script type="text/javascript">
  (function() {
    // Ensure we're not redefining the chart if it already exists
    if (window.echartsInstances === undefined) {
      window.echartsInstances = {};
    }

    // Wait for DOM to be ready
    function initChart() {
      if (window.echartsInstances['{{ $id }}']) {
        // If chart already exists, dispose it first to prevent duplicates
        window.echartsInstances['{{ $id }}'].dispose();
      }

      // Initialize ECharts instance
      var chartDom = document.getElementById('{{ $id }}');
      if (!chartDom) return; // Safeguard

      var chart = echarts.init(chartDom);
      window.echartsInstances['{{ $id }}'] = chart;

      // Specify chart configuration and data
      {{ .Inner | safeJS }}

      // Use the specified configuration and data to display the chart
      chart.setOption(option);
    }

    // Initialize the chart when DOM is ready
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', initChart);
    } else {
      initChart();
    }

    // Handle window resize
    window.addEventListener('resize', function() {
      if (window.echartsInstances['{{ $id }}']) {
        window.echartsInstances['{{ $id }}'].resize();
      }
    });
  })();
</script>

This updated solution:

  1. Uses an immediately-invoked function expression (IIFE) to create a scope
  2. Adds safeguards to prevent duplicate initialization
  3. Properly disposes of any existing chart before creating a new one
  4. Tracks chart instances in a global object
  5. Handles window resize events
  6. Waits for the DOM to be ready

Now, let's update the final version to ensure it's the best solution to the specific problems described:

Looking back at the original shortcode and the user's description, I notice that they're trying to use a canvas element directly for ECharts, but ECharts typically uses a div container. Also, the shortcode isn't using the width and height parameters passed from the shortcode call.

Here's my final update to the echarts.html shortcode:

{{ $id := printf "echarts-js-%d" .Ordinal }}
{{ $width := default "800px" (.Get "width") }}
{{ $height := default "600px" (.Get "height") }}

<!-- Prepare a DOM container for ECharts -->
<div id="{{ $id }}" style="width: {{ $width }}; height: {{ $height }};"></div>

<script type="text/javascript">
  (function() {
    // Initialize ECharts instance
    var chart = echarts.init(document.getElementById('{{ $id }}'));

    // Specify chart configuration and data
    {{ .Inner | safeJS }}

    // Use the specified configuration and data to display the chart
    if (typeof option !== 'undefined') {
      chart.setOption(option);
    }

    // Handle window resize
    window.addEventListener('resize', function() {
      chart.resize();
    });
  })();
</script>

This should address the issues of:

  1. The chart being rendered with incorrect sizing by properly using the width and height parameters
  2. Using a proper div container for ECharts instead of a canvas element
  3. Adding a resize handler to make sure the chart adapts to window resizing

The user also mentioned that "the JS plot seems to be rendered twice" - this could be due to multiple executions of the script or multiple DOM elements with the same ID. The IIFE pattern helps create a scope for the script and prevent variable leaks, which might be contributing to the duplicate rendering issue.

With these changes, the ECharts embedding should work properly and with the correct sizing.
</think>

Fixing ECharts Hugo Shortcode

I can see a few issues with your ECharts implementation that could be causing the double rendering and incorrect sizing. Let's fix these issues:

1. Fix the ECharts Shortcode Template

The main problems in your shortcode are:

  • It's using a canvas element directly instead of a div container that ECharts expects
  • The width and height parameters from the shortcode aren't being used
  • There's no handling for window resizing

Here's the updated code:

{{ $id := printf "echarts-js-%d" .Ordinal }}
{{ $width := default "800px" (.Get "width") }}
{{ $height := default "600px" (.Get "height") }}

<!-- Prepare a DOM container for ECharts -->
<div id="{{ $id }}" style="width: {{ $width }}; height: {{ $height }};"></div>

<script type="text/javascript">
  (function() {
    // Initialize ECharts instance
    var chart = echarts.init(document.getElementById('{{ $id }}'));

    // Specify chart configuration and data
    {{ .Inner | safeJS }}

    // Use the specified configuration and data to display the chart
    if (typeof option !== 'undefined') {
      chart.setOption(option);
    }

    // Handle window resize
    window.addEventListener('resize', function() {
      chart.resize();
    });
  })();
</script>

Key changes:

  1. Used a div instead of canvas for the container
  2. Added proper parameter handling for width and height
  3. Added a window resize event handler for responsiveness
  4. Wrapped the JavaScript in an IIFE to prevent variable leaks
  5. Added a check to ensure the option variable exists before using it

This should fix both the sizing issues and prevent duplicate rendering.

Usage Example

Your example in the markdown file looks good. Make sure to use it as follows:

{{<echarts width="900px" height="600px">}}
var option = {
  // your ECharts options here
};
{{</echarts>}}

The width and height parameters are now properly passed from the shortcode to the template.

    nan.xiao 谢谢。我试了一下,果然解决了。我只动了模版的一小部分,没有全抄。

    {{ $id := printf "echarts-js-%d" .Ordinal }}
    {{ $width := default "800px" (.Get "width") }}
    {{ $height := default "600px" (.Get "height") }}
    
    <!-- 为 ECharts 准备一个定义了宽高的 DOM -->
    
    <div id="{{ $id }}" style="width: {{ $width }}; height: {{ $height }};"></div>
    
    <script type="text/javascript">
      // 基于准备好的dom,初始化echarts实例
      var chart = echarts.init(document.getElementById({{ $id }}));
    
      // 指定图表的配置项和数据
      {{ .Inner | safeJS }}
    
      // 使用刚指定的配置项和数据显示图表。
      chart.setOption(option);
      
    </script>