Skip to main content
  1. Posts/

Creating an Apache ECharts Hugo shortcode

·883 words·5 mins
Matt Weagle
Author
Matt Weagle

This is mostly documentation for myself summarizing what I did to add a echarts shortcode to Hugo that allows me to support Apache Echarts on this blog. I initially thought I would use this to add a Treemap visualization of Todd Conklin’s Five Principles of Human Performance. These steps are very similar to Navendu Pottekkat’s Adding Diagrams to Your Hugo Blog With Mermaid.

Some of these steps are depend on the hugo-theme-stack partials. They will likely need to be changed to support other themes.

Steps
#

Include the ECharts source
#

Download the ECharts source from the download page. Expand the archive and ensure the contents exist in the static/echarts directory.

Configure the theme params
#

Configure the light and dark echart themes via the site’s config.yaml. The partial renderers will use these basenames when resolving theme paths in the static/echarts/theme directory.

params:
  echarts:
    theme:
      light: infographic
      dark: dark-digerati

Header Partial
#

Upsert the layouts/partials/head/custom.html partial so that it conditionally includes the ECharts partial render. Posts will declare whether they need EChart support by frontmatter. This eliminates downloading unnecessary files for the majority of the pages.

<!-- Add echarts js file -->
{{ if (.Params.echarts) }}
{{ partial "echarts.html" }}
{{ end }}

The layouts/partials/head/custom.html conditionally includes the EChart specific partial layouts/partials/echarts.html. The JavaScript in the echarts shortcode will check the Hugo theme’s local storage value when rendering to determine which theme to use.

<!-- Include the ECharts file you just downloaded -->
<script src="/echarts/dist/echarts.js"></script>

<!-- Include the theme files -->
<script src="/echarts/theme/{{ if site.Params.echarts.theme.light }}{{ site.Params.echarts.theme.light }}{{ else }}default{{ end }}.js"></script>
<script src="/echarts/theme/{{ if site.Params.echarts.theme.dark }}{{ site.Params.echarts.theme.dark }}{{ else }}dark{{ end }}.js"></script>

This source optionally dereferences the theme names defined in the site’s primary config.yaml. There’s also the ECharts Theme Builder to create a completely custom design.

Usage
#

The first requirement is to declare an EChart dependency in the post’s frontmatter:

echarts: true

To build a graph, the shortcode requires user data to create the DOM elements and initialize the chart:

  • Required
    • ElementID
    • Chart Definition (either inline or as an external JS file)
  • Optional
    • Dimensions (width and height optional)
    • Interactivity

Required
#

The shortcode requires an element ID to use for the DOM. This enables multiple charts per page:

{{< echarts id="contentInline" >}}

Chart content can be provided either inline or externally by a JavaScript file that defines an immediately executed function. The options returned by either source are provided to the setOption call.

For inline data:

{{< echarts id="contentInline" >}}
{
  title: {
      text: "Inline Chart Definition",
  },
  xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
  },
  yAxis: {
    type: 'value'
  },
  series: [
    {
      data: [150, 230, 224, 218, 135, 147, 260],
      type: 'line'
    }
  ]
}
{{< /echarts >}}

Renders as (ignoring the optional width and height params):

I started with this approach, but missed VSCode’s syntax highlighting. An alternative version is to define an immediate JS function in an external file.

{{< echarts id="externalContent" srcChart="content/posts/2024/10/EChart Shortcode/line.js" >}}
{{< /echarts >}}

Renders as:

You can view the full line.js source here.

Optional - Dimensions
#

The shortcode supports optional width (default=1024px) and height (default=800px) params:

{{< echarts id="externalContent" width="800px" height="600px" >}}
{{< /echarts >}}

Optional - Interactivity
#

Some ECharts like the gauge require event handlers to be configured. For these types of charts the entire configuration can be externalized into a jsSource param:

{{< echarts id="jsInteractive" width="400px" height="400px" jsSource="./gauge.js" >}}
{{< /echarts >}}

The content of the jsSource file is an immediate JavaScript function like:

(function() {
    var chartOps = {
        ...
    };
    return chartOps;
})

View the full source here to see more. The content of this file renders as:

Shortcode
#

The shortcode itself is responsible for creating a unique DOM element, constructing the chart input from either the inline or external source, and then optionally loading the interactive data.


{{ $idName := .Get "id"}}
{{ $safeJSIDalue := $idName | safeJS}}
<div id="{{ $idName }}" 
  style="width: {{ if .Get "width" }}{{ .Get "width" }}{{ else }}1024px{{ end }}; height: {{ if .Get "height" }}{{ .Get "height" }}{{ else }}800px{{ end }};"></div>

<script type="text/javascript"> 
    // Determine which theme to use. Reference:
    // https://github.com/CaiJimmy/hugo-theme-stack/blob/839fbd0ecb5bba381f721f31f5195fb6517fc260/layouts/partials/head/colorScheme.html#L24
    var echartTheme = '{{ if site.Params.echarts.theme.light }}{{ site.Params.echarts.theme.light }}{{ else }}default{{ end }}';
    if (document.documentElement.dataset.scheme == 'dark') {
      echartTheme = '{{ if site.Params.echarts.theme.dark }}{{ site.Params.echarts.theme.dark }}{{ else }}dark{{ end }}';
    }
    // Initialize the echarts instance based on the prepared dom
    var myChart = echarts.init(document.getElementById('{{ $idName }}'), echartTheme);

    // Specify the configuration items and data for the chart
    var defaultOptions = {
      title: {
        text: ''
      },
    };
    var userOptions = {};
    try {
      // Load the data either from an external file or inline
      {{ if isset .Params "srcChart" }}
        {{- $srcPath := .Get "srcChart" -}}
        {{- $chartSourceData := readFile $srcPath -}}
        console.log("Using external script data");
        const userChartOptions = eval({{ $chartSourceData }});
        userOptions = userChartOptions;
      {{ else }}
        console.log("Using .Inner data");
        userOptions = eval("(" + {{.Inner}} + ")");
      {{ end }}
    }
    catch (err) {
      console.log("Failed to parse echart data: " + err)
    }
    // Display the chart using the configuration items and data just specified.
    const chartOptions_{{ $safeJSIDalue }} = { ...defaultOptions, ...userOptions};
    myChart.setOption(chartOptions_{{ $safeJSIDalue }});
</script>
{{ if isset .Params "jsSource"}}<script src={{ .Get "jsSource" }} ></script>{{ end }}

Attributions
#

Photo by Pankaj Patel on Unsplash