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
...
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>
<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>
<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 for="rngYearFrom">
<input type="checkbox" id="cbSeparateYears">
SEPARATE YEARS
</label>
<br />
</div>
</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 for="rngYearFrom">
<input type="checkbox" id="cbSeparateYears">
SEPARATE YEARS
</label>
<br />
<label for="rngYearFrom">
FROM
<label for="rngYearFrom">
FROM
<input id="rngYearFrom" type="range" />
<output id="opYearFrom" for="rngYearFrom"></output>
</label>
<br />
<label for="rngYearTo">
TO
<br />
<label for="rngYearTo">
TO
<input id="rngYearTo" type="range" />
<output id="opYearTo" for="rngYearTo"></output>
</label>
</div>
</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%;
div { outline:1px solid rgb(255, 0, 0); }
#container
{
width: 100%;
height: 600px;
}
#dashboard
{
width: 100%;
#dashboard
{
width: 100%;
height: 100px;
text-align: center;
}
label
{
display: inline-block;
label
{
display: inline-block;
width: 20em;
font-family: verdana;
color: rgba(200, 150, 0, 1);
text-align: right;
}
</style>
</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>
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>
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")
});
});
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())
});
});
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 => {
});
});
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");
});
});
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 => {
});
});
});
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];
});
});
});
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]);
});
});
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);
});
});
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");
});
});
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;
});
});
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>
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 = [];
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");
}
{
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);
}
{
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
{
}
}
{
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
{
}
}
{
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;
}
}
{
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)
{
});
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 parseInt() function.
function renderLineChart()
{
var rngYearFrom = document.getElementById("rngYearFrom");
var rngYearTo = document.getElementById("rngYearTo");
var yearFrom = parseInt(rngYearFrom.value);
var yearTo = parseInt(rngYearTo.value);
}
{
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});
}
{
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]);
}
{
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 = [];
{
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"
}
}
];
}
{
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", {
});
}
{
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" }
}
});
}
{
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)"
}
});
}
{
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
});
}
{
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();
});
});
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();
}
{
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.







No comments:
Post a Comment