Attempting to generate a line graph that includes circle tooltips

I am attempting to develop an interactive line chart with a circle tooltip similar to the one showcased in this example.

Below is my code snippet: HTML

<head>
  <style>
    circle {
      fill: steelblue;
    }

    body {
      font: 12px Arial;
    }

    path {
      stroke: steelblue;
      stroke-width: 2;
      fill: none;
    }

    .axis path,
    .axis line {
      fill: none;
      stroke: grey;
      stroke-width: 1;
      shape-rendering: crispEdges;
    }

    div.tooltip {
      position: absolute;
      text-align: center;
      width: 80px;
      height: 64px;
      padding: 2px;
      font: 14px sans-serif;
      color: black;
      background: lightsteelblue;
      border: 0px;
      border-radius: 8px;
      pointer-events: none;
    }

    .overlay {
      fill: none;
      pointer-events: all;
    }

    .focus circle {
      fill: #F1F3F3;
      stroke: #6F257F;
      stroke-width: 5px;
    }

    .hover-line {
      stroke: #6F257F;
      stroke-width: 2px;
      stroke-dasharray: 3, 3;
    }
  </style>

</head>

<body>
  <svg class='line-chart2'></svg>
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="./regression.js"></script>

</body>

regression.js

var gdp = [387.65, 410.32, 415.73, 452.69, 462.14,
  478.96, 508.06, 599.59, 699.68, 808.90,
  920.31, 1201.11, 1186.95, 1323.94, 1656.61,
  1823.04, 1827.63, 1856.72, 2039.12, 2102.39,
  2274.22, 2600.81
]; //y or GDP of India
var years = ['1996', '1997', '1998', '1999', '2000', '2001', '2002', '2003', '2004', '2005', '2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013', '2014', '2015', '2016', '2017'];

var data_gdp = []
for (i = 0; i < years.length; i++) {
  data_gdp.push({
    year: years[i],
    value: gdp[i]
  })
}

function drawChart_gdp(data, class_name) {
  var svgWidth = 1200,
    svgHeight = 400;
  var margin = {
    top: 60,
    right: 60,
    bottom: 60,
    left: 60
  };
  var width = svgWidth - margin.left - margin.right;
  var height = svgHeight - margin.top - margin.bottom;
  var svg = d3.select(class_name)
    .attr("width", svgWidth)
    .attr("height", svgHeight);
  var bisectDate = d3.bisector(function(d) {
    return d.year;
  }).left;
  var g = svg.append("g")
    .attr("transform",
      "translate(" + margin.left + "," + margin.top + ")"
    );
  var x = d3.scaleTime().range([0, width]);
  var y = d3.scaleLinear().rangeRound([height, 0]);

  var line = d3.line()
    .x(function(d) {
      return x(new Date(parseInt(d.year), 0))
    })
    .y(function(d) {
      return y(d.value)
    })
  x.domain(d3.extent(data, function(d) {
    return new Date(parseInt(d.year), 0);
  }));
  y.domain(d3.extent(data, function(d) {
    return d.value
  }));

  g.append("g")
    .attr("transform", "translate(0," + height + ")")
    .call(d3.axisBottom(x))
    .append("text")
    .attr("fill", "#000")
    .text("Year")
    .attr("dy", "1.90em")
    .attr("y", 5)
    .attr("x", 500)
    .attr("font-size", "20px")
    .select(".domain")
    .remove();

  g.append("g")
    .call(d3.axisLeft(y))
    .append("text")
    .attr("fill", "#000")
    .attr("transform", "rotate(-90)")
    .attr("y", -80)
    .attr("x", -55)
    .attr("dy", "1.90em")
    .attr("text-anchor", "center")
    .attr("font-size", "20px")
    .text("GDP ($)")

  g.append("path")
    .datum(data)
    .attr("fill", "none")
    .attr("stroke", "steelblue")
    .attr("stroke-linejoin", "round")
    .attr("stroke-linecap", "round")
    .attr("stroke-width", 1.5)
    .attr("d", line);

  var focus = g.append("g")
    .attr("class", "focus")
    .style("display", "none");

  focus.append("line")
    .attr("class", "x-hover-line hover-line")
    .attr("y1", 0)
    .attr("y2", height);

  focus.append("line")
    .attr("class", "y-hover-line hover-line")
    .attr("x1", width)
    .attr("x2", width);

  focus.append("circle")
    .attr("r", 7.5);

  focus.append("text")
    .attr("x", 15)
    .attr("dy", ".31em");

  svg.append("rect")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
    .attr("class", "overlay")
    .attr("width", width)
    .attr("height", height)
    .on("mouseover", function() {
      focus.style("display", null);
    })
    .on("mouseout", function() {
      focus.style("display", "none");
    })
    .on("mousemove", function() { //potential issue in this function
      var x0 = d3.timeFormat("%Y")(x.invert(d3.mouse(this)[0])),
        i = bisectDate(data, x0, 1);
      d0 = data[i - 1],
        d1 = data[i],
        d = x0 - d0.year > d1.year - x0 ? d1 : d0;
      focus.attr("transform", "translate(" + x(d.year) + "," + y(d.value) + ")");
      focus.select("text").text(function() {
        return d.value;
      });
      focus.select(".x-hover-line").attr("y2", height - y(d.value));
      focus.select(".y-hover-line").attr("x2", width + width);
    });

}
drawChart_gdp(data_gdp, '.line-chart2');

I have a suspicion that the example pulls data from a JSON file while I fetching it from an array, which may be causing some issues due to differences in the nature of the data. My objective remains to establish a circle tool that displays values on the y-axis.

Answer №1

The issue you're currently encountering is quite the opposite of what you faced in your previous inquiry: previously, you were mishandling date objects as if they were strings.

Now, it seems that you are treating strings as though they are date objects. Specifically, in your dataset, the year is simply a string, such as "1996".

Consequently, this snippet:

focus.attr("transform", "translate(" + x(d.year) + "," + y(d.value) + ")");

Should be adjusted to:

focus.attr("transform", "translate(" + x(d3.timeParse("%Y")(d.year)) + "," + y(d.value) + ")"); 
//conducting a date parsing here----------------------^

Below is the revised code incorporating this change:

var gdp = [387.65, 410.32, 415.73, 452.69, 462.14,
  478.96, 508.06, 599.59, 699.68, 808.90,
  920.31, 1201.11, 1186.95, 1323.94, 1656.61,
  1823.04, 1827.63, 1856.72, 2039.12, 2102.39,
  2274.22, 2600.81
]; //y or GDP of India
var years = ['1996', '1997', '1998', '1999', '2000', '2001', '2002', '2003', '2004', '2005', '2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013', '2014', '2015', '2016', '2017'];

var data_gdp = []
for (i = 0; i < years.length; i++) {
  data_gdp.push({
    year: years[i],
    value: gdp[i]
  })
}

function drawChart_gdp(data, class_name) {
  var svgWidth = 1200,
    svgHeight = 400;
  var margin = {
    top: 60,
    right: 60,
    bottom: 60,
    left: 60
  };
  var width = svgWidth - margin.left - margin.right;
  var height = svgHeight - margin.top - margin.bottom;
  var svg = d3.select(class_name)
    .attr("width", svgWidth)
    .attr("height", svgHeight);
  var bisectDate = d3.bisector(function(d) {
    return d.year;
  }).left;
  var g = svg.append("g")
    .attr("transform",
      "translate(" + margin.left + "," + margin.top + ")"
    );
  var x = d3.scaleTime().range([0, width]);
  var y = d3.scaleLinear().rangeRound([height, 0]);

  var line = d3.line()
    .x(function(d) {
      return x(new Date(parseInt(d.year), 0))
    })
    .y(function(d) {
      return y(d.value)
    })
  x.domain(d3.extent(data, function(d) {
    return new Date(parseInt(d.year), 0);
  }));
  y.domain(d3.extent(data, function(d) {
    return d.value
  }));

  g.append("g")
    .attr("transform", "translate(0," + height + ")")
    .call(d3.axisBottom(x))
    .append("text")
    .attr("fill", "#000")
    .text("Year")
    .attr("dy", "1.90em")
    .attr("y", 5)
    .attr("x", 500)
    .attr("font-size", "20px")
    .select(".domain")
    .remove();

  g.append("g")
    .call(d3.axisLeft(y))
    .append("text")
    .attr("fill", "#000")
    .attr("transform", "rotate(-90)")
    .attr("y", -80)
    .attr("x", -55)
    .attr("dy", "1.90em")
    .attr("text-anchor", "center")
    .attr("font-size", "20px")
    .text("GDP ($)")

  g.append("path")
    .datum(data)
    .attr("fill", "none")
    .attr("stroke", "steelblue")
    .attr("stroke-linejoin", "round")
    .attr("stroke-linecap", "round")
    .attr("stroke-width", 1.5)
    .attr("d", line);

  var focus = g.append("g")
    .attr("class", "focus")
    .style("display", "none");

  focus.append("line")
    .attr("class", "x-hover-line hover-line")
    .attr("y1", 0)
    .attr("y2", height);

  focus.append("line")
    .attr("class", "y-hover-line hover-line")
    .attr("x1", width)
    .attr("x2", width);

  focus.append("circle")
    .attr("r", 7.5);

  focus.append("text")
    .attr("x", 15)
    .attr("dy", ".31em");

  svg.append("rect")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
    .attr("class", "overlay")
    .attr("width", width)
    .attr("height", height)
    .on("mouseover", function() {
      focus.style("display", null);
    })
    .on("mouseout", function() {
      focus.style("display", "none");
    })
    .on("mousemove", function() { //issue in this function
      var x0 = d3.timeFormat("%Y")(x.invert(d3.mouse(this)[0])),
        i = bisectDate(data, x0, 1);
      d0 = data[i - 1],
        d1 = data[i],
        d = x0 - d0.year > d1.year - x0 ? d1 : d0;
      focus.attr("transform", "translate(" + x(d3.timeParse("%Y")(d.year)) + "," + y(d.value) + ")");
      focus.select("text").text(function() {
        return d.value;
      });
      focus.select(".x-hover-line").attr("y2", height - y(d.value));
      focus.select(".y-hover-line").attr("x2", width + width);
    });

}
drawChart_gdp(data_gdp, '.line-chart2');
<head>
  <style>
    circle {
      fill: steelblue;
    }

    body {
      font: 12px Arial;
    }

    path {
      stroke: steelblue;
      stroke-width: 2;
      fill: none;
    }

    .axis path,
    .axis line {
      fill: none;
      stroke: grey;
      stroke-width: 1;
      shape-rendering: crispEdges;
    }

    div.tooltip {
      position: absolute;
      text-align: center;
      width: 80px;
      height: 64px;
      padding: 2px;
      font: 14px sans-serif;
      color: black;
      background: lightsteelblue;
      border: 0px;
      border-radius: 8px;
      pointer-events: none;
    }

    .overlay {
      fill: none;
      pointer-events: all;
    }

    .focus circle {
      fill: #F1F3F3;
      stroke: #6F257F;
      stroke-width: 5px;
    }

    .hover-line {
      stroke: #6F257F;
      stroke-width: 2px;
      stroke-dasharray: 3, 3;
    }

  </style>

</head>

<body>
  <svg class='line-chart2'></svg>
  <script src="https://d3js.org/d3.v4.min.js"></script>
</body>

It's evident at this point that the complexity arises from having strings within your data array while utilizing a time scale like x. To mitigate this confusion, my recommendation would be to parse the strings within your data array into date objects consistently across your codebase. Treat all data as date objects rather than strings for improved consistency.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The NgModel does not accurately show the chosen value in a dropdown menu that is populated based on the selection of another dropdown

After the component loads, I expect both mainTask and mainJob to be displayed as selections in the dropdown. However, only mainTask is updated while mainJob remains unchanged in the UI. Interestingly, when I manually choose a value from the taskList dropdo ...

angular2 variable turns null during post request, synchronization breakdown

Currently, I am in the process of developing an ecommerce web application using Angular2 and have encountered a issue with saving ordered information in session. addToCart(productId:string,noOfItems:number):void{ let itemCounts; let selectedItems= ...

Tips on eliminating the "other..." choice from the HTML color picker?

https://i.sstatic.net/73PuH.png Is there a way to remove the "Other..." button from this code snippet? This is the HTML code I am working with: The purpose of this code is to change the color of text by selecting colors from a dropdown menu. However, I o ...

Avoid displaying the image when encountering a 404 error, but sometimes a broken image may still appear momentarily

Here is the code snippet I'm currently using: <img ng-show="json.user.picture" ng-src="{{json.user.picture}}" ng-error="json.user.picture = false"> When accessing an image from an external website without permission, a 404 error code is return ...

Include an expand and collapse icon in a hyperlink

I have successfully implemented a functionality whereby clicking a link collapses other hidden names. However, I am now facing a challenge in trying to change an image once the link is clicked. Here is the code snippet that I currently have: $(docu ...

Extract data from an RSS feed and showcase it on an HTML webpage

Having trouble parsing a Yahoo Finance RSS news feed and displaying the information on a webpage. I've tried different methods but can't seem to get it right. Hoping someone out there can assist me with this. I came across a tutorial on how to p ...

Steps for submitting a randomly selected non-repeating variable from a list

My webpage automatically submits a value "val" in the input field when loaded: <form method="post" action="website.com/xxxx"> <input type="text" name="t" style="width:250px" value="val"> <button type="submit"><p>Submit<img src=" ...

Using jQuery each with an asynchronous ajax call may lead to the code executing before the ajax call is completed

In my jQuery script, I utilize an each loop to iterate through values entered in a repeated form field on a Symfony 3 CRM platform. The script includes a $.post function that sends the inputted value to a function responsible for checking database duplicat ...

Guide on building a personalized Print Preview interface with Datatables.Net in Angular

I'm utilizing Datatables.Net within my Angular project and everything is functioning properly. I have successfully added a Print Button and can preview the table contents. However, I am looking to customize it further by adding additional details just ...

Webpack: Live reloading is not functioning properly, however the changes are still successfully compiling

Could someone help me understand why my React application, set up with Webpack hot reload, is not functioning properly? Below is the content of my webpack.config.js: const path = require('path'); module.exports = { mode: 'development&apo ...

Utilizing the map function in Angular while disregarding any null values

I have an array of objects in my dataset. Here's a glimpse of how it is structured: [ { "id": 1, "name": "john", "address": { "number": 42, "street": "High Street"} }, { ...

Utilizing the ampersand as a parent selector within nested selectors

Is it possible that this is not documented or simply not feasible? #parent { #child { width: 75%; .additional_parent_class & { width: 50%; } } } As a result, the code will transform into: .additional_parent_class #parent #chil ...

Webpack 5 is unable to load animated gif files

Hello, I am currently diving into webpack and following the official documentation at https://webpack.js.org/guides/asset-management/#setup. My goal is to incorporate an animated gif using webpack 5. Although the placeholder for the gif is loading, I enco ...

Exploring the world of CSS: accessing style attributes using JavaScript

So I created a basic HTML file with a div and linked it to a CSS file for styling: HTML : <html> <head> <title>Simple Movement</title> <meta charset="UTF-8"> <link rel="stylesheet" type=&qu ...

Creating an HTML table directly from a text string

After crafting a function that creates an html table as a string: tableCode = "<table className='Board'><tbody><tr key="0"><Cell key="0-0" isLit={true} /><Cell key="0-1" isLit={true} / ...

Finding a particular CSS3 Keyframe using JavaScript

Is there a way to detect when an animation reaches a specific keyframe, like 50% or 75%? I attempted the following: element.addEventListener("animationend", AnimationListener, false); However, this only supports animationstart, animationiteration, and a ...

Creating Uniform Heights Across Devices with Responsive Design Strategies

I am struggling with a height consistency issue that I cannot figure out. The problem can be seen in this Fiddle: FIDDLE Here is the code snippet: $(document).ready(function() { if ($(window).width() > 768) { $(window).ready(function() { ...

Customizing Django Allauth - Adding a unique CSS class to form fields

So, in my login.html file I currently have this setup: <form class="login" method="POST" action="{% url 'account_login' %}"> {% csrf_token %} {{form.as_p}} I'm struggling to figure out how to add custom CSS classes and other att ...

Looking for a way to store data in local storage so it remains even after the page is reloaded? I tried adding a local storage function, but it doesn't appear to be

I am currently working on a project involving a work day scheduler and I am facing challenges in saving data within the schedule events, like at 8am, and making sure it persists on reload. The colored time block element that changes as hours pass in the da ...

Issues with submitting data using jQuery AJAX PUT request

I have a PHP script with the following content: if ($_SERVER['REQUEST_METHOD'] === 'PUT') { echo '{ "response": "' . $_REQUEST['id'] . '" }'; } Now, I am trying to use jQuery to make an AJAX request t ...