D3.JS: How to animate lines - javascript

Relatively new to D3, an have a question regarding the translation (animation) of lines. I animate circles using the following code:
var data = d3.range(100).map(function() {
return new agent(
0,
0,
(Math.random()-0.5)*.05,
(Math.random()-0.5)*.05,
'rgb(255, 0, 213)');
});
var circles = canvas.selectAll('circle')
.data(data)
.enter().append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', 5)
.attr('fill', function(d) {
return d.color;
});
circles.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
});
The last 3 lines in particular are of interest to me - is there an equivalent translate for lines?

There are several possible options to animate lines in D3, e.g.
Changing the underlying data
svg.select(".line")
.duration(animationLength)
.attr("d", valueline(lineData))
valueLine is a d3.svg.line() function.
Filling the line from start to end via stroke-dashoffset
var lineGraph = svg.append("path")
.attr("class", "line")
.attr("d", lineFunction(lineData))
.attr("stroke-dasharray", 200 + " " + 200)
.attr("stroke-dashoffset", 200)
.attr("fill", "none")
.transition()
.duration(animationLength)
.ease("linear")
.attr("stroke-dashoffset", 0);
Using transform to move the line
svg.select(".line")
.duration(animationLength)
.attr("transform", "translate(" + 100 * Math.random() + "," + 100 * Math.random() + ")");
var animationLength = 2000;
var lineData = [{
"x": 1,
"y": 5
}, {
"x": 20,
"y": 20
}, {
"x": 40,
"y": 10
}, {
"x": 60,
"y": 40
}, {
"x": 80,
"y": 5
}, {
"x": 100,
"y": 60
}];
var lineFunction = d3.svg.line()
.x(function (d) {
return d.x;
})
.y(function (d) {
return d.y;
})
.interpolate("linear");
var valueline = d3.svg.line()
.x(function (d) {
return d.x * (Math.random() + 0.5);
})
.y(function (d) {
return d.y * (Math.random() + 0.5);
});
var svg = d3.select("#line")
.append("svg")
.attr("width", 200)
.attr("height", 200);
var lineGraph = svg.append("path")
.attr("class", "line")
.attr("d", lineFunction(lineData))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("stroke-dasharray", 200 + " " + 200)
.attr("stroke-dashoffset", 200)
.attr("fill", "none")
.transition()
.duration(animationLength)
.ease("linear")
.attr("stroke-dashoffset", 0);
svg = d3.select("body").transition();
svg.select(".line")
.delay(animationLength)
.duration(animationLength)
.attr("d", valueline(lineData))
.attr("transform", "translate(" + 100 * Math.random() + "," + 100 * Math.random() + ")");
<script src="https://d3js.org/d3.v3.min.js"></script>
<div id="line"></div>

Related

Multiline chart x-axis ticks not aligned with data points

I am trying to draw a multi line chart with D3.js. The chart is drawn properly but the x-axis ticks are not aligned correctly with the data points circle.
Find the example code here: https://jsfiddle.net/gopal31795/qsLd7pc5/9/
I assume that there is some error in the code for creating the dots.
// Creating Dots on line
segment.selectAll("dot")
.data(function(d) {
return d.linedata;
})
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) {
//return x(parseDate(new Date(d.mins))) + x.rangeBand() / 2;
return x(d.mins);
})
.attr("cy", function(d) {
return y(d.value);
})
.style("stroke", "white")
.style("fill", function(d) {
return color(this.parentNode.__data__.name);
})
.on("mouseenter", function(d) {
d3.select(this).transition().style("opacity", "0.25");
tooltip.html("<span style='color:" + color(this.parentNode.__data__.name) + ";'>" + this.parentNode.__data__.name + "</span>: " + (d.value + "s")).style("visibility", "visible").style("top", (event.pageY + 10) + "px").style("left", (event.pageX) + "px");
})
.on("mouseleave", function(d) {
d3.select(this).transition().style("opacity", "1");
tooltip.style("visibility", "hidden");
});
The problem in your code (which uses the old v3) is that you're using rangeRoundBands. That would be the correct choice if you had a bar chart, for instance.
However, since you're dealing with data points, you should use rangePoints or rangeRoundPoints, which:
Sets the output range from the specified continuous interval. The array interval contains two elements representing the minimum and maximum numeric value. This interval is subdivided into n evenly-spaced points, where n is the number of (unique) values in the input domain.
So, it should be:
var x = d3.scale.ordinal()
.rangeRoundPoints([0, width]);
Here is your code with that change:
var Data = [{
"name": "R",
"linedata": [{
"mins": 0,
"value": 1120
},
{
"mins": 2,
"value": 1040
},
{
"mins": 4,
"value": 1400
},
{
"mins": 6,
"value": 1500
}
]
},
{
"name": "E",
"linedata": [{
"mins": 0,
"value": 1220
},
{
"mins": 2,
"value": 1500
},
{
"mins": 4,
"value": 1610
},
{
"mins": 6,
"value": 1700
}
]
}
];
var margin = {
top: 20,
right: 90,
bottom: 35,
left: 90
},
width = $("#lineChart").width() - margin.left - margin.right,
height = $("#lineChart").width() * 0.3745 - margin.top - margin.bottom;
//var parseDate = d3.time.format("%d-%b");
var x = d3.scale.ordinal()
.rangeRoundPoints([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var xData = Data[0].linedata.map(function(d) {
return d.mins;
});
var line = d3.svg.line()
.interpolate("linear")
.x(function(d) {
return x(d.mins);
})
.y(function(d) {
return y(d.value);
});
function transition(path) {
path.transition()
.duration(4000)
.attrTween("stroke-dasharray", tweenDash);
}
function tweenDash() {
var l = this.getTotalLength(),
i = d3.interpolateString("0," + l, l + "," + l);
return function(t) {
return i(t);
};
}
var svg = d3.select("#lineChart").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 + ")");
color.domain(Data.map(function(d) {
return d.name;
}));
x.domain(xData);
var valueMax = d3.max(Data, function(r) {
return d3.max(r.linedata, function(d) {
return d.value;
})
});
var valueMin = d3.min(Data, function(r) {
return d3.min(r.linedata, function(d) {
return d.value;
})
});
y.domain([valueMin, valueMax]);
//Drawing X Axis
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "xAxisText")
.attr("x", width)
.attr("dy", "-.41em")
.style("text-anchor", "end")
.style("fill", "white")
.text("Time(m)");
svg.append("g")
.attr("class", "y-axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style("fill", "none")
.style("stroke", "white")
.style("stroke-width", 0.8)
.text("Duration(s)");
// Drawing Lines for each segments
var segment = svg.selectAll(".segment")
.data(Data)
.enter().append("g")
.attr("class", "segment");
segment.append("path")
.attr("class", "line")
.attr("id", function(d) {
return d.name;
})
.attr("visible", 1)
.call(transition)
//.delay(750)
//.duration(1000)
//.ease('linear')
.attr("d", function(d) {
return line(d.linedata);
})
.style("stroke", function(d) {
return color(d.name);
});
// Creating Dots on line
segment.selectAll("dot")
.data(function(d) {
return d.linedata;
})
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) {
//return x(parseDate(new Date(d.mins))) + x.rangeBand() / 2;
return x(d.mins);
})
.attr("cy", function(d) {
return y(d.value);
})
.style("stroke", "white")
.style("fill", function(d) {
return color(this.parentNode.__data__.name);
})
.on("mouseenter", function(d) {
d3.select(this).transition().style("opacity", "0.25");
tooltip.html("<span style='color:" + color(this.parentNode.__data__.name) + ";'>" + this.parentNode.__data__.name + "</span>: " + (d.value + "s")).style("visibility", "visible").style("top", (event.pageY + 10) + "px").style("left", (event.pageX) + "px");
})
.on("mouseleave", function(d) {
d3.select(this).transition().style("opacity", "1");
tooltip.style("visibility", "hidden");
});
path {
fill: none;
stroke: black;
}
line {
stroke: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div class="card-body" id="lineChart">
</div>
Finally, as a tip: don't mix D3 and jQuery.

Updatable chord diagram

I am working on a D3 chord diagram and want to have an update function.
This is the code I have at the moment
// Genres, check de readme daar staat visueel hoe je dit uitleest.
var data = [
[9962, 1196, 94, 93, 18],
[1196, 9102, 11, 343, 169],
[94, 11, 7143, 138, 32],
[93, 343, 138, 6440, 75],
[18, 169, 32, 75, 4886]
]
var genres = ["Psychologischverhaal", "Thriller", "Detective", "Romantischverhaal", "Sciencefiction"]
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
outerRadius = Math.min(width, height) * 0.5 - 40,
innerRadius = outerRadius - 30;
// Zet getallen naar 1K ipv 1000
var formatValue = d3.formatPrefix(",.0", 1e3);
var chord = d3.chord()
.padAngle(0.05)
.sortSubgroups(d3.ascending);
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var ribbon = d3.ribbon()
.radius(innerRadius);
var color = d3.scaleOrdinal()
.range(["#ed0b0b", "#03aa24", "#f2ae04", "#1f03f1", "#e1ed04"]);
var g = svg.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.datum(chord(data));
var group = g.append("g")
.attr("class", "groups")
.selectAll("g")
.data(function(chords) { return chords.groups; })
.enter().append("g");
group.append("path")
.style("fill", function(d) { return color(d.index); })
.style("stroke", function(d) { return d3.rgb(color(d.index)).darker(); })
.attr("id", function(d, i) { return "group" + d.index; })
.attr("d", arc)
.on("mouseover", fade(.1))
.on("mouseout", fade(1));
group.append("title").text(function(d) {
return groupTip(d);
});
group.append("text")
.attr("x", 6)
.attr("dy", 15)
.append("textPath")
.attr("xlink:href", function(d) { return "#group" + d.index; })
.text(function(chords, i){return genres[i];})
.style("fill", "black");
var groupTick = group.selectAll(".group-tick")
.data(function(d) { return groupTicks(d, 1e3); })
.enter().append("g")
.attr("class", "group-tick")
.attr("transform", function(d) { return "rotate(" + (d.angle * 180 / Math.PI - 90) + ") translate(" + outerRadius + ",0)"; });
groupTick.append("line")
.attr("x1", 1)
.attr("y1", 0)
.attr("x2", 5)
.attr("y2", 0)
.style("stroke", "#000")
groupTick
.filter(function(d) { return d.value % 1e3 === 0; })
.append("text")
.attr("x", 8)
.attr("dy", ".35em")
.attr("transform", function(d) { return d.angle > Math.PI ? "rotate(180) translate(-16)" : null; })
.style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.text(function(d) { return formatValue(d.value); });
var ribbons = g.append("g")
.attr("class", "ribbons")
.selectAll("path")
.data(function(chords) { return chords; })
.enter().append("path")
.attr("d", ribbon)
.style("fill", function(d) { return color(d.target.index); })
.style("stroke", function(d) { return d3.rgb(color(d.target.index)).darker(); })
ribbons.append("title").
text(function(d){return chordTip(d);});
// Returns an array of tick angles and values for a given group and step.
function groupTicks(d, step) {
var k = (d.endAngle - d.startAngle) / d.value;
return d3.range(0, d.value, step).map(function(value) {
return {value: value, angle: value * k + d.startAngle};
});
}
function fade(opacity) {
return function(d, i) {
ribbons
.filter(function(d) {
return d.source.index != i && d.target.index != i;
})
.transition()
.style("opacity", opacity);
};
}
function chordTip(d){
var j = d3.formatPrefix(",.0", 1e1)
return "Aantal boeken met genres:\n"
+ genres[d.target.index] + " en " + genres[d.source.index] + ": " + j(d.source.value)
}
function groupTip(d) {
var j = d3.formatPrefix(",.0", 1e1)
return "Totaal aantal boeken met het genre " + genres[d.index] + ":\n" + j(d.value)
}
and
<body>
<h1>Boeken met enkele & dubbele genres</h1>
<button id="doubleGenre">Alleen dubbele genres</button>
<button id="reset">Reset</button>
<svg width="960" height="900"></svg>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="assets/index.js"></script>
This is the result what I have now :
I want to update the chord when a user clicks on the button Alleen dubbele genres so it becomes like this :
So I want to delete the chords to it self so the data looks like :
var data = [
[1196, 94, 93, 18],
[1196, 11, 343, 169],
[94, 11, 138, 32],
[93, 343, 138, 75],
[18, 169, 32, 75]
]
And if a user clicks on the button reset I want to go back to the original view. Is there anyone who can help me with this?
Here is a link to the simplified version of your example with transitions: https://stackblitz.com/edit/q53412789
If you want to remove chords to self, you should replace values on diagonal to zeros (not just delete them: matrix must remain square):
const data2 = [
[0, 1196, 94, 93, 18],
[1196, 0, 11, 343, 169],
[94, 11, 0, 138, 32],
[93, 343, 138, 0, 75],
[18, 169, 32, 75, 0]
]
As #rioV8 suggested, the first step is to wrap code for drawing and updating the chart into a function (I've ommited code for ticks and labels for brevity, the principle is the same for them):
var g = svg.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
var ribbons = g.append("g");
function update(data) {
const chords = chord(data);
const ribbonsUpdate = ribbons.selectAll("path")
.data(chords, ({source, target}) => source.index + '-' + target.index)
const duration = 3000;
ribbonsUpdate
.transition()
.duration(duration)
.attr("d", ribbon)
.style("fill", function(d) { return color(d.target.index); })
.style("stroke", function(d) { return d3.rgb(color(d.target.index)).darker(); })
ribbonsUpdate
.enter()
.append("path")
.attr("opacity", 0)
.attr("d", ribbon)
.style("fill", function(d) { return color(d.target.index); })
.style("stroke", function(d) { return d3.rgb(color(d.target.index)).darker(); })
.transition()
.duration(duration)
.attr('opacity', 1)
ribbonsUpdate
.exit()
.transition()
.duration(duration)
.attr("opacity", 0)
.remove();
}
update(data1);
I've replaced combination of .datum(val) and .data(fun) in your code to single .data(vals). It is not required, but my opinion is that the latter is more common in d3 community.
The second argument to .data is the key function. It ensures, that during transition each path maps to itself in the new data (the number of chords is not equal: we removed chords to itself).
If you do not need animated transitions, the code is even simplier:
function simpleUpdate(data) {
const chords = chord(data);
const ribbonsUpdate = ribbons.selectAll("path")
.data(chords, ({source, target}) => source.index + '-' + target.index)
ribbonsUpdate
.enter().append("path")
.merge(ribbonsUpdate)
.attr("d", ribbon)
.style("fill", function(d) { return color(d.target.index); })
.style("stroke", function(d) { return d3.rgb(color(d.target.index)).darker(); })
ribbonsUpdate.exit().remove();
}

d3.on("mouseover") occurring before mouseover

This is a d3 bar chart. line 162 ,
.on("mouseover", console.log("I am on it"))
should only happen when user is over a bar on the bar chart, but the log is output from the time the page loads.
The running code is here, https://shanegibney.github.io/d3Mouseover/
The full code is here, https://github.com/shanegibney/d3Mouseover
<!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 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 x = d3.scaleTime().range([0, width]),
x2 = d3.scaleTime().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
y2 = d3.scaleLinear().range([height2, 0]),
dur = d3.scaleLinear().range([0, 12]);
var xAxis = d3.axisBottom(x).tickSize(0),
xAxis2 = d3.axisBottom(x2).tickSize(0),
yAxis = d3.axisLeft(y).tickSize(0);
var brush = d3.brushX()
.extent([
[0, 0],
[width, height2]
])
.on("start brush end", brushed);
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.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("dataDefault.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.mouseoverDisplay = parseTime(d.date);
d.date = parseTime(d.date);
d.end = parseTime(d.end);
d.duration = ((d.end - d.date) / (60 * 1000)); // session duration in minutes
d.distance = +d.distance;
d.intensityInverted = (1 / (d.distance / d.duration)); // inverse of intensity so that the light colour is for low intensity and dark colour is for high intensity
d.intensity = Math.round(d.distance / d.duration); // actually intensity, metres per minute.
d.course = d.course.toLowerCase();
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.date;
});
total = String(total).replace(/(.)(?=(\d{3})+$)/g, '$1,') //place thousands comma in total distance string
var xMin = d3.min(data, function(d) {
return d.date;
});
var yMax = Math.max(20, d3.max(data, function(d) {
return d.distance;
}));
dur.domain([0, d3.max(data, function(d) {
return d.duration;
})]);
var colorScale = d3.scaleSequential(d3.interpolateInferno)
.domain([0, d3.max(data, function(d) {
return d.intensityInverted;
})]);
var totalDistance = d3.select("#totalDistance").append("p").text("Total distance: " + total + "m");
x.domain([xMin, new Date()]);
y.domain([0, yMax]);
x2.domain(x.domain());
y2.domain(y.domain());
// append scatter plot to main chart area
var messages = focus.append("g");
messages.attr("clip-path", "url(#clip)");
messages.selectAll("message")
.data(data)
.enter().append("rect")
.style("fill", function(d) {
return colorScale(d.intensityInverted);
})
.attr("class", "message")
.attr("x", function(d) {
return x(d.date);
})
.attr("y", function(d) {
return y(d.distance);
})
.attr("width", function(d) {
return dur(d.duration);
})
.attr("height", function(d) {
return height - y(d.distance);
})
// .on("mouseover", onit);
.on("mouseover", () => console.log("I am now on it for sure!"));
// function onit(d) {
// console.log("I am on it now!")
// }
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");
// focus.append("text")
// .attr("x", width - margin.right)
// .attr("dy", "1em")
// .attr("text-anchor", "end")
// // .text("Messages: " + num_messages(data, x));
// .text("Total distance: " + total + "m");
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);
// append scatter plot to brush chart area
var messages = context.append("g");
messages.attr("clip-path", "url(#clip)");
messages.selectAll("message")
.data(data)
.enter().append("rect")
.style("fill", function(d) {
return colorScale(d.intensityInverted);
})
.attr("class", "message")
.attr("x", function(d) {
return x2(d.date);
})
.attr("y", function(d) {
return y2(d.distance);
})
.attr("width", function(d) {
return dur(d.duration);
})
.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());
});
//create brush function redraw scatterplot with selection
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(".message")
.attr("x", function(d) {
return x(d.date);
})
.attr("y", function(d) {
return y(d.distance);
})
.attr("width", function(d) {
return dur(d.duration);
})
.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 selectedMessages = focus.selectAll('.message').filter(function() {
var xValue = this.getAttribute('x');
return e[0] <= xValue && xValue <= e[1];
});
// console.log(selectedMessages.nodes().length);
}
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(".message")
// .enter().append("rect")
// .style("fill", function(d) {
// return colorScale(d.intensityInverted);
// })
// .attr("class", "message")
.attr("x", function(d) {
return x(d.date);
})
.attr("y", function(d) {
return y(d.distance);
})
.attr("width", function(d) {
return dur(d.duration);
})
.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));
}
function handleMouseOver(d) {
d3.select(this)
.style("fill", "lightBlue");
g.select('text')
.attr("x", 15)
.attr("y", 5)
.text("Session no. " + d.number)
.append('tspan')
.text("Date: " + mouseoverTime(d.mouseoverDisplay))
.attr("x", 15)
.attr("y", 30)
.append('tspan')
.text("Distance: " + d.distance + "m")
.attr("x", 15)
.attr("y", 50)
.append('tspan')
.text("Duration: " + d.duration + " mins")
.attr("x", 15)
.attr("y", 70)
.append('tspan')
.text("Intensity: " + d.intensity + " meters/mins")
.attr("x", 15)
.attr("y", 90)
.append('tspan')
.text("Pool: " + d.pool + " (" + d.course + ")")
.attr("x", 15)
.attr("y", 110);
console.log("handleMouseOver function");
}
function handleMouseOut(d) {
d3.select(this)
.style("fill", function(d) {
return colorScale(d.intensityInverted);
});
g.select('text').text("Total distance since " + minTime(minDate) + ": " + total + "m");
}
</script>
Sample data,
[{
"number": "1",
"date": "2016-11-09 11:15",
"end": "2016-11-09 11:45",
"distance": "1100",
"course": "LC",
"pool": "UCD"
},
{
"number": "2",
"date": "2016-11-10 10:40",
"end": "2016-11-10 11:20",
"distance": "1500",
"course": "LC",
"pool": "UCD"
},
{
"number": "3",
"date": "2016-11-11 16:45",
"end": "2016-11-11 17:50",
"distance": "2000",
"course": "LC",
"pool": "UCD"
},
{
"number": "4",
"date": "2016-11-12 12:48",
"end": "2016-11-12 13:53",
"distance": "2500",
"course": "LC",
"pool": "UCD"
}
]
I added the last two lines here, but still no luck,
var messages = focus.append("g");
messages.attr("clip-path", "url(#clip)");
messages.selectAll("message")
.data(data)
.enter().append("rect")
.style("fill", function(d) {
return colorScale(d.intensityInverted);
})
.attr("class", "message")
.attr("x", function(d) {
return x(d.date);
})
.attr("y", function(d) {
return y(d.distance);
})
.attr("width", function(d) {
return dur(d.duration);
})
.attr("height", function(d) {
return height - y(d.distance);
})
// .on("mouseover", onit);
.on("mouseover", () => console.log("I am now on it for sure!"));
// function onit(d) {
// console.log("I am on it now!")
// }
I suspect the problem is because I am using brushX() and zoom.
When you do this:
.on("mouseover", console.log("I am on it"))
You're passing the result of the console.log function to the callback. Instead of that, you want to pass its reference:
.on("mouseover", function(){
console.log("I am on it")
})
Check this snippet (don't hover over the circle!):
var circle = d3.select("circle");
circle.on("mouseover", console.log("I'm on it!"));
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg>
<circle cx="50" cy="50" r="15" fill="teal"></circle>
</svg>
Now the same code, with the reference to console.log:
var circle = d3.select("circle");
circle.on("mouseover", () => console.log("I'm on it!"));
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg>
<circle cx="50" cy="50" r="15" fill="teal"></circle>
</svg>
The issue is that you are calling the function at the time you are declaring it:
.on("mouseover", console.log("I am on it")) //function call
should be something like this:
.on("mouseover", console.log) //function ref
.on("mouseover", function(d) { console.log("I am on it") }) //function ref

Change single chord color in chord diagram using D3.js

D3 and Javascript newbie here. I'd like to change the color of a single chord in a chord diagram that is rendered with D3. Ideally, this color can be arbitrary, with no relationship to the source/destination entities of the chord.
How do I identify a single chord so I can later access it to fill it?
Here's an image (poorly edited with an image editor) with the desired effect (green chord).
var matrix = [
[11975, 5871, 8916, 2868],
[ 1951, 10048, 2060, 6171],
[ 8010, 16145, 8090, 8045],
[ 1013, 990, 940, 6907]
];
var chord = d3.layout.chord()
.padding(.05)
.sortSubgroups(d3.descending)
.matrix(matrix);
var width = 960,
height = 500,
innerRadius = Math.min(width, height) * .41,
outerRadius = innerRadius * 1.1;
var fill = d3.scale.ordinal()
.domain(d3.range(4))
.range(["#000000", "#FFDD89", "#957244", "#F26223"]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
svg.append("g").selectAll("path")
.data(chord.groups)
.enter().append("path")
.style("fill", function(d) { return fill(d.index); })
.style("stroke", function(d) { return fill(d.index); })
.attr("d", d3.svg.arc().innerRadius(innerRadius).outerRadius(outerRadius))
.on("mouseover", fade(.1))
.on("mouseout", fade(1));
var ticks = svg.append("g").selectAll("g")
.data(chord.groups)
.enter().append("g").selectAll("g")
.data(groupTicks)
.enter().append("g")
.attr("transform", function(d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ "translate(" + outerRadius + ",0)";
});
ticks.append("line")
.attr("x1", 1)
.attr("y1", 0)
.attr("x2", 5)
.attr("y2", 0)
.style("stroke", "#000");
ticks.append("text")
.attr("x", 8)
.attr("dy", ".35em")
.attr("transform", function(d) { return d.angle > Math.PI ? "rotate(180)translate(-16)" : null; })
.style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.text(function(d) { return d.label; });
svg.append("g")
.attr("class", "chord")
.selectAll("path")
.data(chord.chords)
.enter().append("path")
.attr("d", d3.svg.chord().radius(innerRadius))
.style("fill", function(d) { return fill(d.target.index); })
.style("opacity", 1)
.on("mouseover", function(){
d3.select(this).style("opacity", 0.3);
})
.on("mouseout", function(){
d3.select(this).style("opacity", 1);
});
// Returns an array of tick angles and labels, given a group.
function groupTicks(d) {
var k = (d.endAngle - d.startAngle) / d.value;
return d3.range(0, d.value, 1000).map(function(v, i) {
return {
angle: v * k + d.startAngle,
label: i % 5 ? null : v / 1000 + "k"
};
});
}
// Returns an event handler for fading a given chord group.
function fade(opacity) {
return function(g, i) {
svg.selectAll(".chord path")
.filter(function(d) { return d.source.index != i && d.target.index != i; })
.transition()
.style("opacity", opacity);
};
}
body {
font: 10px sans-serif;
}
.chord path {
fill-opacity: .67;
stroke: #000;
stroke-width: .5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I've just added functionality for mouse over and mouse out,
svg.append("g")
.attr("class", "chord")
.selectAll("path")
.data(chord.chords)
.enter().append("path")
.attr("d", d3.svg.chord().radius(innerRadius))
.style("fill", function(d) { return fill(d.target.index); })
.style("opacity", 1)
.on("mouseover", function(){
d3.select(this).style("opacity", 0.3);
})
.on("mouseout", function(){
d3.select(this).style("opacity", 1);
});
In the above code see the mouseover and mouseout callbacks,
Here I'm just changing the opacity, if you want to change the color, use fill attribute to fill the color.
Hope you are looking for this.
If not ask me for more.
:D

add circle in a spiral chart with d3js with line connecting to center

hi I created a spiral chart in d3.js, and I want to add circle to different position of the spiral lines.according to there values.
circle closes to the center will have highest priority.
any idea how to do that.
here is the code which i wrote
var width = 400,
height = 430
num_axes = 8,
tick_axis = 1,
start = 0
end = 4;
var theta = function(r) {
return -2*Math.PI*r;
};
var arc = d3.svg.arc()
.startAngle(0)
.endAngle(2*Math.PI);
var radius = d3.scale.linear()
.domain([start, end])
.range([0, d3.min([width,height])/2-20]);
var angle = d3.scale.linear()
.domain([0,num_axes])
.range([0,360])
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + (height/2+8) +")");
var pieces = d3.range(start, end+0.001, (end-start)/1000);
var spiral = d3.svg.line.radial()
.interpolate("cardinal")
.angle(theta)
.radius(radius);
//svg.append("text")
// .text("And there was much rejoicing!")
// .attr("class", "title")
// .attr("x", 0)
// .attr("y", -height/2+16)
// .attr("text-anchor", "middle")
//svg.selectAll("circle.tick")
// .data(d3.range(end,start,(start-end)/4))
// .enter().append("circle")
// .attr("class", "tick")
// .attr("cx", 0)
// .attr("cy", 0)
// .attr("r", function(d) { return radius(d); })
svg.selectAll(".axis")
.data(d3.range(num_axes))
.enter().append("g")
.attr("class", "axis")
.attr("transform", function(d) { return "rotate(" + -angle(d) + ")"; })
.call(radial_tick)
.append("text")
.attr("y", radius(end)+13)
.text(function(d) { return angle(d) + "°"; })
.attr("text-anchor", "middle")
.attr("transform", function(d) { return "rotate(" + -90 + ")" })
svg.selectAll(".spiral")
.data([pieces])
.enter().append("path")
.attr("class", "spiral")
.attr("d", spiral)
.attr("transform", function(d) { return "rotate(" + 90 + ")" });
function radial_tick(selection) {
selection.each(function(axis_num) {
d3.svg.axis()
.scale(radius)
.ticks(5)
.tickValues( axis_num == tick_axis ? null : [])
.orient("bottom")(d3.select(this))
d3.select(this)
.selectAll("text")
.attr("text-anchor", "bottom")
.attr("transform", "rotate(" + angle(axis_num) + ")")
});
}
please see the second solution for my implementation. Help me with connecting the circle with the center
Here is a model for the technique you seem to be looking for...
var width = 400,
height = 430,
num_axes = 8,
tick_axis = 1,
start = 0,
end = 4,
testValue = 2;
var theta = function (r) {
return -2 * Math.PI * r;
};
var arc = d3.svg.arc()
.startAngle(0)
.endAngle(2 * Math.PI);
var radius = d3.scale.linear()
.domain([start, end])
.range([0, (d3.min([width, height]) / 2 - 20)]);
var angle = d3.scale.linear()
.domain([0, num_axes])
.range([0, 360]);
var chart = d3.select("#chart")
.style("width", width + "px");
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 8) + ")");
var pieces = d3.range(start, end + 0.001, (end - start) / 500);
var spiral = d3.svg.line.radial()
.interpolate("linear")
.angle(theta)
.radius(radius);
svg.append("text")
.text("Title")
.attr("class", "title")
.attr("x", 0)
.attr("y", -height/2+16)
.attr("text-anchor", "middle")
svg.selectAll("circle.tick")
.data(d3.range(end,start,(start-end)/4))
.enter().append("circle")
.attr("class", "tick")
.style({fill: "black", opacity: 0.1})
.attr("cx", 0)
.attr("cy", 0)
.attr("r", function(d) { return radius(d); })
svg.selectAll(".axis")
.data(d3.range(num_axes))
.enter().append("g")
.attr("class", "axis")
.attr("transform", function (d) { return "rotate(" + -angle(d) + ")"; })
.call(radial_tick)
.append("text")
.attr("y", radius(end) + 13)
.text(function (d) { return angle(d) + "°"; })
.attr("text-anchor", "middle")
.attr("transform", function (d) { return "rotate(" + -90 + ")" })
svg.selectAll(".axis path")
.style({fill: "none", stroke: "black"})
.attr("stroke-dasharray", "5 5")
svg.selectAll(".spiral")
.data([pieces])
.enter().append("path")
.attr("class", "spiral")
.attr("fill", "none")
.attr("stroke", "black")
.attr("d", spiral)
.attr("transform", function (d) { return "rotate(" + 90 + ")" });
function radial_tick(selection) {
selection.each(function (axis_num) {
d3.svg.axis()
.scale(radius)
.ticks(5)
.tickValues(axis_num == tick_axis ? null : [])
.tickSize(1)
.orient("bottom")(d3.select(this))
d3.select(this)
.selectAll("text")
.attr("text-anchor", "bottom")
.attr("transform", "rotate(" + angle(axis_num) + ")")
});
}
function radialScale(x) {
var t = theta(x), r = radius(x);
d3.select(this)
.attr("cx", r * Math.cos(t))
.attr("cy", r * Math.sin(t))
}
slider = SliderControl("#circleSlider", "data", update, [start, end], ",.3f");
function update(x) {
if (typeof x != "undefined") testValue = x;
var circles = svg.selectAll(".dataPoints")
.data([testValue]);
circles.enter().append("circle");
circles.attr("class", "dataPoints")
.style({ fill: "black", opacity: 0.6 })
.attr("r", 10)
.each(radialScale)
circles.exit().remove();
return testValue
}
function SliderControl(selector, title, value, domain, format) {
var accessor = d3.functor(value), rangeMax = 1000,
_scale = d3.scale.linear().domain(domain).range([0, rangeMax]),
_$outputDiv = $("<div />", { class: "slider-value" }),
_update = function (value) {
_$outputDiv.css("left", 'calc( '
+ (_$slider.position().left + _$slider.outerWidth()) + 'px + 1em )')
_$outputDiv.text(d3.format(format)(value));
$(".input").width(_$outputDiv.position().left + _$outputDiv.outerWidth() - _innerLeft)
},
_$slider = $(selector).slider({
value: _scale(accessor()),
max: rangeMax,
slide: function (e, ui) {
_update(_scale.invert(ui.value));
accessor(_scale.invert(ui.value));
}
}),
_$wrapper = _$slider.wrap("<div class='input'></div>")
.before($("<div />").text(title + ":"))
.after(_$outputDiv).parent(),
_innerLeft = _$wrapper.children().first().position().left;
_update(_scale.invert($(selector).slider("value")))
return d3.select(selector)
};
.domain {
stroke-width: 1px;
}
<link href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart">
<div id="circleSlider"></div>
</div>

Categories