Skip to content

Instantly share code, notes, and snippets.

@praveenuics
Last active June 24, 2020 14:29
Show Gist options
  • Select an option

  • Save praveenuics/1961da999ff3cc24814fa08efa4256b6 to your computer and use it in GitHub Desktop.

Select an option

Save praveenuics/1961da999ff3cc24814fa08efa4256b6 to your computer and use it in GitHub Desktop.

Revisions

  1. praveenuics revised this gist Jun 24, 2020. 1 changed file with 284 additions and 318 deletions.
    602 changes: 284 additions & 318 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -5,330 +5,296 @@
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script data-require="d3@3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
    <style>
    html {
    font-family: 'Arial', sans-serif;
    }
    .header {
    margin-top: 60px;
    margin-left: 60px;
    text-align: left;
    }
    input {
    margin-right: 10px;
    }
    .form-check-label {
    font-size: 0.8em;
    margin-right: 20px;
    }
    body {
    font: 10px sans-serif;
    }

    .axis path,
    .axis line {
    fill: none;
    stroke: #000;
    shape-rendering: crispEdges;
    }

    .x.axis path {
    display: none;
    }

    .line {
    fill: none;
    stroke: steelblue;
    stroke-width: 1.5px;
    }
    </style>
    </head>
    <body>
    <div class ='container-fluid'>
    <div class='row'>
    <div id="chart">
    </div>
    </div>
    </div>
    <script>
    var glines
    var mouseG
    var tooltip

    var parseDate = d3.timeParse("%Y-%m")
    var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"]

    var margin = {top: 80, right: 200, bottom: 40, left: 80}
    var width = 1400 - margin.left - margin.right
    var height = 500 - margin.top - margin.bottom

    var lineOpacity = 1
    var lineStroke = "2px"

    var axisPad = 6 // axis formatting
    var R = 6 //legend marker

    var color = d3.scaleOrdinal()
    .range(["#2D4057", "#7C8DA4", "#B7433D", "#2E7576", "#EE811D"]);

    var data = [
    {date: 2010-01, serverName: 'Server1', count: 70 },
    {date: 2010-02, serverName: 'Server3', count: 60 },
    {date: 2010-03, serverName: 'Server2', count: 40 },
    {date: 2010-05, serverName: 'Server2', count: 20 },
    {date: 2010-05, serverName: 'Server5', count: 10 }
    ];

    var res = data.map((d,i) => {
    var myData = "date New York San Francisco Austin\n\
    20111001 63.4 62.7 72.2\n\
    20111002 58.0 59.9 67.7\n\
    20111003 53.3 59.1 69.4\n\
    20111004 55.7 58.8 68.0\n\
    20111005 64.2 58.7 72.4\n\
    20111006 58.8 57.0 77.0\n\
    20111007 57.9 56.7 82.3\n\
    20111008 61.8 56.8 78.9\n\
    20111009 69.3 56.7 68.8\n\
    20111010 71.2 60.1 68.7\n\
    20111011 68.7 61.1 70.3\n\
    20111012 61.8 61.5 75.3\n\
    20111013 63.0 64.3 76.6\n\
    20111014 66.9 67.1 66.6\n\
    20111015 61.7 64.6 68.0\n\
    20111016 61.8 61.6 70.6\n\
    20111017 62.8 61.1 71.1\n\
    20111018 60.8 59.2 70.0\n\
    20111019 62.1 58.9 61.6\n\
    20111020 65.1 57.2 57.4\n\
    20111021 55.6 56.4 64.3\n\
    20111022 54.4 60.7 72.4\n";

    var margin = {
    top: 20,
    right: 80,
    bottom: 30,
    left: 50
    },
    width = 900 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

    var parseDate = d3.time.format("%Y%m%d").parse;

    var x = d3.time.scale()
    .range([0, width]);

    var y = d3.scale.linear()
    .range([height, 0]);

    var color = d3.scale.category10();

    var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

    var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

    var line = d3.svg.line()
    .interpolate("basis")
    .x(function(d) {
    return x(d.date);
    })
    .y(function(d) {
    return y(d.temperature);
    });

    var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var data = d3.tsv.parse(myData);

    color.domain(d3.keys(data[0]).filter(function(key) {
    return key !== "date";
    }));

    data.forEach(function(d) {
    d.date = parseDate(d.date);
    });

    var cities = color.domain().map(function(name) {
    return {
    name: name,
    values: data.map(function(d) {
    return {
    date : parseDate(d.month),
    serverName: d.serverName,
    count : d.count
    }
    })

    var xScale = d3.scaleTime()
    .domain(d3.extent(res, d=>d.date))
    .range([0, width])

    var yScale = d3.scaleLinear()
    .domain([0, d3.max(res, d => d.count)])
    .range([height, 0]);

    var svg = d3.select("#chart").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append('g')
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");


    // CREATE AXES //
    // render axis first before lines so that lines will overlay the horizontal ticks
    var xAxis = d3.axisBottom(xScale).ticks(d3.timeYear.every(1)).tickSizeOuter(axisPad*2).tickSizeInner(axisPad*2)
    var yAxis = d3.axisLeft(yScale).ticks(10, "s").tickSize(-width) //horizontal ticks across svg width

    svg.append("g")
    .attr("class", "x axis")
    .attr("transform", `translate(0, ${height})`)
    .call(xAxis)
    .call(g => {
    var years = xScale.ticks(d3.timeYear.every(1))
    var xshift = (width/(years.length))/2
    g.selectAll("text").attr("transform", `translate(${xshift}, 0)`) //shift tick labels to middle of interval
    .style("text-anchor", "middle")
    .attr("y", axisPad)
    .attr('fill', '#A9A9A9')

    g.selectAll("line")
    .attr('stroke', '#A9A9A9')

    g.select(".domain")
    .attr('stroke', '#A9A9A9')

    })

    svg.append("g")
    .attr("class", "y axis")
    .call(yAxis)
    .call(g => {
    g.selectAll("text")
    .style("text-anchor", "middle")
    .attr("x", -axisPad*2)
    .attr('fill', '#A9A9A9')

    g.selectAll("line")
    .attr('stroke', '#A9A9A9')
    .attr('stroke-width', 0.7) // make horizontal tick thinner and lighter so that line paths can stand out
    .attr('opacity', 0.3)

    g.select(".domain").remove()

    })
    .append('text')
    .attr('x', 50)
    .attr("y", -10)
    .attr("fill", "#A9A9A9")
    .text("Singapore Dollars")

    // line generator
    var line = d3.line()
    .x(d => xScale(d.date))
    .y(d => yScale(d.count))

    renderChart(1) // inital chart render (set default to Bidding Exercise 1 data)

    // Update chart when radio button is selected
    // d3.selectAll(("input[name='bidding_no']")).on('change', function(){
    // updateChart(this.value)
    // })

    function updateChart() {

    var res_nested = d3.nest()
    .key(d=>d.count)
    .entries(res)

    glines.select('.line') //select line path within line-group (which represents a vehicle category), then bind new data
    .data(res_nested)
    .transition().duration(750)
    .attr('d', function(d) {
    return line(d.values)
    })

    mouseG.selectAll('.mouse-per-line')
    .data(res_nested)

    mouseG.on('mousemove', function () {
    var mouse = d3.mouse(this)
    updateTooltipContent(mouse, res_nested)
    })
    }

    function renderChart() {

    var res_nested = d3.nest() // necessary to nest data so that keys represent each vehicle category
    .key(d=>d.count)
    .entries(res)

    // APPEND MULTIPLE LINES //
    var lines = svg.append('g')
    .attr('class', 'lines')

    glines = lines.selectAll('.line-group')
    .data(res_nested).enter()
    .append('g')
    .attr('class', 'line-group')

    glines
    .append('path')
    .attr('class', 'line')
    .attr('d', d => line(d.values))
    .style('stroke', (d, i) => color(i))
    .style('fill', 'none')
    .style('opacity', lineOpacity)
    .style('stroke-width', lineStroke)


    // APPEND CIRCLE MARKERS //
    //var gcircle = lines.selectAll("circle-group")
    //.data(res_nested).enter()
    //.append("g")
    //.attr('class', 'circle-group')

    //gcircle.selectAll("circle")
    //.data(d => d.values).enter()
    //.append("g")
    //.attr("class", "circle")
    //.append("circle")
    //.attr("cx", d => xScale(d.date))
    //.attr("cy", d => yScale(d.premium))
    //.attr("r", 2)

    // CREATE HOVER TOOLTIP WITH VERTICAL LINE //
    tooltip = d3.select("#chart").append("div")
    .attr('id', 'tooltip')
    .style('position', 'absolute')
    .style("background-color", "#D3D3D3")
    .style('padding', 6)
    .style('display', 'none')

    mouseG = svg.append("g")
    .attr("class", "mouse-over-effects");

    mouseG.append("path") // create vertical line to follow mouse
    .attr("class", "mouse-line")
    .style("stroke", "#A9A9A9")
    .style("stroke-width", lineStroke)
    .style("opacity", "0");

    var lines = document.getElementsByClassName('line');

    var mousePerLine = mouseG.selectAll('.mouse-per-line')
    .data(res_nested)
    .enter()
    .append("g")
    .attr("class", "mouse-per-line");

    mousePerLine.append("circle")
    .attr("r", 4)
    .style("stroke", function (d) {
    return color(d.key)
    })
    .style("fill", "none")
    .style("stroke-width", lineStroke)
    .style("opacity", "0");

    mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas
    .attr('width', width)
    .attr('height', height)
    .attr('fill', 'none')
    .attr('pointer-events', 'all')
    .on('mouseout', function () { // on mouse out hide line, circles and text
    d3.select(".mouse-line")
    .style("opacity", "0");
    d3.selectAll(".mouse-per-line circle")
    .style("opacity", "0");
    d3.selectAll(".mouse-per-line text")
    .style("opacity", "0");
    d3.selectAll("#tooltip")
    .style('display', 'none')

    })
    .on('mouseover', function () { // on mouse in show line, circles and text
    d3.select(".mouse-line")
    .style("opacity", "1");
    d3.selectAll(".mouse-per-line circle")
    .style("opacity", "1");
    d3.selectAll("#tooltip")
    .style('display', 'block')
    })
    .on('mousemove', function () { // update tooltip content, line, circles and text when mouse moves
    var mouse = d3.mouse(this)

    d3.selectAll(".mouse-per-line")
    .attr("transform", function (d, i) {
    var xDate = xScale.invert(mouse[0]) // use 'invert' to get date corresponding to distance from mouse position relative to svg
    var bisect = d3.bisector(function (d) { return d.date; }).left // retrieve row index of date on parsed csv
    var idx = bisect(d.values, xDate);

    d3.select(".mouse-line")
    .attr("d", function () {
    var data = "M" + xScale(d.values[idx].date) + "," + (height);
    data += " " + xScale(d.values[idx].date) + "," + 0;
    return data;
    });
    return "translate(" + xScale(d.values[idx].date) + "," + yScale(d.values[idx].count) + ")";

    });

    updateTooltipContent(mouse, res_nested)

    })

    }

    function updateTooltipContent(mouse, res_nested) {

    sortingObj = []
    res_nested.map(d => {
    var xDate = xScale.invert(mouse[0])
    var bisect = d3.bisector(function (d) { return d.date; }).left
    var idx = bisect(d.values, xDate)
    sortingObj.push({key: d.values[idx].count, count: d.values[idx].count, year: d.values[idx].date.getFullYear(), month: monthNames[d.values[idx].date.getMonth()]})
    date: d.date,
    temperature: +d[name]
    };
    })

    sortingObj.sort(function(x, y){
    return d3.descending(x.count, y.count);
    })

    var sortingArr = sortingObj.map(d=> d.key)

    var res_nested1 = res_nested.slice().sort(function(a, b){
    return sortingArr.indexOf(a.key) - sortingArr.indexOf(b.key) // rank vehicle category based on price of count
    })

    tooltip.html(sortingObj[0].month + "-" + sortingObj[0].year)
    .style('display', 'block')
    .style('left', d3.event.pageX + 20)
    .style('top', d3.event.pageY - 20)
    .style('font-size', 11.5)
    .selectAll()
    .data(res_nested1).enter() // for each vehicle category, list out name and price of count
    .append('div')
    .style('color', d => {
    return color(d.key)
    })
    .style('font-size', 10)
    .html(d => {
    var xDate = xScale.invert(mouse[0])
    var bisect = d3.bisector(function (d) { return d.date; }).left
    var idx = bisect(d.values, xDate)
    return d.key.substring(0, 3) + " " + d.key.slice(-1) + d.values[idx].count
    })
    }


    </script>
    };
    });

    x.domain(d3.extent(data, function(d) {
    return d.date;
    }));

    y.domain([
    d3.min(cities, function(c) {
    return d3.min(c.values, function(v) {
    return v.temperature;
    });
    }),
    d3.max(cities, function(c) {
    return d3.max(c.values, function(v) {
    return v.temperature;
    });
    })
    ]);

    var legend = svg.selectAll('g')
    .data(cities)
    .enter()
    .append('g')
    .attr('class', 'legend');

    legend.append('rect')
    .attr('x', width - 20)
    .attr('y', function(d, i) {
    return i * 20;
    })
    .attr('width', 10)
    .attr('height', 10)
    .style('fill', function(d) {
    return color(d.name);
    });

    legend.append('text')
    .attr('x', width - 8)
    .attr('y', function(d, i) {
    return (i * 20) + 9;
    })
    .text(function(d) {
    return d.name;
    });

    svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

    svg.append("g")
    .attr("class", "y axis")
    .call(yAxis)
    .append("text")
    .attr("transform", "rotate(-90)")
    .attr("y", 6)
    .attr("dy", ".71em")
    .style("text-anchor", "end")
    .text("Temperature (ºF)");

    var city = svg.selectAll(".city")
    .data(cities)
    .enter().append("g")
    .attr("class", "city");

    city.append("path")
    .attr("class", "line")
    .attr("d", function(d) {
    return line(d.values);
    })
    .style("stroke", function(d) {
    return color(d.name);
    });

    city.append("text")
    .datum(function(d) {
    return {
    name: d.name,
    value: d.values[d.values.length - 1]
    };
    })
    .attr("transform", function(d) {
    return "translate(" + x(d.value.date) + "," + y(d.value.temperature) + ")";
    })
    .attr("x", 3)
    .attr("dy", ".35em")
    .text(function(d) {
    return d.name;
    });

    var mouseG = svg.append("g")
    .attr("class", "mouse-over-effects");

    mouseG.append("path") // this is the black vertical line to follow mouse
    .attr("class", "mouse-line")
    .style("stroke", "black")
    .style("stroke-width", "1px")
    .style("opacity", "0");

    var lines = document.getElementsByClassName('line');

    var mousePerLine = mouseG.selectAll('.mouse-per-line')
    .data(cities)
    .enter()
    .append("g")
    .attr("class", "mouse-per-line");

    mousePerLine.append("circle")
    .attr("r", 7)
    .style("stroke", function(d) {
    return color(d.name);
    })
    .style("fill", "none")
    .style("stroke-width", "1px")
    .style("opacity", "0");

    mousePerLine.append("text")
    .attr("transform", "translate(10,3)");

    mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas
    .attr('width', width) // can't catch mouse events on a g element
    .attr('height', height)
    .attr('fill', 'none')
    .attr('pointer-events', 'all')
    .on('mouseout', function() { // on mouse out hide line, circles and text
    d3.select(".mouse-line")
    .style("opacity", "0");
    d3.selectAll(".mouse-per-line circle")
    .style("opacity", "0");
    d3.selectAll(".mouse-per-line text")
    .style("opacity", "0");
    })
    .on('mouseover', function() { // on mouse in show line, circles and text
    d3.select(".mouse-line")
    .style("opacity", "1");
    d3.selectAll(".mouse-per-line circle")
    .style("opacity", "1");
    d3.selectAll(".mouse-per-line text")
    .style("opacity", "1");
    })
    .on('mousemove', function() { // mouse moving over canvas
    var mouse = d3.mouse(this);
    d3.select(".mouse-line")
    .attr("d", function() {
    var d = "M" + mouse[0] + "," + height;
    d += " " + mouse[0] + "," + 0;
    return d;
    });

    d3.selectAll(".mouse-per-line")
    .attr("transform", function(d, i) {
    console.log(width/mouse[0])
    var xDate = x.invert(mouse[0]),
    bisect = d3.bisector(function(d) { return d.date; }).right;
    idx = bisect(d.values, xDate);

    var beginning = 0,
    end = lines[i].getTotalLength(),
    target = null;

    while (true){
    target = Math.floor((beginning + end) / 2);
    pos = lines[i].getPointAtLength(target);
    if ((target === end || target === beginning) && pos.x !== mouse[0]) {
    break;
    }
    if (pos.x > mouse[0]) end = target;
    else if (pos.x < mouse[0]) beginning = target;
    else break; //position found
    }

    d3.select(this).select('text')
    .text(y.invert(pos.y).toFixed(2));

    return "translate(" + mouse[0] + "," + pos.y +")";
    });
    });

    </script>
    </body>
    </html>
  2. praveenuics revised this gist Jun 18, 2020. No changes.
  3. praveenuics revised this gist Jun 18, 2020. 1 changed file with 1 addition and 49 deletions.
    50 changes: 1 addition & 49 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -28,20 +28,6 @@
    <div class ='container-fluid'>
    <div class='row'>
    <div id="chart">
    <div class='header'>
    <h3>COE Prices from 2010-2018</h3>
    <p>There are 2 COE Open Bidding exercises each month (Bidding Exercise 1 starts on the first Monday while Bidding Exercise 2 starts on the third Monday of the month)</p>
    <div class="form-check-inline">
    <label class="form-check-label">
    <input style="display: inline" type="radio" name="bidding_no" value="1" checked>Bidding Exercise 1
    </label>
    </div>
    <div class="form-check-inline">
    <label class="form-check-label">
    <input type="radio" name="bidding_no" value="2">Bidding Exercise 2
    </label>
    </div>
    </div>
    </div>
    </div>
    </div>
    @@ -63,10 +49,7 @@ <h3>COE Prices from 2010-2018</h3>
    var axisPad = 6 // axis formatting
    var R = 6 //legend marker

    var category = ["Category A", "Category B", "Category C", "Category D", "Category E"]
    // since Category B and E are really close to each other, assign them diverging colors
    var color = d3.scaleOrdinal()
    .domain(category)
    .range(["#2D4057", "#7C8DA4", "#B7433D", "#2E7576", "#EE811D"]);

    var data = [
    @@ -89,12 +72,8 @@ <h3>COE Prices from 2010-2018</h3>
    .domain(d3.extent(res, d=>d.date))
    .range([0, width])

    function roundToNearest10K(x) {
    return Math.round(x / 10000) * 10000
    }

    var yScale = d3.scaleLinear()
    .domain([0, roundToNearest10K(d3.max(res, d => d.count))])
    .domain([0, d3.max(res, d => d.count)])
    .range([height, 0]);

    var svg = d3.select("#chart").append("svg")
    @@ -152,33 +131,6 @@ <h3>COE Prices from 2010-2018</h3>
    .attr("fill", "#A9A9A9")
    .text("Singapore Dollars")


    // CREATE LEGEND //
    var svgLegend = svg.append('g')
    .attr('class', 'gLegend')
    .attr("transform", "translate(" + (width + 20) + "," + 0 + ")")

    var legend = svgLegend.selectAll('.legend')
    .data(category)
    .enter().append('g')
    .attr("class", "legend")
    .attr("transform", function (d, i) {return "translate(0," + i * 20 + ")"})

    legend.append("circle")
    .attr("class", "legend-node")
    .attr("cx", 0)
    .attr("cy", 0)
    .attr("r", R)
    .style("fill", d=>color(d))

    legend.append("text")
    .attr("class", "legend-text")
    .attr("x", R*2)
    .attr("y", R/2)
    .style("fill", "#A9A9A9")
    .style("font-size", 12)
    .text(d=>d)

    // line generator
    var line = d3.line()
    .x(d => xScale(d.date))
  4. praveenuics created this gist Jun 18, 2020.
    1 change: 1 addition & 0 deletions .block
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    license: mit
    1 change: 1 addition & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    Built with [blockbuilder.org](http://blockbuilder.org)
    382 changes: 382 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,382 @@
    <html>
    <head>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

    <script src="https://d3js.org/d3.v4.min.js"></script>
    <style>
    html {
    font-family: 'Arial', sans-serif;
    }
    .header {
    margin-top: 60px;
    margin-left: 60px;
    text-align: left;
    }
    input {
    margin-right: 10px;
    }
    .form-check-label {
    font-size: 0.8em;
    margin-right: 20px;
    }
    </style>
    </head>
    <body>
    <div class ='container-fluid'>
    <div class='row'>
    <div id="chart">
    <div class='header'>
    <h3>COE Prices from 2010-2018</h3>
    <p>There are 2 COE Open Bidding exercises each month (Bidding Exercise 1 starts on the first Monday while Bidding Exercise 2 starts on the third Monday of the month)</p>
    <div class="form-check-inline">
    <label class="form-check-label">
    <input style="display: inline" type="radio" name="bidding_no" value="1" checked>Bidding Exercise 1
    </label>
    </div>
    <div class="form-check-inline">
    <label class="form-check-label">
    <input type="radio" name="bidding_no" value="2">Bidding Exercise 2
    </label>
    </div>
    </div>
    </div>
    </div>
    </div>
    <script>
    var glines
    var mouseG
    var tooltip

    var parseDate = d3.timeParse("%Y-%m")
    var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"]

    var margin = {top: 80, right: 200, bottom: 40, left: 80}
    var width = 1400 - margin.left - margin.right
    var height = 500 - margin.top - margin.bottom

    var lineOpacity = 1
    var lineStroke = "2px"

    var axisPad = 6 // axis formatting
    var R = 6 //legend marker

    var category = ["Category A", "Category B", "Category C", "Category D", "Category E"]
    // since Category B and E are really close to each other, assign them diverging colors
    var color = d3.scaleOrdinal()
    .domain(category)
    .range(["#2D4057", "#7C8DA4", "#B7433D", "#2E7576", "#EE811D"]);

    var data = [
    {date: 2010-01, serverName: 'Server1', count: 70 },
    {date: 2010-02, serverName: 'Server3', count: 60 },
    {date: 2010-03, serverName: 'Server2', count: 40 },
    {date: 2010-05, serverName: 'Server2', count: 20 },
    {date: 2010-05, serverName: 'Server5', count: 10 }
    ];

    var res = data.map((d,i) => {
    return {
    date : parseDate(d.month),
    serverName: d.serverName,
    count : d.count
    }
    })

    var xScale = d3.scaleTime()
    .domain(d3.extent(res, d=>d.date))
    .range([0, width])

    function roundToNearest10K(x) {
    return Math.round(x / 10000) * 10000
    }

    var yScale = d3.scaleLinear()
    .domain([0, roundToNearest10K(d3.max(res, d => d.count))])
    .range([height, 0]);

    var svg = d3.select("#chart").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append('g')
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");


    // CREATE AXES //
    // render axis first before lines so that lines will overlay the horizontal ticks
    var xAxis = d3.axisBottom(xScale).ticks(d3.timeYear.every(1)).tickSizeOuter(axisPad*2).tickSizeInner(axisPad*2)
    var yAxis = d3.axisLeft(yScale).ticks(10, "s").tickSize(-width) //horizontal ticks across svg width

    svg.append("g")
    .attr("class", "x axis")
    .attr("transform", `translate(0, ${height})`)
    .call(xAxis)
    .call(g => {
    var years = xScale.ticks(d3.timeYear.every(1))
    var xshift = (width/(years.length))/2
    g.selectAll("text").attr("transform", `translate(${xshift}, 0)`) //shift tick labels to middle of interval
    .style("text-anchor", "middle")
    .attr("y", axisPad)
    .attr('fill', '#A9A9A9')

    g.selectAll("line")
    .attr('stroke', '#A9A9A9')

    g.select(".domain")
    .attr('stroke', '#A9A9A9')

    })

    svg.append("g")
    .attr("class", "y axis")
    .call(yAxis)
    .call(g => {
    g.selectAll("text")
    .style("text-anchor", "middle")
    .attr("x", -axisPad*2)
    .attr('fill', '#A9A9A9')

    g.selectAll("line")
    .attr('stroke', '#A9A9A9')
    .attr('stroke-width', 0.7) // make horizontal tick thinner and lighter so that line paths can stand out
    .attr('opacity', 0.3)

    g.select(".domain").remove()

    })
    .append('text')
    .attr('x', 50)
    .attr("y", -10)
    .attr("fill", "#A9A9A9")
    .text("Singapore Dollars")


    // CREATE LEGEND //
    var svgLegend = svg.append('g')
    .attr('class', 'gLegend')
    .attr("transform", "translate(" + (width + 20) + "," + 0 + ")")

    var legend = svgLegend.selectAll('.legend')
    .data(category)
    .enter().append('g')
    .attr("class", "legend")
    .attr("transform", function (d, i) {return "translate(0," + i * 20 + ")"})

    legend.append("circle")
    .attr("class", "legend-node")
    .attr("cx", 0)
    .attr("cy", 0)
    .attr("r", R)
    .style("fill", d=>color(d))

    legend.append("text")
    .attr("class", "legend-text")
    .attr("x", R*2)
    .attr("y", R/2)
    .style("fill", "#A9A9A9")
    .style("font-size", 12)
    .text(d=>d)

    // line generator
    var line = d3.line()
    .x(d => xScale(d.date))
    .y(d => yScale(d.count))

    renderChart(1) // inital chart render (set default to Bidding Exercise 1 data)

    // Update chart when radio button is selected
    // d3.selectAll(("input[name='bidding_no']")).on('change', function(){
    // updateChart(this.value)
    // })

    function updateChart() {

    var res_nested = d3.nest()
    .key(d=>d.count)
    .entries(res)

    glines.select('.line') //select line path within line-group (which represents a vehicle category), then bind new data
    .data(res_nested)
    .transition().duration(750)
    .attr('d', function(d) {
    return line(d.values)
    })

    mouseG.selectAll('.mouse-per-line')
    .data(res_nested)

    mouseG.on('mousemove', function () {
    var mouse = d3.mouse(this)
    updateTooltipContent(mouse, res_nested)
    })
    }

    function renderChart() {

    var res_nested = d3.nest() // necessary to nest data so that keys represent each vehicle category
    .key(d=>d.count)
    .entries(res)

    // APPEND MULTIPLE LINES //
    var lines = svg.append('g')
    .attr('class', 'lines')

    glines = lines.selectAll('.line-group')
    .data(res_nested).enter()
    .append('g')
    .attr('class', 'line-group')

    glines
    .append('path')
    .attr('class', 'line')
    .attr('d', d => line(d.values))
    .style('stroke', (d, i) => color(i))
    .style('fill', 'none')
    .style('opacity', lineOpacity)
    .style('stroke-width', lineStroke)


    // APPEND CIRCLE MARKERS //
    //var gcircle = lines.selectAll("circle-group")
    //.data(res_nested).enter()
    //.append("g")
    //.attr('class', 'circle-group')

    //gcircle.selectAll("circle")
    //.data(d => d.values).enter()
    //.append("g")
    //.attr("class", "circle")
    //.append("circle")
    //.attr("cx", d => xScale(d.date))
    //.attr("cy", d => yScale(d.premium))
    //.attr("r", 2)

    // CREATE HOVER TOOLTIP WITH VERTICAL LINE //
    tooltip = d3.select("#chart").append("div")
    .attr('id', 'tooltip')
    .style('position', 'absolute')
    .style("background-color", "#D3D3D3")
    .style('padding', 6)
    .style('display', 'none')

    mouseG = svg.append("g")
    .attr("class", "mouse-over-effects");

    mouseG.append("path") // create vertical line to follow mouse
    .attr("class", "mouse-line")
    .style("stroke", "#A9A9A9")
    .style("stroke-width", lineStroke)
    .style("opacity", "0");

    var lines = document.getElementsByClassName('line');

    var mousePerLine = mouseG.selectAll('.mouse-per-line')
    .data(res_nested)
    .enter()
    .append("g")
    .attr("class", "mouse-per-line");

    mousePerLine.append("circle")
    .attr("r", 4)
    .style("stroke", function (d) {
    return color(d.key)
    })
    .style("fill", "none")
    .style("stroke-width", lineStroke)
    .style("opacity", "0");

    mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas
    .attr('width', width)
    .attr('height', height)
    .attr('fill', 'none')
    .attr('pointer-events', 'all')
    .on('mouseout', function () { // on mouse out hide line, circles and text
    d3.select(".mouse-line")
    .style("opacity", "0");
    d3.selectAll(".mouse-per-line circle")
    .style("opacity", "0");
    d3.selectAll(".mouse-per-line text")
    .style("opacity", "0");
    d3.selectAll("#tooltip")
    .style('display', 'none')

    })
    .on('mouseover', function () { // on mouse in show line, circles and text
    d3.select(".mouse-line")
    .style("opacity", "1");
    d3.selectAll(".mouse-per-line circle")
    .style("opacity", "1");
    d3.selectAll("#tooltip")
    .style('display', 'block')
    })
    .on('mousemove', function () { // update tooltip content, line, circles and text when mouse moves
    var mouse = d3.mouse(this)

    d3.selectAll(".mouse-per-line")
    .attr("transform", function (d, i) {
    var xDate = xScale.invert(mouse[0]) // use 'invert' to get date corresponding to distance from mouse position relative to svg
    var bisect = d3.bisector(function (d) { return d.date; }).left // retrieve row index of date on parsed csv
    var idx = bisect(d.values, xDate);

    d3.select(".mouse-line")
    .attr("d", function () {
    var data = "M" + xScale(d.values[idx].date) + "," + (height);
    data += " " + xScale(d.values[idx].date) + "," + 0;
    return data;
    });
    return "translate(" + xScale(d.values[idx].date) + "," + yScale(d.values[idx].count) + ")";

    });

    updateTooltipContent(mouse, res_nested)

    })

    }

    function updateTooltipContent(mouse, res_nested) {

    sortingObj = []
    res_nested.map(d => {
    var xDate = xScale.invert(mouse[0])
    var bisect = d3.bisector(function (d) { return d.date; }).left
    var idx = bisect(d.values, xDate)
    sortingObj.push({key: d.values[idx].count, count: d.values[idx].count, year: d.values[idx].date.getFullYear(), month: monthNames[d.values[idx].date.getMonth()]})
    })

    sortingObj.sort(function(x, y){
    return d3.descending(x.count, y.count);
    })

    var sortingArr = sortingObj.map(d=> d.key)

    var res_nested1 = res_nested.slice().sort(function(a, b){
    return sortingArr.indexOf(a.key) - sortingArr.indexOf(b.key) // rank vehicle category based on price of count
    })

    tooltip.html(sortingObj[0].month + "-" + sortingObj[0].year)
    .style('display', 'block')
    .style('left', d3.event.pageX + 20)
    .style('top', d3.event.pageY - 20)
    .style('font-size', 11.5)
    .selectAll()
    .data(res_nested1).enter() // for each vehicle category, list out name and price of count
    .append('div')
    .style('color', d => {
    return color(d.key)
    })
    .style('font-size', 10)
    .html(d => {
    var xDate = xScale.invert(mouse[0])
    var bisect = d3.bisector(function (d) { return d.date; }).left
    var idx = bisect(d.values, xDate)
    return d.key.substring(0, 3) + " " + d.key.slice(-1) + d.values[idx].count
    })
    }


    </script>
    </body>
    </html>