Search code examples
jqueryd3.jschartsmultiline

How i can draw additional x-axis on point 0 of y-axis in d3 graph


I want to draw additional line on x-axis which appear at 0 point in y-axis. chart is created on dynamic values so can not provide fixed position. It requires to read the position of x-axis where point 0 appears then draw line. We can not bisect line as 0 position can be anywhere in line not specifically in middle.

Code for current graph is added below

Current graph:

current graph

Expected graph:

expected graph

var data = [{
  "ErrorMessage": null,
  "Ymin": -30,
  "Ymax": 40,
  "Xmin": "2021-04-01 00:00:00",
  "Xmax": "2021-04-08 00:00:00",
  "Points": [{
      Date: "2021-04-01 06:59:06",
      Value: 3.43,
      Label: "2021-04-01 06:59:06",
      data: "BOX"
    },
    {
      Date: "2021-04-02 07:02:05",
      Value: -18.43,
      Label: "2021-04-02 07:02:05",
      data: "BOX"
    },
    {
      Date: "2021-04-03 07:12:06",
      Value: 5.43,
      Label: "2021-04-03 07:12:06",
      data: "BOX"
    },
    {
      Date: "2021-04-04 07:16:07",
      Value: 20.43,
      Label: "2021-04-04 07:16:07",
      data: "BOX"
    },
    {
      Date: "2021-04-03 05:12:20",
      Value: 3.43,
      Label: "2021-04-03 05:12:20",
      data: "Ball"
    },
    {
      Date: "2021-04-05 06:59:06",
      Value: 3.43,
      Label: "2021-04-05 06:59:06",
      data: "Ball"
    },
    {
      Date: "2021-04-06 07:02:05",
      Value: 18.43,
      Label: "2021-04-06 07:02:05",
      data: "Ball"
    },
    {
      Date: "2021-04-07 07:12:06",
      Value: 5.43,
      Label: "2021-04-07 07:12:06",
      data: "Ball"
    },
    {
      Date: "2021-04-07 07:16:07",
      Value: -20.43,
      Label: "2021-04-07 07:16:07",
      data: "BallC"
    }
  ]
}];

function loadChart(data) {
  var points = parseData(data.Points);
  chartDataLoaded(points[0], "#chart", "#my_dataviz", "#metric-tooltip", "V")
}

function chartDataLoaded(data, chartName, legendName, toolTipName, chartTitle, axisTitle) {
  var points = parseData(data.Points);
  drawMultilineData(points, data.Xmin, data.Xmax, data.Ymin, data.Ymax, chartName, legendName, toolTipName, chartTitle, axisTitle);
}

function chartDataFailed(data) {
  alert("Failed to load chart data");
}

function parseData(points) {
  var timeParser = d3.timeParse("%Y-%m-%d %H:%M:%S");

  var arr = [];
  for (var i in points) {
    arr.push({
      date: timeParser(points[i].Date), // Date
      value: points[i].Value, // Convert string to number
      label: points[i].Label,
      name: points[i].data
    });
  }
  console.log(arr);
  var expensesByName = d3.nest()
    .key(function(d) {
      return d.name;
    })
    .entries(arr);
  console.log(expensesByName);
  return expensesByName;
}


chartDataLoaded(data[0], "#chart", "#my_dataviz", "#metric-tooltip", "test", "V")

function drawMultilineData(data, xmin, xmax, ymin, ymax, chartName, legendName, toolTipName, chartTitle, axisTitle) {

  //#region define outline and parameter
  var width = 500;
  var height = 300;
  var margin = 100;

  var duration = 250;

  var lineOpacity = "0.25";
  var lineOpacityHover = "0.85";
  var otherLinesOpacityHover = "0.1";
  var lineStroke = "2px";
  var lineStrokeHover = "3px";

  var circleOpacity = '0.85';
  var circleOpacityOnLineHover = "0.25"
  var circleRadius = 4;
  var circleRadiusHover = 6;

  //#endregion

  //#region get user defined color or random
  var color = d3.scaleOrdinal(d3.schemeCategory10);
  var colors = {
    
  };
  //#endregion

  //time parser for min and max
  var timeParser = d3.timeParse("%Y-%m-%d %H:%M:%S");

  //#region create axis and scale 
  var xScale = d3.scaleTime().rangeRound([0, width]).domain([timeParser(xmin), timeParser(xmax)]);
  var yScale = d3.scaleLinear().rangeRound([height, 0]).domain([ymin, ymax]);
  var xAxis = d3.axisBottom(xScale).ticks(8);
  var yAxis = d3.axisLeft(yScale);


  /* Add SVG */
  var svg = d3.select(chartName).append("svg")
    .attr("width", (width + margin + 100) + "px") //added to increase x axis to edge
    .attr("height", (height + margin + 100) + "px") //added to increase y axis to edge
    .append('g')
    .attr("transform", `translate(${margin}, ${margin})`);

  svg.append("g")
    .attr("class", "x axis")
    .attr('transform', 'translate(0, ' + height + ')') //to start x-axis point
    .call(xAxis)
    .selectAll("text")
    .attr("transform", "translate(-10,10)rotate(-45)")
    .style("text-anchor", "end")

  svg.append("g")
    .attr("class", "y axis")
    .call(yAxis)
    .append('text')
    .attr("y", 150)
    .attr("x", -30)
    .style("font-size", "14px")
    .style("font-weight", "bold")
    .attr("fill", "#000")
    .text(axisTitle)
  //#endregion

  
  //#region create lines

  /* Add line into SVG */
  var line = d3.line()
    .x(d => xScale(d.date))
    .y(d => yScale(d.value));


  let lines = svg.append('g')
    .attr('class', 'lines');

  lines.selectAll('.line-group')
    .data(data).enter()
    .append('g')
    .attr('class', 'line-group')
    .on("mouseover", function(d, i) {
      svg.select(".title-text").remove();
      svg.append("text")
        .attr("class", "title-text")
        .style("fill", (colors[d.key] === undefined) ? color(d.key) : colors[d.key])
        .text(d.key)
        .attr("text-anchor", "middle")
        .attr("x", (width - margin) / 2)
        .attr("y", 5);
    })
    .on("mouseout", function(d) {
      svg.select(".title-text").remove();
    })
    .append('path')
    .attr('class', 'line')
    .attr('d', d => line(d.values))
    .style('stroke', (d, i) => (colors[d.key] === undefined) ? color(d.key) : colors[d.key])
    .style('opacity', lineOpacity)
    .on("mouseover", function(d) {
      d3.selectAll('.line')
        .style('opacity', otherLinesOpacityHover);
      d3.selectAll('.circle')
        .style('opacity', circleOpacityOnLineHover);
      d3.select(this)
        .style('opacity', lineOpacityHover)
        .style("stroke-width", lineStrokeHover)
        .style("cursor", "pointer");
    })
    .on("mouseout", function(d) {
      d3.selectAll(".line")
        .style('opacity', lineOpacity);
      d3.selectAll('.circle')
        .style('opacity', circleOpacity);
      d3.select(this)
        .style("stroke-width", lineStroke)
        .style("cursor", "none");
    });

  //#endregion
  
  //#region create circle
  /* Add circles in the line */
  lines.selectAll("circle-group")
    .data(data).enter()
    .append("g")
    .style("fill", (d, i) => (colors[d.key] === undefined) ? color(d.key) : colors[d.key])
    .selectAll("circle")
    .data(d => d.values).enter()
    .append("g")
    .attr("class", "circle")
    .on("mouseover", function(d) {
      d3.select(this)
        .style("cursor", "pointer")
        .append("text")
        .attr("class", "text")
        .attr("x", d => xScale(d.date) + 5)
        .attr("y", d => yScale(d.value) - 10);
      return tooltip2.style("visibility", "visible");
    })
    .on("mousemove", function(d) {
      return tooltip2.style("left", (d3.mouse(this)[0]) + "px").style("top", (d3.mouse(this)[1]) + "px")
        .html("<span>" + d.label + "<br/>" + d.value + "</span>");
    })
    .on("mouseout", function(d) {
      d3.select(this)
        .style("cursor", "none")
        .transition()
        .duration(duration)
        .selectAll(".text").remove();
      return tooltip2.style("visibility", "hidden");
    })
    .append("circle")
    .attr("cx", d => xScale(d.date))
    .attr("cy", d => yScale(d.value))
    .attr("r", circleRadius)
    .style('opacity', circleOpacity)
    .on("mouseover", function(d) {
      d3.select(this)
        .transition()
        .duration(duration)
        .attr("r", circleRadiusHover);
    })
    .on("mouseout", function(d) {
      d3.select(this)
        .transition()
        .duration(duration)
        .attr("r", circleRadius);
    });

  //#endregion 
}
svg {
  font-family: Sans-Serif, Arial;
}

.line {
  stroke-width: 2;
  fill: none;
}

.axis path {
  stroke: black;
}

.text {
  font-size: 12px;
}

.title-text {
  font-size: 12px;
}
<html>

<head>
  <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-+YQ4JLhjyBLPDQt//I+STsc9iw4uQqACwlvpslubQzn4u2UU2UFM80nGisd026JF" crossorigin="anonymous"></script>
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
</head>

<body>
  <div class="col-md-12 row">
    <div class="col-md-8">
      <div id="chart"></div>
    </div>
  </div>
</body>
<script src="test.js"></script>

</html>


Solution

  • Add the following code:

    const yZero = yScale(0);
    svg.append('line')
      .attr('x1', 0)
      .attr('x2', width)
      .attr('y1', yZero)
      .attr('y2', yZero)
      .style('stroke', '#000');
    

    var data = [{
      "ErrorMessage": null,
      "Ymin": -30,
      "Ymax": 40,
      "Xmin": "2021-04-01 00:00:00",
      "Xmax": "2021-04-08 00:00:00",
      "Points": [{
          Date: "2021-04-01 06:59:06",
          Value: 3.43,
          Label: "2021-04-01 06:59:06",
          data: "BOX"
        },
        {
          Date: "2021-04-02 07:02:05",
          Value: -18.43,
          Label: "2021-04-02 07:02:05",
          data: "BOX"
        },
        {
          Date: "2021-04-03 07:12:06",
          Value: 5.43,
          Label: "2021-04-03 07:12:06",
          data: "BOX"
        },
        {
          Date: "2021-04-04 07:16:07",
          Value: 20.43,
          Label: "2021-04-04 07:16:07",
          data: "BOX"
        },
        {
          Date: "2021-04-03 05:12:20",
          Value: 3.43,
          Label: "2021-04-03 05:12:20",
          data: "Ball"
        },
        {
          Date: "2021-04-05 06:59:06",
          Value: 3.43,
          Label: "2021-04-05 06:59:06",
          data: "Ball"
        },
        {
          Date: "2021-04-06 07:02:05",
          Value: 18.43,
          Label: "2021-04-06 07:02:05",
          data: "Ball"
        },
        {
          Date: "2021-04-07 07:12:06",
          Value: 5.43,
          Label: "2021-04-07 07:12:06",
          data: "Ball"
        },
        {
          Date: "2021-04-07 07:16:07",
          Value: -20.43,
          Label: "2021-04-07 07:16:07",
          data: "BallC"
        }
      ]
    }];
    
    function loadChart(data) {
      var points = parseData(data.Points);
      chartDataLoaded(points[0], "#chart", "#my_dataviz", "#metric-tooltip", "V")
    }
    
    function chartDataLoaded(data, chartName, legendName, toolTipName, chartTitle, axisTitle) {
      var points = parseData(data.Points);
      drawMultilineData(points, data.Xmin, data.Xmax, data.Ymin, data.Ymax, chartName, legendName, toolTipName, chartTitle, axisTitle);
    }
    
    function chartDataFailed(data) {
      alert("Failed to load chart data");
    }
    
    function parseData(points) {
      var timeParser = d3.timeParse("%Y-%m-%d %H:%M:%S");
    
      var arr = [];
      for (var i in points) {
        arr.push({
          date: timeParser(points[i].Date), // Date
          value: points[i].Value, // Convert string to number
          label: points[i].Label,
          name: points[i].data
        });
      }
      console.log(arr);
      var expensesByName = d3.nest()
        .key(function(d) {
          return d.name;
        })
        .entries(arr);
      console.log(expensesByName);
      return expensesByName;
    }
    
    
    chartDataLoaded(data[0], "#chart", "#my_dataviz", "#metric-tooltip", "test", "V")
    
    function drawMultilineData(data, xmin, xmax, ymin, ymax, chartName, legendName, toolTipName, chartTitle, axisTitle) {
    
      //#region define outline and parameter
      var width = 500;
      var height = 300;
      var margin = 100;
    
      var duration = 250;
    
      var lineOpacity = "0.25";
      var lineOpacityHover = "0.85";
      var otherLinesOpacityHover = "0.1";
      var lineStroke = "2px";
      var lineStrokeHover = "3px";
    
      var circleOpacity = '0.85';
      var circleOpacityOnLineHover = "0.25"
      var circleRadius = 4;
      var circleRadiusHover = 6;
    
      //#endregion
    
      //#region get user defined color or random
      var color = d3.scaleOrdinal(d3.schemeCategory10);
      var colors = {
        
      };
      //#endregion
    
      //time parser for min and max
      var timeParser = d3.timeParse("%Y-%m-%d %H:%M:%S");
    
      //#region create axis and scale 
      var xScale = d3.scaleTime().rangeRound([0, width]).domain([timeParser(xmin), timeParser(xmax)]);
      var yScale = d3.scaleLinear().rangeRound([height, 0]).domain([ymin, ymax]);
      var xAxis = d3.axisBottom(xScale).ticks(8);
      var yAxis = d3.axisLeft(yScale);
    
    
      /* Add SVG */
      var svg = d3.select(chartName).append("svg")
        .attr("width", (width + margin + 100) + "px") //added to increase x axis to edge
        .attr("height", (height + margin + 100) + "px") //added to increase y axis to edge
        .append('g')
        .attr("transform", `translate(${margin}, ${margin})`);
    
      svg.append("g")
        .attr("class", "x axis")
        .attr('transform', 'translate(0, ' + height + ')') //to start x-axis point
        .call(xAxis)
        .selectAll("text")
        .attr("transform", "translate(-10,10)rotate(-45)")
        .style("text-anchor", "end")
    
      svg.append("g")
        .attr("class", "y axis")
        .call(yAxis)
        .append('text')
        .attr("y", 150)
        .attr("x", -30)
        .style("font-size", "14px")
        .style("font-weight", "bold")
        .attr("fill", "#000")
        .text(axisTitle)
      //#endregion
    
      
      //#region create lines
    
      /* Add line into SVG */
      var line = d3.line()
        .x(d => xScale(d.date))
        .y(d => yScale(d.value));
    
      const yZero = yScale(0);
      svg.append('line')
        .attr('x1', 0)
        .attr('x2', width)
        .attr('y1', yZero)
        .attr('y2', yZero)
        .style('stroke', '#000');
    
      let lines = svg.append('g')
        .attr('class', 'lines');
    
      lines.selectAll('.line-group')
        .data(data).enter()
        .append('g')
        .attr('class', 'line-group')
        .on("mouseover", function(d, i) {
          svg.select(".title-text").remove();
          svg.append("text")
            .attr("class", "title-text")
            .style("fill", (colors[d.key] === undefined) ? color(d.key) : colors[d.key])
            .text(d.key)
            .attr("text-anchor", "middle")
            .attr("x", (width - margin) / 2)
            .attr("y", 5);
        })
        .on("mouseout", function(d) {
          svg.select(".title-text").remove();
        })
        .append('path')
        .attr('class', 'line')
        .attr('d', d => line(d.values))
        .style('stroke', (d, i) => (colors[d.key] === undefined) ? color(d.key) : colors[d.key])
        .style('opacity', lineOpacity)
        .on("mouseover", function(d) {
          d3.selectAll('.line')
            .style('opacity', otherLinesOpacityHover);
          d3.selectAll('.circle')
            .style('opacity', circleOpacityOnLineHover);
          d3.select(this)
            .style('opacity', lineOpacityHover)
            .style("stroke-width", lineStrokeHover)
            .style("cursor", "pointer");
        })
        .on("mouseout", function(d) {
          d3.selectAll(".line")
            .style('opacity', lineOpacity);
          d3.selectAll('.circle')
            .style('opacity', circleOpacity);
          d3.select(this)
            .style("stroke-width", lineStroke)
            .style("cursor", "none");
        });
    
      //#endregion
      
      //#region create circle
      /* Add circles in the line */
      lines.selectAll("circle-group")
        .data(data).enter()
        .append("g")
        .style("fill", (d, i) => (colors[d.key] === undefined) ? color(d.key) : colors[d.key])
        .selectAll("circle")
        .data(d => d.values).enter()
        .append("g")
        .attr("class", "circle")
        .on("mouseover", function(d) {
          d3.select(this)
            .style("cursor", "pointer")
            .append("text")
            .attr("class", "text")
            .attr("x", d => xScale(d.date) + 5)
            .attr("y", d => yScale(d.value) - 10);
          return tooltip2.style("visibility", "visible");
        })
        .on("mousemove", function(d) {
          return tooltip2.style("left", (d3.mouse(this)[0]) + "px").style("top", (d3.mouse(this)[1]) + "px")
            .html("<span>" + d.label + "<br/>" + d.value + "</span>");
        })
        .on("mouseout", function(d) {
          d3.select(this)
            .style("cursor", "none")
            .transition()
            .duration(duration)
            .selectAll(".text").remove();
          return tooltip2.style("visibility", "hidden");
        })
        .append("circle")
        .attr("cx", d => xScale(d.date))
        .attr("cy", d => yScale(d.value))
        .attr("r", circleRadius)
        .style('opacity', circleOpacity)
        .on("mouseover", function(d) {
          d3.select(this)
            .transition()
            .duration(duration)
            .attr("r", circleRadiusHover);
        })
        .on("mouseout", function(d) {
          d3.select(this)
            .transition()
            .duration(duration)
            .attr("r", circleRadius);
        });
    
      //#endregion 
    }
    svg {
      font-family: Sans-Serif, Arial;
    }
    
    .line {
      stroke-width: 2;
      fill: none;
    }
    
    .axis path {
      stroke: black;
    }
    
    .text {
      font-size: 12px;
    }
    
    .title-text {
      font-size: 12px;
    }
    <html>
    
    <head>
      <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">
      <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-+YQ4JLhjyBLPDQt//I+STsc9iw4uQqACwlvpslubQzn4u2UU2UFM80nGisd026JF" crossorigin="anonymous"></script>
      <script src="https://d3js.org/d3.v4.min.js"></script>
      <script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
    </head>
    
    <body>
      <div class="col-md-12 row">
        <div class="col-md-8">
          <div id="chart"></div>
        </div>
      </div>
    </body>
    <script src="test.js"></script>
    
    </html>