【问题标题】:d3.js stacked bar chart sort / change order of individual values within a stackd3.js 堆叠条形图排序/更改堆栈中单个值的顺序
【发布时间】:2020-10-28 04:00:33
【问题描述】:

在我之前的问题中:d3.js stacked bar chart - modify stack order logic 我了解到 d3 没有现成的解决方案来按堆栈中的单个值进行排序。 D3 的stack.order() 只能对整个系列进行排序。我还了解到我需要编写一个自定义函数来实现这一点。

在我进入这个函数之前,让我解释一下这里的动机。我想在总和旁边显示比例,这是 d3 堆叠条形图擅长的。但是,我希望重点放在排名上。这就是为什么我需要更改单个矩形在每个堆栈中的排序顺序,以反映排名的任何变化。最大值应该在栈顶,最小值应该出现在栈底。如上一篇文章所述,堆叠条形图的典型输出仅根据初始顺序将所有矩形集中到堆栈中。

再次代码供参考:

var margins = {
  top: 100,
  bottom: 300,
  left: 100,
  right: 100
};

var height = 600;
var width = 900;

var totalWidth = width + margins.left + margins.right;
var totalHeight = height + margins.top + margins.bottom;

var svg = d3.select('body')
  .append('svg')
  .attr('width', totalWidth)
  .attr('height', totalHeight);

var graphGroup = svg.append('g')
  .attr('transform', "translate(" + margins.left + "," + margins.top + ")");


var data = [{
    period: 't1',
    fmc1: 2,
    fmc2: 5,
    fmc3: 6,
    fmc4: 9,
    fmc5: 10
  },
  {
    period: 't2',
    fmc1: 3,
    fmc2: 4,
    fmc3: 9,
    fmc4: 8,
    fmc5: 11
  },
  {
    period: 't3',
    fmc1: 3,
    fmc2: 5,
    fmc3: 15,
    fmc4: 12,
    fmc5: 10
  },
];

var groups = d3.map(data, function(d) {
  return (d.period)
}).keys();

var subgroups = Object.keys(data[0]).slice(1);

var stackedData = d3.stack()
  .keys(subgroups)
  (data);

//console.log(stackedData);

var yScale = d3.scaleLinear()
  .domain([0, 80])
  .range([height, 0]);

var xScale = d3.scaleBand()
  .domain(['t1', 't2', 't3'])
  .range([0, width])
  .padding([.5]);

var colorScale = d3.scaleOrdinal()
  .domain(subgroups)
  .range(["#003366", "#366092", "#4f81b9", "#95b3d7", "#b8cce4", "#e7eef8", "#a6a6a6", "#d9d9d9", "#ffffcc", "#f6d18b", "#e4a733", "#b29866", "#a6a6a6", "#d9d9d9", "#e7eef8", "#b8cce4", "#95b3d7", "#4f81b9", "#366092", "#003366"].reverse());

graphGroup.append("g")
  .selectAll("g")
  .data(stackedData)
  .enter().append("g")
  .attr("fill", function(d) {
    return colorScale(d.key);
  })
  .selectAll("rect")
  .data(function(d) {
    return d;
  })
  .enter().append("rect")
  .attr("x", function(d) {
    return xScale(d.data.period);
  })
  .attr("y", function(d) {
    return yScale(d[1]);
  })
  .attr("height", function(d) {
    return yScale(d[0]) - yScale(d[1]);
  })
  .attr("width", xScale.bandwidth());
<script src="https://d3js.org/d3.v5.min.js"></script>

调用d3.stack() 后数据结构变得更加复杂,这对我来说是一个挑战。该任务类似于根据嵌套数组中的对象值对整个数组数组进行排序。

我最好的尝试是:

var sortTest = stackedData.sort(stackedData[1].sort((a, b) => a[0] - b[1]));

返回错误:

排序函数必须是函数或未定义

结果证明这比我想象的更具挑战性,因此我尝试在堆栈之前对数据进行排序,但这导致堆栈不准确,关系丢失。

这个类似的问题带来了一线希望:Sorting a d3.js stacked bar chart

但是,由于该方法没有使用d3.stack(),因此该解决方案不适用于我的特定情况或使用d3.stack() 的任何情况。

问题

当数据为d3.stack()格式时,按单个值排序数据的解决方案是什么?

【问题讨论】:

  • 这能回答你的问题吗? Sorting a d3.js stacked bar chart
  • 上面的链接实际上回答了 OP 的问题,但请注意,它的代码(问题和答案)确实使用d3.stack,所以它可能不适合这里的 OP(问题中的原始 bl.ocks 链接现在被重定向到使用 d3.stack 的 Observable 页面,因此该代码与提问者链接的原始代码不同。
  • 正如 GerardoFurtado 指出的那样,帖子不使用堆栈,因此底层数据结构不同。我无法解决我的问题。谢谢你的建议,都一样。

标签: javascript sorting d3.js


【解决方案1】:

我会使用自定义堆栈函数,如下所示。我试图获得与堆积条形图类似的数据结构,只是我按键而不是按索引对值进行分组。这样,我可以更轻松地对它们进行排序。

var margins = {
  top: 30,
  bottom: 50,
  left: 30,
  right: 30
};

var height = 300;
var width = 600;

var totalWidth = width + margins.left + margins.right;
var totalHeight = height + margins.top + margins.bottom;

var svg = d3.select('body')
  .append('svg')
  .attr('width', totalWidth)
  .attr('height', totalHeight);

var graphGroup = svg.append('g')
  .attr('transform', "translate(" + margins.left + "," + margins.top + ")");

var data = [{
    period: 't1',
    fmc1: 2,
    fmc2: 5,
    fmc3: 6,
    fmc4: 9,
    fmc5: 10
  },
  {
    period: 't2',
    fmc1: 3,
    fmc2: 4,
    fmc3: 9,
    fmc4: 8,
    fmc5: 11
  },
  {
    period: 't3',
    fmc1: 3,
    fmc2: 5,
    fmc3: 15,
    fmc4: 12,
    fmc5: 10
  },
];

var groups = data.map(function(d) {
  return d.period;
});
var subgroups = Object.keys(data[0]).slice(1);

var stackedData = data.map(function(d) {
  var orderedKeys = subgroups.slice().sort(function(a, b) {
    return d[a] - d[b];
  });
  var bottom = 0;

  var result = orderedKeys.map(function(key) {
    var value = d[key];
    var result = [bottom, bottom + value];
    result.data = d;
    result.key = key;
    bottom += value;
    return result;
  });
  result.key = d.period;
  return result;
});
console.log(stackedData);

var yScale = d3.scaleLinear()
  .domain([0, 80])
  .range([height, 0]);

var xScale = d3.scaleBand()
  .domain(['t1', 't2', 't3'])
  .range([0, width])
  .padding([.5]);

var colorScale = d3.scaleOrdinal()
  .domain(subgroups)
  .range(["#003366", "#366092", "#4f81b9", "#95b3d7", "#b8cce4", "#e7eef8", "#a6a6a6", "#d9d9d9", "#ffffcc", "#f6d18b", "#e4a733", "#b29866", "#a6a6a6", "#d9d9d9", "#e7eef8", "#b8cce4", "#95b3d7", "#4f81b9", "#366092", "#003366"].reverse());

graphGroup.append("g")
  .selectAll("g")
  .data(stackedData)
  .enter()
  .append("g")
  .selectAll("rect")
  .data(function(d) {
    return d;
  })
  .enter()
  .append("rect")
  .attr("fill", function(d) {
    return colorScale(d.key);
  })
  .attr("x", function(d) {
    return xScale(d.data.period);
  })
  .attr("y", function(d) {
    return yScale(d[1]);
  })
  .attr("height", function(d) {
    return yScale(d[0]) - yScale(d[1]);
  })
  .attr("width", xScale.bandwidth());
<script src="https://d3js.org/d3.v5.js"></script>

【讨论】:

  • 这很有帮助,我认为它几乎就在那里,但我注意到颜色逻辑不成立。不确定这是我的数据集还是其他。您能否确认颜色编码是否始终保持一致?只想对其他堆栈使用与t1 相同的颜色编码——以保持一致性。
  • 我发现只要对t1进行初步排序,颜色逻辑就可以了。
  • 太棒了!这对您来说是否足够,或者您想看看是否有解决方法?
猜你喜欢
  • 2016-04-15
  • 2022-08-19
  • 2015-12-07
  • 2018-02-15
  • 1970-01-01
  • 1970-01-01
  • 2017-09-17
  • 1970-01-01
  • 2021-07-13
相关资源
最近更新 更多