Here is a parallel coordinate with some sample data
http://plnkr.co/edit/dCNuBsaDNBwr7CrAJUBe?p=preview
Here the numerical axis like column1 , column3 have small number of data so it looks ok. But in practice, I have a big data (something like 600 entries), so numerical ticks in axis become clustered , overlapped.
Is there a way so that it looks good. Like Setting the number of ticks in parallel coordinate. For instance, if I want to change the number of ticks in column1 of this example from 10 to 5.
Any help would be appreciated.
Thanks
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.background path {
fill: none;
stroke: #ddd;
stroke-opacity: .4;
shape-rendering: crispEdges;
}
.foreground path {
fill: none;
stroke: steelblue;
stroke-opacity: .7;
}
.brush .extent {
fill-opacity: .3;
stroke: #fff;
shape-rendering: crispEdges;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
cursor: move;
}
</style>
<body>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script>
var margin = {top: 30, right: 10, bottom: 10, left: 10},
width = 600 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var x = d3.scalePoint().rangeRound([0, width]).padding(1),
y = {},
dragging = {};
var line = d3.line(),
//axis = d3.axisLeft(x),
background,
foreground,
extents;
var container = d3.select("body").append("div")
.attr("class", "parcoords")
.style("width", width + margin.left + margin.right + "px")
.style("height", height + margin.top + margin.bottom + "px");
var svg = container.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 quant_p = function(v){return (parseFloat(v) == v) || (v == "")};
d3.json("convertcsv.json", function(error, cars) {
dimensions = d3.keys(cars[0]);
x.domain(dimensions);
dimensions.forEach(function(d) {
var vals = cars.map(function(p) {return p[d];});
if (vals.every(quant_p)){
y[d] = d3.scaleLinear()
.domain(d3.extent(cars, function(p) {
return +p[d]; }))
.range([height, 0])
console.log(y[d]);
}
else{
vals.sort();
y[d] = d3.scalePoint()
.domain(vals.filter(function(v, i) {return vals.indexOf(v) == i;}))
.range([height, 0],1);
}
})
extents = dimensions.map(function(p) { return [0,0]; });
// Add grey background lines for context.
background = svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path);
// Add blue foreground lines for focus.
foreground = svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path);
// Add a group element for each dimension.
var g = svg.selectAll(".dimension")
.data(dimensions)
.enter().append("g")
.attr("class", "dimension")
.attr("transform", function(d) { return "translate(" + x(d) + ")"; })
.call(d3.drag()
.subject(function(d) { return {x: x(d)}; })
.on("start", function(d) {
dragging[d] = x(d);
background.attr("visibility", "hidden");
})
.on("drag", function(d) {
dragging[d] = Math.min(width, Math.max(0, d3.event.x));
foreground.attr("d", path);
dimensions.sort(function(a, b) { return position(a) - position(b); });
x.domain(dimensions);
g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
})
.on("end", function(d) {
delete dragging[d];
transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
transition(foreground).attr("d", path);
background
.attr("d", path)
.transition()
.delay(500)
.duration(0)
.attr("visibility", null);
}));
// Add an axis and title.
var g = svg.selectAll(".dimension");
g.append("g")
.attr("class", "axis")
.each(function(d) { d3.select(this).call(d3.axisLeft(y[d]));})
//text does not show up because previous line breaks somehow
.append("text")
.attr("fill", "black")
.style("text-anchor", "middle")
.attr("y", -9)
.text(function(d) { return d; });
// Add and store a brush for each axis.
g.append("g")
.attr("class", "brush")
.each(function(d) {
if(y[d].name == 'r'){
// console.log(this);
d3.select(this).call(y[d].brush = d3.brushY().extent([[-8, 0], [8,height]]).on("brush start", brushstart).on("brush", go_brush).on("brush", brush_parallel_chart).on("end", brush_end));
}
else if(y[d].name == 'n')
d3.select(this).call(y[d].brush = d3.brushY().extent([[-8, 0], [15,height]]).on("brush start", brushstart).on("brush", go_brush).on("brush", brush_parallel).on("end", brush_end_ordinal));
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
}); // closing
function position(d) {
var v = dragging[d];
return v == null ? x(d) : v;
}
function transition(g) {
return g.transition().duration(500);
}
// Returns the path for a given data point.
function path(d) {
return line(dimensions.map(function(p) { return [position(p), y[p](d[p])]; }));
}
function go_brush() {
d3.event.sourceEvent.stopPropagation();
}
invertExtent = function(y) {
return domain.filter(function(d, i) { return y === range[i]; });
};
function brushstart(selectionName) {
foreground.style("display", "none")
//console.log(selectionName);
var dimensionsIndex = dimensions.indexOf(selectionName);
//console.log(dimensionsIndex);
extents[dimensionsIndex] = [0, 0];
foreground.style("display", function(d) {
return dimensions.every(function(p, i) {
if(extents[i][0]==0 && extents[i][0]==0) {
return true;
}
return extents[i][1] <= d[p] && d[p] <= extents[i][0];
}) ? null : "none";
});
}
// Handles a brush event, toggling the display of foreground lines.
function brush_parallel_chart() {
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
//if (d3.event.sourceEvent.type === "brush") return;
extents[i]=d3.event.selection.map(y[dimensions[i]].invert,y[dimensions[i]]);
}
}
foreground.style("display", function(d) {
return dimensions.every(function(p, i) {
if(extents[i][0]==0 && extents[i][0]==0) {
return true;
}
return extents[i][1] <= d[p] && d[p] <= extents[i][0];
}) ? null : "none";
});
}
function brush_end(){
if (!d3.event.sourceEvent) return; // Only transition after input.
if (!d3.event.selection) return; // Ignore empty selections.
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
extents[i]=d3.event.selection.map(y[dimensions[i]].invert,y[dimensions[i]]);
extents[i][0] = Math.round( extents[i][0] * 10 ) / 10;
extents[i][1] = Math.round( extents[i][1] * 10 ) / 10;
d3.select(this).transition().call(d3.event.target.move, extents[i].map(y[dimensions[i]]));
}
}
}
// brush for ordinal cases
function brush_parallel() {
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
var yScale = y[dimensions[i]];
var selected = yScale.domain().filter(function(d){
// var s = d3.event.target.extent();
var s = d3.event.selection;
return (s[0] <= yScale(d)) && (yScale(d) <= s[1])
});
var temp = selected.sort();
extents[i] = [temp[temp.length-1], temp[0]];
}
}
foreground.style("display", function(d) {
return dimensions.every(function(p, i) {
if(extents[i][0]==0 && extents[i][0]==0) {
return true;
}
//var p_new = (y[p].ticks)?d[p]:y[p](d[p]);
//return extents[i][1] <= p_new && p_new <= extents[i][0];
return extents[i][1] <= d[p] && d[p] <= extents[i][0];
}) ? null : "none";
});
}
function brush_end_ordinal(){
console.log("hhhhh");
if (!d3.event.sourceEvent) return; // Only transition after input.
if (!d3.event.selection) return; // Ignore empty selections.
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
var yScale = y[dimensions[i]];
var selected = yScale.domain().filter(function(d){
// var s = d3.event.target.extent();
var s = d3.event.selection;
return (s[0] <= yScale(d)) && (yScale(d) <= s[1])
});
var temp = selected.sort();
extents[i] = [temp[temp.length-1], temp[0]];
if(selected.length >1)
d3.select(this).transition().call(d3.event.target.move, extents[i].map(y[dimensions[i]]));
}
}
}
</script>
You can either use d3-axis.ticks() or d3-axis.tickValues([]) to specify custom number of ticks/tick values.
For example, for column1 using tickValues and column3 using ticks:
var g = svg.selectAll(".dimension");
g.append("g")
.attr("class", "axis")
.each(function(d) {
if(d === 'column1') {
d3.select(this).call(d3.axisLeft(y[d]).tickValues([.1, .2, .3, .5,.8]));
} else if(d === 'column3') {
d3.select(this).call(d3.axisLeft(y[d]).ticks(4));
} else {
d3.select(this).call(d3.axisLeft(y[d]));
}
})
A fork of your plunkr: TickValues and ticks DEMO
Hope this helps. Read the docs for more options.
Related
I'm trying to make my map look this way
Unfortunately, my code looks this way, and I don't understand why my text nodes are so gigantic not the way I want it
this is the code that I have going or check my fiddle
This code specifically doesn't seem to produce human readable labels
grp
.append("text")
.attr("fill", "#000")
.style("text-anchor", "middle")
.attr("font-family", "Verdana")
.attr("x", 0)
.attr("y", 0)
.attr("font-size", "10")
.text(function (d, i) {
return name;
});
Here's my full code:
var width = 500,
height = 275,
centered;
var projection = d3.geo
.conicConformal()
.rotate([103, 0])
.center([0, 63])
.parallels([49, 77])
.scale(500)
.translate([width / 2.5, height / 2])
.precision(0.1);
var path = d3.geo.path().projection(projection);
var svg = d3
.select("#map-selector-app")
.append("svg")
.attr("viewBox", `0 0 ${width} ${height}`);
// .attr("width", width)
// .attr("height", height);
svg
.append("rect")
.attr("class", "background-svg-map")
.attr("width", width)
.attr("height", height)
.on("click", clicked);
var g = svg.append("g");
var json = null;
var subregions = {
Western: { centroid: null },
Prairies: { centroid: null },
"Northern Territories": { centroid: null },
Ontario: { centroid: null },
Québec: { centroid: null },
Atlantic: { centroid: null },
};
d3.json(
"https://gist.githubusercontent.com/KatFishSnake/7f3dc88b0a2fa0e8c806111f983dfa60/raw/7fff9e40932feb6c0181b8f3f983edbdc80bf748/canadaprovtopo.json",
function (error, canada) {
if (error) throw error;
json = topojson.feature(canada, canada.objects.canadaprov);
g.append("g")
.attr("id", "provinces")
.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.on("click", clicked);
g.append("g")
.attr("id", "province-borders")
.append("path")
.datum(
topojson.mesh(canada, canada.objects.canadaprov, function (
a,
b
) {
return a !== b;
})
)
.attr("id", "province-borders-path")
.attr("d", path);
// g.select("g")
// .selectAll("path")
// .each(function (d, i) {
// var centroid = path.centroid(d);
// });
Object.keys(subregions).forEach((rkey) => {
var p = "";
json.features.forEach(function (f, i) {
if (f.properties.subregion === rkey) {
p += path(f);
}
});
var tmp = svg.append("path").attr("d", p);
subregions[rkey].centroid = getCentroid(tmp.node());
subregions[rkey].name = rkey;
tmp.remove();
});
Object.values(subregions).forEach(({ centroid, name }) => {
var w = 80;
var h = 30;
var grp = g
.append("svg")
// .attr("width", w)
// .attr("height", h)
.attr("viewBox", `0 0 ${w} ${h}`)
.attr("x", centroid[0] - w / 2)
.attr("y", centroid[1] - h / 2);
// grp
// .append("rect")
// .attr("width", 80)
// .attr("height", 27)
// .attr("rx", 10)
// .attr("fill", "rgb(230, 230, 230)")
// .attr("stroke-width", "2")
// .attr("stroke", "#FFF");
grp
.append("text")
.attr("fill", "#000")
.style("text-anchor", "middle")
.attr("font-family", "Verdana")
.attr("x", 0)
.attr("y", 0)
.attr("font-size", "10")
.text(function (d, i) {
return name;
});
// var group = g.append("g");
// group
// .append("rect")
// .attr("x", centroid[0] - w / 2)
// .attr("y", centroid[1] - h / 2)
// .attr("width", 80)
// .attr("height", 27)
// .attr("rx", 10)
// .attr("fill", "rgb(230, 230, 230)")
// .attr("stroke-width", "2")
// .attr("stroke", "#FFF");
// group
// .append("text")
// .attr("x", centroid[0] - w / 2)
// .attr("y", centroid[1] - h / 2)
// .text(function (d, i) {
// return name;
// });
});
// g.append("button")
// .attr("class", "wrap")
// .text((d) => d.properties.name);
}
);
function getCentroid(element) {
var bbox = element.getBBox();
return [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2];
}
function clicked(d) {
var x, y, k;
if (d && centered !== d) {
// CENTROIDS for subregion provinces
var p = "";
json.features.forEach(function (f, i) {
if (f.properties.subregion === d.properties.subregion) {
p += path(f);
}
});
var tmp = svg.append("path");
tmp.attr("d", p);
var centroid = getCentroid(tmp.node());
tmp.remove();
// var centroid = path.centroid(p);
x = centroid[0];
y = centroid[1];
k = 2;
if (d.properties.subregion === "Northern Territories") {
k = 1.5;
}
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
g.selectAll("path").classed(
"active",
centered &&
function (d) {
return (
d.properties &&
d.properties.subregion === centered.properties.subregion
);
// return d === centered;
}
);
g.transition()
.duration(650)
.attr(
"transform",
"translate(" +
width / 2 +
"," +
height / 2 +
")scale(" +
k +
")translate(" +
-x +
"," +
-y +
")"
)
.style("stroke-width", 1.5 / k + "px");
}
.background-svg-map {
fill: none;
pointer-events: all;
}
#provinces {
fill: rgb(230, 230, 230);
}
#provinces > path:hover {
fill: #0630a6;
}
#provinces .active {
fill: #0630a6;
}
#province-borders-path {
fill: none;
stroke: #fff;
stroke-width: 1.5px;
stroke-linejoin: round;
stroke-linecap: round;
pointer-events: none;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<div id="map-selector-app"></div>
The basic workflow for labels with a background is:
Position a parent g
Add the text
Add the rectangle and set it's size based on the bounding box of the parent g and then move it behind the text.
For d3v3, I'm going to actually add the rectangle before the text, but not style it until the text is added and the required size is known. If we append it after it will be infront of the text. In d3v4 there's a handy .lower() method that would move it backwards for us, in d3v3, there are ways to do this, but for simplicity, to ensure the rectangle is behind the text, I'll add it first
I'm going to deviate from your code at a more foundational level in my example. I'm not going to append child SVGs as this is introducing some sizing issues for you. Also, instead of using a loop to append the labels, I'm going to use a selectAll()/enter() cycle. This means I need a data array not an object ultimately. In order to help build that array I'll use an object though - by going through your json once, we can build a list of regions and create a geojson feature for each. The geojson feature is nice as it allows us to use path.centroid() which allows us to find the centroid of a feature without additional code.
So first, I need to create the data array:
var subregions = {};
json.features.forEach(function(feature) {
var subregion = feature.properties.subregion;
// Have we already encountered this subregion? If not, add it.
if(!(subregion in subregions)) {
subregions[subregion] = {"type":"FeatureCollection", features: [] };
}
// For every feature, add it to the subregion featureCollection:
subregions[subregion].features.push(feature);
})
// Convert to an array:
subregions = Object.keys(subregions).map(function(key) {
return { name: key, geojson: subregions[key] };
})
Now we can append the parent g with a standard d3 selectAll/enter statement:
// Create a parent g for each label:
var subregionsParent = g.selectAll(null)
.data(subregions)
.enter()
.append("g")
.attr("transform", function(d) {
// position the parent, so we don't need to position each child based on geographic location:
return "translate("+path.centroid(d.geojson)+")";
})
Now we can add the text and rectangle:
// add a rectangle to each parent `g`
var boxes = subregionsParent.append("rect");
// add text to each parent `g`
subregionsParent.append("text")
.text(function(d) { return d.name; })
.attr("text-anchor","middle");
// style the boxes based on the parent `g`'s bbox
boxes
.each(function() {
var bbox = this.parentNode.getBBox();
d3.select(this)
.attr("width", bbox.width + 10)
.attr("height", bbox.height +10)
.attr("x", bbox.x - 5)
.attr("y", bbox.y - 5)
.attr("rx", 10)
.attr("ry", 10)
.attr("fill","#ccc")
})
You can see the centroid method (whether using your existing function or path.centroid()) can be a little dumb when it comes to placement given some of the overlap on the map. There are ways you could modify that - perhaps adding offsets to the data, or manual exceptions when adding the text. Though on a larger SVG there shouldn't be overlap. Annotations are notoriously difficult to do.
Here's my result with the above:
And a snippet to demonstrate (I've removed a fair amount of unnecessary code to make a simpler example, but it should retain the functionality of your example):
var width = 500,
height = 275,
centered;
var projection = d3.geo.conicConformal()
.rotate([103, 0])
.center([0, 63])
.parallels([49, 77])
.scale(500)
.translate([width / 2.5, height / 2])
var path = d3.geo.path().projection(projection);
var svg = d3
.select("#map-selector-app")
.append("svg")
.attr("viewBox", `0 0 ${width} ${height}`);
var g = svg.append("g");
d3.json("https://gist.githubusercontent.com/KatFishSnake/7f3dc88b0a2fa0e8c806111f983dfa60/raw/7fff9e40932feb6c0181b8f3f983edbdc80bf748/canadaprovtopo.json",
function (error, canada) {
if (error) throw error;
json = topojson.feature(canada, canada.objects.canadaprov);
var provinces = g.append("g")
.attr("id", "provinces")
.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.on("click", clicked);
g.append("g")
.attr("id", "province-borders")
.append("path")
.datum(topojson.mesh(canada, canada.objects.canadaprov, function (a,b) { return a !== b; }))
.attr("id", "province-borders-path")
.attr("d", path);
// Add labels:
// Get the data:
var subregions = {};
json.features.forEach(function(feature) {
var subregion = feature.properties.subregion;
if(!(subregion in subregions)) {
subregions[subregion] = {"type":"FeatureCollection", features: [] };
}
subregions[subregion].features.push(feature);
})
// Convert to an array:
subregions = Object.keys(subregions).map(function(key) {
return { name: key, geojson: subregions[key] };
})
// Create a parent g for each label:
var subregionsParent = g.selectAll(null)
.data(subregions)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate("+path.centroid(d.geojson)+")";
})
var boxes = subregionsParent.append("rect");
subregionsParent.append("text")
.text(function(d) { return d.name; })
.attr("text-anchor","middle");
boxes
.each(function() {
var bbox = this.parentNode.getBBox();
d3.select(this)
.attr("width", bbox.width + 10)
.attr("height", bbox.height +10)
.attr("x", bbox.x - 5)
.attr("y", bbox.y - 5)
.attr("rx", 10)
.attr("ry", 10)
.attr("fill","#ccc")
})
// End labels.
function getCentroid(element) {
var bbox = element.getBBox();
return [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2];
}
function clicked(d) {
var x, y, k;
if (d && centered !== d) {
// CENTROIDS for subregion provinces
var p = "";
json.features.forEach(function (f, i) {
if (f.properties.subregion === d.properties.subregion) {
p += path(f);
}
});
var tmp = svg.append("path");
tmp.attr("d", p);
var centroid = getCentroid(tmp.node());
tmp.remove();
x = centroid[0];
y = centroid[1];
k = 2;
if (d.properties.subregion === "Northern Territories") {
k = 1.5;
}
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
g.selectAll("path").classed(
"active",
centered &&
function (d) {
return (
d.properties &&
d.properties.subregion === centered.properties.subregion
);
}
);
g.transition()
.duration(650)
.attr("transform","translate(" + width / 2 + "," + height / 2 +")scale(" +
k +")translate(" + -x + "," + -y + ")"
)
.style("stroke-width", 1.5 / k + "px");
}
})
.background-svg-map {
fill: none;
pointer-events: all;
}
#provinces {
fill: rgb(230, 230, 230);
}
#provinces > path:hover {
fill: #0630a6;
}
#provinces .active {
fill: #0630a6;
}
#province-borders-path {
fill: none;
stroke: #fff;
stroke-width: 1.5px;
stroke-linejoin: round;
stroke-linecap: round;
pointer-events: none;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<div id="map-selector-app"></div>
I am trying to get the node details (id attribute) when it is right clicked and the contextmenu function is called. I am able to get the node object using var self = d3.select(this); but I am not able to work out the
id attribute of the node (i can see it in the console log though)
I am planning to pass the id to the menu function once I'll get the node.id
JSFiddle
var circle = svg.append("g").selectAll("circle") .data(force.nodes())
.enter().append("circle").attr("r", 6) .call(force.drag)
.on('contextmenu', function(){
d3.event.preventDefault();
var self = d3.select(this);
console.log(self);
var n1=(self[0])[0];
console.log(n1);
menu(d3.mouse(svg.node())[0], d3.mouse(svg.node())[1]);
});
You can pass the datum as a parameter of the function called on the contextmenu event:
.on('contextmenu', function(d) { ... }
which allows you to get the id within the function:
console.log(d.id);
.node {
fill: #000;
}
.cursor {
fill: green;
stroke: brown;
pointer-events: none;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
#licensing {
fill: green;
}
.link.licensing {
stroke: green;
}
.link.resolved {
stroke-dasharray: 0,2 1;
}
circle {
fill: green;
stroke: red;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var width = 500, height = 300;
var links = [{source:"simulator",target:"monitor" ,type:"resolved"} , {source:"web",target:"monitor" ,type:"resolved"} ];
var nodes = [ {"id":"monitor", "grp":"system"}, {"id":"simulator", "grp":"system"}, {id:"web", grp:"client"}];
function reset() {
}
function contextMenu() {
var height,
width,
margin = 0.1, // fraction of width
items = [],
rescale = false,
style = {
'rect': {
'mouseout': {
'fill': 'rgb(244,244,244)',
'stroke': 'white',
'stroke-width': '1px'
},
'mouseover': {
'fill': 'rgb(200,200,200)'
}
},
'text': {
'fill': 'steelblue',
'font-size': '13'
}
};
function menu(x, y) {
d3.select('.context-menu').remove();
scaleItems();
// Draw the menu
d3.select('svg')
.append('g').attr('class', 'context-menu')
.selectAll('tmp')
.data(items).enter()
.append('g').attr('class', 'menu-entry')
.style({'cursor': 'pointer'})
.on('mouseover', function(){
d3.select(this).select('rect').style(style.rect.mouseover) })
.on('mouseout', function(){
d3.select(this).select('rect').style(style.rect.mouseout) });
d3.selectAll('.menu-entry')
.append('rect')
.attr('x', x)
.attr('y', function(d, i){ return y + (i * height); })
.attr('width', width)
.attr('height', height)
.style(style.rect.mouseout);
d3.selectAll('.menu-entry')
.append('text')
.text(function(d){ return d; })
.attr('x', x)
.attr('y', function(d, i){ return y + (i * height); })
.attr('dy', height - margin / 2)
.attr('dx', margin)
.style(style.text);
// Other interactions
d3.select('body')
.on('click', function() {
d3.select('.context-menu').remove();
});
}
menu.items = function(e) {
if (!arguments.length) return items;
for (i in arguments) items.push(arguments[i]);
rescale = true;
return menu;
}
// Automatically set width, height, and margin;
function scaleItems() {
if (rescale) {
d3.select('svg').selectAll('tmp')
.data(items).enter()
.append('text')
.text(function(d){ return d; })
.style(style.text)
.attr('x', -1000)
.attr('y', -1000)
.attr('class', 'tmp');
var z = d3.selectAll('.tmp')[0]
.map(function(x){ return x.getBBox(); });
width = d3.max(z.map(function(x){ return x.width; }));
margin = margin * width;
width = width + 2 * margin;
height = d3.max(z.map(function(x){ return x.height + margin / 2; }));
// cleanup
d3.selectAll('.tmp').remove();
rescale = false;
}
}
return menu;
}
var width = 400,
height = 200,
radius = 8;
var map = {}
nodes.forEach(function(d,i){
map[d.id] = i;
})
links.forEach(function(d) {
d.source = map[d.source];
d.target = map[d.target];
})
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(50)
.charge(-200)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var menu = contextMenu().items('first item', 'second option', 'whatever, man');
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.call(force.drag)
.on('contextmenu', function(d){
d3.event.preventDefault();
var self = d3.select(this);
var n1=(self[0])[0];
console.log(d.id);
menu(d3.mouse(svg.node())[0], d3.mouse(svg.node())[1]);
});
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.id; });
var node = svg.selectAll(".node"),
link = svg.selectAll(".link");
function mousedownNode(d, i) {
nodes.splice(i, 1);
links = links.filter(function(l) {
return l.source !== d && l.target !== d;
});
d3.event.stopPropagation();
refresh();
}
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
</script>
In addition: For all the ones that want to know how to be able to show node details (for example the node name) in the context menu itself - this is my solution.
The node details can be taken from the "data" element
The placeholder can be taken from the "d" element
The desired information to show in the context menu has to be written into the "text" attribute
if(d.title == 'ConfigMenuPlaceholder'){
text = 'Config: '+data.name;
}
These lines should be written at the following position:
function createNestedMenu(parent, root, depth = 0) {
var resolve = function (value) {
return utils.toFactory(value).call(root, data, index);
};
parent.selectAll('li')
.data(function (d) {
var baseData = depth === 0 ? menuItems : d.children;
return resolve(baseData);
})
.enter()
.append('li')
.each(function (d) {
var elm = this;
// get value of each data
var isDivider = !!resolve(d.divider);
var isDisabled = !!resolve(d.disabled);
var hasChildren = !!resolve(d.children);
var hasAction = !!d.action;
var text = isDivider ? '<hr>' : resolve(d.title);
if(d.title == 'ConfigMenuPlaceholder'){
text = 'Config: '+data.name;
}
var listItem = d3.select(this)
.classed('is-divider', isDivider)
.classed('is-disabled', isDisabled)
.classed('is-header', !hasChildren && !hasAction)
.classed('is-parent', hasChildren)
.html(text)
.on('click', function () {
// do nothing if disabled or no action
if (isDisabled || !hasAction) return;
d.action(elm, data, index);
//d.action.call(root, data, index);
closeMenu();
});
if (hasChildren) {
// create children(`next parent`) and call recursive
var children = listItem.append('ul').classed('is-children', true);
createNestedMenu(children, root, ++depth)
}
});
}
Here is a plunkr of a matrix scatterplot in d3 V4
http://plnkr.co/edit/7meh4sMhxItcQtCaAtZP?p=preview
(It may take some time to load and display)
I am looking forward to zoom the plot in order to see the cluttered points in a convenient way.
I have used d3.zoom() to rescale the axis but the circles are not being plotted accordingly.
Here is the problem description:
Initially, all the axis are set with different ranges (like top y-axis is having range from 4.5-7.5 , below that y-axis range is from 2.0-4.0 .. )
Now after zooming it, (i.e when scrolling mouse over the circles), all the axis are set in the same ranges that leads to all circles oriented diagonally.
Is there a workaround for that so that we can zoom the axis accordingly and visualize it nicely !!
Thanks. Any help would highly appreciated.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
padding: 10px;
}
.axis,
.frame {
shape-rendering: crispEdges;
}
.axis line {
stroke: #ddd;
}
.axis path {
display: none;
}
.cell text {
font-weight: bold;
text-transform: capitalize;
}
.frame {
fill: none;
stroke: #aaa;
}
circle {
fill-opacity: .7;
}
circle.hidden {
fill: #ccc !important;
}
.extent {
fill: #000;
fill-opacity: .125;
stroke: #fff;
}
div.tooltip {
position: absolute;
text-align: center;
width: 100px;
height: 80px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var tooltipDiv;
d3.helper = {};
var zoomable = true;
var new_xScale;
var new_yScale;
d3.helper.tooltip = function (param1, param2) {
var bodyNode = d3.select('body').node();
function tooltip(selection) {
selection.on('mouseover.tooltip', function (point) {
// Clean up lost tooltips
d3.select('body').selectAll('div.tooltip').remove();
// Append tooltip
tooltipDiv = d3.select('body')
.append('div')
.attr('class', 'tooltip');
var absoluteMousePos = d3.mouse(bodyNode);
//console.log('absoluteMousePos', absoluteMousePos);
tooltipDiv
.style('left', (absoluteMousePos[0] + 10) + 'px')
.style('top', (absoluteMousePos[1] - 30) + 'px');
var line = '';
//var temp_key = d3.keys(point);
var temp_key = [param1, param2];
// _.each(d3.keys(point), function (key, index) {
temp_key.forEach(function (key, index) {
if (index != temp_key.length - 1) {
line += key + ': ' + point[key] + '</br>';
} else {
line += key + ': ' + point[key];
}
});
tooltipDiv.html(line);
})
.on('mousemove.tooltip', function () {
// Move tooltip
var absoluteMousePos = d3.mouse(bodyNode);
tooltipDiv
.style("left", (absoluteMousePos[0] + 10) + 'px')
.style("top", absoluteMousePos[1] < 80 ? absoluteMousePos[1] + 10 :(absoluteMousePos[1] - 70) + 'px');
})
.on('mouseout.tooltip', function () {
// Remove tooltip
tooltipDiv.remove();
});
}
tooltip.attr = function (_x) {
if (!arguments.length) return attrs;
attrs = _x;
return this;
};
tooltip.style = function (_x) {
if (!arguments.length) return styles;
styles = _x;
return this;
};
return tooltip;
};
var width = 500,
size = 150,
padding = 20;
var x = d3.scaleLinear().rangeRound([padding / 2, size - padding / 2]);
var y = d3.scaleLinear().rangeRound([size - padding / 2, padding / 2]);
var xAxis = d3.axisBottom()
.scale(x)
.ticks(7);
var yAxis = d3.axisLeft()
.scale(y)
.ticks(7);
var color = d3.scaleOrdinal(d3.schemeCategory10);
d3.json("flowers.json", function (data) {
var attr2Domain = {},
attrs= ['sepal length', 'sepal width', 'petal length','petal width']
n = attrs.length;
attrs.forEach(function (attr) {
attr2Domain[attr] = d3.extent(data, function (ele) {
return ele[attr];
});
});
xAxis.tickSize(size * n);
yAxis.tickSize(-size * n);
var svg = d3.select("body")
.append("svg")
.attr("width", size * n + padding)
.attr("height", size * n + padding)
.append("g")
.attr("transform", "translate(" + padding + "," + padding / 2 + ")");
svg.selectAll(".x.axis")
.data(attrs)
.enter().append("g")
.attr("class", "x axis")
.attr("transform", function (d, i) {
return "translate(" + (n - i - 1) * size + ",0)";
})
.each(function (d) {
x.domain(attr2Domain[d]);
d3.select(this).call(xAxis);
});
svg.selectAll(".y.axis")
.data(attrs)
.enter().append("g")
.attr("class", "y axis")
.attr("transform", function (d, i) {
return "translate(0," + i * size + ")";
})
.each(function (d) {
y.domain(attr2Domain[d]);
d3.select(this).call(yAxis);
});
var cell = svg.selectAll(".cell")
.data(cross(attrs, attrs))
.enter()
.append("g")
.attr("class", "cell")
.attr("classx", function(d){ return d.x; })
.attr("classy", function(d){ return d.y; })
.attr("transform", function (d) {
return "translate(" + (n - d.i - 1) * size + "," + d.j * size + ")";
});
// Titles for the diagonal.
cell.filter(function (d) {
return d.i === d.j;
})
.append("text")
.attr("x", padding)
.attr("y", padding)
.attr("dy", ".71em")
.style("fill", "black")
.text(function (d) {
return d.x;
});
cell.each(plot);
// plot each cell
function plot(p) {
cell = d3.select(this);
x.domain(attr2Domain[p.x]);
y.domain(attr2Domain[p.y]);
cell.append("rect")
.attr("class", "frame")
.attr("x", padding / 2)
.attr("y", padding / 2)
.attr("width", size - padding)
.style("pointer-events", "none")
.attr("height", size - padding);
var circles = cell.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function (d) {
return x(d[p.x]);
})
.attr("cy", function (d) {
return y(d[p.y]);
})
.attr("r", 4)
//.style("fill", "green");
.style("fill", function (d) {
return color(d.species);
});
circles.on('mousemove', function(){
var param1 = d3.select(this.parentNode).attr("classx");
var param2 = d3.select(this.parentNode).attr("classy");
circles.call(d3.helper.tooltip(param1, param2));
});
}
//--------------------------------------------- applying zoom----------
applyZoom();
function applyZoom() {
if (zoomable) {
var zoom = d3.zoom()
.on("zoom", zoomed);
svg.call(zoom).on("dblclick.zoom", null);
}
}
function zoomed() {
console.log('zoomed');
new_xScale = d3.event.transform.rescaleX(x);
new_yScale = d3.event.transform.rescaleY(y);
console.log("new_xScale", x);
svg.selectAll(".x.axis")
.each(function (d) {
x.domain(attr2Domain[d]);
d3.select(this).call(xAxis.scale(new_xScale));
});
svg.selectAll(".y.axis")
.each(function (d) {
y.domain(attr2Domain[d]);
d3.select(this).call(yAxis.scale(new_yScale));
});
cell.each(plotly);
}
function plotly(p) {
console.log("plotly", p);
//return x(d[p.x])
svg.selectAll("circle")
.attr("cx", function (d) {
return new_xScale(d[p.x]);
})
.attr("cy", function (d) {
return new_yScale(d[p.y]);
});
}
});
function cross(a, b) {
var c = [], n = a.length, m = b.length, i, j;
for (i = 0; i < n; i++) {
for (j = 0; j < m; j++) {
c.push({x: a[i], i: i, y: b[j], j: j});
}
}
return c;
}
</script>
I think move'new_xScale = d3.event.transform.rescaleX(x)' after 'x.domain(attr2Domain[d])' inside each function should work. The same as y.
Thanks
I am looking for parallel coordinate visualization in d3.js that has both ordinal and numerical values in d3 V4. I have created a sample http://plnkr.co/edit/TiM6ZsvMTTBhh8ZdMoFQ?p=preview
I am trying to brush the 'name' column as well. It seems there are lots of changes in d3 brush in V4. Also is there a way where I can snap the brushing area in both cases of ordinal and numerical columns.
Any help would be highly appreciated.
Thanks
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.background path {
fill: none;
stroke: #ddd;
stroke-opacity: .4;
shape-rendering: crispEdges;
}
.foreground path {
fill: none;
stroke: steelblue;
stroke-opacity: .7;
}
.brush .extent {
fill-opacity: .3;
stroke: #fff;
shape-rendering: crispEdges;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
cursor: move;
}
</style>
<body>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script>
var margin = {top: 30, right: 10, bottom: 10, left: 10},
width = 600 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var x = d3.scaleBand().rangeRound([0, width]).padding(1),
y = {},
dragging = {};
var line = d3.line(),
//axis = d3.axisLeft(x),
background,
foreground,
extents;
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 quant_p = function(v){return (parseFloat(v) == v) || (v == "")};
d3.csv("cars.csv", function(error, cars) {
// Extract the list of dimensions and create a scale for each.
//cars[0] contains the header elements, then for all elements in the header
//different than "name" it creates and y axis in a dictionary by variable name
console.log("ok");
dimensions = d3.keys(cars[0]);
x.domain(dimensions);
dimensions.forEach(function(d) {
var vals = cars.map(function(p) {return p[d];});
if (vals.every(quant_p)){
y[d] = d3.scaleLinear()
.domain(d3.extent(cars, function(p) {
return +p[d]; }))
.range([height, 0])
}
else{
y[d] = d3.scalePoint()
.domain(vals.filter(function(v, i) {return vals.indexOf(v) == i;}))
.range([height, 0],1);}
})
extents = dimensions.map(function(p) { return [0,0]; });
// Add grey background lines for context.
background = svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path);
// Add blue foreground lines for focus.
foreground = svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path);
// Add a group element for each dimension.
var g = svg.selectAll(".dimension")
.data(dimensions)
.enter().append("g")
.attr("class", "dimension")
.attr("transform", function(d) { return "translate(" + x(d) + ")"; })
.call(d3.drag()
.subject(function(d) { return {x: x(d)}; })
.on("start", function(d) {
dragging[d] = x(d);
background.attr("visibility", "hidden");
})
.on("drag", function(d) {
dragging[d] = Math.min(width, Math.max(0, d3.event.x));
foreground.attr("d", path);
dimensions.sort(function(a, b) { return position(a) - position(b); });
x.domain(dimensions);
g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
})
.on("end", function(d) {
delete dragging[d];
transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
transition(foreground).attr("d", path);
background
.attr("d", path)
.transition()
.delay(500)
.duration(0)
.attr("visibility", null);
}));
// Add an axis and title.
g.append("g")
.attr("class", "axis")
.each(function(d) { d3.select(this).call(d3.axisLeft(y[d]));})
//text does not show up because previous line breaks somehow
.append("text")
.attr("fill", "black")
.style("text-anchor", "middle")
.attr("y", -9)
.text(function(d) { return d; });
// Add and store a brush for each axis.
g.append("g")
.attr("class", "brush")
.each(function(d) {
d3.select(this).call(y[d].brush = d3.brushY().extent([[-8, 0], [8,height]]).on("brush start", brushstart).on("brush", brush_parallel_chart));
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
});
function position(d) {
var v = dragging[d];
return v == null ? x(d) : v;
}
function transition(g) {
return g.transition().duration(500);
}
// Returns the path for a given data point.
function path(d) {
return line(dimensions.map(function(p) { return [position(p), y[p](d[p])]; }));
}
function brushstart() {
d3.event.sourceEvent.stopPropagation();
}
// Handles a brush event, toggling the display of foreground lines.
function brush_parallel_chart() {
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
extents[i]=d3.event.selection.map(y[dimensions[i]].invert,y[dimensions[i]]);
}
}
foreground.style("display", function(d) {
return dimensions.every(function(p, i) {
if(extents[i][0]==0 && extents[i][0]==0) {
return true;
}
return extents[i][1] <= d[p] && d[p] <= extents[i][0];
}) ? null : "none";
});
}
</script>
I have a parallel coordinate chart. I want to display a legend. From the example from Bostock's website, I wrote something similar. But, I can only see the colours of the lines being shown and not the text.
The text is being returned correctly as well.
What am I performing incorrectly ?
Here is my code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.background path {
fill: none;
stroke: #ddd;
shape-rendering: crispEdges;
}
.foreground path {
fill: none;
stroke: steelblue;
}
.brush .extent {
fill-opacity: .3;
stroke: #fff;
shape-rendering: crispEdges;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
cursor: move;
}
.legend rect {
fill:white;
stroke:black;
opacity:0.8;}
</style>
<body>
<div style="width:1140px; height:550px;margin-left:50px;margin-top:50px;" id="chartDiv"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.5.0/d3-legend.js"></script>
<script>
var margin = {top: 30, right: 10, bottom: 10, left: 10},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal().rangePoints([0, width], 1),
y = {},
dragging = {};
var line = d3.svg.line(),
axis = d3.svg.axis().orient("left"),
background,
foreground;
var svg = d3.select("#chartDiv").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 color = d3.scale.ordinal()
.domain(['white','other','african-american','hispanic','asian/pacific islander'])
.range(['#ffae19','#4ca64c','#4682B4','#737373', '#ff4c4c']);
d3.csv("SurvivalProbability.csv", function(error, cars) {
// Extract the list of dimensions and create a scale for each.
x.domain(dimensions = d3.keys(cars[0]).filter(function(d) {
if(d === "Ethnicity") {
y[d] = d3.scale.ordinal()
.domain(cars.map(function(p) { return p[d]; }))
.rangePoints([height, 0]);
}
else if(d === "Site") {
y[d] = d3.scale.ordinal()
.domain(cars.map(function(p) { return p[d]; }))
.rangePoints([height, 0]);
}
else if(d === "Tcategory") {
y[d] = d3.scale.ordinal()
.domain(cars.map(function(p) { return p[d]; }))
.rangePoints([height, 0]);
}
else if(d === "Nodal_Disease") {
y[d] = d3.scale.ordinal()
.domain(cars.map(function(p) { return p[d]; }))
.rangePoints([height, 0]);
}
else if(d === "Chemotherapy") {
y[d] = d3.scale.ordinal()
.domain(cars.map(function(p) { return p[d]; }))
.rangePoints([height, 0]);
}
else if(d === "Local_Therapy") {
y[d] = d3.scale.ordinal()
.domain(cars.map(function(p) { return p[d]; }))
.rangePoints([height, 0]);
}
else {
y[d] = d3.scale.linear()
.domain(d3.extent(cars, function(p) { return +p[d]; }))
.range([height, 0]);
}
return true;
}));
// Add grey background lines for context.
background = svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path);
// Add blue foreground lines for focus.
/*foreground = svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path);*/
foreground = svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path)
.style("stroke", function(d) {
return color(d.Ethnicity);
})
// Add a group element for each dimension.
var g = svg.selectAll(".dimension")
.data(dimensions)
.enter().append("g")
.attr("class", "dimension")
.attr("transform", function(d) { return "translate(" + x(d) + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return {x: x(d)}; })
.on("dragstart", function(d) {
dragging[d] = x(d);
background.attr("visibility", "hidden");
})
.on("drag", function(d) {
dragging[d] = Math.min(width, Math.max(0, d3.event.x));
foreground.attr("d", path);
dimensions.sort(function(a, b) { return position(a) - position(b); });
x.domain(dimensions);
g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
})
.on("dragend", function(d) {
delete dragging[d];
transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
transition(foreground).attr("d", path);
background
.attr("d", path)
.transition()
.delay(500)
.duration(0)
.attr("visibility", null);
}));
// Add an axis and title.
g.append("g")
.attr("class", "axis")
.each(function(d) { d3.select(this).call(axis.scale(y[d])); })
.append("text")
.style("text-anchor", "middle")
.attr("y", -9)
.text(function(d) { return d; });
// Add and store a brush for each axis.
g.append("g")
.attr("class", "brush")
.each(function(d) {
d3.select(this).call(y[d].brush = d3.svg.brush().y(y[d]).on("brushstart", brushstart).on("brush", brush));
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
var legendRectSize = 10;
var legendSpacing = 100;
var legend = d3.select('svg')
.append("g")
.selectAll("g")
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize;
var x = 925;
var y = (i * height) + 50;
return 'translate(' + x + ',' + y + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d; });
});
function position(d) {
var v = dragging[d];
return v == null ? x(d) : v;
}
function transition(g) {
return g.transition().duration(500);
}
// Returns the path for a given data point.
function path(d) {
return line(dimensions.map(function(p) { return [position(p), y[p](d[p])]; }));
}
function brushstart() {
d3.event.sourceEvent.stopPropagation();
}
// Handles a brush event, toggling the display of foreground lines.
function brush() {
var actives = dimensions.filter(function(p) { return !y[p].brush.empty(); }),
extents = actives.map(function(p) { return y[p].brush.extent(); });
foreground.style("display", function(d) {
return actives.every(function(p, i) {
return extents[i][0] <= d[p] && d[p] <= extents[i][1];
}) ? null : "none";
});
}
</script>
The code is correct to show the legend you have to adjust the margin a little.
//rightmargin is set to 200
var margin = {top: 30, right: 200, bottom: 10, left: 10},
and width is like
width = 1100 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
Placement of the legend text as below
var legendRectSize = 10;
var legendSpacing = 10;
var legend = d3.select('svg')
.append("g")
.selectAll("g")
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize;
var x = 900;
var y = (i * height) + 50;
return 'translate(' + x + ',' + y + ')';
});
That all :)
Working code here
Hope this helps!