Проблема заключается в такой, казалось бы, безобидной привязке данных:
svg.selectAll("rect")
.data(dataset)// <----- here
.sort(function(a, b) {
//etc...
Проблема в том, что вы добавляете данные к ранее отсортированным прямоугольникам по их индексам .Это мешает с сортировкой.
Вот простое объяснение.Предположим, у вас есть 5 элементов (в верхнем регистре), соответствующих массиву данных [d, a, e, c, b]
(в нижнем регистре), который вы хотите отсортировать по алфавиту:
+------+------+------+------+------+
| D(d) | A(a) | E(e) | C(c) | B(b) |
+------+------+------+------+------+
После сортировки у вас есть:
+------+------+------+------+------+
| A(a) | B(b) | C(c) | D(d) | E(e) |
+------+------+------+------+------+
При повторном связывании данных (помните, тот же массив [d, a, e, c, b]
, в этом порядке) с уже отсортированными элементами, у вас есть:
+------+------+------+------+------+
| A(d) | B(a) | C(e) | D(c) | E(b) |
+------+------+------+------+------+
Если вы отсортируете по данным (что и делает selection.sort()
), у вас будет:
+------+------+------+------+------+
| B(a) | E(b) | D(c) | A(d) | C(e) |
+------+------+------+------+------+
И поэтому кажется, что random , как вы сказали.
Существует два решения:
Решение 1: сбросить привязку данных
Самое простое - просто сбросить привязку данных, поскольку у прямоугольников уже есть данные.Вот пример с 3 прямоугольниками, например, каждый из которых имеет наибольшую высоту, ширину или непрозрачность:
var data = [
[45, 90, 0.5],
[95, 10, 0.2],
[15, 40, 0.9]
];
var xScale = d3.scaleOrdinal()
.domain([0, 1, 2])
.range([0, 100, 200]);
var svg = d3.select("svg");
var span = d3.select("span")
svg.selectAll(null)
.data(data)
.enter()
.append("rect")
.attr("width", d => d[1])
.attr("height", d => d[0])
.style("opacity", d => d[2])
.attr("x", (_, i) => xScale(i));
var s = -1;
svg.on("click", function() {
s = (s + 1);
span.html(["height", "width", "opacity"][s % 3]);
svg.selectAll("rect")
.sort(function(a, b) {
return d3.ascending(a[s % 3], b[s % 3]);
})
.transition()
.duration(500)
.attr("x", function(d, i) {
return xScale(i);
})
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<p>Sort by: <span></span></p>
<svg></svg>
Решение 2: использование ключевой функции
Второе решение - использование ключевой функции , поэтому вы привязываете данные не по их индексам, а по уникальному идентификатору для каждого прямоугольника.
В моих фиктивных данных каждое значение высоты уникально, поэтому я просто делаю:
.data(dataset, d=>d[0])
Вот демоверсия:
var data = [
[45, 90, 0.5],
[95, 10, 0.2],
[15, 40, 0.9]
];
var xScale = d3.scaleOrdinal()
.domain([0, 1, 2])
.range([0, 100, 200]);
var svg = d3.select("svg");
var span = d3.select("span")
svg.selectAll(null)
.data(data)
.enter()
.append("rect")
.attr("width", d => d[1])
.attr("height", d => d[0])
.style("opacity", d => d[2])
.attr("x", (_, i) => xScale(i));
var s = -1;
svg.on("click", function() {
s = (s + 1);
span.html(["height", "width", "opacity"][s % 3]);
svg.selectAll("rect")
.data(data, d=>d[0])
.sort(function(a, b) {
return d3.ascending(a[s % 3], b[s % 3]);
})
.transition()
.duration(500)
.attr("x", function(d, i) {
return xScale(i);
})
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<p>Sort by: <span></span></p>
<svg></svg>