Find points under the brushed region - javascript

I have customized the matrixscatterplot in d3 V4 to find the points that are under the brushed region for a particular cell.
Here is the plunkr for that
http://plnkr.co/edit/fL0C3XqvLtQO9SKWoV5c?p=preview
Now, for instance, if I brush a few points in any cell, the highlights are fine but I am getting a lot of data points in console.log "selected points". (With the brushSelection extent logic in the brushend function, I have tried filtering the data points and console log them )
Ideally, I want to get only those data points/ objects in the console that are under the brushed region for any cell.
Thanks. Any help would be 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;
fill: black;
}
.frame {
fill: none;
stroke: #aaa;
}
circle {
fill-opacity: .7;
}
circle.hidden {
fill: #ccc !important;
}
.extent {
fill: #000;
fill-opacity: .125;
stroke: #fff;
}
</style>
<body>
<script src="https://d3js.org/d3.v4.js"></script>
<script>
var width = 960,
size = 230,
padding = 20;
var x = d3.scaleLinear()
.range([padding / 2, size - padding / 2]);
var y = d3.scaleLinear()
.range([size - padding / 2, padding / 2]);
var xAxis = d3.axisBottom()
.scale(x)
.ticks(6);
var yAxis = d3.axisLeft()
.scale(y)
.ticks(6);
var color = d3.scaleOrdinal(d3.schemeCategory10);
d3.csv("flowers.csv", function(error, data) {
if (error) throw error;
var domainByTrait = {},
traits = d3.keys(data[0]).filter(function(d) { return d !== "species"; }),
n = traits.length;
traits.forEach(function(trait) {
domainByTrait[trait] = d3.extent(data, function(d) { return d[trait]; });
});
xAxis.tickSize(size * n);
yAxis.tickSize(-size * n);
var brush = d3.brush()
.on("start", brushstart)
.on("brush", brushmove)
.on("end", brushend)
.extent([[0,0],[size,size]]);
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(traits)
.enter().append("g")
.attr("class", "x axis")
.attr("transform", function(d, i) { return "translate(" + (n - i - 1) * size + ",0)"; })
.each(function(d) { x.domain(domainByTrait[d]); d3.select(this).call(xAxis); });
svg.selectAll(".y.axis")
.data(traits)
.enter().append("g")
.attr("class", "y axis")
.attr("transform", function(d, i) { return "translate(0," + i * size + ")"; })
.each(function(d) { y.domain(domainByTrait[d]); d3.select(this).call(yAxis); });
var cell = svg.selectAll(".cell")
.data(cross(traits, traits))
.enter().append("g")
.attr("class", "cell")
.attr("transform", function(d) { return "translate(" + (n - d.i - 1) * size + "," + d.j * size + ")"; })
.each(plot);
// Titles for the diagonal.
cell.filter(function(d) { return d.i === d.j; }).append("text")
.attr("x", padding)
.attr("y", padding)
.attr("dy", ".71em")
.text(function(d) { return d.x; });
cell.call(brush);
function plot(p) {
var cell = d3.select(this);
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
cell.append("rect")
.attr("class", "frame")
.attr("x", padding / 2)
.attr("y", padding / 2)
.attr("width", size - padding)
.attr("height", size - padding);
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", function(d) { return color(d.species); });
}
var brushCell;
// Clear the previously-active brush, if any.
function brushstart(p) {
if (brushCell !== this) {
d3.select(brushCell).call(brush.move, null);
brushCell = this;
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
}
}
// Highlight the selected circles.
function brushmove(p) {
var e = d3.brushSelection(this);
//svg.selectAll("circle").classed("hidden", function(d) {
d3.select(this).selectAll("circle").classed("hidden", function(d) {
return !e
? false
: (
e[0][0] > x(+d[p.x]) || x(+d[p.x]) > e[1][0]
|| e[0][1] > y(+d[p.y]) || y(+d[p.y]) > e[1][1]
);
});
}
// If the brush is empty, select all circles.
function brushend(p) {
var e = d3.brushSelection(this);
if (e === null) svg.selectAll(".hidden").classed("hidden", false);
var points = [];
d3.select(this).selectAll("circle").filter(function(d){
if (
e[0][0] > x(+d[p.x]) || x(+d[p.x]) > e[1][0]
|| e[0][1] > y(+d[p.y]) || y(+d[p.y]) > e[1][1]
)
points.push(d);
})
console.log('selected points', points);
}
});
function cross(a, b) {
var c = [], n = a.length, m = b.length, i, j;
for (i = -1; ++i < n;) for (j = -1; ++j < m;) c.push({x: a[i], i: i, y: b[j], j: j});
return c;
}
</script>

There are two problems here.
Firstly, p.x and p.y are not accessible inside the brushend function. But you can easily get the circle's positions with:
d3.select(this).attr("cx")
d3.select(this).attr("cy")
Besides that, the logic in the if is not correct, you should use AND to get the circles inside the 4 points:
if (
e[0][0] < d3.select(this).attr("cx") && d3.select(this).attr("cx") < e[1][0]
&& e[0][1] < d3.select(this).attr("cy") && d3.select(this).attr("cy") < e[1][1]
)
Here is the updated Plunker: http://plnkr.co/edit/dA7qcvAhjZBsyqTsheYl?p=preview

Minor change:
The condition you're using in the brushend function (posted below) gets you the hidden class to the circles which you might wanna negate to get you the selected points.
Condition:
(
e[0][0] > x(+d[p.x]) || x(+d[p.x]) > e[1][0]
|| e[0][1] > y(+d[p.y]) || y(+d[p.y]) > e[1][1]
)
Solution 1:
You can either negate the condition or change the symbols:
if (!(
e[0][0] > x(+d[p.x]) || x(+d[p.x]) > e[1][0]
|| e[0][1] > y(+d[p.y]) || y(+d[p.y]) > e[1][1]
))
Solution 2 ( a better approach): You already are using the condition to add hidden class in the brushmove, you can just get the selected points based on that. Here:
d3.select(this).selectAll("circle").filter(function(d){
if(!d3.select(this).classed('hidden'))
points.push(d);
})
Here's a plunkr:
http://plnkr.co/edit/HpSZ5j21SfDrtNOncWCf?p=preview

Related

Getting the node details in the context menu in D3

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)
}
});
}

Multiple brushes on an axis in parallel coordinate in d3 V4?

I have created a parallel coordinate in d3 V4 (with a lot of pain)which has both numerical and ordinal axis, with basic features like axis dragging, brushing, brush snapping.
Here is the working sample
http://plnkr.co/edit/dCNuBsaDNBwr7CrAJUBe?p=preview
I am looking to have multiple brushes in an axis, (for example I want to brush 0.2 to 0.5 and 0.7 to 0.9 of column1 in my example at the same time). So basically based on multiple brush areas the corresponding lines should be highlighted.
Please suggest some way to do this.
Thanks in advance
<!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>
An implementation of multi brush in d3 V4 is there in https://github.com/BigFatDog/parcoords-es
But I guess demo examples are not there.
Here's a Plunkr using multiple brushes in d3 v4 and of course working on your parallel chart:
Code Plunkr for multi brush
https://github.com/shashank2104/d3.svg.multibrush (I'll suggest the v4 patch to the guy who has written the v3 version once it's completely done)
There's one thing that's bothering me which is the brush clicking, as of now, the brushing works with CLICK to start, MOUSEMOVE and CLICK to end. Just play around with the brushing and you'll notice the difference.
The new brushing function is the default one (as follows);
// 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].some(function(e){
return e[0] <= d[p] && d[p] <= e[1];
});
}) ? null : "none";
});
}
I'll be working on it and hopefully it'll be ready by morning.
Until then, ordinal axis shouldn't be a problem to fix.
I'll keep you posted and edit the same post. (It'd be nice if you could not accept the answer as the right one until it's completely fixed. It'll be on my plate to fix)
Let me know if you come across any questions.
Shashank thanks a lot for the working example, Its like first working example of multi brush in V4. I observed the points you mentioned about brushing.Its a bit different from the traditional brushing method. But the multi brush library from V3 has the conventional brushing way , right.
I have placed the code for ordinal brushing also in your (default) brush function.
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) {
var p_new = (y[p].ticks)?d[p]:y[p](d[p]);
return extents[i].some(function(e){
//return e[0] <= d[p] && d[p] <= e[1];
return e[0] <= p_new && p_new <= e[1];
});
}) ? null : "none";
});
}
In my plunkr example, I have used d3.brushY() functions which are in V4 and thats why I have to implement invert functions (for numerical axis , referred from Appended text not showing in d3 v4) and refeered http://bl.ocks.org/chrisbrich/4173587 for ordinal axis. With these combinations I was able to do brush snapping also.
Also Is there a way that I can do brush snapping also (both numerical and ordinal axis) with your multi brush plunkr example.
I know I am asking more, but basically two points:
1) Is there a way to do multi brushing like that in V3, like as click- drag - leave !
2) Brush snapping (numerical and ordinal) in your multi brush example ?
Thanks again for all your effort and time , means a lot. I am making points so that once you suggest V4 patch, its ll be like fully good.:)

D3 Map - Marks Zooming

I have a problem with marks on my D3 map. When I set up zooming for each state the marks are not zooming within the map..
That's my first try for D3 map, so sorry if I messed up something in the code. If I did or missed something I'll really appreciate explanation.
If anyone can help with that I'll be really thankful.
var width = 960,
height = 500,
centered;
var projection = d3.geo.albersUsa()
.scale(1070)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body")
.append("svg")
.attr("viewBox", "0 0 " + width + " " + height )
.attr("preserveAspectRatio", "xMinYMin");
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height)
.on("click", clicked);
var g = svg.append("g");
var div = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
d3.json("https://bl.ocks.org/mbostock/raw/4090846/us.json", function(error, us) {
if (error) throw error;
g.append("g")
.attr("id", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.style("fill", "#26404b")
.on("click", clicked);
g.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("id", "state-borders")
.attr("d", path);
// CITIES
d3.json("https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json", function(data) {
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return projection([d.longitude, d.latitude])[0];
})
.attr("cy", function(d) {
return projection([d.longitude, d.latitude])[1];
})
.attr("r", function(d) {
if(d.population >= 1 && d.population < 10000){
return 1;
}
else if(d.population >= 10000 && d.population < 100000){
return 2;
}
else if(d.population >= 100000 && d.population < 500000){
return 3;
}
else if(d.population >= 500000 && d.population < 1000000){
return 4;
}
else if(d.population >= 1000000 && d.population < 5000000){
return 5;
}
else if(d.population < 5000000){
return 10;
}
else {
return 0;
};
})
.style("fill", "rgba(26, 188, 156,0.8)")
// HOVER
.on("mouseover", function(d) {
d3.select(this)
.transition()
.duration(200)
.style('fill', 'rgba(26, 188, 156, 0.3)')
.style('stroke', '#1abc9c')
.style('stroke-width', 4);
div.transition()
.duration(200)
.style("opacity", .9);
div.html('<span id="place">City: ' + d.city + '</span><br><span id="people">Calls: ' + d.population + '</span>')
.style("left", (d3.event.pageX) + 35 + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
d3.select(this)
.transition()
.duration(200)
.style('fill', 'rgba(26, 188, 156, 0.8)')
.style('stroke-width', 0);
div.transition()
.duration(200)
.style("opacity", 0);
});
});
});
function clicked(d) {
var x, y, k;
if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 4;
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
g.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });
g.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
}
.background {
fill: #435863;
pointer-events: all;
}
path:hover {
fill-opacity: .9;
}
#states .active {
fill-opacity: .9;
}
#state-borders {
fill: none;
stroke: rgba(22, 160, 133, .1);
stroke-width: 1px;
}
/* Style for Custom Tooltip */
div.tooltip {
position: absolute;
height: 28px;
padding: 5px 10px;
font: 12px sans-serif;
background: white;
border: 0px;
border-radius: 5px;
pointer-events: none;
}
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
The problem is that you are appending your circles to the svg directly, but your US state are being appended to a g element. Your zoom transition is only applied to the g, meaning the circles will be unaffected:
g.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
You should be able to correct this by simply appending your circles to the same g element itself, rather than the svg:
d3.json("cities.json", function(data) {
g.selectAll("circle")
.data(data)
.enter()
...

tooltip for a scatter plot matrix - using d3

I have a scatter plot matrix for which I need a tooltip. I tried using the following code, but then, it gives me tooltips at random points and not at the exact cells.
Can someone tell me where am I going wrong ? Or is not possible to generate a tooltip for my data?
<!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;
}
.frame {
fill: none;
stroke: #aaa;
}
circle {
fill-opacity: .7;
}
circle.hidden {
fill: #ccc !important;
}
.extent {
fill: #000;
fill-opacity: .125;
stroke: #fff;
}
</style>
<body>
<div id="chart3"> </div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<script>
var width = 419,
size = 130,
padding = 19.5,
height = 313;
var x = d3.scale.linear().domain([0,100])
.range([padding / 2, size - padding / 2]);
var y = d3.scale.linear().domain([0,1])
.range([size - padding / 2, padding / 2]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var color = d3.scale.ordinal()
.domain(['no chemo', 'induction', 'induction+chemoRT', 'concurrent'])
.range(['#ffae19', '#4ca64c', '#4682B4', '#c51b8a']);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([50,70])
.html(function (d) {
var coordinates = d3.mouse(this);
xValue = x.invert(coordinates[0]);
yValue = y.invert(coordinates[1]);
return "<strong> Age Of Patient " + d3.format(".2f")(xValue * 100)+
" <br/> Probability of Survival : " + d3.format(".2f")(yValue*100) + " % </strong>";
});
d3.csv("SurvivalProbability.csv", function (error, data) {
if (error)
throw error;
var domainByTrait = {},
traits = d3.keys(data[0]).filter(function (d) {
return (d == 'AgeAtTx' || d == 'Probability of Survival')
}),
n = traits.length;
traits.forEach(function (trait) {
domainByTrait[trait] = d3.extent(data, function (d) {
return d[trait];
});
});
xAxis.tickSize(size * n);
yAxis.tickSize(-size * n);
var brush = d3.svg.brush()
.x(x)
.y(y)
.on("brushstart", brushstart)
.on("brush", brushmove)
.on("brushend", brushend);
var svg = d3.select("#chart3").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + padding + "," + padding / 2 + ")");
svg.call(tip);
svg.selectAll(".x.axis")
.data(traits)
.enter().append("g")
.attr("class", "x axis")
.attr("transform", function (d, i) {
return "translate(" + (n - i - 1) * size + ",0)";
})
.each(function (d) {
x.domain(domainByTrait[d]);
d3.select(this).call(xAxis);
});
svg.selectAll(".y.axis")
.data(traits)
.enter().append("g")
.attr("class", "y axis")
.attr("transform", function (d, i) {
return "translate(0," + i * size + ")";
})
.each(function (d) {
y.domain(domainByTrait[d]);
d3.select(this).call(yAxis);
});
var cell = svg.selectAll(".cell")
.data(cross(traits, traits))
.enter().append("g")
.attr("class", "cell")
.attr("transform", function (d) {
return "translate(" + (n - d.i - 1) * size + "," + d.j * size + ")";
})
.each(plot)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
// Titles for the diagonal.
cell.filter(function (d) {
return d.i === d.j;
}).append("text")
.attr("x", padding)
.attr("y", padding)
.attr("dy", ".71em")
.text(function (d) {
return d.x;
});
cell.call(brush);
function plot(p) {
var cell = d3.select(this);
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
cell.append("rect")
.attr("class", "frame")
.attr("x", padding / 2)
.attr("y", padding / 2)
.attr("width", size - padding)
.attr("height", size - padding);
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", 5)
.style("fill", function (d) {
return color(d.Chemotherapy);
});
}
var brushCell;
// Clear the previously-active brush, if any.
function brushstart(p) {
if (brushCell !== this) {
d3.select(brushCell).call(brush.clear());
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
brushCell = this;
}
}
// Highlight the selected circles.
function brushmove(p) {
var e = brush.extent();
svg.selectAll("circle").classed("hidden", function (d) {
return e[0][0] > d[p.x] || d[p.x] > e[1][0]
|| e[0][1] > d[p.y] || d[p.y] > e[1][1];
});
}
// If the brush is empty, select all circles.
function brushend() {
if (brush.empty())
svg.selectAll(".hidden").classed("hidden", false);
}
function cross(a, b) {
var c = [], n = a.length, m = b.length, i, j;
for (i = - 1; ++i < n; )
for (j = - 1; ++j < m; )
c.push({x: a[i], i: i, y: b[j], j: j});
return c;
}
d3.select(self.frameElement).style("height", size * n + padding + 20 + "px");
var legendRectSize = 10;
var legendSpacing = 10;
var legend = svg.append("g")
.selectAll("g")
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function (d, i) {
var height = legendRectSize;
var x = 2 * size;
var y = (i * height) + 120;
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', legendSpacing)
.text(function (d) {
return d;
});
});
</script>
A screenshot of my data - Survival Probability.csv
Ethnicity,AgeAtTx,Site,Tcategory,Nodal_Disease,ecog,Chemotherapy,Local_Therapy,Probability of Survival,KM OS,OS (months),sex
white,65.93972603,supraglottic,T3,N+,0,no chemo,LP/RT alone,0.366190068,0,112.9,Female
white,69.42465753,supraglottic,T3,N+,0,induction,PLRT,0.396018836,0,24.1,Male
white,68.14246575,supraglottic,T3,N0,3,no chemo,LP/RT alone,0.439289384,0,3.566666667,Female
white,40.30410959,supraglottic,T3,N+,1,no chemo,LP/RT alone,0.512773973,1,226.3,Male
white,47.96438356,supraglottic,T3,N+,0,no chemo,PLRT,0.472208904,0,9.6,Female
white,70.3369863,supraglottic,T3,N+,0,no chemo,LP/RT alone,0.324965753,0,25.26666667,Male
white,60.50136986,supraglottic,T3,N+,2,no chemo,LP/RT alone,0.323424658,0,9.5,Female
white,60.72328767,supraglottic,T3,N+,1,no chemo,LP/RT alone,0.321344178,0,15.03333333,Male
white,59.36986301,supraglottic,T3,N0,1,induction,LP/chemoRT,0.646532534,0,4.5,Male
other,57.64931507,supraglottic,T3,N+,1,concurrent,LP/chemoRT,0.662662671,1,52.73333333,Male
This is an interesting situation. It boils down essentially to element append order and mouse-events. First, let's fix the obvious. You want a tooltip on each circle, so you shouldn't be calling tip.show when you mouse over a cell, but on the 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", 5)
.style("fill", function(d) {
return color(d.Chemotherapy);
})
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
But you'll notice with this change, we don't receive the events on our circles. This is because svg.brush is placing a rect over each cell so that you can select with the extent, and it's receiving the mouse events. So to fix that we change the order of drawing to brush then circle:
var cell = svg.selectAll(".cell")
.data(cross(traits, traits))
.enter().append("g")
.attr("class", "cell")
.attr("transform", function(d) {
return "translate(" + (n - d.i - 1) * size + "," + d.j * size + ")";
});
// add the brush stuff
cell.call(brush);
// now the circles
cell.each(plot);
But we still have a problem. We've got one more rect on top of our circles, the frame rect. Since we don't care about mouse events on it just do a simple:
.style("pointer-events", "none");
Putting this all together:
<!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;
}
.frame {
fill: none;
stroke: #aaa;
}
circle {
fill-opacity: .7;
}
circle.hidden {
fill: #ccc !important;
}
.extent {
fill: #000;
fill-opacity: .125;
stroke: #fff;
}
</style>
<body>
<div id="chart3"> </div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<script>
var width = 419,
size = 130,
padding = 19.5,
height = 313;
var x = d3.scale.linear().domain([0, 100])
.range([padding / 2, size - padding / 2]);
var y = d3.scale.linear().domain([0, 1])
.range([size - padding / 2, padding / 2]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var color = d3.scale.ordinal()
.domain(['no chemo', 'induction', 'induction+chemoRT', 'concurrent'])
.range(['#ffae19', '#4ca64c', '#4682B4', '#c51b8a']);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([50, 70])
.html(function(d) {
console.log(d)
var coordinates = d3.mouse(this);
xValue = x.invert(coordinates[0]);
yValue = y.invert(coordinates[1]);
return "<strong> Age Of Patient " + d3.format(".2f")(xValue * 100) +
" <br/> Probability of Survival : " + d3.format(".2f")(yValue * 100) + " % </strong>";
});
//d3.csv("data.csv", function(error, data) {
// if (error)
// throw error;
var data = [{"Ethnicity":"white","AgeAtTx":"65.93972603","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N+","ecog":"0","Chemotherapy":"no chemo","Local_Therapy":"LP/RT alone","Probability of Survival":"0.366190068","KM OS":"0","OS (months)":"112.9","sex":"Female"},{"Ethnicity":"white","AgeAtTx":"69.42465753","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N+","ecog":"0","Chemotherapy":"induction","Local_Therapy":"PLRT","Probability of Survival":"0.396018836","KM OS":"0","OS (months)":"24.1","sex":"Male"},{"Ethnicity":"white","AgeAtTx":"68.14246575","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N0","ecog":"3","Chemotherapy":"no chemo","Local_Therapy":"LP/RT alone","Probability of Survival":"0.439289384","KM OS":"0","OS (months)":"3.566666667","sex":"Female"},{"Ethnicity":"white","AgeAtTx":"40.30410959","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N+","ecog":"1","Chemotherapy":"no chemo","Local_Therapy":"LP/RT alone","Probability of Survival":"0.512773973","KM OS":"1","OS (months)":"226.3","sex":"Male"},{"Ethnicity":"white","AgeAtTx":"47.96438356","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N+","ecog":"0","Chemotherapy":"no chemo","Local_Therapy":"PLRT","Probability of Survival":"0.472208904","KM OS":"0","OS (months)":"9.6","sex":"Female"},{"Ethnicity":"white","AgeAtTx":"70.3369863","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N+","ecog":"0","Chemotherapy":"no chemo","Local_Therapy":"LP/RT alone","Probability of Survival":"0.324965753","KM OS":"0","OS (months)":"25.26666667","sex":"Male"},{"Ethnicity":"white","AgeAtTx":"60.50136986","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N+","ecog":"2","Chemotherapy":"no chemo","Local_Therapy":"LP/RT alone","Probability of Survival":"0.323424658","KM OS":"0","OS (months)":"9.5","sex":"Female"},{"Ethnicity":"white","AgeAtTx":"60.72328767","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N+","ecog":"1","Chemotherapy":"no chemo","Local_Therapy":"LP/RT alone","Probability of Survival":"0.321344178","KM OS":"0","OS (months)":"15.03333333","sex":"Male"},{"Ethnicity":"white","AgeAtTx":"59.36986301","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N0","ecog":"1","Chemotherapy":"induction","Local_Therapy":"LP/chemoRT","Probability of Survival":"0.646532534","KM OS":"0","OS (months)":"4.5","sex":"Male"},{"Ethnicity":"other","AgeAtTx":"57.64931507","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N+","ecog":"1","Chemotherapy":"concurrent","Local_Therapy":"LP/chemoRT","Probability of Survival":"0.662662671","KM OS":"1","OS (months)":"52.73333333","sex":"Male"}];
var domainByTrait = {},
traits = d3.keys(data[0]).filter(function(d) {
return (d == 'AgeAtTx' || d == 'Probability of Survival')
}),
n = traits.length;
traits.forEach(function(trait) {
domainByTrait[trait] = d3.extent(data, function(d) {
return d[trait];
});
});
xAxis.tickSize(size * n);
yAxis.tickSize(-size * n);
var brush = d3.svg.brush()
.x(x)
.y(y)
.on("brushstart", brushstart)
.on("brush", brushmove)
.on("brushend", brushend);
var svg = d3.select("#chart3").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + padding + "," + padding / 2 + ")");
svg.call(tip);
svg.selectAll(".x.axis")
.data(traits)
.enter().append("g")
.attr("class", "x axis")
.attr("transform", function(d, i) {
return "translate(" + (n - i - 1) * size + ",0)";
})
.each(function(d) {
x.domain(domainByTrait[d]);
d3.select(this).call(xAxis);
});
svg.selectAll(".y.axis")
.data(traits)
.enter().append("g")
.attr("class", "y axis")
.attr("transform", function(d, i) {
return "translate(0," + i * size + ")";
})
.each(function(d) {
y.domain(domainByTrait[d]);
d3.select(this).call(yAxis);
});
var cell = svg.selectAll(".cell")
.data(cross(traits, traits))
.enter().append("g")
.attr("class", "cell")
.attr("transform", function(d) {
return "translate(" + (n - d.i - 1) * size + "," + d.j * size + ")";
});
cell.call(brush);
cell.each(plot);
// Titles for the diagonal.
cell.filter(function(d) {
return d.i === d.j;
}).append("text")
.attr("x", padding)
.attr("y", padding)
.attr("dy", ".71em")
.text(function(d) {
return d.x;
});
function plot(p) {
var cell = d3.select(this);
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
cell.append("rect")
.attr("class", "frame")
.attr("x", padding / 2)
.attr("y", padding / 2)
.attr("width", size - padding)
.attr("height", size - padding)
.style("pointer-events", "none");
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", 5)
.style("fill", function(d) {
return color(d.Chemotherapy);
})
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
}
var brushCell;
// Clear the previously-active brush, if any.
function brushstart(p) {
if (brushCell !== this) {
d3.select(brushCell).call(brush.clear());
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
brushCell = this;
}
}
// Highlight the selected circles.
function brushmove(p) {
var e = brush.extent();
svg.selectAll("circle").classed("hidden", function(d) {
return e[0][0] > d[p.x] || d[p.x] > e[1][0] || e[0][1] > d[p.y] || d[p.y] > e[1][1];
});
}
// If the brush is empty, select all circles.
function brushend() {
if (brush.empty())
svg.selectAll(".hidden").classed("hidden", false);
}
function cross(a, b) {
var c = [],
n = a.length,
m = b.length,
i, j;
for (i = -1; ++i < n;)
for (j = -1; ++j < m;)
c.push({
x: a[i],
i: i,
y: b[j],
j: j
});
return c;
}
var legendRectSize = 10;
var legendSpacing = 10;
var legend = svg.append("g")
.selectAll("g")
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize;
var x = 2 * size;
var y = (i * height) + 120;
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', legendSpacing)
.text(function(d) {
return d;
});
//});
</script>

I need to draw a chord in d3 in eclipse shape rather as circle

Here is my sample js file i need to draw a chord but in eclipse shape rather then a circle. Secondly i need to know that what is a matrix use while creating chord diagram and are we able to draw a chord with simple json file (without using matrix) as mentioned http://www.delimited.io/blog/2013/12/8/chord-diagrams-in-d3 here. Because in every example of chord some matrix is give to draw it. I am new to d3 i need to learn lot of things. Can any one help really appreciate it
var outerRadius = 500 / 2,
innerRadius = outerRadius - 100;
var fill = d3.scale.category20c();
var chord = d3.layout.chord()
.padding(.04)
.sortSubgroups(d3.descending)
.sortChords(d3.descending);
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(innerRadius + 20);
var svg = d3.select('#content').append("svg")
.attr("width", outerRadius * 2)
.attr("height", outerRadius * 2)
.append("g")
.attr("transform", "translate(" + outerRadius + "," + outerRadius +
")");
d3.json("readme.json", function(error, imports) {
if (error) throw error;
var indexByName = d3.map(),
nameByIndex = d3.map(),
matrix = [],
n = 0;
// Returns the Flare package name for the given class name.
function name(name) {
return name.substring(0, name.lastIndexOf(".")).substring(6);
}
// Compute a unique index for each package name.
imports.forEach(function(d) {
if (!indexByName.has(d = name(d.name))) {
nameByIndex.set(n, d);
indexByName.set(d, n++);
}
});
// Construct a square matrix counting package imports.
imports.forEach(function(d) {
var source = indexByName.get(name(d.name)),
row = matrix[source];
if (!row) {
row = matrix[source] = [];
for (var i = -1; ++i < n;) row[i] = 0;
}
d.imports.forEach(function(d) { row[indexByName.get(name(d))]++; });
});
chord.matrix(matrix);
var g = svg.selectAll("g.group")
.data(chord.groups())
.enter().append("svg:g")
.attr("class", "group")
.on("mouseover", fade(.02))
.on("mouseout", fade(.80));
// .on("mouseover", mouseover);
//.on("mouseout", fade(1));
g.append("svg:path")
.style("stroke", "none")
.style("fill", function(d) { return fill(d.index); })
.attr("d", arc);
/* g.append("path")
.style("fill", function(d) { return fill(d.index); })
.style("stroke", function(d) { return fill(d.index); })
.attr("d", arc);*/
g.append("text")
.each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; })
.attr("dy", ".35em")
.attr("transform", function(d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ "translate(" + (innerRadius + 26) + ")"
+ (d.angle > Math.PI ? "rotate(180)" : "");
})
.style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.text(function(d) { return nameByIndex.get(d.index); });
svg.selectAll(".chord")
.data(chord.chords)
.enter().append("path")
.attr("class", "chord")
.style("stroke", function(d) { return
d3.rgb(fill(d.source.index)).darker(); })
.style("fill", function(d) { return fill(d.source.index); })
//.style("opacity", 1)
.attr("d", d3.svg.chord().radius(innerRadius));
});
d3.select(self.frameElement).style("height", outerRadius * 5 + "px");
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#tooltip {
color: white;
opacity: .9;
background: #333;
padding: 5px;
border: 1px solid lightgrey;
border-radius: 5px;
position: absolute;
z-index: 10;
visibility: hidden;
white-space: nowrap;
pointer-events: none;
}
#circle circle {
fill: none;
pointer-events: all;
}
path.group {
fill-opacity: .8;
}
path.chord {
fill-opacity: .8;
stroke: #000;
stroke-width: .25px;
}
#circle:hover path.fade {
display: none;
}
</style>
</head>
<body>
<div id="tooltip"></div>
<script src="lib/d3.js"></script>
<script src="lib/underscore.js"></script>
<script src="js/mapper.js"></script>
<script>
//*******************************************************************
// CREATE MATRIX AND MAP
//*******************************************************************
d3.csv('data/CNV.csv', function (error, data) {
var mpr = chordMpr(data);
mpr
.addValuesToMap('chr_no')
.addValuesToMap('MUT')
mpr .setFilter(function (row, a, b) {
return (row.chr_no === a.name && row.MUT === b.name) ||
(row.chr_no === b.name && row.MUT === a.name)
})
.setAccessor(function (recs, a, b) {
if (!recs[0]) return 0;
return recs[0].MUT === a.name ? +recs[0].chr_start :
+recs[0].chr_stop ;
});
drawChords(mpr.getMatrix(), mpr.getMap());
});
//*******************************************************************
// DRAW THE CHORD DIAGRAM
//*******************************************************************
function drawChords (matrix, mmap) {
var w = 980, h = 800, r1 = h / 2, r0 = r1 - 110;
var fill = d3.scale.ordinal()
.range(['#c7b570','#c6cdc7','#335c64','#768935',
'#507282','#5c4a56','#aa7455','#574109','#837722',
'#73342d','#0a5564','#9c8f57','#7895a4','#4a5456',
'#b0a690','#0a3542',]);
var chord = d3.layout.chord()
.padding(.04)
.sortSubgroups(d3.descending)
.sortChords(d3.descending);
var arc = d3.svg.arc()
.innerRadius(r0)
.outerRadius(r0 + 20);
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("id", "circle")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
svg.append("circle")
.attr("r", r0 + 20);
var rdr = chordRdr(matrix, mmap);
chord.matrix(matrix);
var g = svg.selectAll("g.group")
.data(chord.groups())
.enter().append("svg:g")
.attr("class", "group")
.on("mouseover", mouseover)
.on("mouseout", function (d) {
d3.select("#tooltip").style("visibility", "hidden") });
g.append("svg:path")
.style("stroke", "black")
.style("fill", function(d) { return fill(rdr(d).gname); })
.attr("d", arc);
g.append("svg:text")
.each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2;
})
.attr("dy", ".35em")
.style("font-family", "helvetica, arial, sans-serif")
.style("font-size", "9px")
.attr("text-anchor", function(d) { return d.angle > Math.PI ?
"end" : null; })
.attr("transform", function(d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ "translate(" + (r0 + 26) + ")"
+ (d.angle > Math.PI ? "rotate(180)" : "");
})
.text(function(d) { return rdr(d).gname; });
var chordPaths = svg.selectAll("path.chord")
.data(chord.chords())
.enter().append("svg:path")
.attr("class", "chord")
.style("stroke", function(d) { return
d3.rgb(fill(rdr(d).sname)).darker(); })
.style("fill", function(d) { return fill(rdr(d).sname); })
.attr("d", d3.svg.chord().radius(r0))
.on("mouseover", function (d) {
d3.select("#tooltip")
.style("visibility", "visible")
.html(chordTip(rdr(d)))
.style("top", function () { return (d3.event.pageY -
170)+"px"})
.style("left", function () { return (d3.event.pageX -
100)+"px";})
})
.on("mouseout", function (d) {
d3.select("#tooltip").style("visibility", "hidden") });
function chordTip (d) {
var p = d3.format(".0%"), q = d3.format("0d")
return "Choromosome information:<br/>"
+ q(d.sname) + " overlap with " + d.tname +" " +
"<br/>chromosome starts at"+" "+ d.sdata +" " +
"<br/>chromosome ends at"+ " "+ d.tdata
}
}
function mouseover(d, i) {
d3.select("#tooltip")
.style("visibility", "visible")
.html(groupTip(rdr(d)))
.style("top", function () { return (d3.event.pageY - 80)+"px"})
.style("left", function () { return (d3.event.pageX - 130)+"px";})
chordPaths.classed("fade", function(p) {
return p.source.index != i
&& p.target.index != i;
});
}
}
</script>

Categories

Resources