简体   繁体   English

在d3 V4中平行坐标轴上的多个画笔?

[英]Multiple brushes on an axis in parallel coordinate in d3 V4?

I have created a parallel coordinate in d3 V4 (with a lot of pain)which has both numerical and ordinal axis, with basic features like axis dragging, brushing, brush snapping. 我在d3 V4中创建了一个平行坐标(非常痛苦),该坐标同时具有数字轴和序数轴,并具有诸如轴拖动,刷涂,笔刷捕捉之类的基本功能。 Here is the working sample http://plnkr.co/edit/dCNuBsaDNBwr7CrAJUBe?p=preview 这是工作示例http://plnkr.co/edit/dCNuBsaDNBwr7CrAJUBe?p=preview

I am looking to have multiple brushes in an axis, (for example I want to brush 0.2 to 0.5 and 0.7 to 0.9 of column1 in my example at the same time). 我希望在一个轴上有多个笔刷(例如,我想同时笔刷column1的0.2至0.5和0.7至0.9)。 So basically based on multiple brush areas the corresponding lines should be highlighted. 因此,基本上基于多个画笔区域,应突出显示相应的线条。 Please suggest some way to do this. 请提出一些方法。 Thanks in advance 提前致谢

<!DOCTYPE html>
<meta charset="utf-8">
<style>

svg {
  font: 10px sans-serif;
}

.background path {
  fill: none;
  stroke: #ddd;
   stroke-opacity: .4;  
  shape-rendering: crispEdges;
}

.foreground path {
  fill: none;
  stroke: steelblue;
   stroke-opacity: .7;
}

.brush .extent {
  fill-opacity: .3;
  stroke: #fff;

  shape-rendering: crispEdges;
}

.axis line,
.axis path {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.axis text {
  text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
  cursor: move;
}

</style>
<body>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script>


var margin = {top: 30, right: 10, bottom: 10, left: 10},
    width = 600 - margin.left - margin.right,
    height = 200 - margin.top - margin.bottom;

var x = d3.scalePoint().rangeRound([0, width]).padding(1),
    y = {},
    dragging = {};


var line = d3.line(),
    //axis = d3.axisLeft(x),
    background,
    foreground,
    extents;


 var container = d3.select("body").append("div")
    .attr("class", "parcoords")
    .style("width", width + margin.left + margin.right + "px")
    .style("height", height + margin.top + margin.bottom + "px");

var svg = container.append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var quant_p = function(v){return (parseFloat(v) == v) || (v == "")};     

d3.json("convertcsv.json", function(error, cars) {

    dimensions = d3.keys(cars[0]);

    x.domain(dimensions);

    dimensions.forEach(function(d) {
    var vals = cars.map(function(p) {return p[d];});
    if (vals.every(quant_p)){ 
     y[d] = d3.scaleLinear()
        .domain(d3.extent(cars, function(p) { 
            return +p[d]; }))
        .range([height, 0])

        console.log(y[d]);
      }
    else{
     vals.sort();           
      y[d] = d3.scalePoint()
              .domain(vals.filter(function(v, i) {return vals.indexOf(v) == i;}))
              .range([height, 0],1);
       }

  })


 extents = dimensions.map(function(p) { return [0,0]; });


  // Add grey background lines for context.
  background = svg.append("g")
      .attr("class", "background")
    .selectAll("path")
      .data(cars)
    .enter().append("path")
      .attr("d", path);

  // Add blue foreground lines for focus.
  foreground = svg.append("g")
      .attr("class", "foreground")
    .selectAll("path")
      .data(cars)
    .enter().append("path")
      .attr("d", path);



  // Add a group element for each dimension.

  var g = svg.selectAll(".dimension")
      .data(dimensions)
    .enter().append("g")
      .attr("class", "dimension")
      .attr("transform", function(d) {  return "translate(" + x(d) + ")"; })
      .call(d3.drag()
        .subject(function(d) { return {x: x(d)}; })
        .on("start", function(d) {
          dragging[d] = x(d);
          background.attr("visibility", "hidden");
        })
        .on("drag", function(d) {
          dragging[d] = Math.min(width, Math.max(0, d3.event.x));
          foreground.attr("d", path);
          dimensions.sort(function(a, b) { return position(a) - position(b); });
          x.domain(dimensions);
          g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
        })
        .on("end", function(d) {
          delete dragging[d];
          transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
          transition(foreground).attr("d", path);
          background
              .attr("d", path)
            .transition()
              .delay(500)
              .duration(0)
              .attr("visibility", null);
        }));


  // Add an axis and title.
   var g = svg.selectAll(".dimension");
  g.append("g")
      .attr("class", "axis")
      .each(function(d) {  d3.select(this).call(d3.axisLeft(y[d]));})
      //text does not show up because previous line breaks somehow
    .append("text")
      .attr("fill", "black")
      .style("text-anchor", "middle")
      .attr("y", -9) 
      .text(function(d) { return d; });

  // Add and store a brush for each axis.
  g.append("g")
      .attr("class", "brush")
      .each(function(d) {
        if(y[d].name == 'r'){
         // console.log(this);

        d3.select(this).call(y[d].brush = d3.brushY().extent([[-8, 0], [8,height]]).on("brush start", brushstart).on("brush", go_brush).on("brush", brush_parallel_chart).on("end", brush_end));
        }


      else if(y[d].name == 'n')
             d3.select(this).call(y[d].brush = d3.brushY().extent([[-8, 0], [15,height]]).on("brush start", brushstart).on("brush", go_brush).on("brush", brush_parallel).on("end", brush_end_ordinal)); 


      })
    .selectAll("rect")
      .attr("x", -8)
      .attr("width", 16);  
});  // closing

function position(d) {
  var v = dragging[d];
  return v == null ? x(d) : v;
}

function transition(g) {
  return g.transition().duration(500);
}

// Returns the path for a given data point.
function path(d) {
  return line(dimensions.map(function(p) { return [position(p), y[p](d[p])]; }));
}

function go_brush() {
  d3.event.sourceEvent.stopPropagation();
}


invertExtent = function(y) {
  return domain.filter(function(d, i) { return y === range[i]; });
};


function brushstart(selectionName) {
  foreground.style("display", "none")

  //console.log(selectionName);

  var dimensionsIndex = dimensions.indexOf(selectionName);

  //console.log(dimensionsIndex);

  extents[dimensionsIndex] = [0, 0];

  foreground.style("display", function(d) {
    return dimensions.every(function(p, i) {
        if(extents[i][0]==0 && extents[i][0]==0) {
            return true;
        }
      return extents[i][1] <= d[p] && d[p] <= extents[i][0];
    }) ? null : "none";
  });
}



// Handles a brush event, toggling the display of foreground lines.
function brush_parallel_chart() { 

   for(var i=0;i<dimensions.length;++i){


        if(d3.event.target==y[dimensions[i]].brush) {
         //if (d3.event.sourceEvent.type === "brush") return;

            extents[i]=d3.event.selection.map(y[dimensions[i]].invert,y[dimensions[i]]);

    }

}

     foreground.style("display", function(d) {
        return dimensions.every(function(p, i) {
            if(extents[i][0]==0 && extents[i][0]==0) {
                return true;
            }
          return extents[i][1] <= d[p] && d[p] <= extents[i][0];
        }) ? null : "none";
      }); 
}    


function brush_end(){



  if (!d3.event.sourceEvent) return; // Only transition after input.
  if (!d3.event.selection) return; // Ignore empty selections.


for(var i=0;i<dimensions.length;++i){

    if(d3.event.target==y[dimensions[i]].brush) {

  extents[i]=d3.event.selection.map(y[dimensions[i]].invert,y[dimensions[i]]);

   extents[i][0] = Math.round( extents[i][0] * 10 ) / 10;
   extents[i][1] = Math.round( extents[i][1] * 10 ) / 10;



    d3.select(this).transition().call(d3.event.target.move, extents[i].map(y[dimensions[i]]));

     }

  }

}

//   brush for ordinal cases
function brush_parallel() {


for(var i=0;i<dimensions.length;++i){

        if(d3.event.target==y[dimensions[i]].brush) {


       var  yScale = y[dimensions[i]];
       var selected =  yScale.domain().filter(function(d){
            // var s = d3.event.target.extent();
          var s = d3.event.selection;

      return (s[0] <= yScale(d)) && (yScale(d) <= s[1])

      });


var temp = selected.sort();
extents[i] = [temp[temp.length-1], temp[0]];


   }

}

foreground.style("display", function(d) {
        return dimensions.every(function(p, i) {
            if(extents[i][0]==0 && extents[i][0]==0) {
                return true;
            }
          //var p_new = (y[p].ticks)?d[p]:y[p](d[p]); 
          //return extents[i][1] <= p_new && p_new <= extents[i][0];
        return extents[i][1] <= d[p] && d[p] <= extents[i][0];
        }) ? null : "none";
      });     
}




function brush_end_ordinal(){

  console.log("hhhhh");


  if (!d3.event.sourceEvent) return; // Only transition after input.

  if (!d3.event.selection) return; // Ignore empty selections.

  for(var i=0;i<dimensions.length;++i){

        if(d3.event.target==y[dimensions[i]].brush) {


       var  yScale = y[dimensions[i]];
       var selected =  yScale.domain().filter(function(d){
      // var s = d3.event.target.extent();
      var s = d3.event.selection;

      return (s[0] <= yScale(d)) && (yScale(d) <= s[1])

      });

  var temp = selected.sort();
  extents[i] = [temp[temp.length-1], temp[0]];

if(selected.length >1)
d3.select(this).transition().call(d3.event.target.move, extents[i].map(y[dimensions[i]]));


     }  
 } 



}

</script> 

An implementation of multi brush in d3 V4 is there in https://github.com/BigFatDog/parcoords-es d3 V4中的多画笔实现在https://github.com/BigFatDog/parcoords-es中

But I guess demo examples are not there. 但是我想演示示例不存在。

Here's a Plunkr using multiple brushes in d3 v4 and of course working on your parallel chart: 这是在d3 v4中使用多个笔刷的Plunkr,当然可以在并行图表上使用:

Code Plunkr for multi brush 代码Plunkr多刷

https://github.com/shashank2104/d3.svg.multibrush (I'll suggest the v4 patch to the guy who has written the v3 version once it's completely done) https://github.com/shashank2104/d3.svg.multibrush (我会向完全编写v3版本的人建议v4补丁)

There's one thing that's bothering me which is the brush clicking, as of now, the brushing works with CLICK to start, MOUSEMOVE and CLICK to end . 困扰我的是笔刷单击, 到目前为止,该笔刷使用CLICK开始,MOUSEMOVE和CLICK结束 Just play around with the brushing and you'll notice the difference. 玩转刷牙,您会发现其中的区别。

The new brushing function is the default one (as follows); 新的刷牙功能是默认功能(如下所示);

// Handles a brush event, toggling the display of foreground lines.
function brush() {
  var actives = dimensions.filter(function(p) { return !y[p].brush.empty(); }),
  extents = actives.map(function(p) { return y[p].brush.extent(); });
  foreground.style("display", function(d) {
  return actives.every(function(p, i) {
        return extents[i].some(function(e){
            return e[0] <= d[p] && d[p] <= e[1];
        });
  }) ? null : "none";
 });
}

I'll be working on it and hopefully it'll be ready by morning. 我会努力的,希望它能在早上准备好。

Until then, ordinal axis shouldn't be a problem to fix. 在此之前,序数轴应该不是修复问题。

I'll keep you posted and edit the same post. 我会及时通知您,并编辑相同的帖子。 (It'd be nice if you could not accept the answer as the right one until it's completely fixed. It'll be on my plate to fix) (如果您不接受正确的答案,那就太好了,直到它完全解决。它将由我来解决)

Let me know if you come across any questions. 如果您有任何疑问,请告诉我。

Shashank thanks a lot for the working example, Its like first working example of multi brush in V4. Shashank非常感谢您的工作示例,就像V4中的第一个多画笔工作示例一样。 I observed the points you mentioned about brushing.Its a bit different from the traditional brushing method. 我观察了您提到的有关刷牙的观点,这与传统的刷牙方法有些不同。 But the multi brush library from V3 has the conventional brushing way , right. 但是V3中的多笔刷库具有常规的笔刷方式,对。

I have placed the code for ordinal brushing also in your (default) brush function. 我已经在您的(默认)画笔功能中放置了顺序画笔的代码。

function brush() {
  var actives = dimensions.filter(function(p) { return !y[p].brush.empty(); }),
      extents = actives.map(function(p) { return y[p].brush.extent(); });

  foreground.style("display", function(d) {
    return actives.every(function(p, i) {
      var p_new = (y[p].ticks)?d[p]:y[p](d[p]);
            return extents[i].some(function(e){
                //return e[0] <= d[p] && d[p] <= e[1];
        return e[0] <= p_new && p_new <= e[1];
            });
    }) ? null : "none";
  });
}

In my plunkr example, I have used d3.brushY() functions which are in V4 and thats why I have to implement invert functions (for numerical axis , referred from Appended text not showing in d3 v4 ) and refeered http://bl.ocks.org/chrisbrich/4173587 for ordinal axis. 在我的plunkr示例中,我使用了V4中的d3.brushY()函数,这就是为什么我必须实现反转函数(对于数字轴,从d3 v4中未显示的附加文本中引用)并引用为http:// bl。 ocks.org/chrisbrich/4173587用于序数轴。 With these combinations I was able to do brush snapping also. 通过这些组合,我还可以进行画笔捕捉。

Also Is there a way that I can do brush snapping also (both numerical and ordinal axis) with your multi brush plunkr example. 另外,还有一种方法可以通过您的多笔刷plunkr示例进行笔刷捕捉(数字轴和序数轴)。

I know I am asking more, but basically two points: 1) Is there a way to do multi brushing like that in V3, like as click- drag - leave ! 我知道我要问的更多,但基本上有两点:1)是否有一种方法可以像V3中那样进行多刷,就像单击-拖动-离开一样! 2) Brush snapping (numerical and ordinal) in your multi brush example ? 2)在您的多画笔示例中,画笔捕捉(数字和顺序)?

Thanks again for all your effort and time , means a lot. 再次感谢您的所有努力和时间,非常感谢。 I am making points so that once you suggest V4 patch, its ll be like fully good.:) 我要指出的是,以便一旦您建议使用V4补丁,它的效果就会很好。:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM