Earlier this month, we took a look at my contributions over ten years of
GitHub usage. I have the data, and we're going to do some data visualization on this one! Here's a sample of what
hcdata_github.csv looks like. For the purpose of this exercise, I saved it at this
link.
Year,Month,Contributions
2016,1,0
2016,2,4
2016,3,2
2016,4,2
2016,5,2
2016,6,7
2016,7,6
2016,8,6
2016,9,3
2016,10,0
2016,11,0
2016,12,7
2017,1,5
2017,2,3
2017,3,17
...
For this, we want something quick and dirty, so HighCharts it is! Here, we have some boilerplate HTML. Note the script link to the HighCharts library.
<!DOCTYPE html>
<html>
<head>
<title>GitHub Contributions</title>
<style>
</style>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script>
</script>
</head>
<body>
</body>
</html>
We'll have two divs - ids
container and
dashboard respectively. I've included the styling. They both take up full screen width, though
container has a bigger height than
dashboard.
dashboard has an additional specification to say text must be aligned in the middle (this will be relevant very soon). I've set divs to have a
red outline, temporarily, so we can have a better visual.
<!DOCTYPE html>
<html>
<head>
<title>GitHub Contributions</title>
<style>
div { outline:1px solid rgb(255, 0, 0); }
#container
{
width: 100%;
height: 600px;
}
#dashboard
{
width: 100%;
height: 100px;
text-align: center;
}
</style>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script>
</script>
</head>
<body>
<div id="container">
</div>
<div id="dashboard">
</div>
</body>
</html>
Simple enough so far?
Over here in the
dashboard div, we add a checkbox,
cbSeparateYears, within a
label tag.
<div id="dashboard">
<label for="rngYearFrom">
<input type="checkbox" id="cbSeparateYears">
SEPARATE YEARS
</label>
<br />
</div>
And two sliders,
rngYearFrom and
rngYearTo, also within label tags. Beside each slider we have corresponding
output tags.
<div id="dashboard">
<label for="rngYearFrom">
<input type="checkbox" id="cbSeparateYears">
SEPARATE YEARS
</label>
<br />
<label for="rngYearFrom">
FROM
<input id="rngYearFrom" type="range" />
<output id="opYearFrom" for="rngYearFrom"></output>
</label>
<br />
<label for="rngYearTo">
TO
<input id="rngYearTo" type="range" />
<output id="opYearTo" for="rngYearTo"></output>
</label>
</div>
This styling here for labels is just meant to align stuff nicely at the bottom of the chart. Basically, the labels have a fixed width and are aligned right... within that fixed width of 20em. And then the whole 20em worth of labels is aligned smack in the middle of
dashboard! Remember we set
text-align to
middle for
dashboard? I also set the color to
orange. Sorry, the color just speaks to me, y'know?
<style>
div { outline:1px solid rgb(255, 0, 0); }
#container
{
width: 100%;
height: 600px;
}
#dashboard
{
width: 100%;
height: 100px;
text-align: center;
}
label
{
display: inline-block;
width: 20em;
font-family: verdana;
color: rgba(200, 150, 0, 1);
text-align: right;
}
</style>
OK, so here's a preview. The
output tags won't be visible simply because they have no values yet.
We want these sliders to behave a certain way. They will both call the function
setYear(), but with different arguments.
<label for="rngYearFrom">
FROM
<input id="rngYearFrom" type="range" oninput="setYear('from')" />
<output id="opYearFrom" for="rngYearFrom"></output>
</label>
<br />
<label for="rngYearTo">
TO
<input id="rngYearTo" type="range" oninput="setYear('to')" />
<output id="opYearTo" for="rngYearTo"></output>
</label>
Start writing some code to handle events when the page loads. At the same time, we'll define the
setYear() function. It has a parameter,
target. Also, declare
currentData and
yearSeriesData as empty arrays.
<script>
document.addEventListener("DOMContentLoaded", function () {
});
});
function setYear(target)
{
}
let currentData = [];
let yearSeriesData = [];
</script>
When the page loads, we want to get the data, and populate the
min and
max attributes of
rngDateFrom and
rngDateTo, with the minimum and maximum value of the
Year column. No point allowing the user to select an invalid year,
amirite?! So first, we run
fetch() to get the CSV data from the URL I've saved it in.
document.addEventListener("DOMContentLoaded", function () {
fetch("http://www.teochewthunder.com/demo/hc_github/hcdata_github.csv")
});
});
We use
then() to grab the output response once the data is loaded, and resolve it to text using the
text() method.
document.addEventListener("DOMContentLoaded", function () {
fetch("http://www.teochewthunder.com/demo/hc_github/hcdata_github.csv")
.then(response => response.text())
});
});
Once that's done, the next step is to use a chained
then() method call to grab the output,
csvData, and start working on it.
document.addEventListener("DOMContentLoaded", function () {
fetch("http://www.teochewthunder.com/demo/hc_github/hcdata_github.csv")
.then(response => response.text())
.then(csvData => {
});
});
We first want to declare
rows as an array obtained from running the
split() method on
csvData, splitting by newlines in the CSV text content.
document.addEventListener("DOMContentLoaded", function () {
fetch("http://www.teochewthunder.com/demo/hc_github/hcdata_github.csv")
.then(response => response.text())
.then(csvData => {
const rows = csvData.split("\n");
});
});
And now we can fill up
currentData. Each element of rows is a CSV line, separated by commas. We first use the
slice() method on
rows, with the argument 1, to only take into account all the rows
after the first one, which is the header. And then we run the
map() method on the result to iterate through it.
document.addEventListener("DOMContentLoaded", function () {
fetch("http://www.teochewthunder.com/demo/hc_github/hcdata_github.csv")
.then(response => response.text())
.then(csvData => {
const rows = csvData.split("\n");
currentData = rows.slice(1).map(row => {
});
});
});
We define
cols by running the
split() method on
row, with a comma as the argument. We declare
year,
month and
contribution according to which part of
cols we are referencing, and ensure that the result is an integer by using the
parseInt() function. Then we return the array of all these. In short, we return
cols, but with the values converted to integers.
document.addEventListener("DOMContentLoaded", function () {
fetch("http://www.teochewthunder.com/demo/hc_github/hcdata_github.csv")
.then(response => response.text())
.then(csvData => {
const rows = csvData.split("\n");
currentData = rows.slice(1).map(row => {
const cols = row.split(",");
var year = parseInt(cols[0]);
var month = parseInt(cols[1]);
var contributions = parseInt(cols[2]);
return [year, month, contributions];
});
});
});
Now define
years. This is an array that contains all the years in the dataset,
currentData. For this, we run the
map() method on
currentData and just get the first column (index 0) as the value.
document.addEventListener("DOMContentLoaded", function () {
fetch("http://www.teochewthunder.com/demo/hc_github/hcdata_github.csv")
.then(response => response.text())
.then(csvData => {
const rows = csvData.split("\n");
currentData = rows.slice(1).map(row => {
const cols = row.split(",");
var year = parseInt(cols[0]);
var month = parseInt(cols[1]);
var contributions = parseInt(cols[2]);
return [year, month, contributions];
});
var years = currentData.map(col => col[0]);
});
});
Now that we have the array
years, getting the minimum and maximum values is a simple matter of using the
min() and
max() methods of the
Math object, and passing in as an argument all the values of
years. Note the use of the
Spread Syntax here.
document.addEventListener("DOMContentLoaded", function () {
fetch("http://www.teochewthunder.com/demo/hc_github/hcdata_github.csv")
.then(response => response.text())
.then(csvData => {
const rows = csvData.split("\n");
currentData = rows.slice(1).map(row => {
const cols = row.split(",");
var year = parseInt(cols[0]);
var month = parseInt(cols[1]);
var contributions = parseInt(cols[2]);
return [year, month, contributions];
});
var years = currentData.map(col => col[0]);
var yearMin = Math.min(...years);
var yearMax = Math.max(...years);
});
});
Here, we grab the DOM elements from the inputs and output.
document.addEventListener("DOMContentLoaded", function () {
fetch("http://www.teochewthunder.com/demo/hc_github/hcdata_github.csv")
.then(response => response.text())
.then(csvData => {
const rows = csvData.split("\n");
currentData = rows.slice(1).map(row => {
const cols = row.split(",");
var year = parseInt(cols[0]);
var month = parseInt(cols[1]);
var contributions = parseInt(cols[2]);
return [year, month, contributions];
});
var years = currentData.map(col => col[0]);
var yearMin = Math.min(...years);
var yearMax = Math.max(...years);
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var opYearFrom = document.getElementById("opYearFrom");
var opYearTo = document.getElementById("opYearTo");
});
});
We then declare and set the range.
yearFrom and
yearTo are both set to
yearMin. For the sliders, the
min attributes of
rngYearFrom and
rngYearTo are set to
yearMin, and the
max attributes of
rngYearFrom and
rngYearTo are set to
yearMax. The values of the sliders, as well as the outputs, are set to
yearFrom and
yearTo.
document.addEventListener("DOMContentLoaded", function () {
fetch("http://www.teochewthunder.com/demo/hc_github/hcdata_github.csv")
.then(response => response.text())
.then(csvData => {
const rows = csvData.split("\n");
currentData = rows.slice(1).map(row => {
const cols = row.split(",");
var year = parseInt(cols[0]);
var month = parseInt(cols[1]);
var contributions = parseInt(cols[2]);
return [year, month, contributions];
});
var years = currentData.map(col => col[0]);
var yearMin = Math.min(...years);
var yearMax = Math.max(...years);
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var opYearFrom = document.getElementById("opYearFrom");
var opYearTo = document.getElementById("opYearTo");
var yearFrom = yearMin;
var yearTo = yearMin;
rngYearFrom.min = yearMin;
rngYearFrom.max = yearMax;
rngYearFrom.value = yearFrom;
opYearFrom.value = yearFrom;
rngYearTo.min = yearMin;
rngYearTo.max = yearMax;
rngYearTo.value = yearTo;
opYearTo.value = yearTo;
});
});
You see it! Both sliders are set to the minimum, 2016. And the output tags are showing.
We want these sliders to behave a certain way. They will both call the function
setYear(), but with different arguments.
<label for="rngYearFrom">
FROM
<input id="rngYearFrom" type="range" oninput="setYear('from')" />
<output id="opYearFrom" for="rngYearFrom"></output>
</label>
<br />
<label for="rngYearTo">
TO
<input id="rngYearTo" type="range" oninput="setYear('to')" />
<output id="opYearTo" for="rngYearTo"></output>
</label>
In the JavaScript, we create this function. The idea here is that the value of
rngDateFrom can never be greater than the value of
rngDateTo. So
rngDateTo's value needs to be adjusted to the value of
rngdateFrom when that happens, and vice versa. if
rngDateTo's value is less than that of
rngDateFrom, the value of
rngDateFrom needs to be adjusted to the value of
rngdateTo.
rngYearTo.min = yearMin;
rngYearTo.max = yearMax;
rngYearTo.value = yearTo;
opYearTo.value = yearTo;
});
});
function setYear(target)
{
}
let currentData = [];
let yearSeriesData = [];
Let's begin by grabbing the required elements from the DOM - namely, the sliders and outputs.
function setYear(target)
{
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var opYearFrom = document.getElementById("opYearFrom");
var opYearTo = document.getElementById("opYearTo");
}
And we grab the currently selected values.
function setYear(target)
{
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var opYearFrom = document.getElementById("opYearFrom");
var opYearTo = document.getElementById("opYearTo");
var yearFrom = parseInt(rngYearFrom.value);
var yearTo = parseInt(rngYearTo.value);
}
Now, an
If block handles the scenario of which slider was adjusted, based on the parameter, target. It's either "from" or "to".
function setYear(target)
{
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var opYearFrom = document.getElementById("opYearFrom");
var opYearTo = document.getElementById("opYearTo");
var yearFrom = parseInt(rngYearFrom.value);
var yearTo = parseInt(rngYearTo.value);
if (target == "from")
{
}
else
{
}
}
Here, if the selected value of
rngYearFrom,
yearFrom, is greater than
yearTo, that should not be allowed. We set
yearTo to at least be equal to
yearFrom. Then we adjust the slider and output. Don't forget to adjust the output for the "from" slider too.
function setYear(target)
{
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var opYearFrom = document.getElementById("opYearFrom");
var opYearTo = document.getElementById("opYearTo");
var yearFrom = parseInt(rngYearFrom.value);
var yearTo = parseInt(rngYearTo.value);
if (target == "from")
{
if (yearFrom > yearTo)
{
yearTo = yearFrom;
rngYearTo.value = yearFrom;
opYearTo.value = yearFrom;
}
opYearFrom.value = yearFrom;
}
else
{
}
}
And we do the reverse for the other slider!
function setYear(target)
{
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var opYearFrom = document.getElementById("opYearFrom");
var opYearTo = document.getElementById("opYearTo");
var yearFrom = parseInt(rngYearFrom.value);
var yearTo = parseInt(rngYearTo.value);
if (target == "from")
{
if (yearFrom > yearTo)
{
yearTo = yearFrom;
rngYearTo.value = yearFrom;
opYearTo.value = yearFrom;
}
opYearFrom.value = yearFrom;
}
else
{
if (yearTo < yearFrom)
{
yearFrom = yearTo;
rngYearFrom.value = yearTo;
opYearFrom.value = yearTo;
}
opYearTo.value = yearTo;
}
}
Let's test this...
Both the Year FROM and TO start at 2016. Slide FROM to 2022. Does TO follow?
Now slide TO to a value lower than FROM, say, 2018. Does FROM follow?
Now slide TO to a value higher than FROM. FROM should stay put! In effect, FROM should always be lower than TO, or equal.
Rendering the Chart
Finally, eh? Create the
renderLineChart() function.
});
});
function renderLineChart()
{
}
function setYear(target)
{
We get the values from the range sliders, and assign them to the variables
yearFrom and
yearTo, after coercing them to integers using the p
arseInt() function.
function renderLineChart()
{
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var yearFrom = parseInt(rngYearFrom.value);
var yearTo = parseInt(rngYearTo.value);
}
We then declare
dataset. It will be the subset of
currentData whose year column (the first one at index 0) confirms to the range between
yearFrom and
yearTo, inclusive. We use the
filter() method for this.
function renderLineChart()
{
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var yearFrom = parseInt(rngYearFrom.value);
var yearTo = parseInt(rngYearTo.value);
var dataset = currentData.filter((x) =>{ return parseInt(x[0]) >= yearFrom && parseInt(x[0]) <= yearTo});
}
Next, we declare
years. This is actually a label that shows both the month and the year. For this, we use the second column (index 1) and the first column (index 0). We run the second column's value through the
monthToName() function.
function renderLineChart()
{
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var yearFrom = parseInt(rngYearFrom.value);
var yearTo = parseInt(rngYearTo.value);
var dataset = currentData.filter((x) =>{ return parseInt(x[0]) >= yearFrom && parseInt(x[0]) <= yearTo});
var years = dataset.map(col => monthToName(col[1]) + " " + col[0]);
}
Here's the function. It really isn't anything special - just returns a month string based on the integer passed into the function.
function setYear(target)
{
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var opYearFrom = document.getElementById("opYearFrom");
var opYearTo = document.getElementById("opYearTo");
var yearFrom = parseInt(rngYearFrom.value);
var yearTo = parseInt(rngYearTo.value);
if (target == "from")
{
if (yearFrom > yearTo)
{
yearTo = yearFrom;
rngYearTo.value = yearFrom;
opYearTo.value = yearFrom;
}
opYearFrom.value = yearFrom;
}
else
{
if (yearTo < yearFrom)
{
yearFrom = yearTo;
rngYearFrom.value = yearTo;
opYearFrom.value = yearTo;
}
opYearTo.value = yearTo;
}
}
function monthToName(month)
{
monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
return monthNames[parseInt(month) - 1];
}
let currentData = [];
let yearSeriesData = [];
Now we have
series, another array. For now, set
series to contain one single object. Here are its properties.
name - this is the label that will appear on the chart's y-axis.
type - the line type. I chose "spline" because it's smooth and sexy.
data - the array of values. This is an empty array for now.
lineColor,
lineWidth and
dashStyle - Aesthetic choices. I went with a thick
orange line.
marker - I don't want any damn markers, so it's an object with
fillColor set to none.
function renderLineChart()
{
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var yearFrom = parseInt(rngYearFrom.value);
var yearTo = parseInt(rngYearTo.value);
var dataset = currentData.filter((x) =>{ return parseInt(x[0]) >= yearFrom && parseInt(x[0]) <= yearTo});
var years = dataset.map(col => monthToName(col[1]) + " " + col[0]);
var series = [];
series = [
{
name: "commits",
type: "spline",
data:[],
lineColor: "rgba(250, 100, 0, 1)",
lineWidth: 5,
dashStyle: "Solid",
marker:
{
fillColor: "none"
}
}
];
}
Now, to render the chart! We will use the
container div as the target here, for the Highchart object's
chart() method.
function renderLineChart()
{
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var yearFrom = parseInt(rngYearFrom.value);
var yearTo = parseInt(rngYearTo.value);
var dataset = currentData.filter((x) =>{ return parseInt(x[0]) >= yearFrom && parseInt(x[0]) <= yearTo});
var years = dataset.map(col => monthToName(col[1]) + " " + col[0]);
var series = [];
series = [
{
name: "commits",
type: "spline",
data: [],
lineColor: "rgba(250, 100, 0, 1)",
lineWidth: 5,
dashStyle: "Solid",
marker:
{
fillColor: "none"
}
}
];
const chart = Highcharts.chart("container", {
});
}
The first three properties we pass in, are
chart,
title and
subtitle. As you can see, it's all visual styling and aesthetics. I've gone with an
orange color scheme. (surprise, surprise)
function renderLineChart()
{
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var yearFrom = parseInt(rngYearFrom.value);
var yearTo = parseInt(rngYearTo.value);
var dataset = currentData.filter((x) =>{ return parseInt(x[0]) >= yearFrom && parseInt(x[0]) <= yearTo});
var years = dataset.map(col => monthToName(col[1]) + " " + col[0]);
var commits = dataset.map(col => col[2]);
var series = [];
series = [
{
name: "commits",
type: "spline",
data: commits,
lineColor: "rgba(250, 100, 0, 1)",
lineWidth: 5,
dashStyle: "Solid",
marker:
{
fillColor: "none"
}
}
];
const chart = Highcharts.chart("container", {
chart:
{
borderColor: "rgba(250, 100, 0, 1)",
borderRadius: 10,
borderWidth: 2,
},
title:
{
text: "My Contributions",
style: { "color": "rgba(250, 100, 0, 1)", "font-size": "2.5em", "font-weight": "bold" }
},
subtitle:
{
text: "GitHub statistics by TeochewThunder",
style: { "color": "rgba(250, 100, 0, 0.8)", "font-size": "0.8em" }
}
});
}
The next two properties define the labelling and scale. For
xAxis, we pass in the
years array as the value of the
category property. For
yAxis, it's all color scheme and labelling. Feel free to play with different values.
function renderLineChart()
{
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var yearFrom = parseInt(rngYearFrom.value);
var yearTo = parseInt(rngYearTo.value);
var dataset = currentData.filter((x) =>{ return parseInt(x[0]) >= yearFrom && parseInt(x[0]) <= yearTo});
var years = dataset.map(col => monthToName(col[1]) + " " + col[0]);
var series = [];
series = [
{
name: "commits",
type: "spline",
data: [],
lineColor: "rgba(250, 100, 0, 1)",
lineWidth: 5,
dashStyle: "Solid",
marker:
{
fillColor: "none"
}
}
];
const chart = Highcharts.chart("container", {
chart:
{
borderColor: "rgba(250, 100, 0, 1)",
borderRadius: 10,
borderWidth: 2,
},
title:
{
text: "My Contributions",
style: { "color": "rgba(250, 100, 0, 1)", "font-size": "2.5em", "font-weight": "bold" }
},
subtitle:
{
text: "GitHub statistics by TeochewThunder",
style: { "color": "rgba(250, 100, 0, 0.8)", "font-size": "0.8em" }
},
xAxis:
{
categories: years
},
yAxis:
{
title:
{
text: "Commits"
},
gridLineColor: "rgba(250, 100, 0, 0.2)",
tickColor: "rgba(250, 100, 0, 0.2)"
}
});
}
And finally, for the
series property, we pass in
series, which we created earlier.
function renderLineChart()
{
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var yearFrom = parseInt(rngYearFrom.value);
var yearTo = parseInt(rngYearTo.value);
var dataset = currentData.filter((x) =>{ return parseInt(x[0]) >= yearFrom && parseInt(x[0]) <= yearTo});
var years = dataset.map(col => monthToName(col[1]) + " " + col[0]);
var series = [];
series = [
{
name: "commits",
type: "spline",
data: [],
lineColor: "rgba(250, 100, 0, 1)",
lineWidth: 5,
dashStyle: "Solid",
marker:
{
fillColor: "none"
}
}
];
const chart = Highcharts.chart("container", {
chart:
{
borderColor: "rgba(250, 100, 0, 1)",
borderRadius: 10,
borderWidth: 2,
},
title:
{
text: "My Contributions",
style: { "color": "rgba(250, 100, 0, 1)", "font-size": "2.5em", "font-weight": "bold" }
},
subtitle:
{
text: "GitHub statistics by TeochewThunder",
style: { "color": "rgba(250, 100, 0, 0.8)", "font-size": "0.8em" }
},
xAxis:
{
categories: years
},
yAxis:
{
title:
{
text: "Commits"
},
gridLineColor: "rgba(250, 100, 0, 0.2)",
tickColor: "rgba(250, 100, 0, 0.2)"
},
series: series
});
}
Call the function here...
document.addEventListener("DOMContentLoaded", function () {
fetch("http://www.teochewthunder.com/demo/hc_github/hcdata_github.csv")
.then(response => response.text())
.then(csvData => {
const rows = csvData.split("\n");
currentData = rows.slice(1).map(row => {
const cols = row.split(",");
var year = parseInt(cols[0]);
var month = parseInt(cols[1]);
var contributions = parseInt(cols[2]);
return [year, month, contributions];
});
var years = currentData.map(col => col[0]);
var yearMin = Math.min(...years);
var yearMax = Math.max(...years);
var yearFrom = yearMin;
var yearTo = yearMin;
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var opYearFrom = document.getElementById("opYearFrom");
var opYearTo = document.getElementById("opYearTo");
rngYearFrom.min = yearMin;
rngYearFrom.max = yearMax;
rngYearFrom.value = yearFrom;
opYearFrom.value = yearFrom;
rngYearTo.min = yearMin;
rngYearTo.max = yearMax;
rngYearTo.value = yearTo;
opYearTo.value = yearTo;
renderLineChart();
});
});
...and here, at the end of the
setYear() function.
function setYear(target)
{
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var opYearFrom = document.getElementById("opYearFrom");
var opYearTo = document.getElementById("opYearTo");
var yearFrom = parseInt(rngYearFrom.value);
var yearTo = parseInt(rngYearTo.value);
if (target == "from")
{
if (yearFrom > yearTo)
{
yearTo = yearFrom;
rngYearTo.value = yearFrom;
opYearTo.value = yearFrom;
}
opYearFrom.value = yearFrom;
}
else
{
if (yearTo < yearFrom)
{
yearFrom = yearTo;
rngYearFrom.value = yearTo;
opYearFrom.value = yearTo;
}
opYearTo.value = yearTo;
}
renderLineChart();
}
Remove the
red lines.
div { outline:0px solid rgb(255, 0, 0); }
And here is the current placeholder we have for the chart. You should see right now that both range sliders are at the minimum value, 2016.
Next
Displaying chart data.