I am learning the brush concept of d3 and want to know if it is possible to use brush on sequence of rectangles instead of x-axis. I have 12 rectangles created and would like to stretch over the rectangles using the mouse.
My code is :
var margin = {top: 4, right: 50, bottom: 20, left: 50},
width = 960 - margin.left - margin.right,
height = 120 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg").attr("id","svgtimer")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("TimerData.json", function(data) {
CreateLegend('#timer',svg,"rectangle",data,'Jan','Dec');
})
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
var brush = d3.svg.brush()
.x(d3.scale.identity().domain([0, width]))
.y(d3.scale.identity().domain([0, height]))
.on("brush", brushed);
svg.append("g").call(brush);
function brushed() {
console.log(brush.extent());
}
function CreateLegend(div,svg,svgid,data,header,trail)
{
console.log(data);
var traillength=0;
var svgelem;
//alert("Non-empty");
//d3.json(filepath, function(data) {
console.log(" the svg id is " +svgid);
jsondata = data;
rectangle= svg.selectAll("rect").data(data).enter().append("rect");
var RectangleAttrb = rectangle
.attr("id", function (d,i) { return svgid + "id" + i ; })
.attr("x", function (d) { return d.x_axis; })
.attr("y", function (d) { return d.y_axis; })
.attr("width",function(d) { return d.width; } )
.attr("height",function(d) { return d.height; })
.style("stroke", function (d) { return d.border;})
.style("fill", function(d) { return d.color; });
var textparam = svg.selectAll("text").data(data).enter().append("text");
var yearheader = d3.select("#header");
if(yearheader.empty())
{
var textheader = svg.append("text").attr("dx",20).attr("dy",5).text(header).attr("id",header).attr("style","margin-bottom:21px;border-bottom: solid 2px #ffd97f; font-size:12px;")
}
if (trail.length == 0)
{
//console.log(textheader);
d3.select(header).attr("style","font-size:15.1px;text-decoration:underline");
//svg.attr("style","text-decoration:underline");
}
var text = textparam .attr("x", function (d) { traillength = d.x_axis + d.width +10; return d.x_axis + d.width +10; })
.attr("y", function (d) { return d.y_axis + d.height-5; })
.attr("width",30 )
.attr("height",20)
.attr("style", "text-decoration:none")
.text(function(d) { return d.text; });
var yearheader = d3.select("#trail");
if (trail.length > 0 && yearheader.empty() )
{
svg.append("text").attr("id","trail").attr("dx",traillength-10).attr("dy",5).text(trail).attr("style","margin-bottom:21px;border-bottom: solid 2px #ffd97f; font-size:12px;" )
}
//});
}
My timerdata is :
[
{ "x_axis":40, "y_axis": 10,"width":20,"height":15,"color" : "#ffffff","border":"#000000"},
{ "x_axis":60, "y_axis": 10,"width":20,"height":15,"color" : "#ffffff","border":"#000000"},
{ "x_axis":80, "y_axis": 10,"width":20,"height":15,"color" : "#ffffff","border":"#000000"},
{ "x_axis":100, "y_axis":10,"width":20,"height":15,"color" : "#ffffff","border":"#000000"},
{ "x_axis":120, "y_axis":10,"width":20,"height":15,"color" : "#ffffff","border":"#000000"},
{ "x_axis":140, "y_axis":10,"width":20,"height":15,"color": "#ffffff","border":"#000000"},
{ "x_axis":160, "y_axis":10,"width":20,"height":15,"color" : "#ffffff","border":"#000000"},
{ "x_axis":180, "y_axis":10,"width":20,"height":15,"color": "#ffffff","border":"#000000"},
{ "x_axis":200, "y_axis":10,"width":20,"height":15,"color" : "#ffffff","border":"#000000"},
{ "x_axis":220, "y_axis":10,"width":20,"height":15,"color" : "#ffffff","border":"#000000"},
{ "x_axis":240, "y_axis":10,"width":20,"height":15,"color" : "#ffffff","border":"#000000"},
{ "x_axis":260, "y_axis":10,"width":20,"height":15,"color" : "#ffffff","border":"#000000"}
]
How do i use the brush to stretch on the rectangles. I looked at some samples and found that brush is associated with x-axis. Is it not possible to use brush on these rectangles?
You should be able to use d3.svg.brush() without any special modifications. I've taken your code and implemented it here. The code that initialises and attaches the brush is as follows.
var brush = d3.svg.brush()
.x(d3.scale.identity().domain([0, width]))
.y(d3.scale.identity().domain([0, height]))
.on("brush", brushed);
svg.append("g").call(brush);
This initialises the brush and assigns identity scales to the x and y dimensions. In your code, you're not actually using scales to convert the user to screen coordinates, but taking the user coordinates directly. This is what the identity scales does. The domain of each is set to the respective dimension of the graph to tell the brush how big the brushed area can be.
You can specify an initial extent of the brush using the .extent() function. The implementation of the handler looks like this in your case.
function brushed() {
var e = brush.extent(),
selected = svg.selectAll("rect").filter(function(d) {
return d.x_axis <= e[1][0] && d.x_axis + d.width >= e[0][0] && d.y_axis <= e[1][1] && d.y_axis + d.height >= e[0][1];
})
console.log(selected);
}
It first gets the current extent of the brush and then filters the drawn rectangles by it. That is, for each rectangle, the code checks whether it overlaps with the brush rectangle. If it does, it is retained in the list selected. Note that this implementation is not particularly efficient as it iterates over all the rectangles. This is not a problem in your case as you only have a few, but if you want to use this with many more rectangles in two dimensions, I recommend using a more efficient data structure, such as a quadtree.
Related
I created this bars with a tooltip. I need to get them update after the $('.quarter-increase, .quarter-decrease').on('click', function() {
I don't get any errors but nothing gets updated...
$(document).ready(function() {
$('#prof-rendi').click(function() {
$('.graph-loading').show();
$('#svg-quarter').empty();
var tooltip = tooltipd3();
var svg = d3.select("svg#svg-quarter"),
margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var div = d3.select("#svg-quarter").append("div"). // declare the tooltip div
attr("class", "tooltip"). // apply the 'tooltip' class
style("opacity", 0);
d3.csv(base_url() + 'graph/getStatementsQuarterly/', function(d) {
$('.graph-loading').hide();
d.guadagno = +d.guadagno;
return d;
}, function(error, data) {
if (error)
throw error;
x.domain(data.map(function(d) {
return d.periodo;
}));
y.domain([
0,
d3.max(data, function(d) {
return d.guadagno;
})
]);
g.append("g").attr("class", "axis axis--x").attr("transform", "translate(0," + height + ")").call(d3.axisBottom(x));
g.append("g").attr("class", "axis axis--y").call(d3.axisLeft(y).ticks(10)).append("text").attr("transform", "rotate(-90)").attr("y", 6).attr("dy", "0.71em").attr("text-anchor", "end").text("Guadagno")
g.selectAll(".bar").data(data).enter().append("rect").attr("class", "bar").attr("x", function(d) {
return x(d.periodo);
}).attr("y", function(d) {
return y(d.guadagno);
}).attr("width", x.bandwidth()).attr("height", function(d) {
return height - y(d.guadagno);
}).on('mouseover', function(d) {
var html = '<h5>' + d.guadagno + ' €</h5>';
tooltip.mouseover(html); // pass html content
}).on('mousemove', tooltip.mousemove).on('mouseout', tooltip.mouseout);
});
});
$('.quarter-increase, .quarter-decrease').on('click', function() {
$('.rendi-btn.left, .rendi-btn.right').attr('disabled', 'disabled');
var where_at = $('#scroll-statement-quarter').val();
$('.graph-loading').show();
$('#svg-quarter').css({'opacity': 0.4});
var tooltip = tooltipd3();
var svg = d3.select("svg#svg-quarter"),
margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var div = d3.select("#svg-quarter").append("div"). // declare the tooltip div
attr("class", "tooltip"). // apply the 'tooltip' class
style("opacity", 0);
var speed = 500;
d3.csv(base_url() + 'graph/getStatementsQuarterly/' + where_at, function(d) {
$('.graph-loading').hide();
d.guadagno = +d.guadagno;
return d;
}, function(error, data) {
if (error)
throw error;
x.domain(data.map(function(d) {
return d.periodo;
}));
y.domain([
0,
d3.max(data, function(d) {
return d.guadagno;
})
]);
g.append("g").attr("class", "axis axis--x").attr("transform", "translate(0," + height + ")").call(d3.axisBottom(x));
g.append("g").attr("class", "axis axis--y").call(d3.axisLeft(y).ticks(10)).append("text").attr("transform", "rotate(-90)").attr("y", 6).attr("dy", "0.71em").attr("text-anchor", "end").text("Guadagno")
g.selectAll(".bar").data(data).transition().duration(speed).attr("class", "bar").attr("x", function(d) {
return x(d.periodo);
}).attr("y", function(d) {
return y(d.guadagno);
}).attr("width", x.bandwidth()).attr("height", function(d) {
return height - y(d.guadagno);
}).on('mouseover', function(d) {
var html = '<h5>' + d.guadagno + ' €</h5>';
tooltip.mouseover(html); // pass html content
}).on('mousemove', tooltip.mousemove).on('mouseout', tooltip.mouseout);
});
})
});
This is a Plunker to test this:
https://plnkr.co/edit/72GCWqkllMFXZI6mecQE?p=preview
Press "show", then change the year to 2016 and you will see the result.
Your g variable inside the click event handler is a newly appended <group> element.
Therefore, this...
g.selectAll(".bar").data(data).etc...
... won't work, because there is nothing with a class .bar inside that group.
Solution: use the svg variable to select the rectangles:
svg.selectAll(".bar").data(data).etc...
Here is the updated plunker: https://plnkr.co/edit/eNa6Af0WcyrcLejadO2q?p=preview
PS: this code has several problems. I strongly advise you to not mix jQuery and D3, and also to not use d3.csv inside an event handler.
I've converted Mike Botock's Hierarchical Bar Chart to v4 and made some tweaks to fit my needs(vertical, tool-tip, widths of bars fit canvas, etc).
Now I'm trying to make this a stacked bar chart. In my JSON file I have two types of downtime Machine and Die. For the original chart I just add these up to get my overall but now i want to stack them and I'm unsure how to pull these out separately after doing a root.sum on the Hierarchy. This is my first chart so pardon some of the coding but feel free to correct me on anything. I also could clean some things up with if statements but I'm leaving everything separate as it is easier to troubleshoot. Any thoughts on how to make stacked hierarchical bar chart would be appreciated. Even if it means throwing away this code and starting over.
<body>
<script src="d3.min.js"></script>
<script>
//canvas variables
var margin = { top: 30, right: 120, bottom: 300, left: 120 },
width = 960 - margin.left - margin.right,
height = 800 - margin.top - margin.bottom;
// scale y on canvas from largest number to 0
var y = d3.scaleLinear().range([height, 0]);
var barWidth;
var barPadding = 5;
var oldBarWidth = width;
var depth = 0;
var color = d3.scaleOrdinal()
.range(["steelblue", "#ccc"]);
var duration = 750,
delay = 25;
//attach SVG to body with canvas variables declared above
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 divTooltip = d3.select("body").append("div").attr("class", "toolTip");
//attach a rectangle to the entire background for drilling
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height)
.on("click", drillUp);
//append axis to the SVG
svg.append("g")
.attr("class", "yaxis");
svg.append("g")
.append("line")
.attr("transform", "translate(0," + height + ")")
.attr("x1", width)
.attr("stroke", "black")
.attr("shape-rendering", "crispEdges");
//import JSON file
d3.json("data/drilljson2.json", function (error, root) {
if (error) throw error;
//declare root of the JSON file
root = d3.hierarchy(root);
//add all children in hierarchy and get value for all parents
root.sum(function (d) { return (+d.DieDowntime + (+d.MachineDowntime)); });
//scale the 'Y' domain/axis from 0 to root value
y.domain([0, root.value]).nice();
//call the drill down function
drillDown(root, 0);
drillDown(root.children[3], 3);
});
function drillDown(d, i) {
if (!d.children) return;
// get the number of children to parent and calculate barWidth and keep track of depth of drill down.
numChildNodes = d.children.length;
barWidth = (width / numChildNodes) - barPadding;
depth += 1;
var end = duration + numChildNodes * delay;
// Mark any currently-displayed bars as exiting.
var exit = svg.selectAll(".enter")
.attr("class", "exit");
// Entering nodes immediately obscure the clicked-on bar, so hide it.
exit.selectAll("rect").filter(function (p) { return p === d; })
.style("fill-opacity", 0);
// Enter the new bars for the clicked-on data.
// Entering bars are immediately visible.
var enter = drillDownBars(d)
.attr("transform", stackDown(i))
.attr("width", oldBarWidth)
.style("opacity", 1);
// Update the y-scale domain.
y.domain([0, d3.max(d.children, function (d) { return d.value; })]).nice();
// Have the text fade-in, even though the bars are visible.
// Color the bars as parents; they will fade to children if appropriate.
enter.select("text").style("fill-opacity", 0);
enter.select("rect").style("fill", color(true));
// Update the y-axis.
svg.selectAll(".yaxis").transition()
.duration(duration)
.call(d3.axisLeft(y));
// Transition entering bars to their new position.
var enterTransition = enter.transition()
.duration(duration)
.delay(function (d, i) { return i * delay; })
.style("opacity", 1)
.attr("transform", function (d, i) { var transBar = (barWidth +barPadding) * i +barPadding; return "translate("+ transBar + ")"; });
// Transition entering text.
enterTransition.select("text")
.attr("transform", function (d) { return "translate("+(barWidth/2)+","+((height+5) + 10 * depth)+")rotate(90)" })
// working .attr("y", height + 15)
.style("fill-opacity", 1);
// Transition entering rects to the new y-scale.
enterTransition.select("rect")
.attr("y", function (d) { return y(d.value); })
.attr("height", function (d) { return height - y(d.value); })
.attr("width",barWidth)
.style("fill", function (d) { return color(!!d.children); });
// Transition exiting bars to fade out.
var exitTransition = exit.transition()
.duration(duration)
.style("opacity", 0)
.remove();
// Transition exiting bars to the new y-scale.
exitTransition.selectAll("rect")
.attr("y", function (d) { return y(d.value); })
.attr("height", function (d) { return height - y(d.value); });
// Rebind the current node to the background.
svg.select(".background")
.datum(d)
.transition()
.duration(end);
d.index = i;
oldBarWidth = barWidth;
}
function drillUp(d) {
if (!d.parent || this.__transition__) return;
numChildNodes = d.parent.children.length;
barWidth = (width / numChildNodes) - barPadding;
depth -= 1;
var end = duration + d.children.length * delay;
// Mark any currently-displayed bars as exiting.
var exit = svg.selectAll(".enter")
.attr("class", "exit");
// Enter the new bars for the clicked-on data's parent.
var enter = drillUpBars(d.parent)
.attr("transform", function (d, i) {
transBarWidth = (barWidth + barPadding) * i + barPadding;
return "translate(" + transBarWidth + "," + 0 + ")";
})
.style("opacity", 0);
// Color the bars as appropriate.
// Exiting nodes will obscure the parent bar, so hide it.
enter.select("rect")
.style("fill", function (d) { return color(!!d.children); })
.filter(function (p) { return p === d; })
.style("fill-opacity", 0);
// Update the y-scale domain.
y.domain([0, d3.max(d.parent.children, function (d) { return d.value; })]).nice();
// Update the y-axis.
svg.selectAll(".yaxis").transition()
.duration(duration)
.call(d3.axisLeft(y));
// Transition entering bars to fade in over the full duration.
var enterTransition = enter.transition()
.duration(end)
.style("opacity", 1);
// Transition entering rects to the new y-scale.
// When the entering parent rect is done, make it visible!
enterTransition.select("rect")
.attr("y", function (d) { return y(d.value); })
.attr("height", function (d) { return height - y(d.value); })
.on("end", function (p) { if (p === d) d3.select(this).style("fill-opacity", null); });
// Transition entering text.
enterTransition.select("text")
.attr("transform", function (d) { return "translate("+(barWidth/2)+","+((height+5) + 10 * depth)+")rotate(90)" })
.style("fill-opacity", 1);
// Transition exiting bars to the parent's position.
var exitTransition = exit.selectAll("g").transition()
.duration(duration)
.delay(function (d, i) { return i * delay; })
.attr("transform", stackUp(d.index));
// Transition exiting text to fade out.
exitTransition.select("text")
.style("fill-opacity", 0);
// Transition exiting rects to the new scale and fade to parent color.
exitTransition.select("rect")
.attr("y", function (d) { return y(d.value); })
.attr("height", function (d) { return height - y(d.value); })
.attr("width", barWidth)
.style("fill", color(true));
// Remove exiting nodes when the last child has finished transitioning.
exit.transition()
.duration(end)
.remove();
// Rebind the current parent to the background.
svg.select(".background")
.datum(d.parent)
.transition()
.duration(end);
oldBarWidth = barWidth;
}
// Creates a set of bars for the given data node, at the specified index.
function drillUpBars(d) {
var bar = svg.insert("g")
.attr("class", "enter")
.selectAll("g")
.data(d.children)
.enter().append("g")
.style("cursor", function (d) { return !d.children ? null : "pointer"; })
.on("click", drillDown);
bar.append("text")
.attr("dx", ".35em")
.attr("text-anchor", "middle")
.attr("transform", function (d) { return "translate(" + barWidth / 2 + "," + (height + 15) + ") rotate(90)" })
.text(function (d) { return d.data.name; });
bar.append("rect")
.attr("y", function (d) { return y(d.value); } )
.attr("height", function (d) { return height - y(d.value); })
.attr("width", barWidth)
.attr("stroke-width", 1)
.attr("stroke", "white");
return bar;
}
function drillDownBars(d) {
var bar = svg.insert("g")
.attr("class", "enter")
.selectAll("g")
.data(d.children)
.enter().append("g")
.style("cursor", function (d) { return !d.children ? null : "pointer"; })
.on("click", drillDown)
.on("mouseover", mouseover)
.on("mousemove", function (d) {
divTooltip
.text(d.data.name +" " + d.value)
.style("left", (d3.event.pageX - 34) + "px")
.style("top", (d3.event.pageY - 30) + "px");
});
bar.append("text")
.attr("dx", ".35em")
.attr("text-anchor", "middle")
.attr("transform", function (d) { return "translate(" + barWidth / 2 + "," + (height + 15 - y(d.value)) + ") rotate(90)" })
.text(function (d) { return d.data.name; });
bar.append("rect")
.attr("height", function (d) { return height - y(d.value); })
.attr("width", oldBarWidth)
.attr("stroke-width", 1)
.attr("stroke", "white");
return bar;
}
//Creates a stack of bars
function stackDown(i) {
var x0 = (oldBarWidth + barPadding) * i + barPadding;
var y0 = height;
return function (d) {
y0 -= height - y(d.value);
var ty = "translate(" + x0 + "," + y0 + ")";
return ty;
};
}
//
function stackUp(i) {
var x0 = barWidth * i + (barPadding * (i + 1));
var y0 = 0;
return function (d) {
var ty = "translate(" + x0 + "," + y0 + ")";
y0 -= height - y(d.value);
return ty;
};
}
function mouseover() {
divTooltip.style("display", "inline");
}
</script>
Here is a piece of the JSON file which could also use a little cleaning but minor details for now.
{
"name" : "Down Time",
"children" : [{
"name" : "2013",
"children" : [{
"name" : "May 2013",
"children" : [{
"name" : "21 May 2013",
"children" : [{
"name" : "110",
"children" : [{
"MachineDowntime" : ".00000000000000000000",
"DieDowntime" : ".50000000000000000000"
}
]
}, {
"name" : "115",
"children" : [{
"MachineDowntime" : "5.23333333333333333300",
"DieDowntime" : ".00000000000000000000"
}
]
}
]
}, {
"name" : "22 May 2013",
"children" : [{
"name" : "115",
"children" : [{
"MachineDowntime" : "2.96666666666666666730",
"DieDowntime" : ".00000000000000000000"
}
]
}, {
"name" : "110",
"children" : [{
"MachineDowntime" : ".00000000000000000000",
"DieDowntime" : "10.36666666666666667000"
I'm trying to generate a parallel coordinate using d3.js
My problem is that the first scale should display different strings.
with the original code it looks like this:
and with my test it looks like this (no lines):
the error code is:
Error: Invalid value for attribute d="M33,NaNL99,161.37817638266068L165,6.543121881682145L231,16.962488563586458L297,180"
here is my code:
function parallelChart (id, size) {
if(size == 'small') {
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 460 - margin.left - margin.right,
height = 230 - margin.top - margin.bottom;
} else {
var margin = {top: 20, right: 80, bottom: 30, left: 50},
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(id).append("svg")
.attr("class", 'center-block')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Original
d3.csv("dataNew.csv", function(error, healthdata) {
x.domain(dimensions = d3.keys(healthdata[0]).filter(function(d) {
return d != "Datum" && (y[d] = d3.scale.linear()
.domain(d3.extent(healthdata, function(p) { return +p[d]; }))
.range([height, 0]));
}));
// this did not work
// d3.csv("dataNew.csv", function(error, healthdata) {
// x.domain(dimensions = d3.keys(healthdata[0]).filter(function(d) {
// if(d == "Datum") {
// return d == "Datum" && ( (y[d] = d3.time.scale()
// .domain(d3.extent(healthdata, function(p) { return +p[d]; }))
// .range([height, 0])));
// }
// return d != "Datum" && ( (y[d] = d3.scale.linear()
// .domain(d3.extent(healthdata, function(p) { return +p[d]; }))
// .range([height, 0])));
// }));
// Add grey background lines for context.
background = svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(healthdata)
.enter().append("path")
.attr("d", path);
// Add blue foreground lines for focus.
foreground = svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(healthdata)
.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.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);
});
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";
});
}
}
Here goes one example where the author manage string and numbers in the same parallel coord:
http://bl.ocks.org/syntagmatic/4020926
Create an array of dimensions that will be further used...
var dimensions = [
{
name: "name",
scale: d3.scale.ordinal().rangePoints([0, height]),
type: "string"
},
{
name: "economy (mpg)",
scale: d3.scale.linear().range([0, height]),
type: "number"
},
...
]
...before load the data, define the domains by mapping your previous dimensions definition...
var x = d3.scale.ordinal()
.domain(dimensions.map(function(d) { return d.name; }))
.rangePoints([0, width]);
...define a variable dimension (pay attention, dimensions != dimension) with the locations of each axis...
var dimension = svg.selectAll(".dimension")
.data(dimensions)
.enter().append("g")
.attr("class", "dimension")
.attr("transform", function(d) { return "translate(" + x(d.name) + ")"; });
...once the data is loaded, execute a for each to define the domain of each dimension...
d3.csv("cars.small.csv", function(data) {
dimensions.forEach(function(dimension) {
dimension.scale.domain(dimension.type === "number"
? d3.extent(data, function(d) { return +d[dimension.name]; })
: data.map(function(d) { return d[dimension.name]; }).sort());
});
...
}
... axis lines and foreground are still loaded in the same way...
svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(data)
.enter().append("path")
.attr("d", draw);
svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(data)
.enter().append("path")
.attr("d", draw);
... this code will load the text of each axis, observe that it is now using properties from the dimensions that we defined in the beggining.
dimension.append("g")
.attr("class", "axis")
.each(function(d) { d3.select(this).call(yAxis.scale(d.scale)); })
.append("text")
.attr("class", "title")
.attr("text-anchor", "middle")
.attr("y", -9)
.text(function(d) { return d.name; });
that`s all =).
I want to fill the height of the brush selection by default to the height of the rectangles on which it is drawn.
Here is my code:
var margin = {top: 10, right: 50, bottom: 20, left: 50},
width = 800 - margin.left - margin.right,
height = 120 - margin.top - margin.bottom;
var svgheight=40,svggraphheight=400;
var svg,svgcheckins,svgbuilds,svgoss,svgappsec;
var x,j = 0;
var x_axis = 1;
var y_axis = 45;
var xvar=0;
var yvar=40;
var rectangle,RectangleAttrb,rect,rectdata,selected,begindata,enddata;
function loaddata()
{
svg = d3.select("#timer").append("svg").attr("id","svgtimer")
.attr("width", 550)
.attr("height", svgheight)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svgcheckins= d3.select("#legend").append("svg").attr("id","svgcheckins")
.attr("width", 250)
.attr("height", 200)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("TimerData.json", function(data)
{
CreateLegend('#timer',svg,"rectangle",data,'Mar 1','Oct 4');
createbrush();
});
}
function createbrush()
{
var brush = d3.svg.brush()
.x(d3.scale.identity().domain([0, width]))
.y(d3.scale.identity().domain([0, height+30]))
//.on("brushstart", brushstart)
.on("brushend", brushed);
svg.append("g").call(brush);
function brushed()
{
//var e = brush.extent();
var e = d3.event.target.extent();
selected = svg.selectAll("rect").filter(function(d)
{
return d.x_axis <= e[1][0] && d.x_axis + d.width >= e[0][0] && d.y_axis <= e[1][1] && d.y_axis + d.height >= e[0][1];
})
//console.log("e[1][0] " + e[1][0] + " e[0][0] " + e[0][0] + "object " +e);
//createtinderboxes(selected);
}
function brushstart()
{
var e = brush.extent(0,5);
}
}
function CreateLegend(div,svg,svgid,data,header,trail)
{
var traillength=0;
var svgelem;
jsondata = data;
console.log(data);
rectangle= svg.selectAll("rect").data(data).enter().append("rect");
var RectangleAttrb = rectangle
.attr("id", function (d,i) { return svgid + "id" + i ; })
.attr("x", function (d) { return d.x_axis; })
.attr("y", function (d) { return d.y_axis; })
.attr("width",function(d) { return d.width; } )
.attr("height",function(d) { return d.height; })
.style("stroke", function (d) { return d.border;})
.style("fill", function(d) { return d.color; });
var textparam = svg.selectAll("text").data(data).enter().append("text");
var yearheader = d3.select("#header");
if(yearheader.empty())
{
var textheader = svg.append("text").attr("dx",20).attr("dy",5).text(header).attr("id",header).attr("style","margin-bottom:21px;border-bottom: solid 2px #ffd97f; font-size:12px;")
}
if (trail.length == 0)
{
d3.select(header).attr("style","font-size:15.1px;text-decoration:underline");
}
var text = textparam .attr("x", function (d) { traillength = d.x_axis + d.width +10; return d.x_axis + d.width +10; })
.attr("y", function (d) { return d.y_axis + d.height-5; })
.attr("width",30 )
.attr("height",20)
.attr("style", "text-decoration:none")
.text(function(d) { return d.text; });
var yearheader = d3.select("#trail");
if (trail.length > 0 && yearheader.empty() )
{
svg.append("text").attr("id","trail").attr("dx",traillength-10).attr("dy",5).text(trail).attr("style","margin-bottom:21px;border-bottom: solid 2px #ffd97f; font-size:12px;" )
}
}
the data is read from json file:
[
{ "x_axis":40, "y_axis": 10,"width":20,"height":15,"color" : "#ffffff","border":"#000000"},
{ "x_axis":60, "y_axis": 10,"width":20,"height":15,"color" : "#ffffff","border":"#000000"},
{ "x_axis":80, "y_axis":10,"width":20,"height":15,"color" : "#ffffff","border":"#000000"}
]
You can set the extent of the brush in code using brush.extent(), so in your case something like
brush.extent([[40, 80], [10, 25]]);
I am adding legends(through rectangles) on a svg. The data is read from different json files for each group of legend. I would like to draw the rectangles in the same svg. However only one set of legend is being added. Only the first json data is drawn as rectangles. The second is ignored.
My code is :
svgcheckins= d3.select("#legend").append("svg").attr("id","svgcheckins")
.attr("width", 250)
.attr("height", 200)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("checkins.json", function(data)
{
CreateLegend('#legend',svgcheckins,"checkins",data,'A - Checkins','');
});
d3.json("builds.json", function(data)
{
CreateLegend('#legend',svgcheckins,"Builds",data,'B - Builds','');
});
function CreateLegend(div,svg,svgid,data,header,trail)
{
var traillength=0;
var svgelem;
jsondata = data;
console.log(data);
rectangle= svg.selectAll("rect").data(data).enter().append("rect");
var RectangleAttrb = rectangle
.attr("id", function (d,i) { return svgid + "id" + i ; })
.attr("x", function (d) { return d.x_axis; })
.attr("y", function (d) { return d.y_axis; })
.attr("width",function(d) { return d.width; } )
.attr("height",function(d) { return d.height; })
.style("stroke", function (d) { return d.border;})
.style("fill", function(d) { return d.color; });
var textparam = svg.selectAll("text").data(data).enter().append("text");
var yearheader = d3.select("#header");
if(yearheader.empty())
{
var textheader = svg.append("text").attr("dx",20).attr("dy",5).text(header).attr("id",header).attr("style","margin-bottom:21px;border-bottom: solid 2px #ffd97f; font-size:12px;")
}
if (trail.length == 0)
{
d3.select(header).attr("style","font-size:15.1px;text-decoration:underline");
}
var text = textparam .attr("x", function (d) { traillength = d.x_axis + d.width +10; return d.x_axis + d.width +10; })
.attr("y", function (d) { return d.y_axis + d.height-5; })
.attr("width",30 )
.attr("height",20)
.attr("style", "text-decoration:none")
.text(function(d) { return d.text; });
var yearheader = d3.select("#trail");
if (trail.length > 0 && yearheader.empty() )
{
svg.append("text").attr("id","trail").attr("dx",traillength-10).attr("dy",5).text(trail).attr("style","margin-bottom:21px;border-bottom: solid 2px #ffd97f; font-size:12px;" )
}
}
The json data are :
checkins.json
[
{ "x_axis":10, "y_axis": 10,"width":20,"height":15,"color" : "#FE2E2E","border":"#000000"},
{ "x_axis":30, "y_axis": 10,"width":20,"height":15,"color" : "#FE9A2E","border":"#000000"},
{ "x_axis":50, "y_axis": 10,"width":20,"height":15,"color" : "#FFFF00","border":"#000000"},
{ "x_axis":70, "y_axis":10,"width":20,"height":15,"color" : "#D0FA58","border":"#000000"},
{ "x_axis":90, "y_axis":10,"width":20,"height":15,"color" : "#01DF01","border":"#000000"}
]
builds.json
[
{ "x_axis":10, "y_axis":60,"width":20,"height":15,"color" : "#424242","border":"#000000"},
{ "x_axis":30, "y_axis":60,"width":20,"height":15,"color" : "#D8D8D8","border":"#000000"},
{ "x_axis":50, "y_axis":60,"width":20,"height":15,"color" : "#FFFF00","border":"#000000"},
{ "x_axis":70, "y_axis":60,"width":20,"height":15,"color" : "#FAFAFA","border":"#000000"},
{ "x_axis":90, "y_axis":60,"width":20,"height":15,"color" : "#81DAF5","border":"#000000"}
]
This is because of how D3's data matching work. In particular, I'm referring to the following line:
rectangle= svg.selectAll("rect").data(data).enter().append("rect");
This selects all rect elements and matches data to them. For the first legend, no rects are there, so nothing is matched and it works as you expect. However, if there's a rect already, it is matched to data and your enter selection is empty. This is because D3 matches based on array index by default.
There are basically two ways around this. You could either use the optional second argument to .data() to tell D3 how to match, or assign a distinguishing class to the created legend to be used in the .selectAll(). Your data doesn't seem to have any distinguishing attributes in it, but you can use svgid for this purpose:
rectangle= svg.selectAll("rect." + svgid).data(data).enter().append("rect");
rectangle.attr("class", svgid);
// rest of code