Paths not drawn after dragging in parallel coordinate in d3 V4 - javascript

Here is a simple parallel coordinate in d3 V4
http://plnkr.co/edit/Ejg7CI7STPqXKB2tot51?p=preview
It is similar to https://bl.ocks.org/jasondavies/1341281 , which is in V3.
Following are the steps to reproduce the issue:
Step1. Brush some area (say 0.8 to 0.4) in column1....
Step2. Brush some area (say 0.7 to 0.4) in column3....
Step3. Now drag the axis column3 to the position of column2. (So basically axis ordering will get changed, from Column1, 2 , 3, 4 .. it will change to column1, 3 ,2, 4.
Step4. Brush column3 (which is now next to column1) again. You will see no paths are being drawn.
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("convertcsvSO.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])
}
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("start", brushstart).on("brush", brush_parallel_chart));
}
})
.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])]; }));
}
// brush start function
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>

In the drag callbacks, the dimensions are being sorted BUT the extents aren't. I've added a few lines that sorts extents array based on the new dimensions (by using origDimensions which is the original array)
Here's a fork of your plunkr: http://plnkr.co/edit/DquAXNv2mbbok7ssNuoX?p=preview
Relevant code:
var origDimensions = dimensions.slice(0);
And within the dragend callback:
// one way of sorting the extents array based on dimensions
var new_extents = [];
for(var i=0;i<dimensions.length;++i){
new_extents.push(extents[origDimensions.indexOf(dimensions[i])]);
}
extents = new_extents;
origDimensions = dimensions.slice(0); // setting origDimensions to the new array
Hope this helps. (and btw seems like the brushstart is empty which leads to showing NO curves on brush reset - try resetting brush on any axis).

Related

problem with D3 lasso circle element with text

I basically copied the example https://bl.ocks.org/skokenes/a85800be6d89c76c1ca98493ae777572
Then I got the code to work with my data. So, I can now get the lasso to work.
But when I try to add back my old code for the circles to display a text-type tool tip, the lasso breaks. The code then puts the class variables such as not_possible or selected on the "text" elements rather than on the "circle" elements where they need to be.
I found that is the issue by using Chrome developer tools.
When the tool tips code is commented out, the lasso code works and the DOM looks like this:
<circle cx="854" cy="37" fill="red" r="7" class="selected"></circle>
When the tool tips code is live, the tool tips work but the lasso code doesn't work and the DOM looks like this:
<circle cx="854" cy="37" fill="red" r="4.9">
<title r="3.5" class> ==$0
"curr = 89.7, prev = 89.5, geo = Alaska, measure = Percent Citizen, Born in the US"
</title>
</circle>
I've tried changing the styles for the classes, for example, from ".possible" to "circle.possible" but that doesn't help. I've googled for suggestions but haven't found anything that I could make work. I've tried passing the circle selection thru lasso.items(circles) but that doesn't work.
This is the lasso code that does work: the troublesome ".append title" and "text" lines are commented out.
var margin = {top: 20, right: 15, bottom: 60, left: 60}
, width = 960 - margin.left - margin.right
, height = 960 - margin.top - margin.bottom;
var xScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d[1]; })])
.range([0, width]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d[0]; })])
.range([height, 0]);
var svgArea = d3.select('.content')
.append('svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart');
var main = svgArea.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main');
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(d3.axisBottom(xScale));
main.append('g')
.append("text")
.attr("x", width / 2)
.attr("y", height + margin.bottom - 10)
.style("text-anchor", "middle")
.style("font", "14px times")
.text("Current X");
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(d3.axisLeft(yScale));
main.append('g')
.append("text")
.attr("transform", "rotate(-90)")
.attr("x", 0 - (height / 2))
.attr("y", 0 - margin.left / 2)
.style("text-anchor", "middle")
.style("font", "14px times")
.text("Previous Y");
var rScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d[1]; })])
.range([ 4, 5 ]);
var lasso_start = function() {
lasso.items()
.attr("r",7)
.classed("not_possible",true)
.classed("selected",false)
;
};
var lasso_draw = function() {
lasso.possibleItems()
.classed("not_possible",false)
.classed("possible",true)
;
lasso.notPossibleItems()
.classed("not_possible",true)
.classed("possible",false)
;
};
var lasso_end = function() {
lasso.items()
.classed("not_possible",false)
.classed("possible",false)
;
lasso.selectedItems()
.classed("selected",true)
.attr("r", 7)
;
lasso.notSelectedItems()
.attr("r", 3.5)
;
};
var circles = main.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function (d,i) { return xScale(d[1]); } )
.attr("cy", function (d) { return yScale(d[0]); } )
.attr("fill", function (d) { if (d[1] > 75) { return "red"; } else { return "black"; } })
.attr("r", function (d) { return rScale(d[1]); })
//.append("title")
//.text(function(d) {
// return "curr = " + d[1] +
// ", prev = " + d[0] +
// ", geo = " + d[2] +
// ", measure = " + d[3];
// })
;
var lasso = d3.lasso()
.items(circles)
.closePathDistance(75) // max distance for the lasso loop to be closed
.closePathSelect(true) // can items be selected by closing the path?
.targetArea(svgArea) // area where the lasso can be started
.on("start",lasso_start) // lasso start function
.on("draw",lasso_draw) // lasso draw function
.on("end",lasso_end); // lasso end function
svgArea.call(lasso);
Why does including ".title" and ".text" cause a problem?
And how do I solve it?
I don't think the problem is with the CSS, but here it is:
<style>
// styling for D3 chart
.chart {
background: #fdfefe;
}
.main text {
font: 10px sans-serif;
}
// styling for D3-lasso
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
circle {
fill-opacity: 0.4;
}
.dot {
stroke: #000;
}
.lasso path {
stroke: rgb(80,80,80);
stroke-width: 2px;
}
.lasso .drawn {
fill-opacity: 0.05 ;
}
.lasso .loop_close {
fill: none;
stroke-dasharray: 4,4;
}
.lasso .origin {
fill: #3399FF;
fill-opacity: 0.5;
}
.not_possible {
fill: rgb(200,200,200);
}
.possible {
fill: #EC888C;
}
.selected {
fill: steelblue;
}
</style>
The problem appears to be that lasso is adding a radius attribute to the title elements here:
lasso.notSelectedItems()
.attr("r", 3.5)
;
resulting in all your not-selected elements, i.e., circles, and titles, having the attribute assigned, as your example suggests:
<title r="3.5" class>
Rather than calling lasso's selected and notSelected to change the radius and css class of the desired items, use a filter on the items array itself:
// Style the selected dots
lasso.items().filter(function(d) {return d.selected===true})
.classed(...)
.attr("r",7);
// Reset the style of the not selected dots
lasso.items().filter(function(d) {return d.selected===false})
.classed(...)
.attr("r",3.5);
You can get as specific as you want with the return value, i.e., omit any nodes (like title nodes) you don't want affected by the rules you apply to the selection.
The problem was that I couldn't get D3 lasso and my approach to tool tips to work together. I was appending a title element to each circle (point) on a scatter plot. This does NOT work:
var circles = main.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function (d,i) { return xScale(d[1]); } )
.attr("cy", function (d) { return yScale(d[0]); } )
.attr("fill", function (d) { if (d[1] > 75) { return "red"; } else { return "black"; } })
.attr("r", function (d) { return rScale(d[1]); })
.append("title")
.text(function(d) {
return "curr = " + d[1] +
", prev = " + d[0] +
", geo = " + d[2] +
", measure = " + d[3];
})
;
I found a coding example by Mikhail Shabrikov that solved the issue by avoiding .append("title") altogether. This works:
A new CSS element:
.tooltip {
position: absolute;
z-index: 10;
visibility: hidden;
background-color: lightblue;
text-align: center;
padding: 4px;
border-radius: 4px;
font-weight: bold;
color: black;
}
A new DIV element:
var tooltip = d3.select("body")
.append("div")
.attr('class', 'tooltip');
And mainly a modified circles element:
var circles = main.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function (d,i) { return xScale(d[1]); } )
.attr("cy", function (d) { return yScale(d[0]); } )
.attr("fill", function (d) { if (d[1] > 75) { return "red"; } else { return "black"; } })
.attr("r", 5)
.on("mouseover", function(d) {return tooltip.style("visibility", "visible")
.text(
"curr = " + d[1] +
", prev = " + d[0] +
", geo = " + d[2] +
", measure = " + d[3]
)
})
.on("mousemove", function() {
return tooltip.style("top", (event.pageY - 30) + "px")
.style("left", event.pageX + "px");
})
.on("mouseout", function() {
return tooltip.style("visibility", "hidden");
})
;
Shabrikov's code is near the very bottom of this item: circles, tool tips, mouse events

Cant get line in line graph to populate in D3.js

I'm trying to build a line graph in D3 but I'm not seeing any of my lines populate. Can someone look over my code and tell me what I'm doing wrong? When I inspect the line path in the console I see some NaN in the d path, I'm assuming this is the source of my problems however perhaps not
Here is my HTML
<label class="guideline">
Show Guideline & Curtain
<input type="checkbox" id="show_guideline" />
</label>
<svg></svg>
Here is my CSS
<style>
body {
font: 10px sans-serif;
margin: 0;
}
path.line {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
path.area {
fill: #e7e7e7;
}
.axis {
shape-rendering: crispEdges;
}
.x.axis line {
stroke: #fff;
}
.x.axis .minor {
stroke-opacity: .5;
}
.x.axis path {
display: none;
}
.y.axis line, .y.axis path {
fill: none;
stroke: #000;
}
.guideline {
margin-right: 100px;
float: right;
}
</style>
Here is my javascript
<script>
var margin = {top: 80, right: 80, bottom: 80, left: 80},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//var parse = d3.time.format("%b %Y").parse;
var parse = d3.time.format("%Y").parse;
// Scales and axes.
var x = d3.time.scale().range([0, width]),
y = d3.scale.linear().range([height, 0]),
xAxis = d3.svg.axis().scale(x).tickSize(-height).tickSubdivide(true),
yAxis = d3.svg.axis().scale(y).ticks(4).orient("right");
// An area generator, for the light fill.
var area = d3.svg.area()
.interpolate("monotone")
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.incidents); });
// A line generator, for the dark stroke.
var line = d3.svg.line()
.interpolate("monotone")
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.incidents); });
here is my csv doc
d3.csv("static/data/israel/incidents_linechart.csv", type,
function(error, data) {
// Filter to one symbol; the S&P 500.
var values = data.filter(function(d) {
return d.country == "Israel";;
});
var unitedStates = data.filter(function(d) {
return d.country == "unitedStates";
});
// Compute the minimum and maximum date, and the maximum price.
x.domain([values[0].date, values[values.length - 1].date]);
y.domain([0, d3.max(values, function(d) { return d.incidents;
})]).nice();
// Add an SVG element with the desired dimensions and margin.
var svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top +
")")
// Add the clip path.
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
// Add the x-axis.
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the y-axis.
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ",0)")
.call(yAxis);
var colors = d3.scale.category10();
entering my data values here
svg.selectAll('.line')
.data([values, unitedStates]) /// can add however many i want here
.enter()
.append('path')
.attr('class', 'line')
.style('stroke', function(d) {
return colors(Math.random() * 50);
})
.attr('clip-path', 'url(#clip)')
.attr('d', function(d) {
return line(d);
})
/* Add 'curtain' rectangle to hide entire graph */
var curtain = svg.append('rect')
.attr('x', -1 * width)
.attr('y', -1 * height)
.attr('height', height)
.attr('width', width)
.attr('class', 'curtain')
.attr('transform', 'rotate(180)')
.style('fill', '#ffffff')
/* Optionally add a guideline */
var guideline = svg.append('line')
.attr('stroke', '#333')
.attr('stroke-width', 0)
.attr('class', 'guide')
.attr('x1', 1)
.attr('y1', 1)
.attr('x2', 1)
.attr('y2', height)
/* Create a shared transition for anything we're animating */
var t = svg.transition()
.delay(750)
.duration(6000)
.ease('linear')
.each('end', function() {
d3.select('line.guide')
.transition()
.style('opacity', 0)
.remove()
});
t.select('rect.curtain')
.attr('width', 0);
t.select('line.guide')
.attr('transform', 'translate(' + width + ', 0)')
d3.select("#show_guideline").on("change", function(e) {
guideline.attr('stroke-width', this.checked ? 1 : 0);
curtain.attr("opacity", this.checked ? 0.75 : 1);
})
});
// Parse dates and numbers. We assume values are sorted by date.
function type(d) {
d.date = parse(d.date);
d.incidents = +d.incidents;
return d;
}
</script>
Here is some of my data:
any help would be immensely appreciated.
so when I console.log the data in the browser I get the following:
my incidents column is being repeated twice for every row, once correctly and once incorrectly yielding a NaN

d3js scroll visibility - series animation for bar chart

I am working on a d3 application and I am interested in taking the following jsfiddle -- and onload or on an action -- revoking an animation where the bar charts animate one by one.
So the first bar animates to its height, then the second and so forth. Also a reversal of the animation would be good too -- so maybe something that is invoked automatically on scroll visibility?
http://jsfiddle.net/pg886/201/
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<div
class="barchart"
data-role="barchart"
data-width=300
data-height=400
data-data="x"
data-configurations=""
>
</div>
<style>
.barchart{
/*width:100%;
border: 1px solid red;*/
}
.barchart svg{
width:100%;
/*border: 1px solid green;*/
}
.barchartg{
}
.barchart .axis path{
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.barchart .axis line {
fill: none;
stroke: none;
shape-rendering: crispEdges;
}
.barchart .x.axis path {
display: none;
}
.barchart .axis text{
fill: #005a70;
}
.barchart.dark .axis text{
fill: #ffffff;
}
.barchart.dark .axis path{
stroke: #ffffff;
}
.barchart .bar:hover {
fill: #e9168a;
}
</style>
<script>
$(document).ready(function() {
console.log("test")
var $this = $(".barchart");
var w = $this.data("width");
var h = $this.data("height");
var data = $this.data("data");
var data = [{
"label": "Apples",
"value": 100
},
{
"label": "Pears",
"value": 120
},
{
"label": "Bananas",
"value": 20
}];
var configurations = $this.data("configurations");
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#2b2d39", "#c12f39", "#f8dd2f", "#1b91dc"];
return colores_g[n % colores_g.length];
}
//asess the margin bottom for the chart based on the max char label
var charLabelCount = [];
data.map(function(d) {
var labelStr = d.label.toString();
charLabelCount.push(labelStr.length);
})
var maxChars = charLabelCount.reduce(function(a, b) {
return Math.max(a, b);
});
var bottomMarg = 60;
if(maxChars > 15){
bottomMarg = 170;
}
//bottom margin calculation
var margin = {top: 15, right: 20, bottom: bottomMarg, left: 40},
width = w - margin.left - margin.right,
height = h - margin.top - margin.bottom;
var x = d3.scaleBand()
.rangeRound([0, width]).padding(0.1);
var y = d3.scaleLinear()
.range([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var svg = d3.select($this[0])
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("viewBox", "0 0 "+w+" "+h)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "barchartg");
function sortBy(array,key){
var sorted = array.sort(function(a, b) {
return parseFloat(b[key]) - parseFloat(a[key]);
});
return sorted;
}
var sortedMax = 45;//45 as an initial value
//if there is a configuration file - it acts as an overide -- this is so there could be just one chart -- or a set of charts next to each other
if(configurations){
//if its a comparison chart -- use a max value that will be shared amongst a stack of sibling charts
if(configurations[0]["maxValue"]){
sortedMax = configurations[0]["maxValue"] + 5;//add 5 value buffer
}
}
else{
//if its a stand alone chart - adjust the max val by this chart's own values
sortedMax = sortBy(data, "value")[0]["value"] + 5;//add 5 value buffer
}
x.domain(data.map(function(d) {
return d.label;
}));
y.domain([0, sortedMax]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.selectAll(".x.axis text")
.attr("transform", "rotate(-60) translate(-5,-5)")
.style("text-anchor", "end");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("fill", function(d, i) {
return colores_google(i);
})
.attr("x", function(d) {
return x(d.label);
})
.attr("width", x.bandwidth())
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
});
});
</script>
Use .transition() to trigger animation to each rect.
But you have to start each rect with a height of zero and y of zero as well, so that you have something to work with in the animation.
$(document).ready(function() {
console.log("test")
var $this = $(".barchart");
var w = $this.data("width");
var h = $this.data("height");
var data = $this.data("data");
var data = [{
"label": "Apples",
"value": 100
},
{
"label": "Pears",
"value": 120
},
{
"label": "Bananas",
"value": 20
}
];
var configurations = $this.data("configurations");
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#2b2d39", "#c12f39", "#f8dd2f", "#1b91dc"];
return colores_g[n % colores_g.length];
}
//asess the margin bottom for the chart based on the max char label
var charLabelCount = [];
data.map(function(d) {
var labelStr = d.label.toString();
charLabelCount.push(labelStr.length);
})
var maxChars = charLabelCount.reduce(function(a, b) {
return Math.max(a, b);
});
var bottomMarg = 60;
if (maxChars > 15) {
bottomMarg = 170;
}
//bottom margin calculation
var margin = {
top: 15,
right: 20,
bottom: bottomMarg,
left: 40
},
width = w - margin.left - margin.right,
height = h - margin.top - margin.bottom;
var x = d3.scaleBand()
.rangeRound([0, width]).padding(0.1);
var y = d3.scaleLinear()
.range([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var svg = d3.select($this[0])
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("viewBox", "0 0 " + w + " " + h)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "barchartg");
function sortBy(array, key) {
var sorted = array.sort(function(a, b) {
return parseFloat(b[key]) - parseFloat(a[key]);
});
return sorted;
}
var sortedMax = 45;
if (configurations) {
if (configurations[0]["maxValue"]) {
sortedMax = configurations[0]["maxValue"] + 5;
}
} else {
sortedMax = sortBy(data, "value")[0]["value"] + 5;
}
x.domain(data.map(function(d) {
return d.label;
}));
y.domain([0, sortedMax]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.selectAll(".x.axis text")
.attr("transform", "rotate(-60) translate(-5,-5)")
.style("text-anchor", "end");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("fill", function(d, i) {
return colores_google(i);
})
.attr("x", function(d) {
return x(d.label);
})
.attr("width", x.bandwidth())
.attr("y", function(d) {
return y(0);
})
.attr("height", function(d) {
return 0
});
d3.selectAll("rect").transition()
.duration(500)
.delay(function(d,i){ return 500*i;})
.attr("height",function(d){ return height - y(d.value);})
.attr("y",function(d){return y(d.value);});
setTimeout(function(){
d3.selectAll("rect").transition()
.duration(500)
.delay(function(d,i){ return 600*(3-i);})
.attr("height",function(d){ return 0;})
.attr("y",function(d){return y(0);});
},2000);
});
.barchart {
/*width:100%;
border: 1px solid red;*/
}
.barchart svg {
width: 100%;
/*border: 1px solid green;*/
}
.barchartg {}
.barchart .axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.barchart .axis line {
fill: none;
stroke: none;
shape-rendering: crispEdges;
}
.barchart .x.axis path {
display: none;
}
.barchart .axis text {
fill: #005a70;
}
.barchart.dark .axis text {
fill: #ffffff;
}
.barchart.dark .axis path {
stroke: #ffffff;
}
.barchart .bar:hover {
fill: #e9168a;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="barchart" data-role="barchart" data-width=300 data-height=400 data-data="x" data-configurations="">
</div>
you need to add transition for each bar while generating and delay the transion according to your wish.the code is as.
.transition()
.delay(function (d,i){ return i * 300;})
.duration(250)
the updated code with your code block is as follows
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.transition()
.delay(function (d,i){ return i * 300;})
.duration(250)
.attr("fill", function(d, i) {
return colores_google(i);
})
.attr("x", function(d) {
return x(d.label);
})
.attr("width", x.bandwidth())
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
})

D3 v4 Parallel Coordinate Plot Brush Selection

The parallel coordinate plot we are using and the data for the plot can be found here. This parallel coordinate plot does not work with version 4 of d3. We have made changes based on the API changes from v3 to v4. I think the main issue is in the brush function shown below.
function brush() {
let actives = dimensions.filter(function (p) {
return d3.brushSelection(y[p]) !== null;
});
console.log(actives);
let extents = actives.map(function (p) {
return d3.brushSelection(y[p]);
});
foreground.style("display", function (d) {
return actives.every(function (p, i) {
return extents[i][0] <= d[p] && d[p] <= extents[i][1];
}) ? null : "none";
});
}
The log shows "Array []" for actives. Currently we set each dimensions brush extent to be [[-8,0],[8,height]], which may be an issue as well. The full code is provided below.
<!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;
}
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
let margin = {top: 30, right: 10, bottom: 10, left: 10},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
let x = d3.scalePoint().range([0, width]).padding(1),
y = {},
dragging = {};
let line = d3.line(),
axis = d3.axisLeft(), //Argument for axisLeft? Compare to code on original plot
background,
foreground;
let 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 + ")");
d3.csv("cars.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) {
return d !== "name" && (y[d] = d3.scaleLinear()
.domain(d3.extent(cars, function (p) {
return +p[d];
}))
.range([height, 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.
let 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(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.brushY().extent([[-8,0],[8,height]]).on("start", brushstart).on("brush", brush));
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
});
function position(d) {
let 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() {
//return !y[p].brush.empty was the original return value.
let actives = dimensions.filter(function (p) {
return d3.brushSelection(y[p]) !== null;
});
console.log(actives);
let extents = actives.map(function (p) {
return d3.brushSelection(y[p]);
});
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>
If anyone is familiar with d3 and could offer any guidance it would be greatly appreciated. We also tried using d3.event.selection and y[p].brush.selection in the brush function.
I stumbled upon the exact same issue but managed to resolve it after below changes.
Add brush for each axis this way:
y[d] = d3.scaleLinear().domain(d3.extent(data, function(p) {
return +p[d];
})).range([height, 0]);
y[d].brush = d3.brushY()
.extent([[-8, y[d].range()[1]], [8, y[d].range()[0]]])
.on('brush', brush);
Subsequently, give above as the brush callback when adding the brush group:
g.append('g')
.attr('class', 'brush')
.each(function(d) {
d3.select(this).call(y[d].brush);
})
.selectAll('rect')
.attr('x', -8)
.attr('width', 16);
Finally, change the brush handler to be:
function brush() {
const actives = [];
// filter brushed extents
svg.selectAll('.brush')
.filter(function(d): any {
return d3.brushSelection(this as any);
})
.each(function(d) {
actives.push({
dimension: d,
extent: d3.brushSelection(this as any)
});
});
// set un-brushed foreground line disappear
foreground.style('display', function(d) {
return actives.every(function(active) {
const dim = active.dimension;
return active.extent[0] <= y[dim](d[dim]) && y[dim](d[dim]) <= active.extent[1];
}) ? null : 'none';
});
}
If above is confusing, see this standalone example that helped me with correctly brushing on parallel coordinates with d3 v4 : https://gist.github.com/kotomiDu/d1fd0fe9397db41f5f8ce1bfb92ad20d

d3 parallel coordinates - colour coding

Hi I am using d3 for plotting a parallel coordinate chart. I want to colour the lines based on a specific categorical attribute.
Even though I have defined a colour palette and specified that range as well, the colour doesn't seem to plot.
The line colours do not change. What am I doing wrong?
Here's the 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;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.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("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 color = d3.scale.ordinal()
.domain(['white','asian/pacific islander','african-american','hispanic','other'])
.range(['blue','green','black','yellow', 'gray']);
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)
.attr("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);
});
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>

Categories

Resources