Annotation Chart Example

An example of a Google Annotation Chart.

by JakobJingleheimer

HTML

<script src="https://www.gstatic.com/charts/loader.js"></script>
<div class="chart-container">
	<figure
		class="chart"
		data-title="testing…"
	>
		<table class="chart-data">
			<thead>
				<tr>
					<th data-type="date">Period</th>
					<th data-type="number">Allocations</th>
					<th
						data-role="tooltip"
						data-type="string"
					>Note</th>
				</tr>
			</thead>
			<tbody>
				<tr>
					<td data-type="date">Jan 2014</td>
					<td data-type="number">200</td>
				</tr>
				<tr>
					<td data-type="date">Jul 2014</td>
					<td data-type="number">231</td>
				</tr>
				<tr>
					<td data-type="date">Jan 2015</td>
					<td data-type="number">368</td>
				</tr>
				<tr>
					<td data-type="date">Jul 2015</td>
					<td data-type="number">218</td>
				</tr>
				<tr>
					<td data-type="date">Jan 2016</td>
					<td data-type="number">9</td>
				</tr>
				<tr>
					<td data-type="date">Jul 2016</td>
					<td data-emphasis="true" data-type="number">8964</td>
					<td data-type="string">Free money</td>
				</tr>
				<tr>
					<td data-type="date">Jan 2017</td>
					<td data-type="number">168</td>
				</tr>
			</tbody>
		</table>
	</figure>
	<caption></caption>
</div>

SCSS

html {
	font-family: Arial;
	font-size: 14px;
}

html,
body {
	display: block;
	height: 100%;
	width: 100%;
}

.chart-container {
	padding-top: 100%;
	position: relative;
	width: 100%;
}

.chart {
	height: 100%;
	margin: 0;
	position: absolute;
	top: 0;
	width: 100%;
}

.chart-data {
	[data-type="date"],
	[data-type="number"] {
		text-align: right;
	}
	
	th {
		font-weight: bold;
	}
	
	th,
	td {
		padding: 0.4em;
	}
}

JavaScript

let AnnotationChart;
const drawChart = (elm) => {
	if (!elm) return false;

	const sourceData = readData(elm);
	const data = new google.visualization.DataTable();

	sourceData.columns.forEach((col) => {
		data.addColumn(col);
	});
	data.addRows(sourceData.rows);

	const chart = new AnnotationChart(elm);
	const options = Object.assign(
		{
			displayAnnotations: true,
			interpolateNulls: true,
		},
		elm.dataset
	);

	return chart.draw(data, options);
};
const formats = {
	date: (val) => {
		const v = new Date(val);
		const f = new Intl
			.DateTimeFormat(
				'en-UK',
				{
					month: 'short',
					year: 'numeric',
				}
			)
			.format(v);
		return {
			f,
			v
		};
	},
	number: (val) => {
		return {
			v: JSON.parse(val)
		};
	}
};
const reduceTds = (r, td, i) => {
	const formatter = formats[td.dataset.type];
	const v = td.textContent;

	if (formatter) r[i] = formatter(v);
	else r[i] = { v };

	return r;
};
const reduceThs = (r, th, i) => {
	r[i] = Object.assign(
		{},
		{
			label: th.textContent,
		},
		th.dataset
	);
	return r;
};
const readData = (elm) => {
	const table = elm.getElementsByClassName('chart-data')[0];
	const columns = Array
		.from(table.getElementsByTagName('th'))
		.reduce(
			reduceThs,
			[]
		);
	const reduceDataRows = (r, tr, i) => {
		const row = Array
			.from(tr.getElementsByTagName('td'))
			.reduce(
				reduceTds,
				[]
			);
		let sizeDiff = columns.length - row.length;

		while (sizeDiff--) {
			row.push(null);
		}
		
		r[i] = row;
		return r;
	};
	const rows = Array
		.from(table.getElementsByTagName('tbody')[0].getElementsByTagName('tr'))
		.reduce(
			reduceDataRows,
			[]
		);

	return {
		columns,
		rows,
	};
};
const onLoad = () => {
	AnnotationChart = google.visualization.LineChart;

	const charts = Array.from(document.getElementsByClassName('chart'));

	charts.forEach(drawChart);
};

google.charts.load('current', { 'packages': ['corechart'] });

google.charts.setOnLoadCallback(onLoad);