user5323957
user5323957

Reputation: 552

How To Align Text Inside SVG In D3.js

I created a visual data with bar chart but I'm having trouble aligning the text "Gross Domestic Product" and "Units: Billions of Dollars Seasonal Adjustment: Seasonally Adjusted, Annual Rate Notes: A Guide to the National Income and Product Accounts of the United States (NIPA) - (http://www.bea.gov/national/pdf/nipaguid.pdf)"

As of now they are over lapping at the top: enter image description here

I want to align "Gross Domestic Product" at the top and "Units: Billions of Dollars Seasonal Adjustment: Seasonally Adjusted Annual Rate Notes: A Guide to the National Income and Product Accounts of the United States (NIPA) - (http://www.bea.gov/national/pdf/nipaguid.pdf)" at the bottom like enter image description here

Here Is My Code Fiddle Link

var url = "https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/GDP-data.json";

//Fetch Data By .$getJSON Method
$.getJSON(url, function (d) {
    var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
    ];
    var data = d.data;
    /*test data by
    console.log(data[0][0]);
    */
    //create Margin
    var margin = { top: 40, right: 20, bottom: 30, left: 50 },
        width = 960 - margin.left - margin.right,
        height = 600 - margin.top - margin.bottom;
    /*
       Define Min & Max Data for Scale
    */
    console.log(d.description);
    var minDate = new Date(data[0][0]);
    var maxDate = new Date(data[d.data.length - 1][0]);
    /*
     test data by 
     console.log(minDate);
     console.log(maxDate);
    */
    /*
    define scale then followed by axis
    */
    // define x and y scales
    // define x and y scales
    var xScale = d3.time.scale().
        domain([minDate, maxDate]).
        range([0, width]);
    var yScale = d3.scale.linear().
        domain([0, d3.max(data, function (d) {
            return d[1];
        })]).
        range([height, 0]);
    // define x axis and y axis
    var xAxis = d3.svg.axis().
        scale(xScale).
        orient("bottom").
        ticks(d3.time.years, 5);
    var yAxis = d3.svg.axis().
        scale(yScale).
        orient("left").
        ticks(10, "");
    var thisDate = new Date(data[0][0]);
    /*
    Create Tooltip
    */
    var toolTip = d3.tip()
      .attr('class', 'd3-tip')
      .offset([-10, 0])
      .html(function (d) {
          return ('<strong>$' + d[1].toLocaleString() + ' Billion</strong><p>' + thisDate.getFullYear() + ' - ' + monthNames[thisDate.getMonth()] + '</p>');
      });
    /*
    create svg element then append height and width and g which act as a container
    */
    var svg = d3.select(".mainContainer").
      attr({
          "width": width + margin.right + margin.left,
          "height": height + margin.top + margin.bottom
      }).
    append("g").
      attr("transform", "translate(" + margin.left + "," + margin.right + ")");

    //call toolTip
    svg.call(toolTip);
    // Draw xAxis
    svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);
    //Draw yAxis
    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("Gross Domestic Product, USA");
    /*
  create bar or bind data
  */
    //bind data
    svg.selectAll(".bar")
      .data(data)
   //enter data
    .enter().
        append("rect")
   //update data
      .attr("class", "bar")
      .attr("x", function (d) { return xScale(new Date(d[0])); })
      .attr("width", Math.ceil(width / data.length))
      .attr("y", function (d) { return yScale(d[1]); })
      .attr("height", function (d) { return height - yScale(d[1]); })
      .on('mouseover', toolTip.show)
      .on('mouseout', toolTip.hide);
    //add description on top and bottom of svg
    svg.
        attr("class", "title").
        append("text").
        html("Gross Domestic Product </br>")
    svg.
        attr("class", "notes").
        append("text").
        text(d.description);
    

});
svg {
  
  margin-left: auto;
  margin-right: auto;
  display: block;
  background-color:antiquewhite;
}
body {
  font: 10px sans-serif;
}

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

.bar:hover {
  fill: orangered ;
}
.d3-tip {
  line-height: 1;
  font-weight: bold;
  padding: 12px;
  background: rgba(0, 0, 0, 0.8);
  color: #fff;
  border-radius: 2px;
}
/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
  box-sizing: border-box;
  display: inline;
  font-size: 10px;
  width: 100%;
  line-height: 1;
  color: rgba(0, 0, 0, 0.8);
  content: "\25BC";
  position: absolute;
  text-align: center;
}

/* Style northward tooltips differently */
.d3-tip.n:after {
  margin: -1px 0 0 0;
  top: 100%;
  left: 0;
}
.notes {
  font-size: 12px;
  font-family: sans-serif;
  color: steelblue;
  padding: 20px;
  text-align: center;
  vertical-align:bottom;
}
.title {
  font-size: 2.5em;
  font-family: sans-serif;
  color: steelblue;
  text-align: center;
  padding: 15px 0px 5px 0px;
}
<!DOCTYPE html>
<html>
<head>
    <title>D3-Zipline: GDP Bar Graph</title>
    <meta charset="utf-8" />
    <link href="../Content/bootstrap-theme.min.css" rel="stylesheet" />
    <link href="../Content/bootstrap.min.css" rel="stylesheet" />
    <script src="../Scripts/d3/d3.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.6.7/d3-tip.min.js"></script>
    <link href="demo.css" rel="stylesheet" />
</head>
<body>
    
    <div class="container-fluid text-center">
        <br /><br />
        <svg class="mainContainer">
        </svg>
    </div>
    <script src="../Scripts/jquery-2.2.1.min.js"></script>
    <script src="../Scripts/bootstrap.min.js"></script>
    <script src="demo.js"></script>
</body>
</html>

Upvotes: 1

Views: 3943

Answers (1)

Henry S
Henry S

Reputation: 3112

There are a few things to address here:

  1. Create an x-axis label that is properly aligned
  2. Split the long x-axis label over multiple lines (it's too long otherwise)
  3. Set the CSS classes correctly for the title, notes, and other text elements

Taking them in that order:

X-axis label under the axis, centered

The simplest way to do this is to append your text between the code where you draw the x-axis and the y-axis.

svg.append("text")    // text label for the x axis
  .attr("class", "notes")
  .attr("transform", "translate(" + width/2 + ")")
  .attr("y",  height + margin.bottom)
  .text(d.description)
  .call(splitLongLabel);    // The next section explains this bit...

Split the long x-axis label over multiple lines

To do this, I've borrowed a little from this answer: https://stackoverflow.com/a/13275930/3442309

It looks like the most sensible place to split the d.description text is at the "-" character: This is how it's done on the screenshot of how you want the chart to look.

To achieve the split, user tspan elements

var insertLinebreaks = function (d) {
    var el = d;    // Get the current element
    var words = d.text().split("-");    // Split text  at the hyphen
    el.text('');
    for (var i = 0; i < words.length; i++) {    // Then build up the tspans
        var tspan = el.append('tspan')
            .attr('x', 0)
            .text(words[i])
            .style("text-anchor", "middle");
        if (i > 0)
            tspan
                .attr('x', 0)
                .attr('dy', '15')
                .style("text-anchor", "middle");
    }
};

To change how and where this label is split, or to make it more generic (eg basing the split on length, rather than a specific character), simply modify how split is defined

Setting CSS classes for text

In your example, the title was not using the title CSS class, so not getting the expected style. The reason for this is the following code at the end of your script:

svg.
    attr("class", "notes").
    append("text").
    text(d.description);

What this was doing was setting the notes style for all text in the svg. More correctly, you should have set the class attribute after appending the text.

Correcting for this, for the title, we now have:

svg.
    //attr("class", "title"). <- Removed from here
    append("text").
    attr("class", "title").    // <- added here
    html("Gross Domestic Product </br>")

and I've added some positioning to the javascript for `title, rather than in the CSS:

    ....
    .attr('x', width / 2)
    .attr('y', 20)
    .style("text-anchor", "middle");

Lastly, I've corrected your CSS, which used color for applying text color, instead of fill, which is the correct way

Here's a Fiddle with all the changes described: https://jsfiddle.net/henbox/nuwzxoz8/1/

Upvotes: 2

Related Questions