【问题标题】:Dynamically set the width and height of SVG based on data根据数据动态设置SVG的宽高
【发布时间】:2019-10-27 23:01:53
【问题描述】:

首先,我使用的是 jQuery-3.2.1 和 bootstrap-3.3.7,无法更改。

我的基本目标是启动引导模式并在此模式中显示动态甘特图。我说动态是因为要在图表中显示的项目数量以及时间刻度可以并且会不时变化。我的问题是我不知道如何实现动态方面,最终不得不定义模式中显示的 SVG 的高度和宽度。这意味着如果图表中的项目数大于 SVG 的高度,这些项目将不会在图表中可见。同样,x 轴以刻度线/网格线之间的最小距离呈现,因此几乎无法读取图表。

到目前为止我做了什么:

我在以下链接 D3 v4 Gantt Chart 中找到了一个漂亮的图表,并使用它进行了一些小的修改来反转时间刻度,以便它从左到右显示最近到最旧的事件。

我有一个 jsfiddle 来说明问题 jsFiddle,请参见下面的屏幕截图,其中显示了刻度的狭窄程度以及读取具有代码中定义的宽度和高度的时间线是多么困难

var w = 650;
var h = 450;
var svg = d3.selectAll("#chart")
    .append("svg")
    .attr("width", w)
    .attr("height", h)
    .attr("class", "svg");

如何定义 SVG 和模式,以便在最小日期和最大日期之间的差异消失时,x 轴的刻度线/网格线仍然可读?我是否必须检查之间的天数,然后使用它来指定宽度或如何实现?与高度类似,我是否必须检查类别的数量并使用此数字来确定需要设置的 SVG 的高度,还是有其他方法可以实现这一点?

最后,正如您在小提琴中注意到的那样,将鼠标悬停在图表中的项目上的工具提示与它们应该显示的位置完全不一致。我在 bootstrap.min.js 文件中看到了关于将 isSVG 设置为 false 的提示,但没有效果。

对此的任何帮助将不胜感激 - 此外,如果需要,我们非常乐意进一步详细说明,但我希望小提琴能说明问题。提前感谢您的帮助。

【问题讨论】:

    标签: javascript d3.js svg dynamic bootstrap-modal


    【解决方案1】:

    在我看来,您希望将刻度数调整为宽度,而不是相反(更多内容见下文)。在这种情况下,不用像d3.timeDay 那样使用间隔,只需设置(近似)刻度数:

    axis.ticks(10)
    

    但是,如果实际上你想修改 SVG 的宽度,你可以计算天数并相应地设置 SVG 宽度:

    var dayWidth = 30;//set the width for each day here
    var numberOfDays = d3.timeDay.count(timeScale.domain()[0], timeScale.domain()[1]);//get the total number of days in the data
    w = numberOfDays * dayWidth;
    svg.attr("width", w);
    

    这只是一个快速代码,让您了解如何做到这一点,更好的解决方案相应地设置边距(我没有这样做,因为您的代码中有很多神奇的数字)。

    关于高度,你可以根据任务的数量来计算,并相应地设置SVG:

    h = tasks.length * gap + topPadding + 40;
    svg.attr("height", h);
    

    请记住,这里仍然有幻数。尽量避开它们。

    以下是这些更改的代码:

    $(document).ready(function() {
    
      myObj = JSON.parse('{"QUAL":[{"task": "milk", "type": "Ordered", "startTime": "14/10", "endTime": "16/11"},{"task": "butter", "type": "Completed", "startTime": "22/09", "endTime": "23/09"},{"task": "butter", "type": "Completed", "startTime": "24/09", "endTime": "25/09"},{"task": "bread", "type": "Completed", "startTime": "04/10", "endTime": "15/10"},{"task": "water", "type": "Completed", "startTime": "11/10", "endTime": "16/10"},{"task": "fish", "type": "Discontinued", "startTime": "21/09", "endTime": "23/09"},{"task": "mince", "type": "Discontinued", "startTime": "26/09", "endTime": "27/09"},{"task": "soda", "type": "Discontinued", "startTime": "04/10", "endTime": "08/10"},{"task": "sugar", "type": "Discontinued", "startTime": "04/10", "endTime": "08/10"},{"task": "flour", "type": "Discontinued", "startTime": "09/10", "endTime": "11/10"},{"task": "shampoo", "type": "Discontinued", "startTime": "10/10", "endTime": "11/10"},{"task": "salt", "type": "On Hold", "startTime": "04/10", "endTime": "04/10"}]}')
    
    
    
      myObj2 = JSON.parse('{"QUAL":[{"task": "milk", "type": "Ordered", "startTime": "14/10", "endTime": "16/11"},{"task": "butter", "type": "Completed", "startTime": "22/09", "endTime": "23/09"},{"task": "butter", "type": "Completed", "startTime": "24/09", "endTime": "25/09"},{"task": "bread", "type": "Completed", "startTime": "04/10", "endTime": "15/10"},{"task": "water", "type": "Completed", "startTime": "11/10", "endTime": "16/10"},{"task": "fish", "type": "Discontinued", "startTime": "21/09", "endTime": "23/09"},{"task": "mince", "type": "Discontinued", "startTime": "26/09", "endTime": "27/09"}]}')
    
    
      var taskArray = [];
    
    
      $('.modal').on('hidden.bs.modal', function() {
    
        document.getElementById("chart").innerHTML = ""
        taskArray = [];
    
      });
    
    
      $('.modal').on('show.bs.modal', function() {
    
      });
    
    
    
      $('#btn1').on('click', function() {
    
        $.each(myObj.QUAL, function(i, j) {
          taskArray.push({
            task: j.task,
            type: j.type,
            startTime: j.startTime,
            endTime: j.endTime
          })
        })
        makeChart();
      });
    
    
      $('#btn2').on('click', function() {
    
    
    
        $.each(myObj2.QUAL, function(i, j) {
          taskArray.push({
            task: j.task,
            type: j.type,
            startTime: j.startTime,
            endTime: j.endTime
          })
        })
        makeChart();
      });
    
      makeChart = function() {
    
    
        var w,
          h,
          dayWidth = 30;
    
        $('#amsModal').modal({
          backdrop: 'static'
        });
        // $('#amsDetailBodyImage').css('display', 'none');
    
        var svg = d3.selectAll("#chart")
          //.selectAll("svg")
          .append("svg")
          .attr("class", "svg");
    
        //var dateFormat = d3.timeParse("%Y-%m-%d");
        var dateFormat = d3.timeParse("%d/%m");
    
        var timeScale = d3.scaleTime()
          .domain([d3.min(taskArray, function(d) {
              return dateFormat(d.startTime);
            }),
            d3.max(taskArray, function(d) {
              return dateFormat(d.endTime);
            })
          ]);
    
        var numberOfDays = d3.timeDay.count(timeScale.domain()[0], timeScale.domain()[1]);
    
        w = numberOfDays * dayWidth;
    
        svg.attr("width", w);
    
        timeScale.range([w - 150, 0]);
    
        var categories = new Array();
    
        for (var i = 0; i < taskArray.length; i++) {
          categories.push(taskArray[i].type);
        }
    
        var catsUnfiltered = categories; //for vert labels
    
        categories = checkUnique(categories);
    
        makeGant(taskArray, w);
    
        var title = svg.append("text")
          .text("Groceries")
          .attr("x", w / 2)
          .attr("y", 25)
          .attr("text-anchor", "middle")
          .attr("font-size", 18)
          .attr("fill", "#000000");
    
    
    
        function makeGant(tasks, pageWidth) {
    
          var barHeight = 20;
          var gap = barHeight + 4;
          var topPadding = 75;
          var sidePadding = 75;
    
          h = tasks.length * gap + topPadding + 40;
          svg.attr("height", h);
    
          var colorScale = d3.scaleLinear()
            .domain([0, categories.length])
            .range(["#00B9FA", "#F95002"])
            .interpolate(d3.interpolateHcl);
    
          makeGrid(sidePadding, topPadding, pageWidth, h);
          drawRects(tasks, gap, topPadding, sidePadding, barHeight, colorScale, pageWidth, h);
          vertLabels(gap, topPadding, sidePadding, barHeight, colorScale);
    
    
          //$('.modal-button').attr('disabled', true);
        }
    
    
        function drawRects(theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w, h) {
    
          var bigRects = svg.append("g")
            .selectAll("rect")
            .data(theArray)
            .enter()
            .append("rect")
            .attr("x", 0)
            .attr("y", function(d, i) {
              return i * theGap + theTopPad - 2;
            })
            .attr("width", function(d) {
              return w - theSidePad / 2;
            })
            .attr("height", theGap)
            .attr("stroke", "none")
            .attr("fill", function(d) {
              for (var i = 0; i < categories.length; i++) {
                if (d.type == categories[i]) {
                  return d3.rgb(theColorScale(i));
                }
              }
            })
            .attr("opacity", 0.2);
    
    
          var rectangles = svg.append('g')
            .selectAll("rect")
            .data(theArray)
            .enter();
    
    
          var innerRects = rectangles.append("rect")
            .attr("rx", 3)
            .attr("ry", 3)
            .attr("x", function(d) {
              // return timeScale(dateFormat(d.startTime)) + theSidePad;
              return timeScale(dateFormat(d.endTime)) + theSidePad;
            })
            .attr("y", function(d, i) {
              return i * theGap + theTopPad;
            })
            .attr("width", function(d) {
              //return (timeScale(dateFormat(d.endTime))-timeScale(dateFormat(d.startTime)));
              return (timeScale(dateFormat(d.startTime)) - timeScale(dateFormat(d.endTime)));
            })
            .attr("height", theBarHeight)
            .attr("stroke", "none")
            .attr("fill", function(d) {
              for (var i = 0; i < categories.length; i++) {
                if (d.type == categories[i]) {
                  return d3.rgb(theColorScale(i));
                }
              }
            })
    
    
          var rectText = rectangles.append("text")
            .text(function(d) {
              return d.task;
            })
            .attr("x", function(d) {
              return (timeScale(dateFormat(d.endTime)) - timeScale(dateFormat(d.startTime))) / 2 + timeScale(dateFormat(d.startTime)) + theSidePad;
            })
            .attr("y", function(d, i) {
              return i * theGap + 14 + theTopPad;
            })
            .attr("font-size", 11)
            .attr("text-anchor", "middle")
            .attr("text-height", theBarHeight)
            .attr("fill", "#000000");
    
    
          rectText.on('mouseover', function(e) {
            // console.log(this.x.animVal.getItem(this));
            var tag = "";
    
            if (d3.select(this).data()[0].details != undefined) {
              tag = "Task: " + d3.select(this).data()[0].task + "<br/>" +
                "Type: " + d3.select(this).data()[0].type + "<br/>" +
                "Starts: " + d3.select(this).data()[0].startTime + "<br/>" +
                "Ends: " + d3.select(this).data()[0].endTime + "<br/>" +
                "Details: " + d3.select(this).data()[0].details;
            } else {
              tag = "Task: " + d3.select(this).data()[0].task + "<br/>" +
                "Type: " + d3.select(this).data()[0].type + "<br/>" +
                "Starts: " + d3.select(this).data()[0].startTime + "<br/>" +
                "Ends: " + d3.select(this).data()[0].endTime;
            }
            var output = document.getElementById("tag");
    
            var x = this.x.animVal.getItem(this) + "px";
            var y = this.y.animVal.getItem(this) + 25 + "px";
    
            output.innerHTML = tag;
            output.style.top = y;
            output.style.left = x;
            output.style.display = "block";
          }).on('mouseout', function() {
            var output = document.getElementById("tag");
            output.style.display = "none";
          });
    
    
          innerRects.on('mouseover', function(e) {
            //console.log(this);
            var tag = "";
    
            if (d3.select(this).data()[0].details != undefined) {
              tag = "Task: " + d3.select(this).data()[0].task + "<br/>" +
                "Type: " + d3.select(this).data()[0].type + "<br/>" +
                "Starts: " + d3.select(this).data()[0].startTime + "<br/>" +
                "Ends: " + d3.select(this).data()[0].endTime + "<br/>" +
                "Details: " + d3.select(this).data()[0].details;
            } else {
              tag = "Task: " + d3.select(this).data()[0].task + "<br/>" +
                "Type: " + d3.select(this).data()[0].type + "<br/>" +
                "Starts: " + d3.select(this).data()[0].startTime + "<br/>" +
                "Ends: " + d3.select(this).data()[0].endTime;
            }
            var output = document.getElementById("tag");
    
            var x = (this.x.animVal.value + this.width.animVal.value / 2) + "px";
            var y = this.y.animVal.value + 25 + "px";
    
            output.innerHTML = tag;
            output.style.top = y;
            output.style.left = x;
            output.style.display = "block";
          }).on('mouseout', function() {
            var output = document.getElementById("tag");
            output.style.display = "none";
    
          });
        }
    
    
        function makeGrid(theSidePad, theTopPad, w, h) {
    
          var xAxis = d3.axisBottom(timeScale)
            .ticks(d3.timeDay, 1)
            .tickSize(-h + theTopPad + 20, 0, 0)
            //			.tickFormat(d3.timeFormat('%d %b'));
            .tickFormat(d3.timeFormat('%d/%m'));
          var grid = svg.append('g')
            .attr('class', 'grid')
            //			.attr('transform', 'translate(' +theSidePad + ', ' + (h - 50) + ')')
            .attr('transform', 'translate(' + theSidePad + ', ' + (h - 20) + ')')
            .call(xAxis)
            .selectAll("text")
            .style("text-anchor", "middle")
            .attr("fill", "#000000")
            .attr("stroke", "none")
            .attr("font-size", 10)
            //							.attr("dy", "1em")
            .attr("dy", "0.35em")
            .attr("transform", "rotate(-65)");
    
        }
    
        function vertLabels(theGap, theTopPad, theSidePad, theBarHeight, theColorScale) {
          var numOccurances = new Array();
          var prevGap = 0;
    
          for (var i = 0; i < categories.length; i++) {
            numOccurances[i] = [categories[i], getCount(categories[i], catsUnfiltered)];
          }
    
          var axisText = svg.append("g") //without doing this, impossible to put grid lines behind text
            .selectAll("text")
            .data(numOccurances)
            .enter()
            .append("text")
            .text(function(d) {
              return d[0];
            })
            .attr("x", 10)
            .attr("y", function(d, i) {
              if (i > 0) {
                for (var j = 0; j < i; j++) {
                  prevGap += numOccurances[i - 1][1];
                  // console.log(prevGap);
                  return d[1] * theGap / 2 + prevGap * theGap + theTopPad;
                }
              } else {
                return d[1] * theGap / 2 + theTopPad;
              }
            })
            .attr("font-size", 11)
            .attr("text-anchor", "start")
            .attr("text-height", 14)
            .attr("fill", function(d) {
              for (var i = 0; i < categories.length; i++) {
                if (d[0] == categories[i]) {
                  //  console.log("true!");
                  return d3.rgb(theColorScale(i)).darker();
                }
              }
            });
    
        }
    
        //from this stackexchange question: http://stackoverflow.com/questions/1890203/unique-for-arrays-in-javascript
        function checkUnique(arr) {
          var hash = {},
            result = [];
          for (var i = 0, l = arr.length; i < l; ++i) {
            if (!hash.hasOwnProperty(arr[i])) { //it works with objects! in FF, at least
              hash[arr[i]] = true;
              result.push(arr[i]);
            }
          }
          return result;
        }
    
        //from this stackexchange question: http://stackoverflow.com/questions/14227981/count-how-many-strings-in-an-array-have-duplicates-in-the-same-array
        function getCounts(arr) {
          var i = arr.length, // var to loop over
            obj = {}; // obj to store results
          while (i) obj[arr[--i]] = (obj[arr[i]] || 0) + 1; // count occurrences
          return obj;
        }
    
        // get specific from everything
        function getCount(word, arr) {
          return getCounts(arr)[word] || 0;
        }
    
        $('#amsModal').modal({
          backdrop: 'static'
        });
    
      };
    
    
    
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <!DOCTYPE html>
    <html>
    
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      
      #container {
        margin: 0 auto;
        position: relative;
        /*width: 1000px;*/
        overflow: visible;
      }
      
      #chart {
        /*    width: 800px;
        height: 400px;*/
        overflow: scroll;
        /*position: absolute;*/
      }
      
      .grid .tick {
        stroke: lightgrey;
        opacity: 0.3;
        shape-rendering: crispEdges;
      }
      
      .grid path {
        stroke-width: 0;
      }
      
      #tag {
        color: white;
        background: #FA283D;
        width: 150px;
        position: absolute;
        display: none;
        padding: 3px 6px;
        margin-left: -80px;
        font-size: 11px;
      }
      
      #tag:before {
        border: solid transparent;
        content: ' ';
        height: 0;
        left: 50%;
        margin-left: -5px;
        position: absolute;
        width: 0;
        border-width: 10px;
        border-bottom-color: #FA283D;
        top: -20px;
      }
      
      .container-fluid {
        margin-left: 10%;
        margin-right: 10%;
      }
      
      .button {
        max-width: 200px;
      }
      
      .modal-body-detail {
        max-height: calc(100vh - 200px);
        overflow-y: auto;
      }
    </style>
    
    <head>
      <title>Meh</title>
    </head>
    
    <body>
    
      <div class='container-fluid'>
        <div class="row">
    
          <button id="btn1" type="button" class="btn btn-primary">
              Long
            </button>
    
          <button id="btn2" type="button" class="btn btn-primary">
              Short
            </button>
        </div>
    
    
        <div class="modal fade autoModal " id="amsModal" tabindex="-1" role="dialog" aria-labelledby="amsModalLabel" aria-hidden="true">
          <div class="modal-dialog modal-lg" role="document">
            <div class="modal-content">
              <div class="modal-header">
                <h4 class="modal-title" id="amsModalLabel">Orders</h4>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                  </button>
              </div>
              <div id="detailBody" class="modal-body ">
                <div id="container">
                  <div id="chart"></div>
                  <!-- chart -->
                  <div id="tag"></div>
                  <!-- tooltip on hover -->
                </div>
              </div>
    
    
              <div class="modal-footer">
                <button type="button" class="btn btn-secondary modal-button" data-dismiss="modal">Close</button>
              </div>
            </div>
          </div>
        </div>
    
    
    
      </div>
    
    </body>
    
    </html>

    【讨论】:

    • 非常感谢您的回复 Gerardo,非常感谢。我有点走神了,所以还没有机会看这个,但我真的很高兴在发回之前检查一下。再次,非常感谢!
    • 抱歉这么久才回复您。这确实是一种享受,感激不尽!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-29
    • 2015-08-27
    • 2021-08-12
    • 2018-09-06
    • 2018-06-09
    • 2011-09-27
    相关资源
    最近更新 更多