Miorita
Miorita

Reputation: 340

Why the maximum variable take the d3.max() value but the minimum one d3.min() does not work properly?

My task is to organize a timeline visualization.

I took the example from - http://bl.ocks.org/bunkat/2338034.

Making necessary modifications I notice that timeBegin variable is equal to "0" and if I try to modify it by introducing:

d3.min(timeSegments, function (d) { 
    return d.segment_start; 
})

The graph starts to enlarge horizontally. Only when the variable timeBegin is 0 the graph is displaying in a correct manner. Where is my wrong minding in D3?

I found another example - https://codepen.io/manglass/pen/MvLBRz, but this cases are slightly complicated for me.

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>RePLICA</title>

  <style type="text/css">
    .chart-table {
      shape-rendering: crispEdges;
    }
    
    .graph-square text {
      font: 10px sans-serif;
    }
    
    div.tooltip-donut {
      position: absolute;
      text-align: center;
      padding: .5rem;
      background: #FFFFFF;
      color: #313639;
      border: 1px solid #313639;
      border-radius: 8px;
      pointer-events: none;
      font-size: 1.3rem;
    }
    
    .brush .extent {
      stroke: gray;
      fill: dodgerblue;
      fill-opacity: .365;
    }
  </style>

</head>

<body>

  <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
  <script type="text/javascript">
    //data
    var lanes = ["Chinese", "Japanese", "Korean", "Moldova"],
      laneLength = lanes.length,
      timeSegments = [{
          "lane": 0,
          "id": "Qin",
          "segment_start": 100,
          "segment_end": 210,
          "flag": false
        },
        {
          "lane": 0,
          "id": "Jin",
          "segment_start": 210,
          "segment_end": 420,
          "flag": true
        },
        {
          "lane": 0,
          "id": "Sui",
          "segment_start": 420,
          "segment_end": 615,
          "flag": false
        },

        {
          "lane": 1,
          "id": "Yamato",
          "segment_start": 300,
          "segment_end": 530,
          "flag": false
        },
        {
          "lane": 1,
          "id": "Asuka",
          "segment_start": 530,
          "segment_end": 700,
          "flag": true
        },
        {
          "lane": 1,
          "id": "Nara",
          "segment_start": 710,
          "segment_end": 800,
          "flag": false
        },
        {
          "lane": 1,
          "id": "Heian",
          "segment_start": 800,
          "segment_end": 1180,
          "flag": true
        },

        {
          "lane": 2,
          "id": "Three Kingdoms",
          "segment_start": 100,
          "segment_end": 670,
          "flag": false
        },
        {
          "lane": 2,
          "id": "North and South States",
          "segment_start": 670,
          "segment_end": 900,
          "flag": true
        },

        {
          "lane": 3,
          "id": "Chisinau",
          "segment_start": 250,
          "segment_end": 600,
          "flag": false
        },
        {
          "lane": 3,
          "id": "Balti",
          "segment_start": 600,
          "segment_end": 900,
          "flag": true
        },
        {
          "lane": 3,
          "id": "Ungheni",
          "segment_start": 920,
          "segment_end": 1380,
          "flag": false
        }

      ],
      timeBegin = 0, // !!! d3.min(timeSegments, function (d) { return d.segment_start; })   ---- Does not work !!!
      timeEnd = d3.max(timeSegments, function(d) {
        return d.segment_end;
      });


    var widthTotal = 1300,
      heightTotal = 500,
      margin = {
        top: 10,
        right: 15,
        bottom: 0,
        left: 100
      },
      widthSVG = widthTotal - margin.right - margin.left,
      heightSVG = heightTotal - margin.top - margin.bottom,
      graphHeight = laneLength * 10 + heightTotal / 3; // - 3 just a coonstant

    // scales
    var scaleX = d3.scaleLinear()
      .domain([timeBegin, timeEnd])
      .range([0, widthSVG]);

    var scaleY = d3.scaleLinear()
      .domain([0, laneLength])
      .range([0, graphHeight]);


    var colorScale = d3.scaleOrdinal(d3.schemeCategory10);

    var chart = d3.select("body")
      .append("svg")
      .attr("width", widthSVG + margin.right + margin.left)
      .attr("height", heightSVG + margin.top + margin.bottom)
      .attr("class", "chart-table");

    var graph = chart.append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
      .attr("width", widthSVG)
      .attr("height", graphHeight)
      .attr("class", "graph-square");


    // Draw the axis
    chart.append("g")
      .attr("transform", "translate(" + margin.left + ", " + (graphHeight + 20) + ")") // This controls the vertical position of the Axis
      .call(d3.axisBottom(scaleX));


    // Delimitation lines
    graph.append("g").selectAll(".laneLines")
      .data(timeSegments)
      .enter().append("line")
      .attr("x1", 0)
      .attr("y1", function(d) {
        return scaleY(d.lane);
      })
      .attr("x2", widthSVG)
      .attr("y2", function(d) {
        return scaleY(d.lane);
      })
      .attr("stroke", "lightgray");

    // Lanes Names display
    graph.append("g").selectAll(".laneText")
      .data(lanes)
      .enter().append("text")
      .text(function(d) {
        return d;
      })
      .attr("x", -margin.right)
      .attr("y", function(d, i) {
        return scaleY(i + .5);
      })
      .attr("dy", ".5ex")
      .attr("text-anchor", "end")
      .attr("class", "laneText");


    // Add DIV for "hover_info"
    var div = d3.select("body").append("div")
      .attr("class", "tooltip-donut")
      .style("opacity", 0);

    // Graph item rects
    graph.append("g").selectAll(".graphItem")
      .data(timeSegments)
      .enter().append("rect")
      .attr("x", function(d) {
        return scaleX(d.segment_start);
      })
      .attr("y", function(d) {
        let shiftVertical = 9;
        if (d.flag) {
          shiftVertical = 0
        };
        return scaleY(d.lane + .5) - shiftVertical;
      })
      .attr("width", function(d) {
        return scaleX(d.segment_end - d.segment_start);
      })
      .attr("height", 10)
      .style("fill", function(d) {
        return colorScale(d.lane);
      })

      // Hover effect
      .on('mouseover', function(d, i) {
        d3.select(this).transition()
          .duration('50')
          .attr('opacity', '.5');

        div.transition()
          .duration(50)
          .style("opacity", 1);

        let hover_info = ("id:" + d.id + "<br/>" + "start:" + d.segment_start + "<br/>" + "end:" + d.segment_end).toString();

        //Makes the new div appear on hover:
        div.html(hover_info)
          .style("left", (d3.event.pageX + 10) + "px")
          .style("top", (d3.event.pageY - 15) + "px");
      })

      .on('mouseout', function(d, i) {
        d3.select(this).transition()
          .duration('50')
          .attr('opacity', '1')

        //Makes the new div disappear:
        div.transition()
          .duration('50')
          .style("opacity", 0);
      });
  </script>

</body>

</html>

Upvotes: 1

Views: 160

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102188

The d3.min function you have does work, that's not the problem. The problem is the math you're using to calculate the width of the rectangles:

.attr("width", function(d) {
    return scaleX(d.segment_end - d.segment_start);
})

As you can see, that only works if the scale starts at 0. For a more dynamic scale, like the one you want, the width should be:

.attr("width", function(d) {
    return scaleX(d.segment_end - d.segment_start + scaleX.domain()[0]);
})

That is, you add the first value of the domain to the number you pass to the scale.

Here is your code with that change only:

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>RePLICA</title>

    <style type="text/css">
      .chart-table {
        shape-rendering: crispEdges;
      }

      .graph-square text {
        font: 10px sans-serif;
      }

      div.tooltip-donut {
        position: absolute;
        text-align: center;
        padding: .5rem;
        background: #FFFFFF;
        color: #313639;
        border: 1px solid #313639;
        border-radius: 8px;
        pointer-events: none;
        font-size: 1.3rem;
      }

      .brush .extent {
        stroke: gray;
        fill: dodgerblue;
        fill-opacity: .365;
      }

    </style>

  </head>

  <body>

    <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
    <script type="text/javascript">
      //data
      var lanes = ["Chinese", "Japanese", "Korean", "Moldova"],
        laneLength = lanes.length,
        timeSegments = [{
            "lane": 0,
            "id": "Qin",
            "segment_start": 100,
            "segment_end": 210,
            "flag": false
          },
          {
            "lane": 0,
            "id": "Jin",
            "segment_start": 210,
            "segment_end": 420,
            "flag": true
          },
          {
            "lane": 0,
            "id": "Sui",
            "segment_start": 420,
            "segment_end": 615,
            "flag": false
          },

          {
            "lane": 1,
            "id": "Yamato",
            "segment_start": 300,
            "segment_end": 530,
            "flag": false
          },
          {
            "lane": 1,
            "id": "Asuka",
            "segment_start": 530,
            "segment_end": 700,
            "flag": true
          },
          {
            "lane": 1,
            "id": "Nara",
            "segment_start": 710,
            "segment_end": 800,
            "flag": false
          },
          {
            "lane": 1,
            "id": "Heian",
            "segment_start": 800,
            "segment_end": 1180,
            "flag": true
          },

          {
            "lane": 2,
            "id": "Three Kingdoms",
            "segment_start": 100,
            "segment_end": 670,
            "flag": false
          },
          {
            "lane": 2,
            "id": "North and South States",
            "segment_start": 670,
            "segment_end": 900,
            "flag": true
          },

          {
            "lane": 3,
            "id": "Chisinau",
            "segment_start": 250,
            "segment_end": 600,
            "flag": false
          },
          {
            "lane": 3,
            "id": "Balti",
            "segment_start": 600,
            "segment_end": 900,
            "flag": true
          },
          {
            "lane": 3,
            "id": "Ungheni",
            "segment_start": 920,
            "segment_end": 1380,
            "flag": false
          }

        ],
        timeBegin = d3.min(timeSegments, function(d) {
          return d.segment_start;
        }),
        timeEnd = d3.max(timeSegments, function(d) {
          return d.segment_end;
        });


      var widthTotal = 1300,
        heightTotal = 500,
        margin = {
          top: 10,
          right: 15,
          bottom: 0,
          left: 100
        },
        widthSVG = widthTotal - margin.right - margin.left,
        heightSVG = heightTotal - margin.top - margin.bottom,
        graphHeight = laneLength * 10 + heightTotal / 3; // - 3 just a coonstant

      // scales
      var scaleX = d3.scaleLinear()
        .domain([timeBegin, timeEnd])
        .range([0, widthSVG]);

      var scaleY = d3.scaleLinear()
        .domain([0, laneLength])
        .range([0, graphHeight]);


      var colorScale = d3.scaleOrdinal(d3.schemeCategory10);

      var chart = d3.select("body")
        .append("svg")
        .attr("width", widthSVG + margin.right + margin.left)
        .attr("height", heightSVG + margin.top + margin.bottom)
        .attr("class", "chart-table");

      var graph = chart.append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
        .attr("width", widthSVG)
        .attr("height", graphHeight)
        .attr("class", "graph-square");


      // Draw the axis
      chart.append("g")
        .attr("transform", "translate(" + margin.left + ", " + (graphHeight + 20) + ")") // This controls the vertical position of the Axis
        .call(d3.axisBottom(scaleX));


      // Delimitation lines
      graph.append("g").selectAll(".laneLines")
        .data(timeSegments)
        .enter().append("line")
        .attr("x1", 0)
        .attr("y1", function(d) {
          return scaleY(d.lane);
        })
        .attr("x2", widthSVG)
        .attr("y2", function(d) {
          return scaleY(d.lane);
        })
        .attr("stroke", "lightgray");

      // Lanes Names display
      graph.append("g").selectAll(".laneText")
        .data(lanes)
        .enter().append("text")
        .text(function(d) {
          return d;
        })
        .attr("x", -margin.right)
        .attr("y", function(d, i) {
          return scaleY(i + .5);
        })
        .attr("dy", ".5ex")
        .attr("text-anchor", "end")
        .attr("class", "laneText");


      // Add DIV for "hover_info"
      var div = d3.select("body").append("div")
        .attr("class", "tooltip-donut")
        .style("opacity", 0);

      // Graph item rects
      graph.append("g").selectAll(".graphItem")
        .data(timeSegments)
        .enter().append("rect")
        .attr("x", function(d) {
          return scaleX(d.segment_start);
        })
        .attr("y", function(d) {
          let shiftVertical = 9;
          if (d.flag) {
            shiftVertical = 0
          };
          return scaleY(d.lane + .5) - shiftVertical;
        })
        .attr("width", function(d) {
          return scaleX(d.segment_end - d.segment_start + scaleX.domain()[0]);
        })
        .attr("height", 10)
        .style("fill", function(d) {
          return colorScale(d.lane);
        })

        // Hover effect
        .on('mouseover', function(d, i) {
          d3.select(this).transition()
            .duration('50')
            .attr('opacity', '.5');

          div.transition()
            .duration(50)
            .style("opacity", 1);

          let hover_info = ("id:" + d.id + "<br/>" + "start:" + d.segment_start + "<br/>" + "end:" + d.segment_end).toString();

          //Makes the new div appear on hover:
          div.html(hover_info)
            .style("left", (d3.event.pageX + 10) + "px")
            .style("top", (d3.event.pageY - 15) + "px");
        })

        .on('mouseout', function(d, i) {
          d3.select(this).transition()
            .duration('50')
            .attr('opacity', '1')

          //Makes the new div disappear:
          div.transition()
            .duration('50')
            .style("opacity", 0);
        });

    </script>

  </body>

</html>

Upvotes: 1

Related Questions