简体   繁体   English

D3js Grouped Scatter图,没有碰撞

[英]D3js Grouped Scatter plot with no collision

I am using this example to make scatter plot: 我正在使用此示例制作散点图:

https://www.d3-graph-gallery.com/graph/boxplot_show_individual_points.html https://www.d3-graph-gallery.com/graph/boxplot_show_individual_points.html

Now this example uses jitter to randomize x position of the dots for demonstration purpose, but my goal is to make these dots in that way so they don't collide and to be in the same row if there is collision. 现在这个例子使用抖动来随机化点的x位置以用于演示目的,但我的目标是以这种方式制作这些点,以便它们不会发生碰撞并且如果存在碰撞则处于同一行。

Best example of what I am trying to do (visually) is some sort of beeswarm where data is represented like in this fiddle: 我想要做的最直接的例子(视觉上)是某种形式的假象,其中数据表示如下这个小提琴:

https://jsfiddle.net/n444k759/4/ https://jsfiddle.net/n444k759/4/

Snippet of first example: 第一个例子的片段:

 // set the dimensions and margins of the graph var margin = {top: 10, right: 30, bottom: 30, left: 40}, width = 460 - margin.left - margin.right, height = 400 - margin.top - margin.bottom; // append the svg object to the body of the page var svg = d3.select("#my_dataviz") .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 + ")"); // Read the data and compute summary statistics for each specie d3.csv("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/iris.csv", function(data) { // Compute quartiles, median, inter quantile range min and max --> these info are then used to draw the box. var sumstat = d3.nest() // nest function allows to group the calculation per level of a factor .key(function(d) { return d.Species;}) .rollup(function(d) { q1 = d3.quantile(d.map(function(g) { return g.Sepal_Length;}).sort(d3.ascending),.25) median = d3.quantile(d.map(function(g) { return g.Sepal_Length;}).sort(d3.ascending),.5) q3 = d3.quantile(d.map(function(g) { return g.Sepal_Length;}).sort(d3.ascending),.75) interQuantileRange = q3 - q1 min = q1 - 1.5 * interQuantileRange max = q3 + 1.5 * interQuantileRange return({q1: q1, median: median, q3: q3, interQuantileRange: interQuantileRange, min: min, max: max}) }) .entries(data) // Show the X scale var x = d3.scaleBand() .range([ 0, width ]) .domain(["setosa", "versicolor", "virginica"]) .paddingInner(1) .paddingOuter(.5) svg.append("g") .attr("transform", "translate(0," + height + ")") .call(d3.axisBottom(x)) // Show the Y scale var y = d3.scaleLinear() .domain([3,9]) .range([height, 0]) svg.append("g").call(d3.axisLeft(y)) // Show the main vertical line svg .selectAll("vertLines") .data(sumstat) .enter() .append("line") .attr("x1", function(d){return(x(d.key))}) .attr("x2", function(d){return(x(d.key))}) .attr("y1", function(d){return(y(d.value.min))}) .attr("y2", function(d){return(y(d.value.max))}) .attr("stroke", "black") .style("width", 40) // rectangle for the main box var boxWidth = 100 svg .selectAll("boxes") .data(sumstat) .enter() .append("rect") .attr("x", function(d){return(x(d.key)-boxWidth/2)}) .attr("y", function(d){return(y(d.value.q3))}) .attr("height", function(d){return(y(d.value.q1)-y(d.value.q3))}) .attr("width", boxWidth ) .attr("stroke", "black") .style("fill", "#69b3a2") // Show the median svg .selectAll("medianLines") .data(sumstat) .enter() .append("line") .attr("x1", function(d){return(x(d.key)-boxWidth/2) }) .attr("x2", function(d){return(x(d.key)+boxWidth/2) }) .attr("y1", function(d){return(y(d.value.median))}) .attr("y2", function(d){return(y(d.value.median))}) .attr("stroke", "black") .style("width", 80) var simulation = d3.forceSimulation(data) .force("x", d3.forceX(function(d) { return x(d.Species); })) // .force("y", d3.forceX(function(d) { return y(d.Sepal_lenght) })) .force("collide", d3.forceCollide() .strength(1) .radius(4+1)) .stop(); for (var i = 0; i < data.length; ++i) simulation.tick(); // Add individual points with jitter var jitterWidth = 50 svg .selectAll("points") .data(data) .enter() .append("circle") .attr("cx", function(d){return( dx )}) .attr("cy", function(d){return(y(d.Sepal_Length))}) .attr("r", 4) .style("fill", "white") .attr("stroke", "black") }) 
 <!-- Load d3.js --> <script src="https://d3js.org/d3.v4.js"></script> <!-- Create a div where the graph will take place --> <div id="my_dataviz"></div> 

I tried to make something like this: 我试着做这样的事情:

var simulation = d3.forceSimulation(data)
  .force("x", d3.forceX(function(d) { return x(d.Species); }))
  .force("collide", d3.forceCollide(4)
            .strength(1)
            .radius(4+1))
  .stop();

  for (var i = 0; i < 120; ++i) simulation.tick();

// Append circle points
svg.selectAll(".point")
.data(data)
.enter()
.append("circle")
    .attr("cx", function(d){ 
        return(x(d.x))
    })
    .attr("cy", function(d){
        return(y(d.y))
    })
    .attr("r", 4)
    .attr("fill", "white")
    .attr("stroke", "black")

but it does not even prevent collision and I am a bit confused with it. 但它甚至没有防止碰撞,我对它有点困惑。

I also tried to modify plot from this example: 我还试图从这个例子修改情节:

http://bl.ocks.org/asielen/92929960988a8935d907e39e60ea8417 http://bl.ocks.org/asielen/92929960988a8935d907e39e60ea8417

where beeswarm looks exactly what I need to achieve. 在那里,beeswarm看起来正是我需要实现的目标。 But this code is way too expanded as it is made to fit the purpose of reusable charts and I can't track what exact formula is used to achieve this: 但是这个代码太过扩展,因为它符合可重用图表的目的,我无法跟踪用于实现此目的的确切公式:

在此输入图像描述

Any help would be great.. Thanks 任何帮助都会很棒..谢谢

Here's a quick example which combines the ideas of your beeswarm example with your initial boxplot. 这是一个快速示例,它将您的beeswarm示例的想法与您的初始箱图相结合。 I've commented the tricky parts below: 我评论了以下棘手的部分:

 <!DOCTYPE html> <html> <head> </head> <body> <!-- Load d3.js --> <script src="https://d3js.org/d3.v4.js"></script> <!-- Create a div where the graph will take place --> <div id="my_dataviz"></div> <script> // set the dimensions and margins of the graph var margin = { top: 10, right: 30, bottom: 30, left: 40 }, width = 460 - margin.left - margin.right, height = 400 - margin.top - margin.bottom; // append the svg object to the body of the page var svg = d3.select("#my_dataviz") .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 + ")"); // Read the data and compute summary statistics for each specie d3.csv("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/iris.csv", function(data) { // Compute quartiles, median, inter quantile range min and max --> these info are then used to draw the box. var sumstat = d3.nest() // nest function allows to group the calculation per level of a factor .key(function(d) { return d.Species; }) .rollup(function(d) { q1 = d3.quantile(d.map(function(g) { return g.Sepal_Length; }).sort(d3.ascending), .25) median = d3.quantile(d.map(function(g) { return g.Sepal_Length; }).sort(d3.ascending), .5) q3 = d3.quantile(d.map(function(g) { return g.Sepal_Length; }).sort(d3.ascending), .75) interQuantileRange = q3 - q1 min = q1 - 1.5 * interQuantileRange max = q3 + 1.5 * interQuantileRange return ({ q1: q1, median: median, q3: q3, interQuantileRange: interQuantileRange, min: min, max: max }) }) .entries(data) // Show the X scale var x = d3.scaleBand() .range([0, width]) .domain(["setosa", "versicolor", "virginica"]) .paddingInner(1) .paddingOuter(.5) svg.append("g") .attr("transform", "translate(0," + height + ")") .call(d3.axisBottom(x)) // Show the Y scale var y = d3.scaleLinear() .domain([3, 9]) .range([height, 0]) svg.append("g").call(d3.axisLeft(y)) // Show the main vertical line svg .selectAll("vertLines") .data(sumstat) .enter() .append("line") .attr("x1", function(d) { return (x(d.key)) }) .attr("x2", function(d) { return (x(d.key)) }) .attr("y1", function(d) { return (y(d.value.min)) }) .attr("y2", function(d) { return (y(d.value.max)) }) .attr("stroke", "black") .style("width", 40) // rectangle for the main box var boxWidth = 100 svg .selectAll("boxes") .data(sumstat) .enter() .append("rect") .attr("x", function(d) { return (x(d.key) - boxWidth / 2) }) .attr("y", function(d) { return (y(d.value.q3)) }) .attr("height", function(d) { return (y(d.value.q1) - y(d.value.q3)) }) .attr("width", boxWidth) .attr("stroke", "black") .style("fill", "#69b3a2") // Show the median svg .selectAll("medianLines") .data(sumstat) .enter() .append("line") .attr("x1", function(d) { return (x(d.key) - boxWidth / 2) }) .attr("x2", function(d) { return (x(d.key) + boxWidth / 2) }) .attr("y1", function(d) { return (y(d.value.median)) }) .attr("y2", function(d) { return (y(d.value.median)) }) .attr("stroke", "black") .style("width", 80) var r = 8; // create a scale that'll return a discreet value // so that close y values fall in a line var yPtScale = y.copy() .range([Math.floor(y.range()[0] / r), 0]) .interpolate(d3.interpolateRound) .domain(y.domain()); // bucket the data var ptsObj = {}; data.forEach(function(d,i) { var yBucket = yPtScale(d.Sepal_Length); if (!ptsObj[d.Species]){ ptsObj[d.Species] = {}; } if (!ptsObj[d.Species][yBucket]){ ptsObj[d.Species][yBucket] = []; } ptsObj[d.Species][yBucket].push({ cy: yPtScale(d.Sepal_Length) * r, cx: x(d.Species) }); }); // determine the x position for (var x in ptsObj){ for (var row in ptsObj[x]) { var v = ptsObj[x][row], // array of points m = v[0].cx, // mid-point l = m - (((v.length / 2) * r) - r/2); // left most position based on count of points in the bucket v.forEach(function(d,i){ d.cx = l + (r * i); // x position }); } } // flatten the data structure var flatData = Object.values(ptsObj) .map(function(d){return Object.values(d)}) .flat(2); svg .selectAll("points") .data(flatData) .enter() .append("circle") .attr("cx", function(d) { return d.cx; }) .attr("cy", function(d) { return d.cy; }) .attr("r", 4) .style("fill", "white") .attr("stroke", "black") }) </script> </body> </html> 

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

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