I am trying to implement the horizontal bar chart using d3.js.Some of the chart labels are too long.
How to do word wrap for the chart labels on y aixs?
Source code:
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);
Here is a working implementation I've written by pulling together various bits. As the other answer suggests, foreignObject is still the way to go. First the function:
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();
};
This takes in a text element (t), the text content (d), and the width to wrap to. It then gets the parentNode of the text object, and attaches a foreignObject node to it into which an xhtml:p is added. The foreignObject is set to the desired width and offset -width/2 to center. Finally, the original text element is deleted.
This can then be applied to your axis elements as follows:
d3.select('#xaxis')
.selectAll('text')
.each(function(d,i){ insertLinebreaks(this, d, x1.rangeBand()*2 ); });
Here I've used rangeBand to get the width (with *2 for 2 bars on the graph).
I was looking for solutions to this problem, and found that Mike Bostock has published a working example using D3. The example is shown to work for the x-axis, but can easily be adapted for the y-axis.
Here's a function I wrote not only to solve the y-axis word wrap problem, but also wrap word that is more than 1 line in length, and also align the corresponding 'tick' in the center of the label:
Result is like this:
See this snippet:
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>
Main function about word wrap is this one: wrapText.
Modified from
https://bl.ocks.org/ericsoco/647db6ebadd4f4756cae
and
https://bl.ocks.org/mbostock/7555321
Hope it can help.
You can't do automatic word wrap in SVG. You could use foreignObject and HTML divs for that purpose, but that would require modifying the code that creates the axis labels. Alternatively, you can rotate the axis labels so that they have more space. See for example here for how to do that.
Here's some code to plumb Mike Bostock's chart() function into angular-nvd3. For background, see 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
}
}
Related
I'm trying to create a set of text elements and place them above various rect elements so that it looks as if they were inside. the thing is that I haven't been able to accomplish this simple task.
The text elements I need inside the column of rect's are the elements of the array: var dataDnt4 = [42,31,16,4,3,2,1];
I'll leave a running snippet so that you can my progress so far.
Your help is very appreciated. thanks
var icon2 = '<g><path class="st0" d="M23.1,34.9c6.9,0,12.5-5.6,12.5-12.5c0-6.9-5.6-12.5-12.5-12.5c-6.9,0-12.5,5.6-12.5,12.5 C10.6,29.3,16.2,34.9,23.1,34.9L23.1,34.9z"/><path class="st0" d="M39.2,54.6c0.2,0,0.4,0,0.7,0c-3.7-3-6-7.5-6-12.6c0-1.2,0.1-2.4,0.4-3.6c-0.1,0-0.3,0-0.4,0H12.4 C5.5,38.5-0.1,44.1-0.1,51v17.9h23.3C24.1,60.8,30.9,54.6,39.2,54.6L39.2,54.6z"/><path class="st0" d="M76.8,34.9c6.9,0,12.5-5.6,12.5-12.5c0-6.9-5.6-12.5-12.5-12.5c-6.9,0-12.5,5.6-12.5,12.5 C64.2,29.3,69.9,34.9,76.8,34.9L76.8,34.9z"/><path class="st0" d="M87.5,38.5H66c-0.1,0-0.3,0-0.4,0c0.3,1.1,0.4,2.3,0.4,3.6c0,5.1-2.4,9.6-6,12.6c0.2,0,0.4,0,0.7,0 c8.3,0,15.1,6.3,16,14.3H100V51C100,44.1,94.4,38.5,87.5,38.5L87.5,38.5z"/><path class="st0" d="M49.9,54.6c6.9,0,12.5-5.6,12.5-12.5c0-6.9-5.6-12.5-12.5-12.5c-6.9,0-12.5,5.6-12.5,12.5 C37.4,49,43,54.6,49.9,54.6L49.9,54.6z"/><path class="st0" d="M60.7,58.1H39.2c-6.9,0-12.5,5.6-12.5,12.5v17.9h46.5V70.7C73.2,63.7,67.6,58.1,60.7,58.1L60.7,58.1z"/></g>'
var dataDnt4 = [42, 31, 16, 4, 3, 2, 1];
var distanciaRect = [25, 50, 75, 100, 125, 150, 175]
var width = 512,
height = 600
radius = (Math.min(width, height) / 2.5) - 60;
var sym = "%"
var legendTextArr = ["alpha", "beta", "Gamma", "vvv", "www", "xxx", "yyy", "zzz"]
var color_rect = ["#00338D", "#BC204B", "#0091DA", "#eaaa00", "#005eb8", "#f68d2e", "#009444", "#470a68"]
var pie = d3.pie()
.value(function(d) {
return d
})(dataDnt4);
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(radius - (radius / 2.4));
var labelArc = d3.arc()
.outerRadius(radius - 35)
.innerRadius(radius - 35);
var svg = d3.select("#chartdiv")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2.4 + ")");
var title = svg.append("text")
.attr("font-weight", "bold")
.attr("class", "title1")
.html("title 1")
.attr("transform", function() {
return "translate(" + (-184) + "," + (-180) + ")"
})
var title = svg.append("text")
.attr("font-weight", "bold")
.attr("class", "title2")
.html("title 2")
.attr("transform", function() {
return "translate(" + (-184) + "," + (-160) + ")"
})
var legendG = svg.append("g")
.attr("class", "legendG")
.attr("transform", function() {
return "translate(" + (-60) + "," + (155) + ")"
})
var legendG = svg.append("g")
.attr("class", "legendG")
.attr("transform", function() {
return "translate(" + (-60) + "," + (155) + ")"
})
var legendText = legendG.selectAll("text")
.data(distanciaRect)
.enter()
.append("text")
.attr("x", -80)
.attr("y", function(d, i) {
return d + 10
})
.data(legendTextArr)
.html(function(d) {
return d
})
var legends = legendG.selectAll(".rect")
.data(distanciaRect)
.enter()
.append("rect")
.attr("x", -120)
.attr("y", function(d, i) {
return d
})
.attr("width", 25)
.attr("height", 17)
.attr("class", "icon1")
.data(color_rect)
.attr("fill", function(d, i) {
return d
})
var g = svg.selectAll("arc")
.data(pie)
.enter().append("g")
.attr("class", "arc");
function easeInverse(ease) {
return function(e) {
var min = 0,
max = 1;
while (max - min > 1e-3) {
var mid = (max + min) * 0.5;
emid = ease(mid);
if (emid > e) {
max = mid;
} else {
min = mid;
}
}
return max;
}
}
var inverseCubic = easeInverse(d3.easeCubic);
var oneOver2Pi = 1.0 / (2 * Math.PI);
var total_msec = 2000;
g.append("path")
.attr("d", arc)
.attr("transform", function() {
return "translate(" + (-16) + "," + (0) + ")"
})
.style("fill", function(d, i) {
return color_rect[i];
})
.transition()
.ease(d3.easeLinear)
.delay(function(d) {
return total_msec * inverseCubic(d.startAngle * oneOver2Pi);
})
.duration(function(d) {
return total_msec * (inverseCubic(d.endAngle * oneOver2Pi) - inverseCubic(d.startAngle * oneOver2Pi));
})
.attrTween("d", arcTween);
function arcTween(d) {
var i = d3.interpolate(inverseCubic(d.startAngle * oneOver2Pi), inverseCubic(d.endAngle * oneOver2Pi));
return function(t) {
d.endAngle = 2 * Math.PI * d3.easeCubic(i(t));
return arc(d);
}
}
svg.append("g")
.attr("class", "icon2")
.html(icon2);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="chartdiv"></div>
There are numerous ways to do this, so here is one possible way. I would group together the three pieces of the legend--the rectangle, the key text, and the text over the rectangle--in a g element and bind dataDnt4 to each item. The rectangle colour and the legend text can be retrieved by position, i.e. the first dataDnt4 item corresponds to color_rect[0] and legendTextArr[0], the second to color_rect[1] and legendTextArr[1], etc.
I've cut out the code that is not relevant to the positioning of the legend items -- you can restore that in your script.
var width = 512,
height = 600,
radius = (Math.min(width, height) / 2.5) - 60;
var sym = "%"
var legendTextArr = ["alpha", "beta", "Gamma", "vvv", "www", "xxx", "yyy", "zzz"]
var dataDnt4 = [42, 31, 16, 4, 3, 2, 1];
var color_rect = ["#00338D", "#BC204B", "#0091DA", "#eaaa00", "#005eb8", "#f68d2e", "#009444", "#470a68"]
var svg = d3.select("#chartdiv")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2.4 + ")");
var title = svg.append("text")
.attr("font-weight", "bold")
.attr("class", "title1")
.html("scroll down!")
.attr("transform", function() {
return "translate(" + -184 + "," + -180 + ")"
})
var legendG = svg.append("g")
.attr("class", "legendG")
.attr("transform", function() {
// this moves the whole legend box
// you can change this to whatever transformation is appropriate for your chart
return "translate(" + -((width / 2)-40) + "," + 120 + ")"
})
// group each legend item in a `g` element
var legendText = legendG.selectAll("g")
.data(dataDnt4)
.enter()
.append('g')
.attr('transform', function(d, i) {
// instead of having a hard-coded list of multiples of 25, you can multiply
// the array index, `i`, by 25 to get the correct position
return 'translate(0,' + (i*25) + ')';
});
legendText.append("rect")
.attr("width", 25)
.attr("height", 17)
.attr("class", "icon1")
.attr("fill", function(d, i) {
return color_rect[i];
})
// the text "in" the rectangle
// use 'text-anchor: middle' and an x offset of 12.5 (rectangle width / 2)
// to centre the labels
// change the `y` attribute to alter the vertical positioning
legendText.append("text")
.attr("x", 12.5)
.attr("y", 13)
.attr('text-anchor', 'middle')
.attr('fill', 'white')
// d is the items in dataDnt4
.text(function(d) {
return d;
})
// legend text items
legendText.append("text")
.attr("x", 40)
.attr("y", 13)
// take legendTextArr item in position i
.text(function(d,i) {
return legendTextArr[i];
})
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="chartdiv"></div>
You have some errors in your code (e.g. you declare the variables legendG and title twice), and it would probably be helpful for you to run your code through a code linter so you can see the problems that you might not pick up by eye.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
The problem: Using scaleband does not pan smoothly using brush.
https://shanegibney.github.io/D3-v4-Bar-chart-Scaleband-with-Brush/
Here is an example of smooth panning with dates on x-axis.
https://shanegibney.github.io/d3-Bar-Chart-Pan-And-Zoom/
But this only works when the x-domain is continuous.
Using a linearscale, I would like to replace the values on the x-axis with the string in data d.name. The issue is that when zooming new ticks are created in between the value. For example the values 1 and 2 when zoomed will produce values and tciks for 1, 1.5, 2 and 2.5 etc. This means that the names do not match up anymore.
This could be solved with two things;
Prevent new ticks appearing when zooming in. I don't know how to do this.
Replace each label on the x-axis ticks with d.name from the data
These are my attempts to do this,
var xAxis = d3.axisBottom(x)
.tickFormat(function(d, i) {
label = data[i].name;
local.set(this, data[i].name);
return "test";
return data[i].name;
return label;
return i;
return d;
});
None of which work.
The Plunker
https://plnkr.co/edit/zbWu6AV8MNO9N8m0byre
Inside the tickFormat function, check if the numeric tick corresponds to any value in the data array. If it does, return the name, otherwise return nothing:
var xAxis = d3.axisBottom(x)
.tickFormat(function(d) {
var found = data.find(function(e){
return e.num === d
});
return found ? found.name : null;
})
Here is the code with that change:
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
body {
font-family: avenir next, sans-serif;
font-size: 12px;
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
.axis {
stroke-width: 0.5px;
stroke: #888;
font: 10px avenir next, sans-serif;
}
.axis>path {
stroke: #888;
}
</style>
<body>
<div id="totalDistance">
</div>
</body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
/* Adapted from: https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172 */
var data = [{
"name": "A",
"date": "2016-11-09 11:15",
"num": "1",
"distance": "1900"
},
{
"name": "B",
"date": "2016-11-10 10:40",
"num": "2",
"distance": "1500"
},
{
"name": "C",
"date": "2016-11-11 16:45",
"num": "3",
"distance": "2500"
},
{
"name": "D",
"date": "2016-11-12 12:48",
"num": "4",
"distance": "2300"
},
{
"name": "E",
"date": "2016-11-15 20:00",
"num": "5",
"distance": "2000"
}
];
var margin = {
top: 20,
right: 20,
bottom: 90,
left: 50
},
margin2 = {
top: 230,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom,
height2 = 300 - margin2.top - margin2.bottom;
var parseTime = d3.timeParse("%Y-%m-%d %H:%M");
var local = d3.local();
var x = d3.scaleLinear().range([0, width]),
x2 = d3.scaleLinear().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
y2 = d3.scaleLinear().range([height2, 0]);
// dur = d3.scaleLinear().range([0, 12]);
var tickLabels = ['a', 'b', 'c', 'd'];
var newData = [];
data.forEach(function(e) {
newData.push({
"name": e.name
});
});
//var xAxis = d3.axisBottom(x).tickSize(0);
var xAxis2 = d3.axisBottom(x2).tickSize(0)
.tickFormat(function(d) {
var found = data.find(function(e) {
return e.num === d
});
return found ? found.name : null;
})
yAxis = d3.axisLeft(y).tickSize(0);
var xAxis = d3.axisBottom(x)
.tickFormat(function(d) {
var found = data.find(function(e) {
return e.num === d
});
if (!found) {
d3.select(this.parentNode).select("line").remove()
}
return found ? found.name : null;
})
// var xAxis = d3.axisBottom(x)
// .tickFormat(function(d, i) {
// label = data[i].name;
// local.set(this, data[i].name);
// return "test";
// return data[i].name;
// return label;
// return i;
// return d;
// });
var brush = d3.brushX()
.extent([
[0, 0],
[width, height2]
])
.on("start brush end", brushed);
var zoom = d3.zoom()
.scaleExtent([1, 10])
.translateExtent([
[0, 0],
[width, height]
])
.extent([
[0, 0],
[width, height]
])
.on("zoom", zoomed);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
// d3.json("data.json", function(error, data) {
// if (error) throw error;
var parseTime = d3.timeParse("%Y-%m-%d %H:%M");
var mouseoverTime = d3.timeFormat("%a %e %b %Y %H:%M");
var minTime = d3.timeFormat("%b%e, %Y");
var parseDate = d3.timeParse("%b %Y");
data.forEach(function(d) {
d.distance = +d.distance;
d.num = +d.num;
return d;
},
function(error, data) {
if (error) throw error;
});
var total = 0;
data.forEach(function(d) {
total = d.distance + total;
});
var minDate = d3.min(data, function(d) {
return d.num;
});
var xMin = d3.min(data, function(d) {
return d.num;
});
var yMax = Math.max(20, d3.max(data, function(d) {
return d.distance;
}));
x.domain([xMin, d3.max(data, function(d) {
return d.num;
})]);
y.domain([0, yMax]);
x2.domain(x.domain());
y2.domain(y.domain());
var rects = focus.append("g");
rects.attr("clip-path", "url(#clip)");
rects.selectAll("rects")
.data(data)
.enter().append("rect")
.style("fill", function(d) {
return "#8DA5ED";
})
.attr("class", "rects")
.attr("x", function(d) {
return x(d.num);
})
.attr("y", function(d) {
return y(d.distance);
})
.attr("width", function(d) {
return 10;
})
.attr("height", function(d) {
return height - y(d.distance);
});
focus.append("g")
.attr("class", "axis x-axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
focus.append("g")
.attr("class", "axis axis--y")
.call(yAxis);
// Summary Stats
focus.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Distance in meters");
svg.append("text")
.attr("transform",
"translate(" + ((width + margin.right + margin.left) / 2) + " ," +
(height + margin.top + margin.bottom) + ")")
.style("text-anchor", "middle")
.text("Date");
svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
var rects = context.append("g");
rects.attr("clip-path", "url(#clip)");
rects.selectAll("rects")
.data(data)
.enter().append("rect")
.style("fill", function(d) {
return "#8DA5ED";
})
.attr("class", "rects")
.attr("x", function(d) {
return x2(d.num);
})
.attr("y", function(d) {
return y2(d.distance);
})
.attr("width", function(d) {
return 10;
})
.attr("height", function(d) {
return height2 - y2(d.distance);
});
context.append("g")
.attr("class", "axis x-axis")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
// });
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
var s = d3.event.selection || x2.range();
x.domain(s.map(x2.invert, x2));
focus.selectAll(".rects")
.attr("x", function(d) {
return x(d.num);
})
.attr("y", function(d) {
return y(d.distance);
})
.attr("width", function(d) {
return 10;
})
.attr("height", function(d) {
return height - y(d.distance);
});
focus.select(".x-axis").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
var e = d3.event.selection;
var selectedrects = focus.selectAll('.rects').filter(function() {
var xValue = this.getAttribute('x');
return e[0] <= xValue && xValue <= e[1];
});
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
focus.selectAll(".rects")
.attr("x", function(d) {
return x(d.num);
})
.attr("y", function(d) {
return y(d.distance);
})
.attr("width", function(d) {
return 10;
})
.attr("height", function(d) {
return height - y(d.distance);
});
focus.select(".x-axis").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
}
</script>
PS: this is clearly a XY Problem: disguising a linear scale as an categorical scale, as you're doing here, may not be the correct solution. Instead of that, you should find out how to brush the band scale itself.
I have a horizontal bar graph with positive and negative values. I would like to position the labels on the y axis to the left of the axis when the value is positive and to the right of the axis when the value is negative.
{value: -70, dataset:"foo"},
{value: -20, dataset:"bar"},
{value: 30, dataset:"baz"}
etc...
The value of the first bar is negative and the label foo is on the right side of the axis, which is where I want it. But the value of the third bar is positive, so I would like to see that label on the left of the y axis. Is this possible?
Here is the JSFiddle: https://jsfiddle.net/Kavitha_2817/fmr1x1gu/18/
A simple solution (out of many) is calling two axis generators, axisLeft and axisRight, and formatting the ticks according to the dataset values:
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + x(0) + ",0)")
.call(d3.axisRight(y).tickFormat(function(d) {
var value = data.filter(function(e) {
return e.dataset === d
})[0].value;
return value > 0 ? null : d;
}));
svg.append("g")
.attr("class", "y axis left")
.attr("transform", "translate(" + x(0) + ",0)")
.call(d3.axisLeft(y).tickFormat(function(d) {
var value = data.filter(function(e) {
return e.dataset === d
})[0].value;
return value < 0 ? null : d;
}));
Here is a demo:
var margin = {
top: 30,
right: 10,
bottom: 50,
left: 50
},
width = 500,
height = 300;
var data = [{
value: -10,
dataset: "barbaz"
}, {
value: 40,
dataset: "barbar"
}, {
value: -10,
dataset: "foobaz"
}, {
value: -50,
dataset: "foobar"
}, {
value: 30,
dataset: "baz"
}, {
value: -20,
dataset: "bar"
}, {
value: -70,
dataset: "foo"
}];
// Add svg to
var svg = d3.select('body').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 + ')');
// set the ranges
var y = d3.scaleBand()
.range([height, 0])
.padding(0.1);
var x = d3.scaleLinear()
.range([0, width]);
// Scale the range of the data in the domains
x.domain(d3.extent(data, function(d) {
return d.value;
}));
y.domain(data.map(function(d) {
return d.dataset;
}));
// append the rectangles for the bar chart
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", function(d) {
return "bar bar--" + (d.value < 0 ? "negative" : "positive");
})
.attr("x", function(d) {
return x(Math.min(0, d.value));
})
.attr("y", function(d) {
return y(d.dataset);
})
.attr("width", function(d) {
return Math.abs(x(d.value) - x(0));
})
.attr("height", y.bandwidth());
// add the x Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the y Axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + x(0) + ",0)")
.call(d3.axisRight(y).tickFormat(function(d) {
var value = data.filter(function(e) {
return e.dataset === d
})[0].value;
return value > 0 ? null : d;
}));
svg.append("g")
.attr("class", "y axis left")
.attr("transform", "translate(" + x(0) + ",0)")
.call(d3.axisLeft(y).tickFormat(function(d) {
var value = data.filter(function(e) {
return e.dataset === d
})[0].value;
return value < 0 ? null : d;
}));
.bar--positive {
fill: steelblue;
}
.bar--negative {
fill: darkorange;
}
.y.axis.left {
fill: none;
stroke: none;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
I've modified the y.domain of my D3 bar chart so it starts at a value above zero. However, I want to add a little "zig zag line" to indicate this, as in the picture below.
How could I do this in D3? Many thanks!
I'd just hack this on by adding another path to the y axis:
// define how much space you'd like to create the axis "break" in
var axisBreakSpace = 50;
// Add the X Axis, with the space
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height + axisBreakSpace) + ")")
.call(xAxis);
// Add the Y Axis, normally
var yG = svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// add the zigzags path
yG.append("path")
.attr("d", function(){
var numZags = 10, // number of zigzags
zagDist = (axisBreakSpace - 5) / numZags; // y distance on each zig or zag, -5 is a bit of space to finish it off
// build the path at
var curZig = height,
d = "M0," + curZig;
for (var i = 0; i < numZags; i++){
curZig += zagDist;
d += (i % 2 === 0) ? " L10," + curZig : " L-10," + curZig;
}
// finish it off to the x-axis
d += " L0," + (height + axisBreakSpace);
return d;
});
Full working code sample:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
/* set the CSS */
body {
font: 12px Arial;
}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
// Set the dimensions of the canvas / graph
var margin = {
top: 30,
right: 20,
bottom: 100,
left: 50
},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom,
axisBreakSpace = 50;
// Set the ranges
var x = d3.scale.linear().range([0, width])
.domain([0, 10]);
var y = d3.scale.linear().range([height, 0])
.domain([200,1000]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom")
var yAxis = d3.svg.axis().scale(y)
.orient("left");
// Define the line
var line = d3.svg.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return y(d.y);
});
// Adds the svg canvas
var svg = d3.select("body")
.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 + ")");
var data = d3.range(10).map(function(d){
return {
x: d,
y: (Math.random() * 800) + 200
}
});
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", line(data));
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height + axisBreakSpace) + ")")
.call(xAxis);
// Add the Y Axis
var yG = svg.append("g")
.attr("class", "y axis")
.call(yAxis);
yG.append("path")
.attr("d", function(){
var numZags = 10,
zagDist = (axisBreakSpace - 5) / numZags;
var curZig = height,
d = "M0," + curZig;
for (var i = 0; i < numZags; i++){
curZig += zagDist;
d += (i % 2 === 0) ? " L10," + curZig : " L-10," + curZig;
}
d += " L0," + (height + axisBreakSpace);
return d;
});
</script>
</body>
I would create some data and pass it to the D3 library. Something similar to this :
var data = [{
x1: xAxisSTARTPOINTX, //start
y1; xAxisSTARTPOINTY,
x2: firstXPointOnZigZag,
y2; firstYPointOnZigZag},{
.
.
. },{
x1: lastXPointOnZigZag, //end
y1; lastYPointOnZigZag,
x2: yAxisSTARTPOINTX,
y2; yAxisSTARTPOINTY}
}]
The values you put between will be the points on the zig zag which you can make up/generate.
Then pass this to this :
d3.select(container).data(data).enter().append('path')
.attr('x1', function(d){ return d.x1})
.attr('y1', function(d){ return d.y1})
.attr('x2', function(d){ return d.x2})
.attr('y2', function(d){ return d.y2})
.style('stroke','black');
You could generate the points yourself so you can change how many 'zigzags' you want by changing 'i' in the for loop.
A function to create points, something similar to this :
function createPoints(xAxisStartPoint, yAxisStartPoint){ //pass two arrays
var xAxisStartX = xAxisStartPoint[0], //xAxisStartPointX
xAxisStartY = xAxisStartPoint[1], //xAxisStartPointY
yAxisStartX = yAxisStartPoint[0], //xAxisStartPointX
yAxisStartY = yAxisStartPoint[1]; //yAxisStartPointY
var difference = xAxisStartY-yAxisStartY; //gets the difference between xAxis and yAxis to make sure the points are equal distance apart.
var allPoints = []; //array to populate with points
var numberOfPoints = 4; //number of zigzags
var movement = 20; //movement left and right
for(var i=0;i<=numberOfPoints;i++){
var thisPoint = [];
if(i===0){ //push xAxisStartPoint
thisPoint.push({
x:xAxisStartX,
y:xAxisStartY
})
} else if(i===4){ //push yAxisStartPoint
thisPoint.push({
x:yAxisStartX,
y:yAxisStartY
})
} else {
thisCalcPointX;
if(i%2 > 0){ //if i is odd move left
thisCalcPointX = xAxisStartX-movement; //move point to the left
} else { //if it's even move right
thisCalcPointX = xAxisStartX+movement; //move point to the right
}
thisCalcPointY = xAxisStartY + difference/i; //move point up from xAxis start point at equal distance between xAxis and yAxis
thisPoint.push({
x: xAxisStartX,
y: thisCalcPointY
})
}
allPoints.push(thisPoint); //push this point to array of points
}
return allPoints; //return the points
}
//then pass this to create the path
var xAxisStart = [ xAxisStartX, xAxisStartY];
var yAxisStart= [ yAxisStartX, yAxisStartY];
var dataPoints = createPoints([xAxisStart, yAxisStart])
d3.select(container).data(dataPoints).enter().append('path')
.attr('x1', function(d){ return d.x1})
.attr('y1', function(d){ return d.y1})
.attr('x2', function(d){ return d.x2})
.attr('y2', function(d){ return d.y2})
.style('stroke','black');
Above code is not tested and just done on the fly, may need playing with, but the logic should work to create random points either side between both axis.
I'm trying to build an animated time series chart which shows a 'trace' or snail trail following the moving dot. I have been trying to integrate KoGor's http://bl.ocks.org/KoGor/8163022 but haven't had luck- I think the problem lies in tweenDash() - The original function was designed for a single trace- this one has one per company.
Attached below is a working example- the time series scrubbing and movable data labels work, just not the trace aspect.
Thanks,
RL
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.10/d3.min.js"></script>
<!DOCTYPE html>
<meta charset="utf-8">
<body bgcolor="#000000">
<title>BPS</title>
<style>
#import url(style.css);
#chart {
margin-left: -40px;
height: 506px;
display:inline;
}
#buffer {
width: 100px;
height:506px;
float:left;
}
text {
font: 10px sans-serif;
color: #ffffff;
}
.dot {
stroke: #000;
}
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.label {
fill: #777;
}
.year.label {
font: 900 125px "Helvetica Neue";
fill: #ddd;
}
.year.label.active {
fill: #aaa;
}
.overlay {
fill: none;
pointer-events: all;
cursor: ew-resize;
}
</style>
<div>
<div id="buffer"></div><div id="chart"></div>
</div>
<script src="d3.v3.min.js"></script>
<script>
var source = '[{"name":"ABCD","AUM":[[2010,1000.6],[2011,1200.6],[2012,1300.1],[2013,1400.5],[2014,1600.0]],"AUA":[[2010,3000.6],[2011,3300.2],[2012,4000.0],[2013,4500.8],[2014,6000.3]],"marketPercentage":[[2010,40.4],[2011,39.7],[2012,38.5],[2013,37.1],[2014,36.5]],"fill":[[2010,0],[2011,-1],[2012,-1],[2013,-1],[2014,-1]],"xOffset":[[2010,5],[2011,5],[2012,5],[2013,5],[2014,5]],"yOffset":[[2010,-30],[2011,-20],[2012,-20],[2013,-20],[2014,-10]]},{"name":"EFGH","AUM":[[2010,32.8],[2011,43.2],[2012,58.3],[2013,78.8],[2014,92]],"AUA":[[2010,327.3],[2011,439.3],[2012,547.0],[2013,710.0],[2014,824.0]],"marketPercentage":[[2010,1.0],[2011,1.2],[2012,1.5],[2013,1.8],[2014,1.9]],"fill":[[2010,0],[2011,1],[2012,1],[2013,1],[2014,1]],"xOffset":[[2010,5],[2011,5],[2012,5],[2013,5],[2014,5]],"yOffset":[[2010,-10],[2011,-10],[2012,-10],[2013,-10],[2014,-10]]},{"name":"HIJK","AUM":[[2010,0.1],[2011,0.5],[2012,1.2],[2013,2.4],[2014,2.6]],"AUA":[[2010,159.6],[2011,176.7],[2012,199.9],[2013,235.1],[2014,269.0]],"marketPercentage":[[2010,0.1],[2011,0.1],[2012,0.1],[2013,0.1],[2014,0.1]],"fill":[[2010,0],[2011,0],[2012,0],[2013,1],[2014,1]],"xOffset":[[2010,5],[2011,5],[2012,5],[2013,5],[2014,5]],"yOffset":[[2010,-10],[2011,-10],[2012,-10],[2013,-10],[2014,-10]]}]';
// Various accessors that specify the four dimensions of data to visualize.
function x(d) { return d.AUM; }
function y(d) { return d.AUA; }
function xo(d) {return d.xOffset; }
function yo(d) {return d.yOffset; }
function radius(d) { return d.marketPercentage; }
function key(d) { return d.name; }
// Chart dimensions.
var margin = {top: 19.5, right: 19.5, bottom: 19.5, left: 39.5},
width = 960 - margin.right,
height = 500 - margin.top - margin.bottom;
// Various scales. These domains make assumptions of data, naturally.
var xScale = d3.scale.linear().domain([0, 2000]).range([0, width]),
yScale = d3.scale.linear().domain([0, 5000]).range([height, 0]),
radiusScale = d3.scale.sqrt().domain([0, 500]).range([0, 40]),
colorScale = d3.scale.category10();
// The x & y axes.
var xAxis = d3.svg.axis().orient("bottom").scale(xScale).ticks(12, d3.format(",d")),
yAxis = d3.svg.axis().scale(yScale).orient("left");
// Create the SVG container and set the origin.
var svg = d3.select("#chart").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 + ")");
// Add the x-axis.
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.style("fill", "#FFFFFF")
.call(xAxis);
// Add the y-axis.
svg.append("g")
.attr("class", "y axis")
.style("fill", "#FFFFFF")
.call(yAxis);
// Add an x-axis label.
svg.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.style("fill", "#FFFFFF")
.attr("x", width)
.attr("y", height - 6);
//.text("income per capita, inflation-adjusted (dollars)");
// Add a y-axis label.
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", 6)
.attr("dy", ".75em")
.style("fill", "#FFFFFF")
.attr("transform", "rotate(-90)")
// .text("life expectancy (years)")
;
// Add the year label; the value is set on transition.
var label = svg.append("text")
.attr("class", "year label")
.attr("text-anchor", "end")
.attr("y", height - 24)
.attr("x", width)
.text(2010);
//d3.json("investments_v04ANON.json", function(companies) {
companies = JSON.parse(source)
// A bisector since many company's data is sparsely-defined.
var bisect = d3.bisector(function(d) { return d[0]; });
// Add a dot per company. Initialize the data at 2010, and set the colors.
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(interpolateData(2010))
.enter().append("circle")
.attr("class", "dot")
// .style("fill", function(d) { return colorScale(color(d)); })
.style("fill", function(d) {return colorScale(interpolateData(2010)) })
.call(position)
.sort(order);
var lineTraces = svg.append("path")
.attr("class", "lineTrace")
.selectAll(".traces")
.attr("stroke-width", 2)
.attr("stroke", "grey")
.data(interpolateData(2010));
//yields a mouseover label - "title" precludes need for separate mouseover event.
// dot.append("title")
// .text(function(d) { return d.name; });
//.text(function(d) {return d.AUM});
var theLabel = svg.append("g")
.attr("class", "texts")
.selectAll(".theLabel")
.data(interpolateData(2010))
.enter().append("text")
.attr("class", "text")
.text("hey")
.call(position2);
// Add an overlay for the year label.
var box = label.node().getBBox();
var overlay = svg.append("rect")
.attr("class", "overlay")
.attr("x", box.x)
.attr("y", box.y)
.attr("width", box.width)
.attr("height", box.height)
.on("mouseover", enableInteraction);
// Start a transition that interpolates the data based on year.
svg.transition()
.duration(30000)
.ease("linear")
.tween("year", tweenYear)
.attrTween("stroke-dasharray", tweenDash)
.each("end", enableInteraction);
// Positions the dots based on data.
function position(dot) {
dot .attr("cx", function(d) { return xScale(x(d)); })
.attr("cy", function(d) { return yScale(y(d)); })
.attr("r", function(d) { return radiusScale(radius(d)); })
.style("fill", function(d) {return d.fill>0 ? "green" : "red"} );//{return d.fill});
}
//function from: http://bl.ocks.org/KoGor/8163022
function tweenDash() {
var i = d3.interpolateString("0," + 5, 5 + "," + 5); // interpolation of stroke-dasharray style attr
// var l = path.node().getTotalLength();
// var i = d3.interpolateString("0," + l, l + "," + l); // interpolation of stroke-dasharray style attr
return function(t) {
var marker = d3.select(".dots");
// var p = path.node().getPointAtLength(t * l);
var p = lineTraces.node().getPointAtLength(t * 5);
marker.attr("transform", "translate(" + p.x + "," + p.y + ")");//move marker
return i(t);
}
}
function position2(theLabel) {
theLabel.attr("x", function(d) { return xScale(x(d)) + xo(d); })
.attr("y", function(d) { return yScale(y(d)) + yo(d); })
.attr("text-anchor", "end")
.style("fill", "#FFFFFF")
.text(function(d) { return d.name + ": AUM:" + Math.round(d.AUM) + ", AUA: " + Math.round(d.AUA) });//{return d.fill});
}
// Defines a sort order so that the smallest dots are drawn on top.
function order(a, b) {
return radius(b) - radius(a);
}
// After the transition finishes, you can mouseover to change the year.
function enableInteraction() {
var yearScale = d3.scale.linear()
.domain([2010, 2014])
.range([box.x + 10, box.x + box.width - 10])
.clamp(true);
// Cancel the current transition, if any.
svg.transition().duration(0);
overlay
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("mousemove", mousemove)
.on("touchmove", mousemove);
function mouseover() {
label.classed("active", true);
}
function mouseout() {
label.classed("active", true);
label.classed("active", false);
}
function mousemove() {
displayYear(yearScale.invert(d3.mouse(this)[0]));
}
}
// Tweens the entire chart by first tweening the year, and then the data.
// For the interpolated data, the dots and label are redrawn.
function tweenYear() {
var year = d3.interpolateNumber(2010, 2014);
return function(t) { displayYear(year(t)); };
}
// Updates the display to show the specified year.
function displayYear(year) {
dot.data(interpolateData(year), key).call(position).sort(order);
theLabel.data(interpolateData(year), key).call(position2).sort(order);
label.text(Math.round(year));
}
// Interpolates the dataset for the given (fractional) year.
function interpolateData(year) {
return companies.map(function(d) {
return {
// name: d.name + ": AUM:" + interpolateValues(d.AUM, year) + ", AUA: " + interpolateValues(d.AUA, year),
// name: d.name + ": AUM:" + d.AUM + ", AUA: " + d.AUA,
// name: interpolateValues(d.AUM, year),
name: d.name,
AUM: interpolateValues(d.AUM, year),
marketPercentage: interpolateValues(d.marketPercentage, year),
AUA: interpolateValues(d.AUA, year),
fill: interpolateValues(d.fill, year),
xOffset: interpolateValues(d.xOffset, year),
yOffset: interpolateValues(d.yOffset, year)
};
});
}
// Finds (and possibly interpolates) the value for the specified year.
function interpolateValues(values, year) {
var i = bisect.left(values, year, 0, values.length - 1),
a = values[i];
if (i > 0) {
var b = values[i - 1],
t = (year - a[0]) / (b[0] - a[0]);
return a[1] * (1 - t) + b[1] * t;
}
return a[1];
};
//});
</script>
Mark- the second version you built works very well. I'm now trying to address the individual line segments. I've added an attribute 'toggleSwitch' but the below code runs 1x and captures only the initial state of the object.
var lineTraces = svg.append("g")
.selectAll(".traces")
.data([0,1,2,4,5,6,7,8,9,10,11,12])
.enter()
.append("path")
.attr("stroke-width", 2)
.attr("stroke", "grey")
.attr("class", "lineTrace")
.attr("d", line)
.each(function(d,i){
d3.select(this)
.datum([someData[i]])
.attr("nothing", function(i) {console.log(i[0])})
.attr("d", line)
.style("stroke-dasharray", function(i) {return (i[0]["toggleSwitch"]<0 ? "0,0": "3,3")})
});
console log, one per object:
Object { name: "TheName", Impact: 120, bubbleSize: 30.4, YoY: 11, toggleSwitch: 0, xOffset: 5, yOffset: -30 }
The example you linked to had a pre-established path and then attrTweened the "stroke-dasharray" on it. Your first problem is that you need to establish that path for each company. Then you can tween it.
// set up a line to create the path
var line = d3.svg.line()
.x(function(d) { return xScale(x(d)); })
.y(function(d) { return yScale(y(d)); })
.interpolate("basis");
// for each company add the path
var lineTraces = svg.append("g")
.selectAll(".traces")
.attr("fill","red")
.data([0,1,2]) // 3 companies
.enter()
.append("path")
.attr("stroke-width", 2)
.attr("stroke", "grey")
.attr("class", "lineTrace")
.each(function(d,i){
// get the line data and add path
var lineData = [interpolateData(2010)[i],interpolateData(2011)[i],
interpolateData(2012)[i],interpolateData(2013)[i],interpolateData(2014)[i]];
d3.select(this)
.datum(lineData)
.attr("d", line);
});
Now set up the transitions on each path:
lineTraces.each(function(){
var path = d3.select(this);
path.transition()
.duration(30000)
.ease("linear")
.attrTween("stroke-dasharray", tweenDash)
});
Where tweenDash is:
function tweenDash() {
var l = lineTraces.node().getTotalLength();
var i = d3.interpolateString("0," + l, l + "," + l); // interpolation of stroke-dasharray style attr
return function(t) {
var p = lineTraces.node().getPointAtLength(t);
return i(t);
}
}
Here's an example.
You'll see it's not perfect, the timings are off. If I get a bit more time, I'll try and come back and straighten it out.
EDITS
Gave this some thought last night and it dawned on me that there's an easier, more succinct way to add the trace. Instead of pre-defining the path and then attrTweening the "stroke-dasharray", just build the path as you go:
var someData = interpolateData(2010);
// add the paths like before
var lineTraces = svg.append("g")
.selectAll(".traces")
.data([0,1,2])
.enter()
.append("path")
.attr("stroke-width", 2)
.attr("stroke", "grey")
.attr("class", "lineTrace")
.attr("d", line)
.each(function(d,i){
d3.select(this)
.datum([someData[i]])
.attr("d", line);
});
// Tweens the entire chart by first tweening the year, and then the data.
// For the interpolated data, the dots and label are redrawn.
function tweenYear() {
var year = d3.interpolateNumber(2010, 2014);
// added "addTrace" function
return function(t) { addTrace(year(t)); displayYear(year(t)); };
}
// append the data and draw the path
function addTrace(year){
var thisData = interpolateData(year);
lineTraces.each(function(d,i){
var trace = d3.select(this);
trace.datum().push(thisData[i]);
trace.attr("d", line);
});
}
This produces much better results.