Metadata in SVG

author(s): Øystein Moseng

by oysteinmoseng

HTML

<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/modules/series-label.js"></script>
<script src="https://code.highcharts.com/modules/exporting.js"></script>
<script src="https://code.highcharts.com/modules/export-data.js"></script>
<script src="https://code.highcharts.com/modules/accessibility.js"></script>

<h1>Metadata embedded in SVG</h1>

<figure class="highcharts-figure">
    <div id="container-bar"></div>
    <div>
        <h2>JSON</h2>
        <pre class="json" id="bar-json"></pre>
    </div>
</figure>

<figure class="highcharts-figure">
    <div id="container-line"></div>
    <div>
        <h2>JSON</h2>
        <pre class="json" id="line-json"></pre>
    </div>
</figure>

CSS

body {
    font-family: sans-serif;
}

h2 {
    font-size: 0.8rem;
}

.json {
    background-color: #f8f8f8;
    border: 1px solid #ccc;
    border-radius: 8px;
    padding: 10px;
    width: 600px;
    overflow: scroll;
}

#container-line {
    height: 480px;
}
#line-json {
    height: 430px;
}
#container-bar {
    height: 400px;
}
#bar-json {
    height: 350px;
}

#container-line,
#container-bar {
    flex-grow: 1;
    min-width: 450px;
}

.highcharts-figure {
    margin-bottom: 50px;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    gap: 20px;
    max-width: 1400px;
}

.highcharts-data-table table {
    min-width: 450px;
    font-family: Verdana, sans-serif;
    border-collapse: collapse;
    border: 1px solid #ebebeb;
    margin: 10px auto;
    text-align: center;
    width: 100%;
    max-width: 500px;
}

.highcharts-data-table caption {
    padding: 1em 0;
    font-size: 1.2em;
    color: #555;
}

.highcharts-data-table th {
    font-weight: 600;
    padding: 0.5em;
}

.highcharts-data-table td,
.highcharts-data-table th,
.highcharts-data-table caption {
    padding: 0.5em;
}

.highcharts-data-table thead tr,
.highcharts-data-table tr:nth-child(even) {
    background: #f8f8f8;
}

.highcharts-data-table tr:hover {
    background: #f1f7ff;
}

JavaScript

// ---------------------------------------------------------------
// Wrapper for embedding metadata in the SVG
// ---------------------------------------------------------------

function getPointMetadata(point) {
	return {
    	x: Highcharts.pick(point.name || point.category, point.x),
        y: point.y
    };
}

function getSeriesMetadata(series) {
	return {
    	name: series.name,
        type: series.type,
        data: series.points.map(getPointMetadata)
    };
}

function getChartMetadataJSON(chart) {
	const metadata = {
    	title: chart.options.title.text,
        subtitle: chart.options.subtitle.text,
        description: chart.options.accessibility.description,
        series: chart.series.map(getSeriesMetadata)
    };
	return JSON.stringify(metadata, null, ' ');
}

// Store metadata in the SVG as a custom element under the metadata element
// The metadata is stored as raw JSON.
Highcharts.addEvent(Highcharts.Chart, 'render', function () {
	const chart = this;
	if (!chart.metadata) {
    	const container = document.createElementNS('http://www.w3.org/2000/svg','metadata'),
        	xmlns = 'http://www.highcharts.com/xmlns/svgmetadata';
        chart.renderer.box.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:svgmetadata', xmlns);
        chart.metadata = document.createElementNS(xmlns, 'svgmetadata:json');
        container.appendChild(chart.metadata);
        chart.renderer.box.appendChild(container);
    }
    chart.metadata.textContent = getChartMetadataJSON(chart);
});

// Output the JSON to make it visible below the charts
Highcharts.addEvent(Highcharts.Chart, 'render', function () {
	const outID = this.options.chart.type === 'column' ? 'bar-json' : 'line-json',
    	outEl = document.getElementById(outID);
  	outEl.textContent = this.metadata.textContent;
});



// ---------------------------------------------------------------
// Demonstrate by creating some charts
// ---------------------------------------------------------------

//...