4
\$\begingroup\$

I have the following data in a csv file called BarData.csv:

Fruit,dt,amount
Apple,12/28/2016,4566
Apple,12/29/2016,5898
Apple,12/30/2016,3694
Apple,12/31/2016,5586
Apple,1/1/2017,4558
Apple,1/2/2017,6696
Apple,1/3/2017,7757
Apple,1/4/2017,8528
Apple,1/5/2017,5543
Apple,1/6/2017,3363
Apple,1/7/2017,5464
Pear,12/25/2017,250
Pear,12/26/2017,669
Pear,12/27/2017,441
Pear,12/28/2017,159
Pear,12/29/2017,357
Pear,12/30/2017,775
Pear,12/31/2017,669

The following html, css, and javascript is in one .html file:

<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <title>BAR SINGLE FUNCTION</title>
    <script src="http://d3js.org/d3.v3.js"></script>
    <style type="text/css">
    #radioDiv {
        top: 45px;
        font-family: verdana;
        font-size: 8px;
        width: 455px;
    }

    #TOPbarChart {
        position: absolute;
        top: 50px;
        left: 30px;
        width: 750px;
        height: 195px;
    }

    .axis--y path,
    .axis--x path {
        display: none;
    }

    .axis--x line,
    .axis--y line {
        stroke: black;
        fill: none;
        stroke-width: 2px
    }

    .yAxis text,
    .xAxis text {
        font: 7pt Verdana;
        stroke: none;
        fill: black;
    }

    .title,
    .titleX {
        font-family: Verdana;
        font-size: 10px;
    }
    </style>
</head>

<body>
    <div id="radioDiv">
        <label>
            <input id="radioFrt" type="radio" name="frt" value="Apple" class="radioB" checked> APPLE
        </label>
        <label>
            <input type="radio" name="frt" value="Pear" class="radioB"> PEAR
        </label>
    </div>
    <div id="TOPbarChart"></div>
    <script type="text/javascript">
    var currentFruit = "Apple";
    var currentColr = "#00a5b6";

    var barDataCSV_Dly = "BarData.csv";

    //
    //
    // radio button
    document.getElementById("radioFrt").checked = true;
    d3.selectAll('input[name="frt"]').on("change", function change() {
        currentFruit = this.value;
        TOPbarChart(currentFruit, currentColr);
    });

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



    // 
    // BASIC SIZING
    // 
    function barChartBasics() {
        var margin = {
                top: 25,
                right: 35,
                bottom: 25,
                left: 70
            },
            width = 550 - margin.left - margin.right,
            height = 155 - margin.top - margin.bottom,
            colorBar = d3.scale.category20(),
            barPaddingFine = 1,
            barPaddingThick = 2;
        return {
            margin: margin,
            width: width,
            height: height,
            colorBar: colorBar,
            barPaddingFine: barPaddingFine,
            barPaddingThick: barPaddingThick
        };
    }


    // create svg element
    var basics = barChartBasics();
    var svg = d3.select("#TOPbarChart")
        .append("svg")
        .attr({
            "width": basics.width + basics.margin.left + basics.margin.right,
            "height": basics.height + basics.margin.top + basics.margin.bottom,
            id: "svgTOPbarChart"
        });

    // create svg  group
    var plot = svg
        .append("g")
        .attr({
            "transform": "translate(" + basics.margin.left + "," + basics.margin.top + ")",
            id: "svgPlotTOPbarChart"
        });

    var axisPadding = 2;
    var leftAxisGroup = svg
        .append('g')
        .attr({
            transform: 'translate(' + (basics.margin.left - axisPadding) + ',' + (basics.margin.top) + ')',
            'class': "yAxis axis--y",
            id: "yAxisGTOPbarChart"
        });

    var bottomAxisGroup = svg
        .append('g')
        .attr({
            'class': "xAxis axis--x",
            id: "xAxisGTOPbarChart"
        });

    var titleTxt = svg.append("text")
        .attr({
            x: basics.margin.left + 12,
            y: 20,
            'class': "title",
            'text-anchor': "start"
        })

    // create scales with ranges
    var xScale = d3.time.scale().range([0, basics.width]);
    var yScale = d3.scale.linear().range([basics.height, 0]);




    function TOPbarChart(
        frt, colorChosen) {

        // get the data
        d3.csv(barDataCSV_Dly, function(rows) {

            TOPbarData = rows.map(function(d) {
                return {
                    "Fruit": d.Fruit,
                    "dt": parseDate(d.dt),
                    "amount": +d.amount
                };
            }).filter(function(row) {
                if (row['Fruit'] == frt) {
                    return true;
                }
            });


            // create domains for the scales
            xScale.domain(d3.extent(TOPbarData, function(d) {
                return d.dt;
            }));
            var amounts = TOPbarData.map(function(d) {
                return d.amount;
            });
            var yMax = d3.max(amounts);
            var yMin = d3.min(amounts);
            var yMinFinal = 0;
            if (yMin < 0) {
                yMinFinal = yMin;
            }
            yScale.domain([yMinFinal, yMax]);


            // introduce the bars
            // var plot = d3.select("#svgPlotTOPbarChart")
            var sel = plot.selectAll("rect")
                .data(TOPbarData);

            sel.enter()
                .append("rect")
                .attr({
                    x: function(d, i) {
                        return xScale(d.dt);
                    },
                    y: function(d) {
                        return yScale(d.amount);
                    },
                    width: (basics.width / TOPbarData.length - basics.barPaddingFine),
                    height: function(d) {
                        return basics.height - yScale(d.amount);
                    },
                    fill: colorChosen,
                    'class': "bar"
                });

            // this little function will create a small ripple affect during transition
            var dlyRipple = function(d, i) {
                return i * 100;
            };
            sel
                .transition()
                .duration(dlyRipple) //1000
                .attr({
                    x: function(d, i) {
                        return xScale(d.dt);
                    },
                    y: function(d) {
                        return yScale(d.amount);
                    },
                    width: (basics.width / TOPbarData.length - basics.barPaddingFine),
                    height: function(d) {
                        return basics.height - yScale(d.amount);
                    },
                    fill: colorChosen
                });

            sel.exit().remove();


            // add/transition y axis - with ticks and tick markers
            var axisY = d3.svg.axis()
                .orient('left')
                .scale(yScale)
                .tickFormat(d3.format("s")) // use abbreviations, e.g. 5M for 5 Million
                .outerTickSize(0);
            leftAxisGroup.transition().duration(1000).call(axisY);

            // add/transition x axis - with ticks and tick markers
            var axisX = d3.svg.axis()
                .orient('bottom')
                .scale(xScale);

            bottomAxisGroup
                .attr({
                    transform: 'translate(' + (basics.margin.left + ((basics.width / TOPbarData.length) / 2)) + ',' + (basics.margin.top + basics.height) + ')',
                })
                .transition().duration(1000).call(axisX.ticks(5));


            titleTxt.text("Daily: last " + TOPbarData.length + " days");
            // console.log(TOPbarData.length)

        });
    }

    //
    //
    //
    //
    TOPbarChart(currentFruit, currentColr);
    //
    //
    //
    //
    </script>
</body>

</html>

In terms of the structure:

In reality my d3 visuals will be more dashboard-orientated so should I get into the habit of seperating the css, js and html into seperate files? If I go down the route is it easy to implement?

In terms of javascript in general:

I realise javascript has some "bad bits" - am I using any of them in my code? and how do I refactor to avoid them?

In terms of the d3:

There might be lots of interaction in the full version e.g. the radio button might have many more options which get pressed many times, should I move the call to the csv d3.csv to another part of the script so that the data is not being read everytime another option is selected?

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

There is something wrong regarding the use of D3 requests.

You asked:

Should I move the call to the csv d3.csv to another part of the script so that the data is not being read everytime another option is selected?

Definitely. The way your code is right now, with d3.csv inside TOPbarChart function (which is called clicking the radio button) makes very little sense to any seasoned D3 programmer, for two reasons:

  1. d3.csv is asynchronous, meaning that the browser has to get the data (sometimes not even in the same server), parse it and then resume the chart creation. You should avoid the best you can putting d3.csv (or d3.json, d3.tsv etc...) inside an event listener for a button, a radio button, a dropdown menu or any other input like those, because the user experience will be very poor.
  2. There is no difference in the CSV file being loaded and parsed. Thus, you are doing a new request again and again to get the same data, just wasting time and resources.

Therefore, you should load and parse the CSV just once. Then, inside the TOPbarChart function, you just need to manipulate the already loaded data the way you want (but take care here: some methods change the original array).

The good news is that the changes you need to do are minimal.

This is a general idea of how your code should look like:

d3.csv("foo.csv", function(data) {

    //data is available here

    d3.select("input").on("change", function() {
        //call TOPbarChart function
        TOPbarChart();
    })

    functionTOPbarChart() {
        //manipulate data here and draw your chart
    }

});

And here is a working Plunker showing your code with those changes: https://plnkr.co/edit/Zbw5begLu0stbOUmtu5e?p=preview

As you can see, the code loads and parses the CSV file only once, manipulating the data according to the inputs.

\$\endgroup\$
1
  • 2
    \$\begingroup\$ as always I appreciate the time you've taken over one of my questions - thanks Gerardo \$\endgroup\$
    – whytheq
    Commented Aug 13, 2017 at 17:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.