簡體   English   中英

D3js Grouped Scatter圖,沒有碰撞

[英]D3js Grouped Scatter plot with no collision

我正在使用此示例制作散點圖:

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

現在這個例子使用抖動來隨機化點的x位置以用於演示目的,但我的目標是以這種方式制作這些點,以便它們不會發生碰撞並且如果存在碰撞則處於同一行。

我想要做的最直接的例子(視覺上)是某種形式的假象,其中數據表示如下這個小提琴:

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

第一個例子的片段:

 // 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> 

我試着做這樣的事情:

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")

但它甚至沒有防止碰撞,我對它有點困惑。

我還試圖從這個例子修改情節:

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

在那里,beeswarm看起來正是我需要實現的目標。 但是這個代碼太過擴展,因為它符合可重用圖表的目的,我無法跟蹤用於實現此目的的確切公式:

在此輸入圖像描述

任何幫助都會很棒..謝謝

這是一個快速示例,它將您的beeswarm示例的想法與您的初始箱圖相結合。 我評論了以下棘手的部分:

 <!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