#javascript #d3.js
#javascript #d3.js
Вопрос:
Я пытаюсь разработать визуальную диаграмму пончиков d3, которая просто принимает определенный пользователем процент — где угодно от 0 до 100%. Исходя из этого значения, я хочу, чтобы у пончика были сегменты, равные пропорции. Например, если 50%, то будет добавлена ровно половина сегментов диаграммы пончика. Аналогично, если 100%, то будет нарисована вся диаграмма пончиков; все сегменты будут добавлены. Я не мог понять, как добиться этого элегантным способом, но я нашел грубый обходной путь, который вы можете увидеть ниже во фрагменте.
var data =
[{'value':0,'interval':6.25},
{'value':6.25,'interval':6.25},
{'value':12.5,'interval':6.25},
{'value':18.75,'interval':6.25},
{'value':25,'interval':6.25},
{'value':31.25,'interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25}];
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.linear()
.range(["#0005fd","#00fe80"]).domain([0,35]);
var explode = function(x,index) {
var offset = (index==5) ? 80:0;
var angle = (x.startAngle x.endAngle)/2;
var xOff = Math.sin(angle)*offset;
var yOff = -Math.cos(angle)*offset;
return "translate(" xOff "," yOff ")";
}
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(radius - 70);
var pie = d3.layout.pie()
.padAngle(.05)
.sort(null)
.value(function(d) { return d.interval; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" width / 2 "," height / 2 ")");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
//.attr('transform', explode)
.style('stroke','none')
.style("fill", function(d) {
if (d.data.value=='none') {
return 'none'
}
return color(d.data.value); });
g.append("text")
.attr("transform", function(d) { return "translate(" arc.centroid(d) ")"; })
.attr("dy", ".35em")
//.text(function(d) { return d.data.age; });
svg.append('text')
.text('37.5%') // magic number
.attr('font-family','Play')
.attr('font-size','140px')
.attr('text-anchor','middle')
.attr('x',0)
.attr('y',40);
function type(d) {
d.interval = d.interval;
return d;
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
По сути, происходит то, что доля записей со значениями в записях с надписью «нет» составляет 37,5% — это мое магическое число (6 значений и 10 нулей, таким образом, 6/16 = 37,5%). Излишне говорить, что это вообще не масштабируется.
Вопрос
Есть ли какие-либо встроенные средства или другие менее трудоемкие решения на моем конкретном этапе? Я просто хочу иметь возможность передавать число от 0 до 100 в функцию, а затем извлекать этот процент сегментов пончика. В моем конкретном случае я выбрал 6.25, потому что он казался наиболее эстетичным.
Возможно, пользовательская заливка прозрачностью для имитации эффекта разнесенных сегментов? Кажется слишком хакерским…
Примечание: Версии являются необязательными. То есть я не против решений d3.v5, я просто использовал d3.v3, поскольку я еще не использовал donuts в версии 5.
Комментарии:
1. Вам нужны только целые сегменты? Или вам нужно показывать фрагменты сегментов в случае, если процент не равен круглому числу (сегментов)?
2. @AndrewReid В любом случае, я думаю, для меня было бы нормально. Фрагменты сегментов были бы идеальными, но я согласен с небольшой ошибкой размера, если это слишком хлопотно. Возможно, установив относительно небольшую длину сегмента, можно убедиться, что расхождение не будет слишком большим.
Ответ №1:
Самый простой способ сделать это — добавить еще одну дугу поверх остальных сегментов. Эта дуга может быть произвольной длины, поэтому она покрывает все сегменты, которые не нужно показывать. Это может быть сделано с:
var percentage = .35;
g.append("path")
.attr("d", d3.svg.arc()
.endAngle(Math.PI*2)
.startAngle(percentage * Math.PI*2)
.outerRadius(radius - 10)
.innerRadius(radius - 70)
)
.attr("fill","white")
Мы начинаем с конечного угла, Math.PI * 2, который равен одному полному повороту, что соответствует 100% завершению. Затем мы движемся назад с меньшим начальным углом, покрывая все между 100% и тем процентом, который у нас есть.
В зависимости от того, как вы хотите ее оформить, вы могли бы даже сделать ее слегка прозрачной, чтобы показать, какая часть является неполной.
Вот пример:
var data = d3.range(16).map(function(d) {
return { value: d*6.25, interval: 6.25 };
})
var width = 400,
height = 400,
radius = Math.min(width, height) / 2;
var color = d3.scale.linear()
.range(["#0005fd","#00fe80"]).domain([0,35]);
var explode = function(x,index) {
var offset = (index==5) ? 80:0;
var angle = (x.startAngle x.endAngle)/2;
var xOff = Math.sin(angle)*offset;
var yOff = -Math.cos(angle)*offset;
return "translate(" xOff "," yOff ")";
}
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(radius - 70);
var pie = d3.layout.pie()
.padAngle(.05)
.sort(null)
.value(function(d) { return d.interval; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" width / 2 "," height / 2 ")");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
//.attr('transform', explode)
.style('stroke','none')
.style("fill", function(d) {return color(d.data.value); });
g.append("text")
.attr("transform", function(d) { return "translate(" arc.centroid(d) ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.data.age; });
// Extra arc:
var percentage = .35;
g.append("path")
.attr("d", d3.svg.arc()
.endAngle(Math.PI*2)
.startAngle(percentage * Math.PI*2)
.outerRadius(radius - 10)
.innerRadius(radius - 70)
)
.attr("fill","white")
svg.append('text')
.text(percentage * 100 "%") // magic number
.attr('font-family','Play')
.attr('font-size','140px')
.attr('text-anchor','middle')
.attr('x',0)
.attr('y',40);
function type(d) {
d.interval = d.interval;
return d;
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
Этот подход также позволяет легко анимировать:
var data = d3.range(16).map(function(d) {
return { value: d*6.25, interval: 6.25 };
})
var width = 400,
height = 400,
radius = Math.min(width, height) / 2;
var color = d3.scale.linear()
.range(["#0005fd","#00fe80"]).domain([0,35]);
var explode = function(x,index) {
var offset = (index==5) ? 80:0;
var angle = (x.startAngle x.endAngle)/2;
var xOff = Math.sin(angle)*offset;
var yOff = -Math.cos(angle)*offset;
return "translate(" xOff "," yOff ")";
}
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(radius - 70);
var pie = d3.layout.pie()
.padAngle(.05)
.sort(null)
.value(function(d) { return d.interval; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" width / 2 "," height / 2 ")");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
//.attr('transform', explode)
.style('stroke','none')
.style("fill", function(d) {return color(d.data.value); });
g.append("text")
.attr("transform", function(d) { return "translate(" arc.centroid(d) ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.data.age; });
// Extra arc:
var percentage = .35;
var coverArc = g.append("path")
.attr("fill","white")
var label = svg.append('text')
.text(percentage * 100 "%") // magic number
.attr('font-family','Play')
.attr('font-size','140px')
.attr('text-anchor','middle')
.attr('x',0)
.attr('y',40);
function transition() {
coverArc
.transition()
.tween("d", function(d) {
var that = d3.select(this),
i = d3.interpolateNumber(0, percentage);
return function(t) {
that.attr("d", d3.svg.arc()
.endAngle(Math.PI*2)
.startAngle(i(t) * Math.PI*2)
.outerRadius(radius - 10)
.innerRadius(radius - 70)
)
label.text(Math.round(i(t) * 100) "%");
};
})
.duration(1000)
// other way:
.transition()
.tween("d", function(d) {
var that = d3.select(this),
i = d3.interpolateNumber(percentage, 0);
return function(t) {
that.attr("d", d3.svg.arc()
.endAngle(Math.PI*2)
.startAngle(i(t) * Math.PI*2)
.outerRadius(radius - 10)
.innerRadius(radius - 70)
)
label.text(Math.round(i(t) * 100) "%");
};
})
.duration(1000)
.each("end",transition);
}
transition();
function type(d) {
d.interval = d.interval;
return d;
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
В ответе используется d3v3, для d3v4 есть некоторые незначительные изменения, касающиеся, в частности, arc, d3.svg.arc теперь является d3.arc, d3.layout.круг теперь d3.pie и d3.scale.линейный теперь d3.scaleLinear, а для анимации, используемой во 2-м фрагменте, переход.теперь каждый является transition.on:
var data = d3.range(16).map(function(d) {
return { value: d*6.25, interval: 6.25 };
})
var width = 400,
height = 400,
radius = Math.min(width, height) / 2;
var color = d3.scaleLinear()
.range(["#0005fd","#00fe80"]).domain([0,35]);
var explode = function(x,index) {
var offset = (index==5) ? 80:0;
var angle = (x.startAngle x.endAngle)/2;
var xOff = Math.sin(angle)*offset;
var yOff = -Math.cos(angle)*offset;
return "translate(" xOff "," yOff ")";
}
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(radius - 70);
var pie = d3.pie()
.padAngle(.05)
.sort(null)
.value(function(d) { return d.interval; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" width / 2 "," height / 2 ")");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
//.attr('transform', explode)
.style('stroke','none')
.style("fill", function(d) {return color(d.data.value); });
g.append("text")
.attr("transform", function(d) { return "translate(" arc.centroid(d) ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.data.age; });
// Extra arc:
var percentage = .35;
var coverArc = g.append("path")
.attr("fill","white")
var label = svg.append('text')
.text(percentage * 100 "%") // magic number
.attr('font-family','Play')
.attr('font-size','140px')
.attr('text-anchor','middle')
.attr('x',0)
.attr('y',40);
function transition() {
coverArc
.transition()
.tween("d", function(d) {
var that = d3.select(this),
i = d3.interpolateNumber(0, percentage);
return function(t) {
that.attr("d", d3.arc()
.endAngle(Math.PI*2)
.startAngle(i(t) * Math.PI*2)
.outerRadius(radius - 10)
.innerRadius(radius - 70)
)
label.text(Math.round(i(t) * 100) "%");
};
})
.duration(1000)
// other way:
.transition()
.tween("d", function(d) {
var that = d3.select(this),
i = d3.interpolateNumber(percentage, 0);
return function(t) {
that.attr("d", d3.arc()
.endAngle(Math.PI*2)
.startAngle(i(t) * Math.PI*2)
.outerRadius(radius - 10)
.innerRadius(radius - 70)
)
label.text(Math.round(i(t) * 100) "%");
};
})
.duration(1000)
.on("end",transition);
}
transition();
function type(d) {
d.interval = d.interval;
return d;
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<script src="http://d3js.org/d3.v5.min.js"></script>