Drawing multipe items in svg g elements using d3 - svg

I want to draw three different shapes collected inside a g element. I want to draw text, line and a graph. The problem im having with the code below is that the text is shown but the line does not show. It is drawn and I can see it in the source but it is not visible on the screen. What am I missing here?
//Bind data to a new g element
line = svg.selectAll("g")
.data(source)
.enter()
.append("g")
.attr({
"transform": function(d,i){ return "translate(50 " + (height - 50 + 15 * i) + ")"}
});
line.append("text")
.text(function(d,i){
return d.name;
});
line.append("line")
.attr({
"opacity" : 1,
"stroke-width" : 2,
"stroke" : "blue",
"class" : "crisp",
"x1" : function (d) { return vfTimelineScale(d.start); },
"y1" : function (d,i) { return (height - 50 + 15 * i); },
"x2" : function (d) { return vfTimelineScale(d.stop); },
"y2" : function (d,i) { return (height - 50 + 15 * i); },
})

You are already positioning through the transformation, I presume you don't need the lines to be 450 px offset vertically from the text. Change the formula to something such as...
"x1" : function (d) { return vfTimelineScale(d.start); },
"y1" : 50,
"x2" : function (d) { return vfTimelineScale(d.stop); },
"y2" : 50
You will need to change the 50 value to suit your needs of course.

Related

how to change opacity of links when clicked on a node in sankey diagram using d3 library

Description : data comes from json. nodes have property name it contains "loc1|April" while links have properties source : 0, target : 1, value : 2
so i have assigned the location of target node as a class to all the links using the code line in bold.
code :
var link = svg.append("g").selectAll(".link")
.data(energy.links)
.enter().append("path")
.attr("class", function(d){ return "link " + d.target.name; })
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.style("stroke", function(d) { return d.color = color(d.target.name.split("|")[0]); })
.sort(function(a, b) { return b.dy - a.dy; });
So when i click on a particular node lets say node having loc1 as its location which i store it in variable loc.Then based on that location i select all the links having that location as a class and then based on that i want to change the opacity of the selected node to 0.5. But the following code doesn't work.
code:
d3.selectAll(".node")
.on("click",function(d){
var loc = "." + d.name.split("|")[0];
//console.log(loc);
d3.selectAll(loc)
.style("stroke-opacity",.5);
})

In d3js, how to plot line chart from csv file, taking data from seperate columns for each row

I am new to d3js. I have data in csv file. The data in csv file is something like this:
Name,val1,val2,val3,val4
x,5,4,8,2
y,10,5,4,13
z,7,3,2,11
I need to plot line chart for each row depending upon choice selected, i.e a chart for x with values 5,4,8 or for y with corresponding values & so on.
Here is the code I am working on:
var slots=["0-6", "6-12", "12-18", "18-23"];
newdata = rawdata.filter(function(d)
{
return (d["Name"] == ch);
});
maxval = d3.max(rawdata, function(d)
{
return Math.max(d["Val1"],d["Val2"],d["Val3"],d["Val4"]);
});
var rangeX=d3.scale.ordinal().rangeRoundBands([0, w], 0.2).domain(slots);
var rangeY=d3.scale.linear().range([h, 0]).domain([0, (maxval+50)]);
var axis_x=d3.svg.axis().scale(rangeX).tickSize(2).ticks(4);
var axis_y=d3.svg.axis().scale(rangeY).tickSize(2).orient("left");
p.append("g").call(axis_x).attr("transform", "translate(0," + (h) + ")");;
p.append("g").call(axis_y).attr("transform", "translate(0,0)");
var lineGen = d3.svg.line()
.x(slots, function(slots, i){return xScale(slots[i]);})
.y(newdata, function (d, i){return yScale(d["Data "+(i+1)]);})
p.selectAll("line").attr("d", lineGen()).attr("stroke", "red").attr("stroke-width", 2).attr("fill", "none");
I know the "lineGen" function cannot be used the way I am using, I cant have the "slots" & "newdata" passed like this in "x" & "y". But was giving a try.
Can somebody guide me what could be done?
Here's one possible solution. First, use a d3.csv .row to organize the data into an object of key: [values] where key is your x, y and z and values is an array of val1, val2, ...
d3.csv("data.csv")
.row(function(d) {
return {
key: d.Name,
values: [+d.val1, +d.val2, +d.val3, +d.val4]
};
})
.get(function(error, data) {
// draw plot
}
Then your line generator is simply:
var line = d3.svg.line()
.interpolate("basis")
.x(function(d,i) { return rangeX(slots[i]); })
.y(function(d,i) { return rangeY(d); });
And you draw each line as:
var g = p.selectAll(".lineGroup")
.data(data)
.enter().append("g")
.attr("class", "lineGroup");
g.append("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.values);
})
.style("stroke", function(d,i) {
return color(i);
})
.attr("fill","none");
Here's an example.

NVD3 line chart with vertical line

I'm trying to generate a linechart in NVD3 with a vertical line. Particularly this kind of line chart.
The linechart has two panels a viewing panel and a zoom panel, I would want the line to be on both.
Something like this:
Is this feasible?
Edit:
I found out a way to do this by just appending to the data an extra stream which represents a line. e.g.
streams[3] = {key:'myline', values:[{x:68,y:0},{x:68,y:7}]}
Is there a better way?
Yes it's possible,
Here is an example of how to do it :
https://gist.github.com/timelyportfolio/80d85f78a5a975fa29d7#file-code-r
The solution here is to add a javascript function drawing vertical lines using NVD3 ( read carefully the comments ) :
function drawVerticalLines(opts) {
// CAREFUL HERE !!! the css pasth ".nvd3 .nv-focus .nv-linesWrap" depends on the type of chart you are using, lineChart would use only ".nvd3 .nv-linesWrap" ... !
if (!(d3.select('#' + opts.id + ' the css pasth ".nvd3 .nv-focus .nv" depends on the type of chart you are using, lineChart would use only -linesWrap').select('.vertical-lines')[0][0])) {
// Adds new g element with .vertical-lines class; use a css debugger to verify
d3.select('#' + opts.id + ' .nvd3 .nv-focus .nv-linesWrap').append('g')
.attr('class', 'vertical-lines')
}
vertLines = d3.select('#' + opts.id + ' .nvd3 .nv-focus .nv-linesWrap').select('.vertical-lines').selectAll('.vertical-line')
.data(
[{
'date': new Date('1967-11-30'),
'label': 'something to highlight 1967'
}, {
'date': new Date('2001-11-30'),
'label': 'something to highlight 2001'
}])
var vertG = vertLines.enter()
.append('g')
.attr('class', 'vertical-line')
vertG.append('svg:line')
vertG.append('text')
vertLines.exit().remove()
// CAREFUL 2 : chart.xAxis.scale() scale depends how you are defining your x Axis in nvd3 chart ... if your are using timestamps, (d.date / 60 / 60 / 24 / 1000) becomes (d.date)
vertLines.selectAll('line')
.attr('x1', function(d) {
return chart.xAxis.scale()(d.date / 60 / 60 / 24 / 1000)
})
.attr('x2', function(d) {
return chart.xAxis.scale()(d.date / 60 / 60 / 24 / 1000)
})
.attr('y1', chart.yAxis.scale().range()[0])
.attr('y2', chart.yAxis.scale().range()[1])
.style('stroke', 'red')
vertLines.selectAll('text')
.text(function(d) {
return d.label
})
.attr('dy', '1em')
//x placement ; change dy above for minor adjustments but mainly
// change the d.date/60/60/24/1000
//y placement ; change 2 to where you want vertical placement
//rotate -90 but feel free to change to what you would like
.attr('transform', function(d) {
return 'translate(' +
chart.xAxis.scale()(d.date / 60 / 60 / 24 / 1000) +
',' +
chart.yAxis.scale()(2) +
') rotate(-90)'
})
//also you can style however you would like
//here is an example changing the font size
.style('font-size', '80%')
}
And call this method in nv.addGraph Callback :
var sharedChart = null; // Shared reference on the chart
nv.addGraph(function() {
.....
sharedChart = chart;
return chart;
,
function() {
drawVerticalLines(opts, sharedChart);
}
);
With opts ... (obviously you don't really need it):
var opts${widgetID.replace('-', '0')} = {
"dom": "chart${widgetID}",
"width": 800,
"height": 400,
"x": "date",
"y": "value",
"group": "variable",
"type": "lineWithFocusChart",
"id": "chart${widgetID}"
};
Hope this helps, it took me quite a long time to find it and to make it work !

How to smoothen lines using SVG?

I am looking at this example. How this can be done using Raphael for below example ?
Raphael("canvas", function () {
var win = Raphael._g.win,
doc = win.document,
hasTouch = "createTouch" in doc,
M = "M",
L = "L",
d = "d",
COMMA = ",",
// constant for waiting doodle stop
INTERRUPT_TIMEOUT_MS = hasTouch ? 100 : 1,
// offset for better visual accuracy
CURSOR_OFFSET = hasTouch ? 0 : -10,
paper = this,
path = "", // hold doodle path commands
// this element draws the doodle
doodle = paper.path(path).attr({
"stroke": "rgb(255,0,0)"
}),
// this is to capture mouse movements
tracker = paper.rect(0, 0, paper.width, paper.height).attr({
"fill": "rgb(255,255,255)",
"fill-opacity": "0.01"
}),
active = false, // flag to check active doodling
repath = false, // flag to check if a new segment starts
interrupt; // this is to connect jittery touch
tracker.mousedown(function () {
interrupt && (interrupt = clearTimeout(interrupt));
active = true;
repath = true;
});
tracker.mousemove(function (e, x, y) {
// do nothing if doodling is inactive
if (!active) {
return;
}
// Fix for Raphael's touch xy bug
if (hasTouch &&
(e.originalEvent.targetTouches.length === 1)) {
x = e.clientX +
(doc.documentElement.scrollTop || doc.body.scrollTop || 0);
y = e.clientY +
(doc.documentElement.scrollLeft || doc.body.scrollLeft || 0);
e.preventDefault();
}
// Insert move command for a new segment
if (repath) {
path += M + (x + CURSOR_OFFSET) + COMMA +
(y + CURSOR_OFFSET);
repath = false;
}
path += L + (x + CURSOR_OFFSET) + COMMA +
(y + CURSOR_OFFSET); // append line point
// directly access SVG element and set path
doodle.node.setAttribute(d, path);
});
// track window mouse up to ensure mouse up even outside
// paper works.
Raphael.mouseup(function () {
interrupt && (interrupt = clearTimeout(interrupt));
// wait sometime before deactivating doodle
interrupt = setTimeout(function () {
active = false;
}, INTERRUPT_TIMEOUT_MS);
});
Above code is copied from https://stackoverflow.com/a/17781275/1595858
To create a smooth line through several points, use Raphael's Catmull-Rom extension to SVG paths. See: http://raphaeljs.com/reference.html#Paper.path .
// Creating a line:
var line = paper.path("M x0 y0 R x1 y1 x2 y2 x3 y3 x4 y4");
// Adding next point:
line.attr("path", line.attr("path") + " x5 y5");
The easiest way to smoothen the lines in your case (using Raphael) is to use the Raphael._path2curve function.
The place where you are doing doodle.node.setAttribute(d, path); replace it with the following line, thus replacing all line segments with curves.
doodle.node.setAttribute(d, Raphael._path2curve(path));
Note that this will result in degradation of performance. There is a huge scope of improvement of this by realtime converting path to curves instead of converting the entire path every time.

Recalculate points in an SVG path when first or last point changes

I have built a graph with D3.js based on this example of a force-directed graph, but rather than having straight lines between the nodes I am creating curved lines using SVG path elements. The data structure for an individual link includes a source and target which represent the nodes to which the link is connected. Also the link data structure also contains a line element which contains an array of points defining the path. The data structure for a link looks like this:
{
id:4,
type:"link",
fixed:true,
source: {
id:1,
name: "A",
type:"node",
x:226,
y:190,
fixed:1,
index:0,
weight:1,
},
target: {
id:2,
name: "B",
type:"node",
x:910,
y:85,
fixed:1,
index:1,
weight:1,
},
line:[{x:387, y:69}, {x:541.5, y:179}, {x:696, y:179}]
}
Now in my on tick event handler I have the current x and y co-ordinates for for nodes A and B by means of the references d.source.x, d.source.y and d.target.x, d.target.y. I also have the initial position of node A (first element of d.line) and of node B (last element of d.line). What I am trying to do is to recalculate the points in between the first and last points based on the changes made to the positions of nodes A and B.
Here is my on tick event handler:
force.on("tick", function() {
svg.selectAll("g.node").attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
svg.selectAll("g.link .line").attr("d", function(d) {
var xOffset = 0, // Want to calculate this
yOffset = 0; // Want to calculate this
var line = [ ];
for (var i=0; i<d.line.length; i++) {
if (i==0) {
line[line.length] = { x : d.source.x, y: d.source.y }
}
else if (i==d.line.length-1) {
line[line.length] = { x : d.target.x, y: d.target.y }
}
else {
line[line.length] = { x: d.line[i].x + xOffset, y: d.line[i].y + yOffset }
}
}
return self.lineGenerator(line);
})
});
Not offsetting the x and y co-ordinates for the points in the middle of the path results in these points staying static when nodes A or B are dragged. Can anyone explain how to go about calculating xOffset and yOffset so that the path will correctly move/rotate when the nodes are dragged? Or if anyone knows of a better (read "easier"!) way of accomplishing this with D3.js then please let me know.
UPDATE:
Here is my line generator code in the chart constructor:
this.lineGenerator = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("cardinal");
Let me explain the problem with some images, hopefully they will make the problem that I am having clearer. It is not a matter of the lines being linear, my lines are curved by the line generator, but when I drag a node, I want the curve to drag as well. So all the points on the line must update to reflect the change made to the position of the node. In other words the path must not get distorted by the nodes being moved.
So, in pictures; given this situation:
If I drag node B down and to the left, I want to see something like this:
(Note that this was rotated with image editting software, hence the background is also rotated)
Instead I am getting something like this:
Note how the one intermediary point in the line has stayed static when node B was moved, causing the curve to distort. This is what I am trying to avoid happening.
Hopefully this makes the problem clearer.

Resources