轴范围滑块对齐

时间:2018-08-10 09:12:53

标签: javascript html d3.js svg

我需要设计一个d3组件,如下图所示。

Desired Visualization

我引用了this link中的现有代码示例,并对其进行了修改以创建类似这样的内容。

My Intermediate working version

左更改轴的宽度,我尝试通过更改 domain class stroke-width 属性来尝试。但是,我结束了这样的事情。

Not working version

问题:

  1. 滑块手柄未与轴对齐。
  2. 轴颜色印在滑块上。
  3. 轴的末端不是完美的圆形。

问题:

  1. 我不知道如何平移/变换以对齐滑块和轴。
  2. 我尝试摆弄不透明度值,但没有帮助。
  3. 我将 stroke-linecap 设置为 round ,但是仍然不是完全圆形。

我正在为此使用d3 v4。还有我的final code is here的jsfiddle。

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
  <script src="https://d3js.org/d3.v5.min.js"></script>
  <style>
.tick{
  visibility:hidden;
}

 .domain {
    stroke: grey;
    stroke-width:10px;
    stroke-linecap: round;
  }
  
  .selection {
    fill:red
  }

</style>
</head>

<body>
  <div style="margin-left: 20px;margin-top: 20px;">
    <span></span> to <span></span>
  </div>


<script>

    var margin = 20,
        width = 400 - margin * 2,
        height = 15;

    // v3 = var x = d3.scale.linear()
    var x = d3.scaleLinear()
        .domain([0,100])
        .range([0, width]);

    /*
    var brush = d3.svg.brush()
      .x(x)
      .extent([20, 50]);
    */
    var brush = d3.brushX()
        .extent([[0,0], [width,height]])
        .on("brush", brushed);

    var svg = d3.select("body").append("svg")
        .attr("width", width + margin * 2)
        .attr("height", 100)
      .append("g")
        .attr("transform", "translate(" + margin + "," + margin + ")")
        .call(d3.axisBottom()
            .scale(x)
            .tickSize(0));
  
    var brushg = svg.append("g")
        .attr("class", "brush")
        .call(brush)
        
     // left circle
	
    
    var left_text = brushg.append("text")
    .attr("class", "label")
    .attr("fill", "black") 
    .attr("text-anchor", "middle")
    .text("hello world")
    .attr("transform", "translate(0," + (35) + ")")
    
    var right_text = brushg.append("text")
    .attr("class", "label")
    .attr("fill", "black") 
    .attr("text-anchor", "middle")
    .text("hello world")
    .attr("transform", "translate(0," + (35) + ")")
        
    
    /* 
      Height of the brush's rect is now 
        generated by brush.extent():
    brushg.selectAll("rect")
        .attr("height", height);
    */

    function brushed() {
      /*
        The brush attributes are no longer stored 
        in the brush itself, but rather in the 
        element it is brushing. That's where much of
        the confusion around v4's brushes seems to be.
        The new method is a little difficult to adapt
        to, but seems more efficient. I think much of
        this confusion comes from the fact that 
        brush.extent() still exists, but means
        something completely different.

        Instead of calling brush.extent() to get the 
        range of the brush, call 
        d3.brushSelection(node) on what is being 
        brushed.

      d3.select('#start-number')
        .text(Math.round(brush.extent()[0]));
      d3.select('#end-number')
        .text(Math.round(brush.extent()[1]));
      */


      var range = d3.brushSelection(this)
          .map(x.invert);
      
      console.log('range->'+range)
      d3.selectAll("span")
          .text(function(d, i) {
            console.log(Math.round(range[i]))
            return Math.round(range[i])
          })
          
      left_text.attr("x", x(range[0]));
      left_text.text(Math.round(range[0]));
      right_text.attr("x", x(range[1]));
      right_text.text(Math.round(range[1]));
      
      d3.selectAll("rect").attr("dy", "-5em")
          
    }
    

    // v3:  brushed();
    brush.move(brushg, [20, 40].map(x));

</script>
</body>
</html>

2 个答案:

答案 0 :(得分:2)

轴和画笔实际上完全对齐!

如果将stroke-width设置为1px,则可以看到以下内容:

.as-console-wrapper { max-height: 30% !important;}
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
  <script src="https://d3js.org/d3.v5.min.js"></script>
  <style>
.tick{
  visibility:hidden;
}

 .domain {
    stroke: grey;
    stroke-width:1px;
    stroke-linecap: round;
  }
  
  .selection {
    fill:red
  }

</style>
</head>

<body>
  <div style="margin-left: 20px;margin-top: 20px;">
    <span></span> to <span></span>
  </div>


<script>

    var margin = 20,
        width = 400 - margin * 2,
        height = 15;

    // v3 = var x = d3.scale.linear()
    var x = d3.scaleLinear()
        .domain([0,100])
        .range([0, width]);

    /*
    var brush = d3.svg.brush()
      .x(x)
      .extent([20, 50]);
    */
    var brush = d3.brushX()
        .extent([[0,0], [width,height]])
        .on("brush", brushed);

    var svg = d3.select("body").append("svg")
        .attr("width", width + margin * 2)
        .attr("height", 100)
      .append("g")
        .attr("transform", "translate(" + margin + "," + margin + ")")
        .call(d3.axisBottom()
            .scale(x)
            .tickSize(0));
  
    var brushg = svg.append("g")
        .attr("class", "brush")
        .call(brush)
        
     // left circle
	
    
    var left_text = brushg.append("text")
    .attr("class", "label")
    .attr("fill", "black") 
    .attr("text-anchor", "middle")
    .text("hello world")
    .attr("transform", "translate(0," + (35) + ")")
    
    var right_text = brushg.append("text")
    .attr("class", "label")
    .attr("fill", "black") 
    .attr("text-anchor", "middle")
    .text("hello world")
    .attr("transform", "translate(0," + (35) + ")")
        
    
    /* 
      Height of the brush's rect is now 
        generated by brush.extent():
    brushg.selectAll("rect")
        .attr("height", height);
    */

    function brushed() {
      /*
        The brush attributes are no longer stored 
        in the brush itself, but rather in the 
        element it is brushing. That's where much of
        the confusion around v4's brushes seems to be.
        The new method is a little difficult to adapt
        to, but seems more efficient. I think much of
        this confusion comes from the fact that 
        brush.extent() still exists, but means
        something completely different.

        Instead of calling brush.extent() to get the 
        range of the brush, call 
        d3.brushSelection(node) on what is being 
        brushed.

      d3.select('#start-number')
        .text(Math.round(brush.extent()[0]));
      d3.select('#end-number')
        .text(Math.round(brush.extent()[1]));
      */


      var range = d3.brushSelection(this)
          .map(x.invert);
      
      console.log('range->'+range)
      d3.selectAll("span")
          .text(function(d, i) {
            console.log(Math.round(range[i]))
            return Math.round(range[i])
          })
          
      left_text.attr("x", x(range[0]));
      left_text.text(Math.round(range[0]));
      right_text.attr("x", x(range[1]));
      right_text.text(Math.round(range[1]));
      
      d3.selectAll("rect").attr("dy", "-5em")
          
    }
    

    // v3:  brushed();
    brush.move(brushg, [20, 40].map(x));

</script>
</body>
</html>

那么,这是怎么回事?问题是,当您告诉浏览器走一条线(在这种情况下,这是一条路径,但没有关系)并将其笔触增加到例如100像素时,它将增加一侧的50像素和50的像素像素到另一边。因此,该粗轴的中间恰好在画笔矩形的顶部。

这里有几种解决方案,例如绘制矩形。但是,如果您想采用增加.domain笔触宽度的方法,那么让我们打破选择,然后将轴stroke-width的一半向下移动(这里我将宽度增加到20像素,因此更容易看到对齐方式):

.as-console-wrapper { max-height: 30% !important;}
<!DOCTYPE html>
<meta charset="utf-8">
<script src="//d3js.org/d3.v4.min.js"></script>
<!-- 
  axes and brushes are styled out of the box, 
    so this is no longer needed
<style>
  
  .axis path, .axis line {
    fill: none;
    stroke: #000;
    shape-rendering: crispEdges;
  }
  .brush .extent {
    fill-opacity: .125;
    shape-rendering: crispEdges;
  }

</style>
-->
<style>
  .tick {
    visibility: hidden;
  }

  .domain {
    stroke: grey;
    stroke-width: 20px;
    stroke-linecap: round;
  }

  .selection {
    fill: red
  }

</style>

<body>
  <div style="margin-left: 20px;margin-top: 20px;">
    <span></span> to <span></span>
  </div>
</body>

<script>
  var margin = 20,
    width = 400 - margin * 2,
    height = 20;

  // v3 = var x = d3.scale.linear()
  var x = d3.scaleLinear()
    .domain([0, 100])
    .range([0, width]);

  /*
  var brush = d3.svg.brush()
    .x(x)
    .extent([20, 50]);
  */
  var brush = d3.brushX()
    .extent([
      [0, 0],
      [width, height]
    ])
    .on("brush", brushed);

  var svg = d3.select("body").append("svg")
    .attr("width", width + margin * 2)
    .attr("height", 100);

  svg.append("g")
    .attr("transform", "translate(" + margin + "," + (margin + 10) + ")")
    .call(d3.axisBottom()
      .scale(x)
      .tickSize(0));

  var brushg = svg.append("g")
    .attr("transform", "translate(" + margin + "," + margin + ")")
    .attr("class", "brush")
    .call(brush)

  // left circle


  var left_text = brushg.append("text")
    .attr("class", "label")
    .attr("fill", "black")
    .attr("text-anchor", "middle")
    .text("hello world")
    .attr("transform", "translate(0," + (35) + ")")

  var right_text = brushg.append("text")
    .attr("class", "label")
    .attr("fill", "black")
    .attr("text-anchor", "middle")
    .text("hello world")
    .attr("transform", "translate(0," + (35) + ")")


  /* 
    Height of the brush's rect is now 
      generated by brush.extent():
  brushg.selectAll("rect")
      .attr("height", height);
  */

  function brushed() {
    /*
      The brush attributes are no longer stored 
      in the brush itself, but rather in the 
      element it is brushing. That's where much of
      the confusion around v4's brushes seems to be.
      The new method is a little difficult to adapt
      to, but seems more efficient. I think much of
      this confusion comes from the fact that 
      brush.extent() still exists, but means
      something completely different.

      Instead of calling brush.extent() to get the 
      range of the brush, call 
      d3.brushSelection(node) on what is being 
      brushed.

    d3.select('#start-number')
      .text(Math.round(brush.extent()[0]));
    d3.select('#end-number')
      .text(Math.round(brush.extent()[1]));
    */


    var range = d3.brushSelection(this)
      .map(x.invert);

    console.log('range->' + range)
    d3.selectAll("span")
      .text(function(d, i) {
        console.log(Math.round(range[i]))
        return Math.round(range[i])
      })

    left_text.attr("x", x(range[0]));
    left_text.text(Math.round(range[0]));
    right_text.attr("x", x(range[1]));
    right_text.text(Math.round(range[1]));

    d3.selectAll("rect").attr("dy", "-5em")

  }


  // v3:  brushed();
  brush.move(brushg, [20, 40].map(x));

</script>

答案 1 :(得分:0)

轴上的路径是闭合的形状,并且抚摸会产生问题。另外,您也不想打勾,为什么不自己绘制“轴”。然后,将正确绘制圆形边缘。

    var svg = d3.select("body").append("svg")
        .attr("width", width + margin * 2)
        .attr("height", 100)
      .append("g")
        .attr("transform", "translate(" + margin + "," + margin + ")")
        // .call(d3.axisBottom()
        //     .scale(x)
        //     .tickSize(0))
        ;

    svg.append("path")
       .attr("class", "domain")
       .attr("d", `M${x(0)},0 ${x(100)},0`);

您必须将笔刷范围与描边的路径表面匹配

    var margin = 20,
        width = 400 - margin * 2,
        height = 10; // same as stroke width

    var brush = d3.brushX()
        .extent([[0,-height*0.5], [width,height*0.5]])
        .on("brush", brushed);

dy属性没有目的

      //d3.selectAll("rect").attr("dy", "-5em")

设置所选内容的填充不透明度

.selection {
  fill:red;
  fill-opacity: 1;
}

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
  <script src="https://d3js.org/d3.v5.min.js"></script>
  <style>
.tick{
  visibility:hidden;
}

 .domain {
    stroke: grey;
    stroke-width:10;
    stroke-linecap: round;
}

.selection {
  fill:red;
  fill-opacity: 1;
}

</style>
</head>

<body>
  <div style="margin-left: 20px;margin-top: 20px;">
    <span></span> to <span></span>
</div>

<script>
    var margin = 20,
        width = 400 - margin * 2,
        height = 10; // same as stroke width

    // v3 = var x = d3.scale.linear()
    var x = d3.scaleLinear()
        .domain([0,100])
        .range([0, width]);

    /*
    var brush = d3.svg.brush()
      .x(x)
      .extent([20, 50]);
    */
    var brush = d3.brushX()
        .extent([[0,-height*0.5], [width,height*0.5]])
        .on("brush", brushed);

    var svg = d3.select("body").append("svg")
        .attr("width", width + margin * 2)
        .attr("height", 100)
      .append("g")
        .attr("transform", "translate(" + margin + "," + margin + ")")
        // .call(d3.axisBottom()
        //     .scale(x)
        //     .tickSize(0))
        ;
    svg.append("path")
       .attr("class", "domain")
       .attr("d", `M${x(0)},0 ${x(100)},0`);

    var brushg = svg.append("g")
        .attr("class", "brush")
        .call(brush)

     // left circle


    var left_text = brushg.append("text")
    .attr("class", "label")
    .attr("fill", "black")
    .attr("text-anchor", "middle")
    .text("hello world")
    .attr("transform", "translate(0," + (35) + ")")

    var right_text = brushg.append("text")
    .attr("class", "label")
    .attr("fill", "black")
    .attr("text-anchor", "middle")
    .text("hello world")
    .attr("transform", "translate(0," + (35) + ")")


    /*
      Height of the brush's rect is now
        generated by brush.extent():
    brushg.selectAll("rect")
        .attr("height", height);
    */

    function brushed() {
      /*
        The brush attributes are no longer stored
        in the brush itself, but rather in the
        element it is brushing. That's where much of
        the confusion around v4's brushes seems to be.
        The new method is a little difficult to adapt
        to, but seems more efficient. I think much of
        this confusion comes from the fact that
        brush.extent() still exists, but means
        something completely different.

        Instead of calling brush.extent() to get the
        range of the brush, call
        d3.brushSelection(node) on what is being
        brushed.

      d3.select('#start-number')
        .text(Math.round(brush.extent()[0]));
      d3.select('#end-number')
        .text(Math.round(brush.extent()[1]));
      */

      var range = d3.brushSelection(this)
          .map(x.invert);

      //console.log('range->'+range)
      d3.selectAll("span")
          .text(function(d, i) {
            //console.log(Math.round(range[i]))
            return Math.round(range[i])
          })

      left_text.attr("x", x(range[0]));
      left_text.text(Math.round(range[0]));
      right_text.attr("x", x(range[1]));
      right_text.text(Math.round(range[1]));

      //d3.selectAll("rect").attr("dy", "-5em")

    }

    // v3:  brushed();
    brush.move(brushg, [20, 40].map(x));

</script>
</body>
</html>