5
\$\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?

asked Feb 28, 2017 at 18:26
\$\endgroup\$

1 Answer 1

3
\$\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.

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

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.