我正在尝试使用d3.js实现水平条形图。某些图表标签太长。 如何对y aixs上的图表标签进行自动换行?
源代码:
var data = [{"Name": "Label 1", "Count": "428275" }, { "Name": "Label 2", "Count": "365005" }, { "Name": "Label 3", "Count": "327619" }];
var m = [30, 10, 10, 310],
w = 1000 - m[1] - m[3],
h = 550 - m[0] - m[2];
var format = d3.format(",.0f");
var x = d3.scale.linear().range([0, w + 10]),
y = d3.scale.ordinal().rangeRoundBands([0, h], .4);
var xAxis = d3.svg.axis().scale(x).orient("bottom").tickSize(h),
yAxis = d3.svg.axis().scale(y).orient("left").tickSize(0);
$("#chartrendering").empty();
var svg = d3.select("#chartrendering").append("svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.append("g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
// Set the scale domain.
x.domain([0, d3.max(data, function (d) { return d.Count; })]);
y.domain(data.map(function (d) { return d.Name; }));
var bar = svg.selectAll("g.bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function (d) { return "translate(0," + y(d.Name) + ")"; });
bar.append("rect")
.attr("width", function (d) { return x(d.Count); })
.attr("height", y.rangeBand());
bar.append("text")
.attr("class", "value")
.attr("x", function (d) { return x(d.Count); })
.attr("y", y.rangeBand() / 2)
.attr("dx", +55)
.attr("dy", ".35em")
.attr("text-anchor", "end")
.text(function (d) { return format(d.Count); });
svg.append("g")
.attr("class", "x axis")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
答案 0 :(得分:9)
这是我通过将各个位拉在一起而编写的工作实现。正如另一个答案所暗示的那样,foreignObject仍然是可行的方法。首先是功能:
var insertLinebreaks = function (t, d, width) {
var el = d3.select(t);
var p = d3.select(t.parentNode);
p.append("foreignObject")
.attr('x', -width/2)
.attr("width", width)
.attr("height", 200)
.append("xhtml:p")
.attr('style','word-wrap: break-word; text-align:center;')
.html(d);
el.remove();
};
这将获取文本元素(t),文本内容(d)和要包装的宽度。然后它获取text
对象的parentNode,并在其中附加foreignObject
节点,其中添加了xhtml:p
。 foreignObject
设置为所需width
,偏移-width/2
为中心。最后,删除原始文本元素。
然后可以按如下方式将其应用于轴元素:
d3.select('#xaxis')
.selectAll('text')
.each(function(d,i){ insertLinebreaks(this, d, x1.rangeBand()*2 ); });
这里我使用了rangeBand来获取宽度(图中有2个条形的* 2)。
答案 1 :(得分:5)
我一直在寻找这个问题的解决方案,并发现Mike Bostock使用D3发布了working example。示例显示适用于x轴,但可以很容易地适应y轴。
答案 2 :(得分:2)
这是我写的一个函数,不仅用于解决y轴自动换行问题,还包括长度超过1行的单词,并且还在标签的中心对齐“tick”:
请参阅此代码段:
var tempArray2 = [{date: "2017/3/11", ratio: 1}, {date: "2017/3/12", ratio: 0.5}, {date: "2017/3/13", ratio: 0.3}, {date: "2017/3/14", ratio: 0}, {date: "2017/3/15", ratio: 0.8}];
var margin = {
top: 20,
right: 20,
bottom: 40,
left: 80
},
width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
barHeight = 40;
labelWidth = 0;
tempArray2.sort(function(a, b) {
return new Date(a.date) - new Date(b.date);
})
dateRange = Math.round((new Date(tempArray2[tempArray2.length - 1].date) - new Date(tempArray2[0].date)) / 1000 / 3600 / 24);
svg = d3.select('body')
.append("svg")
.attr("style", "width: 500px\; height: 300px\;");
var x = d3.scaleUtc().range([0, width])
.domain([toUTCDate(tempArray2[0].date), calculateDays(toUTCDate(tempArray2[tempArray2.length - 1].date), 1)]);
var y = d3.scaleBand()
.range([height, 0])
.padding(0.1)
.domain(["Domain for testinginginging", "Another domain used for testing", "Horizontal bar"]);
passBar = svg.selectAll(".passBar")
.data(tempArray2)
.enter();
passBar.append("rect")
.attr("class", "passBar")
.attr("height", barHeight)
.attr("width", function(d) {
return x(calculateDays(toUTCDate(d.date), d.ratio)) - x(toUTCDate(d.date));
})
.attr("y", y("Horizontal bar") + (y.bandwidth() - barHeight) / 2)
.attr("transform", function(d) {
return "translate(" + (margin.left + x(toUTCDate(d.date))) + ", 0)";
});
failBar = svg.selectAll(".failBar")
.data(tempArray2)
.enter();
failBar.append("rect")
.attr("class", "failBar")
.attr("height", barHeight)
.attr("width", function(d) {
return x(calculateDays(toUTCDate(d.date), 1 - d.ratio)) - x(toUTCDate(d.date));
})
.attr("y", y("Horizontal bar") + (y.bandwidth() - barHeight) / 2)
.attr("transform", function(d) {
return "translate(" + (margin.left + x(toUTCDate(d.date)) + x(calculateDays(toUTCDate(d.date), d.ratio)) - x(toUTCDate(d.date))) + ", 0)";
});
//add grid lines
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(" + margin.left + "," + height + ")")
.call(make_x_gridlines(dateRange)
.tickSize(-height)
.tickFormat("")
)
// always draw axis at last
svg.append("g")
.attr("transform", "translate(" + margin.left + "," + height + ")")
.attr("class", "xAxis")
.call(d3.axisBottom(x).ticks(dateRange).tickFormat(d3.utcFormat("%m-%d")))
.selectAll("text")
.style("text-anchor", "middle");
svg.append("g")
.attr("transform", "translate(" + margin.left + ", 0)")
.attr("class", "yAxis")
.call(d3.axisLeft(y))
.selectAll("text")
.attr("class", "cateName")
.style("text-anchor", "start")
.call(wrapText, margin.left - 13);
function calculateDays(date, number) {
date.setUTCDate(date.getUTCDate() + number);
return date;
}
function make_x_gridlines(tickTime) {
return d3.axisBottom(x).ticks(tickTime);
}
function toUTCDate(input) {
var tempDate = new Date(input);
return new Date(Date.UTC(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate()));
}
function wrapText(text, width) {
text.each(function() {
var text = d3.select(this),
textContent = text.text(),
tempWord = addBreakSpace(textContent).split(/\s+/),
x = text.attr('x'),
y = text.attr('y'),
dy = parseFloat(text.attr('dy') || 0),
tspan = text.text(null).append('tspan').attr('x', x).attr('y', y).attr('dy', dy + 'em');
for (var i = 0; i < tempWord.length; i++) {
tempWord[i] = calHyphen(tempWord[i]);
}
textContent = tempWord.join(" ");
var words = textContent.split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
spanContent,
breakChars = ['/', '&', '-'];
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(' '));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
spanContent = line.join(' ');
breakChars.forEach(char => {
// Remove spaces trailing breakChars that were added above
spanContent = spanContent.replace(char + ' ', char);
});
tspan.text(spanContent);
line = [word];
tspan = text.append('tspan').attr('x', x).attr('y', y).attr('dy', lineHeight+'em').text(word);
}
}
var emToPxRatio = parseInt(window.getComputedStyle(text._groups[0][0]).fontSize.slice(0, -2));
text.attr("transform", "translate(-" + (margin.left - 13) + ", -" + lineHeight + ")");
function calHyphen(word) {
tspan.text(word);
if (tspan.node().getComputedTextLength() > width) {
var chars = word.split('');
var asword = "";
for (var i = 0; i < chars.length; i++) {
asword += chars[i];
tspan.text(asword);
if (tspan.node().getComputedTextLength() > width) {
if (chars[i - 1] !== "-") {
word = word.slice(0, i - 1) + "- " + calHyphen(word.slice(i - 1));
}
i = chars.length;
}
}
}
return word;
}
});
function addBreakSpace(inputString) {
var breakChars = ['/', '&', '-']
breakChars.forEach(char => {
// Add a space after each break char for the function to use to determine line breaks
inputString = inputString.replace(char, char + ' ');
});
return inputString;
}
}
svg {
width: 100%;
height: 100%;
position: center;
}
.passBar {
fill: #a6f3a6;
}
.failBar {
fill: #f8cbcb;
}
.grid line {
stroke: white;
stroke-width: 2px;
}
.grid path {
stroke-width: 0;
}
.xAxis {
font-size: 15px;
shape-rendering: crispEdges;
}
.yAxis {
font-size: 15px;
shape-rendering: crispEdges;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<body></body>
关于自动换行的主要功能是这一个:wrapText
。
修改自
https://bl.ocks.org/ericsoco/647db6ebadd4f4756cae
和
https://bl.ocks.org/mbostock/7555321
希望它可以提供帮助。
答案 3 :(得分:1)
你不能在SVG中进行自动换行。您可以使用foreignObject
和HTML div
来实现此目的,但这需要修改创建轴标签的代码。或者,您可以旋转轴标签,以便它们有更多空间。有关如何执行此操作,请参阅here。
答案 4 :(得分:0)
这里有一些代码可以将Mike Bostock的chart()
函数转换为angular-nvd3。有关背景信息,请参阅https://github.com/krispo/angular-nvd3/issues/36。
discretebar: {
dispatch: {
renderEnd: function(e){
d3.selectAll(".tick text").call(wrap,_chart.xAxis.rangeBand());
}
}
},
callback: function(chart){
_chart = chart; //global var
}
}