"NaN" when zooming and selecting by id - javascript

I'm probably doing something wrong but the following fiddle is displaying some really strange behavior:
https://jsfiddle.net/pkerpedjiev/42w01t3e/8/
Before I explain it, here's the code:
function skiAreaElevationsPlot() {
var width = 550;
var height = 400;
var margin = {
'top': 30,
'left': 30,
'bottom': 30,
'right': 40
};
function chart(selection) {
selection.each(function(data) {
// Select the svg element, if it exists.
var svg = d3.select(this).selectAll("svg").data([data]);
// Otherwise, create the skeletal chart.
var gEnter = svg.enter().append("svg").append("g");
svg.attr('width', width)
.attr('height', height);
var zoom = d3.behavior.zoom()
.on("zoom", draw);
data = Object.keys(data).map(function(key) {
return data[key];
}).sort(function(a, b) {
return b.max_elev - a.max_elev;
});
svg.insert("rect", "g")
.attr("class", "pane")
.attr("width", width)
.attr("height", height)
.attr('pointer-events', 'all')
.call(zoom);
var yScale = d3.scale.linear()
.domain([0, d3.max(data.map(function(d) {
return d.max_elev;
}))])
.range([height - margin.top - margin.bottom, 0]);
var xScale = d3.scale.linear()
.domain([0, data.length])
.range([0, width - margin.left - margin.right]);
var widthScale = d3.scale.linear()
.domain(d3.extent(data.map(function(d) {
return d.area;
})))
.range([10, 30]);
zoom.x(xScale).scaleExtent([1, data.length / 30]);
var gMain = gEnter.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
gMain.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width - margin.left - margin.right)
.attr("height", height - margin.top - margin.bottom);
function skiAreaMouseover(d) {
gMain.select('#n-' + d.uid)
.attr('visibility', 'visible');
}
function skiAreaMouseout(d) {
gMain.select('#n-' + d.uid)
.attr('visibility', 'visible');
}
// the rectangle showing each rect
gMain.selectAll('.resort-rect')
.data(data)
.enter()
.append('rect')
.classed('resort-rect', true)
.attr("clip-path", "url(#clip)")
.attr('id', function(d) {
return 'n-' + d.uid;
})
.on('mouseover', skiAreaMouseover)
.on('mouseout', skiAreaMouseout);
var gYAxis = svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + (width - margin.right) + "," + margin.top + ")");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("right")
.tickSize(-(width - margin.left - margin.right))
.tickPadding(6);
gYAxis.call(yAxis);
draw();
function draw() {
function scaledX(d, i) {
console.log('xd', d);
return xScale(i);
}
function rectWidth(d, i) {
return widthScale(d.area);
}
gMain.selectAll('.resort-rect')
.attr('x', scaledX)
.attr('y', function(d) {
console.log('d', d);
return yScale(d.max_elev);
})
.attr('width', 20)
.attr('height', function(d) {
console.log('d:', d)
return yScale(d.min_elev) - yScale(d.max_elev);
})
.classed('resort-rect', true);
}
});
}
chart.width = function(_) {
if (!arguments.length) return width;
width = _;
return chart;
};
chart.height = function(_) {
if (!arguments.length) return height;
height = _;
return chart;
};
return chart;
}
var elevationsPlot = skiAreaElevationsPlot()
.width(550)
.height(300);
data = [{
"min_elev": 46,
"max_elev": 54,
"uid": "9809641c-ab03-4dec-8d51-d387c7e4f114",
"num_lifts": 1,
"area": "0.00"
}, {
"min_elev": 1354,
"max_elev": 1475,
"uid": "93eb6ade-8d78-4923-9806-c8522578843f",
"num_lifts": 1,
"area": "0.00"
}, {
"min_elev": 2067,
"max_elev": 2067,
"uid": "214fdca9-ae62-473b-b463-0ba3c5755476",
"num_lifts": 1,
"area": "0.00"
}];
d3.select('#ski-area-elevations')
.datum(data)
.call(elevationsPlot)
So, when the page is first loaded, a rectangle will be visible in the middle. If you try scrolling on the graph, the console.log statements in the draw function will produce output. Notice that the xd: and d: statements all consist of just one object from the data set.
Now, if you mouseover the rectangle and try zooming again (using the scroll wheel). A bunch of NaN errors will be displayed. Now some of the d: and xd: statements will now print lists of objects.
Why is this happening? The underlying bound data never changed.
What puzzles me is that if these statements:
gMain.select('#n-' + d.uid)
Are changed to:
gMain.selectAll('#n-' + d.uid)
The fiddle behaves properly. Why does this make a difference? Is this a bug, or am I missing something?
For googleability, here's the error I get:
Error: Invalid value for <rect> attribute y="NaN"

The simple solution is to replace gMain.select/gMain.selectAll in the mouse event routines with d3.select(this)
The complicated solution seems to be that a single select binds a parents data to whatever is selected if you're acting on an existing selection. gMain is an existing selection and has the 3 data values as an array bound to it - console.log (gMain.datum()) to see - so when you do a gMain.select("#oneoftherects") you replace the single object in #oneoftherects with that array, thus knackering the x,y,width,height etc routines that expect one object. (Using d3.select doesn't do the same as d3 isn't a selection)
http://bost.ocks.org/mike/selection/#non-grouping

Related

Recreate D3 Multi-Line Chart on Resize (responsive)

In order to have a responsive D3 multi-line chart I have added a resize function but it doesn't seem to work although the function gets called:
var data = [{
Date: "2016-10-10",
ValueOne: 1,
ValueTwo: 0
}, {
Date: "2016-10-17",
ValueOne: 23,
ValueTwo: 2
}, {
Date: "2016-10-24",
ValueOne: 32,
ValueTwo: 17
}, {
Date: "2016-10-31",
ValueOne: 57,
ValueTwo: 40
}, {
Date: "2016-11-07",
ValueOne: 74,
ValueTwo: 56
}];
var margin = {top: 10, right: 50, bottom: 100, left: 50},
// Set default width and height
widther = (window.innerWidth),
width = widther - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// Determine current size, which determines vars
function set_vars() {
var width = window.innerWidth - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
}
function drawGraphic() {
var svg = d3.select('#charts')
.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 + ")");
//Parses date for correct time format
var formatTime = d3.timeFormat("%Y-%m-%d");
data.forEach(function(d) {
d.Date = new Date(d.Date)
});
var valueOneData = data.map(function(d) {
return {
date: d.Date,
value: d.ValueOne
}
});
var valueTwoData = data.map(function(d) {
return {
date: d.Date,
value: d.ValueTwo
}
});
var xScale = d3.scaleTime()
.range([0, width])
.domain(d3.extent(data, function(d) {
return d.Date
}));
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function(d) {
return d.ValueOne
}) * 1.05]);
var lineGenerator = d3.line()
.x(function(d) {
return xScale(d.date)
})
.y(function(d) {
return yScale(d.value)
});
var gX = svg.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(xScale).tickFormat(function(d) {
return formatTime(d)
}).tickValues(data.map(function(d) {
return d.Date
})))
.selectAll("text")
.style("text-anchor", "end")
.attr("transform", "rotate(-65)")
.attr("y", 4)
.attr("x", -10)
.attr("dy", ".35em");
var gY = svg.append("g")
.call(d3.axisLeft(yScale));
var valueOneLine = svg.append("path")
.datum(valueOneData)
.attr("d", lineGenerator)
.style("fill", "none")
.style("stroke", "#124");
var valueTwoLine = svg.append("path")
.datum(valueTwoData)
.attr("d", lineGenerator)
.style("fill", "none")
.style("stroke", "#c7003b");
//RESPONSIVENESS ATTEMPT NO1
d3.select(window).on("resize", resized);
}
//Resize function
function resized() {
d3.select("svg").remove();
set_vars();
drawGraphic();
console.log("FUNCTION IS BEING CALLED")
}
set_vars();
drawGraphic();
//RESPONSIVENESS ATTEMPT NO2
window.addEventListener("resize", function(){ d3.select("svg").remove(); set_vars(); drawGraphic(); });
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="charts"></div>
In the snippet, I have tried two ways to do that. None of them make the chart recreate from scratch. The same issue applied in this jsfiddle.
The issue was with the parsing of the data every time the window was resized.
As the date within the data is parsed the first time, calling parseDate(d.date) will fail on every other call as it's already been parsed to a valid date. Do you get it?
Hence, moving the parsing code so that it's executed just once:
// parse data just once
data.forEach(function(d) {
d.date = parseDate(d.date);
d.value = +d.value;
});
Fiddle link: https://jsfiddle.net/a5rqt0L1/
Suggestion: I feel this isn't the right way to make a responsive chart i.e. removing SVG and re-appending to the body with all the configuration done multiple times. Here's how I'd do it:
Parse the data, append svg with initial height and width, append X, Y axes just once but move drawBars (to draw the actual bars) to a separate function that will use d3's own enter, exit and update selection logic.
On window resize, just change the SVG's height and width, re-render the axes by .call(xAxis)... and just call the drawBars function.
Hope this helps.

Update selection not working

I'm new to D3 and I am trying to display a simple d3 bar chart that changes which data attribute it is visualizing based on a dropdown menu - the data remains the same and I am displaying the same labels (x-axis) with each dropdown selection, just the labels should transition/change their ordering and the bar values should transition/change based on which attribute they are showing.
When the dropdown menu changes however, the transition (update) selection isn't getting called - it is only called when the chart loads for the first time. Therefore, based on the code, the y-Axis is changing its numerical values, but the heights always remain the same as they are initiated so the bars don't animate at all despite the labels changing.
updateChart(menuSelection) { // called when dropdown selection changes, and initially upon page load with default menuSelection
// I sort the data by the attribute of the dropdown item selected
this.myData.sort(function(a,b){
if(menuSelection == 1) return b.count - a.count;
else if(menuSelection == 2) return b.positiveCount - a.positiveCount;
else return b.negativeCount - a.negativeCount;
});
var m = this.myData.length;
var svg = d3.select(".chart"),
margin = {top: 40, right: 25, bottom: 40, left: 25},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var g = svg.select("g.rectGroup").attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("class", "rectGroup");
if(g.empty()) {
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("class", "rectGroup");
}
var x = d3.scaleBand()
.domain(myData.map(function(d) { return d._id; }))
.range([0, width])
.padding(0.08);
var yMax = d3.max(this.myData, function(d) {
if(this.menuSelection == 1) return d.count;
else if(this.menuSelection == 2) return d.positiveCount;
else return d.negativeCount;
});
var y = d3.scaleLinear()
.domain([0, yMax])
.range([height, 0]);
var xAxis = d3.axisBottom()
.scale(x);
var yAxis = d3.axisLeft()
.scale(y);
var yz = getHeights(m, menuSelection, this.myData); // ARRAY OF HEIGHTS DEPENDING ON MENU DROP DOWN SELECTION
var bars = g.selectAll(".bar")
.data(this.myData, function(d) {
return d._id; // KEY IS ITEM NAME FOR OBJECT CONSTANCY; ALL ITEMS ARE DISPLAYED REGARDLESS OF ATTRIBUTE SELECTED, BUT ORDER/VALUES CHANGE FOR EACH ITEM
})
.enter().append("rect")
.attr("class", "bar")
.attr("height", 0)
.attr("y", height);
bars.transition().duration(700)
.attr("x", function(d) { return x(d._id); })
.attr("width", x.bandwidth())
.attr("y", function(d, i) { return y(yz[i])})
.attr("height", function(d, i) {
return height - y(yz[i])
});
bars.exit().remove();
svg.selectAll(".axis").remove();
var height_to_use = +svg.attr("height") - margin.bottom
var xAxis_g = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + margin.left + "," + height_to_use + ")")
.call(xAxis)
.selectAll(".tick text")
.call(wrap, x.bandwidth());
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(yAxis);
function getHeights(m, menuSelection, data) {
var values = [];
for (var i = 0; i < m; ++i) {
if(menuSelection == 1) {
values[i] = data[i].count;
} else if(menuSelection == 2) {
values[i] = data[i].positiveCount;
} else {
values[i] = data[i].negativeCount;
}
}
return values;
}
}
Actually, you don't have an update selection in your code.
For having an update selection, break up your "enter" selection, like this:
//this is the update selection
var bars = g.selectAll(".bar")
.data(data, function(d) {
return d._id;
});
//and the remainder is the enter selection
bars.enter().append("rect")
.attr("class", "bar")
.attr("height", 0)
.attr("y", height);
Also, it's worth mentioning that, since you don't have an update selection in your code, this...
bars.exit().remove();
... is useless.

Trying to transfer Tributary D3.js example into JSFiddle

I'm rather a beginner at both JS and D3.js; my JSFiddle is here.
JSHint shows no errors, so I think I'm doing the D3 wrong.
I'm trying to do the same thing as in these questions, adapting a Tributary example to run outside of that platform: "Exporting" a Tributary example that makes use of the tributary object - d3.js and Getting horizontal stack bar example to display using d3.js (I'm even adapting the same code as the latter) Unfortunately, there is no corrected JSFiddle or other example in either answer
Here's my code:
var VanillaRunOnDomReady = function() {
var margins = {
top: 12,
left: 48,
right: 24,
bottom: 24
};
var data = [
{"key":"Nonviolent", "cat1":0.69, "cat2":0.21, "cat3":0.10},
{"key":"Violent", "cat1":0.53, "cat2":0.29, "cat3":0.18}
];
var catnames = {cat1: "No mental illness",
cat2: "Mild mental illness",
cat3: "Moderate or severe mental illness"};
var svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')');
var n = 3, // number of layers
m = data.length, // number of samples per layer
stack = d3.layout.stack(),
labels = data.map(function(d) {return d.key;}),
//go through each layer (cat1, cat2 etc, that's the range(n) part)
//then go through each object in data and pull out that objects's catulation data
//and put it into an array where x is the index and y is the number
layers = stack(d3.range(n).map(function(d) {
var a = [];
for (var i = 0; i < m; ++i) {
a[i] = {x: i, y: data[i]['cat' + (d+1)]};
}
return a;
})),
//the largest single layer
yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),
//the largest stack
yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
var margin = {top: 40, right: 10, bottom: 20, left: 50},
width = 677 - margin.left - margin.right,
height = 533 - margin.top - margin.bottom;
var y = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([2, height], 0.08);
var x = d3.scale.linear()
.domain([0, yStackMax])
.range([0, width]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return color(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { return y(d.x); })
.attr("x", function(d) { return x(d.y0); })
.attr("height", y.rangeBand())
.attr("width", function(d) { return x(d.y); });
var yAxis = d3.svg.axis()
.scale(y)
.tickSize(1)
.tickPadding(6)
.tickValues(labels)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
};
I think you are just missing a (); at the end of your function and before the semicolon. That would make it self executing. I forked your fiddle.
var VanillaRunOnDomReady = function() {
var margins = {
top: 12,
left: 48,
right: 24,
bottom: 24
};
var data = [
{"key":"Nonviolent", "cat1":0.69, "cat2":0.21, "cat3":0.10},
{"key":"Violent", "cat1":0.53, "cat2":0.29, "cat3":0.18}
];
var catnames = {cat1: "No mental illness",
cat2: "Mild mental illness",
cat3: "Moderate or severe mental illness"};
var svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')');
var n = 3, // number of layers
m = data.length, // number of samples per layer
stack = d3.layout.stack(),
labels = data.map(function(d) {return d.key;}),
//go through each layer (cat1, cat2 etc, that's the range(n) part)
//then go through each object in data and pull out that objects's catulation data
//and put it into an array where x is the index and y is the number
layers = stack(d3.range(n).map(function(d) {
var a = [];
for (var i = 0; i < m; ++i) {
a[i] = {x: i, y: data[i]['cat' + (d+1)]};
}
return a;
})),
//the largest single layer
yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),
//the largest stack
yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
var margin = {top: 40, right: 10, bottom: 20, left: 50},
width = 677 - margin.left - margin.right,
height = 533 - margin.top - margin.bottom;
var y = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([2, height], 0.08);
var x = d3.scale.linear()
.domain([0, yStackMax])
.range([0, width]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
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 + ")");
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return color(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { return y(d.x); })
.attr("x", function(d) { return x(d.y0); })
.attr("height", y.rangeBand())
.attr("width", function(d) { return x(d.y); });
var yAxis = d3.svg.axis()
.scale(y)
.tickSize(1)
.tickPadding(6)
.tickValues(labels)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
}();

D3 bar chart not parsing csv

I'm trying to build a basic, responsive bar chart with data loaded from a CSV. My chart isn't reading the CSV and gives me NaNs. Here's a screenshot of what my chart looks like. Here is my current code:
<script type="text/javascript">
var url = "data/data.csv",
margin = {top: 30, right: 10, bottom: 30, left: 10},
width = parseInt(d3.select('#chart').style('width'), 10),
width = width - margin.left - margin.right,
height = 200, // placeholder
barHeight = 20,
spacing = 5,
total = d3.format('n');
// scales and axes
var x = d3.scale.linear()
.range([0, width])
.domain([0, 4000]); // hard-coding this because I know the data
var y = d3.scale.ordinal();
var xAxis = d3.svg.axis()
.scale(x)
.ticks(3);
// create the chart
var chart = d3.select('#chart').append('svg')
.style('width', (width + margin.left + margin.right) + 'px')
.append('g')
.attr('transform', 'translate(' + [margin.left, margin.top] + ')');
d3.csv(url).row(function(d) {
d.total = +d.total;
d.name = +d.name;
return d;
}).get(function(err, data) {
// sort
data = _.sortBy(data, 'total').reverse();
// set y domain
y.domain(d3.range(data.length))
.rangeBands([0, data.length * barHeight]);
// set height based on data
height = y.rangeExtent()[1];
d3.select(chart.node().parentNode)
.style('height', (height + margin.top + margin.bottom) + 'px')
// render the chart
// add top and bottom axes
chart.append('g')
.attr('class', 'x axis top')
.call(xAxis.orient('top'));
chart.append('g')
.attr('class', 'x axis bottom')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis.orient('bottom'));
var bars = chart.selectAll('.bar')
.data(data)
.enter().append('g')
.attr('class', 'bar')
.attr('transform', function(d, i) { return 'translate(0,' + y(i) + ')'; });
bars.append('rect')
.attr('class', 'background')
.attr('height', y.rangeBand())
.attr('width', width);
bars.append('rect')
.attr('class', 'total')
.attr('height', y.rangeBand())
.attr('width', function(d) { return x(d.total); })
bars.append('text')
.text(function(d) { return d.name; })
.attr('class', 'name')
.attr('y', y.rangeBand() - 5)
.attr('x', spacing);
bars.append('line')
.attr('class', 'median')
.attr('x1', x(median))
.attr('x2', x(median))
.attr('y1', 1)
.attr('y2', y.rangeBand() - 1);
});
// resize
d3.select(window).on('resize', resize);
function resize() {
// update width
width = parseInt(d3.select('#chart').style('width'), 10);
width = width - margin.left - margin.right;
// resize the chart
x.range([0, width]);
d3.select(chart.node().parentNode)
.style('height', (y.rangeExtent()[1] + margin.top + margin.bottom) + 'px')
.style('width', (width + margin.left + margin.right) + 'px');
chart.selectAll('rect.background')
.attr('width', width);
chart.selectAll('rect.total')
.attr('width', function(d) { return x(d.total); });
// update axes
chart.select('.x.axis.top').call(xAxis.orient('top'));
chart.select('.x.axis.bottom').call(xAxis.orient('bottom'));
}
// highlight code blocks
hljs.initHighlighting();
</script>
Here's the data:
name, total
Brian, 1514
Frankie, 1439
Jeffery, 1615
Jerry, 685
Kenneth, 3233
Michael, 116
Roy, 817
Timothy, 2184
I can see a couple of issues with your code and data:
For the issues with the data, you can either clean up the rows by removing the spaces, eg:
name,total
Brian,1514
Frankie,1439
Jeffery,1615
Jerry,685
Kenneth,3233
Michael,116
Roy,817
Timothy,2184
Or you can reference them in your code using the [] notation, eg:
d[' total'] = +d[' total'];
d3 is adding the leading space to the attribute name for each record. When you reference the attributes as d.total, it's returning undefined and undefined coerced to a number is NaN.
Eg. data[0]['total'] is undefined but data[0][' total'] has "1514" as you'd expect. Removing the spaces from the data is the easiest way of dealing with this.
In your code, you're doing this:
d.name = +d.name;
This will translate the name value into NaN, as it's a text string beginning with an alpha character, so javascript doesn't know how to coerce it to a number.
You're also referencing a variable named median which isn't declared in your code.
I've created a jsfiddle to help: http://jsfiddle.net/BenLyall/eh1r6j2e/12/
Note: I've changed the d3.csv... line to work with an element in the DOM to store the data.
Why are you making name into Number
below code
d3.csv(url).row(function(d) {
d.total = +d.total;
d.name = +d.name;
return d;
})
remove conversion of name.Okay

Scaling a D3.js Bullet Chart keeps messing up

I am new to D3.js and I am drawing a bullet chart for a website I am working on. I took the code from here as a starting point:
http://bl.ocks.org/jugglinmike/6004102
I have a chart drawn, and for a basic case, I do not need to worry about switching the data shown. I really need to, however, have the entire chart and all of its elements scale with the window resize.
Right now, I have two files, bullet.js and draw_bullet.js. This is the code for draw_bullet.js:
var margin = {top: 5, right: 40, bottom: 20, left: 120},
width = ($(window).width() * .3) - margin.left - margin.right,
height = 50 - margin.top - margin.bottom;
var chart = d3.bullet()
.width(width)
.height(height);
function fillChart() {
d3.json("/static/response.json", function(error, data) {
var svg = d3.select("#zone1").selectAll("svg")
.data(data)
.enter().append("svg")
.attr("class", "bullet")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(chart1);
var title = svg.append("g")
.style("text-anchor", "end")
.attr("transform", "translate(-6," + height / 2 + ")");
title.append("text")
.attr("class", "title")
.text(function(d) { return d.title; });
title.append("text")
.attr("class", "subtitle")
.attr("dy", "1em")
.text(function(d) { return d.subtitle; });
});
}
And my code for bullet.js:
d3.bullet = function() {
var orient = "left", // TODO top & bottom
reverse = false,
duration = 0,
ranges = bulletRanges,
measures = bulletMeasures,
width = parseInt(d3.select("#zone1").style("width"), 10),
height = 30,
tickFormat = null;
// For each small multiple…
function bullet(g) {
g.each(function(d, i) {
var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
measurez = measures.call(this, d, i).slice().sort(d3.descending),
g = d3.select(this);
// Compute the new x-scale.
var x1 = d3.scale.linear()
.domain([0, Math.max(rangez[0], measurez[0])])
.range(reverse ? [width, 0] : [0, width]);
// Retrieve the old x-scale, if this is an update.
var x0 = this.__chart__ || d3.scale.linear()
.domain([0, Infinity])
.range(x1.range());
// Stash the new scale.
this.__chart__ = x1;
// Derive width-scales from the x-scales.
var w0 = bulletWidth(x0),
w1 = bulletWidth(x1);
// Update the range rects.
var range = g.selectAll("rect.range")
.data(rangez);
range.enter().append("rect")
.attr("class", function(d, i) { return "range s" + i; })
.attr("width", w0)
.attr("height", height)
.attr("x", reverse ? x0 : 0)
.transition()
.duration(duration)
.attr("width", w1)
.attr("x", reverse ? x1 : 0);
range.transition()
.duration(duration)
.attr("x", reverse ? x1 : 0)
.attr("width", w1)
.attr("height", height);
// Update the measure rects.
var measure = g.selectAll("rect.measure")
.data(measurez);
measure.enter().append("rect")
.attr("class", function(d, i) { return "measure s" + i; })
.attr("width", w0)
.attr("height", height / 3)
.attr("x", reverse ? x0 : 0)
.attr("y", height / 3)
.transition()
.duration(duration)
.attr("width", w1)
.attr("x", reverse ? x1 : 0);
measure.transition()
.duration(duration)
.attr("width", w1)
.attr("height", height / 3)
.attr("x", reverse ? x1 : 0)
.attr("y", height / 3);
// Compute the tick format.
var format = tickFormat || x1.tickFormat(8);
// Update the tick groups.
var tick = g.selectAll("g.tick")
.data(x1.ticks(8), function(d) {
return this.textContent || format(d);
});
// Initialize the ticks with the old scale, x0.
var tickEnter = tick.enter().append("g")
.attr("class", "tick")
.attr("transform", bulletTranslate(x0))
.style("opacity", 1e-6);
tickEnter.append("line")
.attr("y1", height)
.attr("y2", height * 7 / 6);
tickEnter.append("text")
.attr("text-anchor", "middle")
.attr("dy", "1em")
.attr("y", height * 7 / 6)
.text(format);
// Transition the entering ticks to the new scale, x1.
tickEnter.transition()
.duration(duration)
.attr("transform", bulletTranslate(x1))
.style("opacity", 1);
// Transition the updating ticks to the new scale, x1.
var tickUpdate = tick.transition()
.duration(duration)
.attr("transform", bulletTranslate(x1))
.style("opacity", 1);
tickUpdate.select("line")
.attr("y1", height)
.attr("y2", height * 7 / 6);
tickUpdate.select("text")
.attr("y", height * 7 / 6);
// Transition the exiting ticks to the new scale, x1.
tick.exit().transition()
.duration(duration)
.attr("transform", bulletTranslate(x1))
.style("opacity", 1e-6)
.remove();
});
d3.timer.flush();
}
// left, right, top, bottom
bullet.orient = function(x) {
if (!arguments.length) return orient;
orient = x;
reverse = orient == "right" || orient == "bottom";
return bullet;
};
// ranges (bad, satisfactory, good)
bullet.ranges = function(x) {
if (!arguments.length) return ranges;
ranges = x;
return bullet;
};
// measures (actual, forecast)
bullet.measures = function(x) {
if (!arguments.length) return measures;
measures = x;
return bullet;
};
bullet.width = function(x) {
if (!arguments.length) return width;
width = x;
return bullet;
};
bullet.height = function(x) {
if (!arguments.length) return height;
height = x;
return bullet;
};
bullet.tickFormat = function(x) {
if (!arguments.length) return tickFormat;
tickFormat = x;
return bullet;
};
bullet.duration = function(x) {
if (!arguments.length) return duration;
duration = x;
return bullet;
};
return bullet;
};
function bulletRanges(d) {
return d.ranges;
}
function bulletMeasures(d) {
return d.measures;
}
function bulletTranslate(x) {
return function(d) {
return "translate(" + x(d) + ",0)";
};
}
function bulletWidth(x) {
var x0 = x(0);
return function(d) {
return Math.abs(x(d) - x0);
};
}
})();
I am using jQuery and I know I have to embed a function in $(window).resize(), however I have tried many different things and none of them seem to adjust correctly. I try to set chart.width() to my new value in the function, and later when I call the width of the chart, it shows it as being my new value, but doesn't adjust its view on the screen. Is it necessary to redraw the entire chart and all its elements to resize? And then, also, when I tried to rescale the range for the chart, I found it very difficult to do so because the range variable is embedded in the anonymous function inside of bullet.js. Any help you can provide in pointing me in the right direction would be awesome. I tried using this tutorial, but it didn't seem to apply to my situation too much because they are different types of charts.
http://eyeseast.github.io/visible-data/2013/08/28/responsive-charts-with-d3/
Thanks!

Categories