Related
I'm trying to make my map look this way
Unfortunately, my code looks this way, and I don't understand why my text nodes are so gigantic not the way I want it
this is the code that I have going or check my fiddle
This code specifically doesn't seem to produce human readable labels
grp
.append("text")
.attr("fill", "#000")
.style("text-anchor", "middle")
.attr("font-family", "Verdana")
.attr("x", 0)
.attr("y", 0)
.attr("font-size", "10")
.text(function (d, i) {
return name;
});
Here's my full code:
var width = 500,
height = 275,
centered;
var projection = d3.geo
.conicConformal()
.rotate([103, 0])
.center([0, 63])
.parallels([49, 77])
.scale(500)
.translate([width / 2.5, height / 2])
.precision(0.1);
var path = d3.geo.path().projection(projection);
var svg = d3
.select("#map-selector-app")
.append("svg")
.attr("viewBox", `0 0 ${width} ${height}`);
// .attr("width", width)
// .attr("height", height);
svg
.append("rect")
.attr("class", "background-svg-map")
.attr("width", width)
.attr("height", height)
.on("click", clicked);
var g = svg.append("g");
var json = null;
var subregions = {
Western: { centroid: null },
Prairies: { centroid: null },
"Northern Territories": { centroid: null },
Ontario: { centroid: null },
Québec: { centroid: null },
Atlantic: { centroid: null },
};
d3.json(
"https://gist.githubusercontent.com/KatFishSnake/7f3dc88b0a2fa0e8c806111f983dfa60/raw/7fff9e40932feb6c0181b8f3f983edbdc80bf748/canadaprovtopo.json",
function (error, canada) {
if (error) throw error;
json = topojson.feature(canada, canada.objects.canadaprov);
g.append("g")
.attr("id", "provinces")
.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.on("click", clicked);
g.append("g")
.attr("id", "province-borders")
.append("path")
.datum(
topojson.mesh(canada, canada.objects.canadaprov, function (
a,
b
) {
return a !== b;
})
)
.attr("id", "province-borders-path")
.attr("d", path);
// g.select("g")
// .selectAll("path")
// .each(function (d, i) {
// var centroid = path.centroid(d);
// });
Object.keys(subregions).forEach((rkey) => {
var p = "";
json.features.forEach(function (f, i) {
if (f.properties.subregion === rkey) {
p += path(f);
}
});
var tmp = svg.append("path").attr("d", p);
subregions[rkey].centroid = getCentroid(tmp.node());
subregions[rkey].name = rkey;
tmp.remove();
});
Object.values(subregions).forEach(({ centroid, name }) => {
var w = 80;
var h = 30;
var grp = g
.append("svg")
// .attr("width", w)
// .attr("height", h)
.attr("viewBox", `0 0 ${w} ${h}`)
.attr("x", centroid[0] - w / 2)
.attr("y", centroid[1] - h / 2);
// grp
// .append("rect")
// .attr("width", 80)
// .attr("height", 27)
// .attr("rx", 10)
// .attr("fill", "rgb(230, 230, 230)")
// .attr("stroke-width", "2")
// .attr("stroke", "#FFF");
grp
.append("text")
.attr("fill", "#000")
.style("text-anchor", "middle")
.attr("font-family", "Verdana")
.attr("x", 0)
.attr("y", 0)
.attr("font-size", "10")
.text(function (d, i) {
return name;
});
// var group = g.append("g");
// group
// .append("rect")
// .attr("x", centroid[0] - w / 2)
// .attr("y", centroid[1] - h / 2)
// .attr("width", 80)
// .attr("height", 27)
// .attr("rx", 10)
// .attr("fill", "rgb(230, 230, 230)")
// .attr("stroke-width", "2")
// .attr("stroke", "#FFF");
// group
// .append("text")
// .attr("x", centroid[0] - w / 2)
// .attr("y", centroid[1] - h / 2)
// .text(function (d, i) {
// return name;
// });
});
// g.append("button")
// .attr("class", "wrap")
// .text((d) => d.properties.name);
}
);
function getCentroid(element) {
var bbox = element.getBBox();
return [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2];
}
function clicked(d) {
var x, y, k;
if (d && centered !== d) {
// CENTROIDS for subregion provinces
var p = "";
json.features.forEach(function (f, i) {
if (f.properties.subregion === d.properties.subregion) {
p += path(f);
}
});
var tmp = svg.append("path");
tmp.attr("d", p);
var centroid = getCentroid(tmp.node());
tmp.remove();
// var centroid = path.centroid(p);
x = centroid[0];
y = centroid[1];
k = 2;
if (d.properties.subregion === "Northern Territories") {
k = 1.5;
}
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
g.selectAll("path").classed(
"active",
centered &&
function (d) {
return (
d.properties &&
d.properties.subregion === centered.properties.subregion
);
// return d === centered;
}
);
g.transition()
.duration(650)
.attr(
"transform",
"translate(" +
width / 2 +
"," +
height / 2 +
")scale(" +
k +
")translate(" +
-x +
"," +
-y +
")"
)
.style("stroke-width", 1.5 / k + "px");
}
.background-svg-map {
fill: none;
pointer-events: all;
}
#provinces {
fill: rgb(230, 230, 230);
}
#provinces > path:hover {
fill: #0630a6;
}
#provinces .active {
fill: #0630a6;
}
#province-borders-path {
fill: none;
stroke: #fff;
stroke-width: 1.5px;
stroke-linejoin: round;
stroke-linecap: round;
pointer-events: none;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<div id="map-selector-app"></div>
The basic workflow for labels with a background is:
Position a parent g
Add the text
Add the rectangle and set it's size based on the bounding box of the parent g and then move it behind the text.
For d3v3, I'm going to actually add the rectangle before the text, but not style it until the text is added and the required size is known. If we append it after it will be infront of the text. In d3v4 there's a handy .lower() method that would move it backwards for us, in d3v3, there are ways to do this, but for simplicity, to ensure the rectangle is behind the text, I'll add it first
I'm going to deviate from your code at a more foundational level in my example. I'm not going to append child SVGs as this is introducing some sizing issues for you. Also, instead of using a loop to append the labels, I'm going to use a selectAll()/enter() cycle. This means I need a data array not an object ultimately. In order to help build that array I'll use an object though - by going through your json once, we can build a list of regions and create a geojson feature for each. The geojson feature is nice as it allows us to use path.centroid() which allows us to find the centroid of a feature without additional code.
So first, I need to create the data array:
var subregions = {};
json.features.forEach(function(feature) {
var subregion = feature.properties.subregion;
// Have we already encountered this subregion? If not, add it.
if(!(subregion in subregions)) {
subregions[subregion] = {"type":"FeatureCollection", features: [] };
}
// For every feature, add it to the subregion featureCollection:
subregions[subregion].features.push(feature);
})
// Convert to an array:
subregions = Object.keys(subregions).map(function(key) {
return { name: key, geojson: subregions[key] };
})
Now we can append the parent g with a standard d3 selectAll/enter statement:
// Create a parent g for each label:
var subregionsParent = g.selectAll(null)
.data(subregions)
.enter()
.append("g")
.attr("transform", function(d) {
// position the parent, so we don't need to position each child based on geographic location:
return "translate("+path.centroid(d.geojson)+")";
})
Now we can add the text and rectangle:
// add a rectangle to each parent `g`
var boxes = subregionsParent.append("rect");
// add text to each parent `g`
subregionsParent.append("text")
.text(function(d) { return d.name; })
.attr("text-anchor","middle");
// style the boxes based on the parent `g`'s bbox
boxes
.each(function() {
var bbox = this.parentNode.getBBox();
d3.select(this)
.attr("width", bbox.width + 10)
.attr("height", bbox.height +10)
.attr("x", bbox.x - 5)
.attr("y", bbox.y - 5)
.attr("rx", 10)
.attr("ry", 10)
.attr("fill","#ccc")
})
You can see the centroid method (whether using your existing function or path.centroid()) can be a little dumb when it comes to placement given some of the overlap on the map. There are ways you could modify that - perhaps adding offsets to the data, or manual exceptions when adding the text. Though on a larger SVG there shouldn't be overlap. Annotations are notoriously difficult to do.
Here's my result with the above:
And a snippet to demonstrate (I've removed a fair amount of unnecessary code to make a simpler example, but it should retain the functionality of your example):
var width = 500,
height = 275,
centered;
var projection = d3.geo.conicConformal()
.rotate([103, 0])
.center([0, 63])
.parallels([49, 77])
.scale(500)
.translate([width / 2.5, height / 2])
var path = d3.geo.path().projection(projection);
var svg = d3
.select("#map-selector-app")
.append("svg")
.attr("viewBox", `0 0 ${width} ${height}`);
var g = svg.append("g");
d3.json("https://gist.githubusercontent.com/KatFishSnake/7f3dc88b0a2fa0e8c806111f983dfa60/raw/7fff9e40932feb6c0181b8f3f983edbdc80bf748/canadaprovtopo.json",
function (error, canada) {
if (error) throw error;
json = topojson.feature(canada, canada.objects.canadaprov);
var provinces = g.append("g")
.attr("id", "provinces")
.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.on("click", clicked);
g.append("g")
.attr("id", "province-borders")
.append("path")
.datum(topojson.mesh(canada, canada.objects.canadaprov, function (a,b) { return a !== b; }))
.attr("id", "province-borders-path")
.attr("d", path);
// Add labels:
// Get the data:
var subregions = {};
json.features.forEach(function(feature) {
var subregion = feature.properties.subregion;
if(!(subregion in subregions)) {
subregions[subregion] = {"type":"FeatureCollection", features: [] };
}
subregions[subregion].features.push(feature);
})
// Convert to an array:
subregions = Object.keys(subregions).map(function(key) {
return { name: key, geojson: subregions[key] };
})
// Create a parent g for each label:
var subregionsParent = g.selectAll(null)
.data(subregions)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate("+path.centroid(d.geojson)+")";
})
var boxes = subregionsParent.append("rect");
subregionsParent.append("text")
.text(function(d) { return d.name; })
.attr("text-anchor","middle");
boxes
.each(function() {
var bbox = this.parentNode.getBBox();
d3.select(this)
.attr("width", bbox.width + 10)
.attr("height", bbox.height +10)
.attr("x", bbox.x - 5)
.attr("y", bbox.y - 5)
.attr("rx", 10)
.attr("ry", 10)
.attr("fill","#ccc")
})
// End labels.
function getCentroid(element) {
var bbox = element.getBBox();
return [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2];
}
function clicked(d) {
var x, y, k;
if (d && centered !== d) {
// CENTROIDS for subregion provinces
var p = "";
json.features.forEach(function (f, i) {
if (f.properties.subregion === d.properties.subregion) {
p += path(f);
}
});
var tmp = svg.append("path");
tmp.attr("d", p);
var centroid = getCentroid(tmp.node());
tmp.remove();
x = centroid[0];
y = centroid[1];
k = 2;
if (d.properties.subregion === "Northern Territories") {
k = 1.5;
}
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
g.selectAll("path").classed(
"active",
centered &&
function (d) {
return (
d.properties &&
d.properties.subregion === centered.properties.subregion
);
}
);
g.transition()
.duration(650)
.attr("transform","translate(" + width / 2 + "," + height / 2 +")scale(" +
k +")translate(" + -x + "," + -y + ")"
)
.style("stroke-width", 1.5 / k + "px");
}
})
.background-svg-map {
fill: none;
pointer-events: all;
}
#provinces {
fill: rgb(230, 230, 230);
}
#provinces > path:hover {
fill: #0630a6;
}
#provinces .active {
fill: #0630a6;
}
#province-borders-path {
fill: none;
stroke: #fff;
stroke-width: 1.5px;
stroke-linejoin: round;
stroke-linecap: round;
pointer-events: none;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<div id="map-selector-app"></div>
var w = 300;
var h = 150;
var padding = 2;
var dataset =[5, 10, 15, 20, 25];
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
function colorPicker(v){
if (v<=20) { return "#666666"; }
else if (v>20) { return "#FF0033"; }
}
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) { return (i*(w/dataset.length)); })
.attr("y", function(d) { return h-(d*4); })
.attr("width", w/dataset.length-padding)
.attr("height", function(d) { return d*4;})
.attr("fill", function(d){
return colorPicker(d);
});
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {return d; })
.attr({"text-anchor": "middle"})
.attr({
x: function(d, i) {return i* (w / dataset.length);},
y: function(d) {return h - (d*4); }
});
I am following a D3.js tutorial and I'm trying to get the text-anchor to work, but it won't append. No text appears, can anyone shed any light into what I'm doing wrong?
It should display the number above every rectangle
In the new (not so new, actually) V4.x version, you cannot use objects to set the attr() method.
Besides that you have another problem, which will avoid the texts to be rendered: there is no value property in your dataset (which is just an array of numbers). Thus, it should be:
.text(function(d){return d})
Here is your code with the necessary changes:
var w = 300;
var h = 150;
var padding = 2;
var dataset = [5, 10, 15, 20, 25];
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
function colorPicker(v) {
if (v <= 20) {
return "#666666";
} else if (v > 20) {
return "#FF0033";
}
}
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return (i * (w / dataset.length));
})
.attr("y", function(d) {
return h - (d * 4);
})
.attr("width", w / dataset.length - padding)
.attr("height", function(d) {
return d * 4;
})
.attr("fill", function(d) {
return colorPicker(d);
});
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.attr("text-anchor", "middle")
.text(function(d) {
return d;
})
.attr("x", function(d, i) {
return i * (w / dataset.length) + ((w / dataset.length - padding) / 2);
})
.attr("y", function(d) {
return h - (d * 4);
});
<script src="https://d3js.org/d3.v4.min.js"></script>
I have a grid represented using d3 and svg. I am trying to select the neighbouring (adjacent) tiles to any specific tile on the grid. the tiles are accessed via their x and y coordinates on the grid.
What I have feels fairly messy, and doesn't do exactly what I want, I don't want the clicked tile to be selected, or the tiles diagonal to it.
var w = 960,
h = 500,
z = 20,
x = w / z,
y = h / z;
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(d3.range(x * y))
.enter().append("rect")
.attr("width", z)
.attr("height", z)
.attr("clicked", false)
.attr('x', horizontalpos)
.attr('y', verticalpos)
.on("click", test)
.style("stroke", "rgb(6,120,155)")
.style("stroke-width", 2)
.style("fill", "rgb(255, 255, 255)")
function translate(d) {
return "translate(" + (d % x) * z + "," + Math.floor(d / x) * z + ")";
}
function verticalpos(d) {
return ((d % x) * z);
}
function horizontalpos(d) {
return (Math.floor(d / x) * z );
}
function test(){
var d = d3.selectAll("[x='40']").filter("[y='40']");
d3.selectAll("[x=" + "'"+ (parseInt(d.attr("x")) +20).toString() +"'" +"],[x=" + "'"+ (parseInt(d.attr("x")) -20).toString() +"'" +"],"+ "[x=" + "'"+ (parseInt(d.attr("x"))).toString() +"'" +"]")
.filter("[y=" + "'"+ (parseInt(d.attr("y"))).toString() +"'" +"],[y=" + "'"+ (parseInt(d.attr("y")) +20).toString() +"'" +"]"+",[y=" + "'"+ (parseInt(d.attr("y")) -20).toString() +"'" +"]")
.transition()
.style("fill", "black");
}
jsfiddle - https://jsfiddle.net/wkencq2w/15/
What I'm wondering is - Is there a way to select the data via two attributes, like this:
d3.select("[x='40'], [y='40']")
This does not work for me, but the logic behind it is how I would like to select the data.
Because it's D3, I won't do this based on calculations of positions but rather on data binding. This will simplify matters a lot and reduce the amount and the complexity of your code. One possible way might be to define a two-dimensional array of objects having x and y properties which is then bound to a D3 selection:
var grid = d3.range(y).map(function(dy) {
return d3.range(x).map(function(dx) {
return {x: dx, y: dy};
});
});
var g = svg.selectAll("g")
.data(grid)
.enter().append("g") // Group each row's rects in a svg:g
.selectAll("rect") // Do a nested selection
.data(function(d) { return d; }) // Bind the sub-array for this row
The part which benefits most from this approach is your test() function which may now act on the data bound to each rect rather than having to get attribute values and doing calculation with them.
function test(d) {
var clicked = d3.select(this).datum(); // Object bound to the rect.
d3.selectAll("rect").filter(function(d) {
// Do the filtering based on data rather than on positions.
return d.x === clicked.x && Math.abs(d.y - clicked.y) === 1 ||
d.y === clicked.y && Math.abs(d.x - clicked.x) === 1;
})
.transition()
.style("fill", "black");
}
Have a look at this JSFiddle for a full example.
You can select data via two attributes just by putting them together e.g. [x='40'][y='40']. This together with the , css operator allows the generation of a css selection string that gives you what you asked for.
var w = 960,
h = 500,
z = 20,
x = w / z,
y = h / z;
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(d3.range(x * y))
.enter().append("rect")
.attr("width", z)
.attr("height", z)
.attr("clicked", false)
.attr('x', horizontalpos)
.attr('y', verticalpos)
.on("click", test)
.style("stroke", "rgb(6,120,155)")
.style("stroke-width", 2)
.style("fill", "rgb(255, 255, 255)")
function translate(d) {
return "translate(" + (d % x) * z + "," + Math.floor(d / x) * z + ")";
}
function verticalpos(d) {
return ((d % x) * z);
}
function horizontalpos(d) {
return (Math.floor(d / x) * z );
}
function test(d) {
x = parseInt(d3.select(this).attr("x"));
y = parseInt(d3.select(this).attr("y"));
var selector = ""
for (var dx=-20;dx<=20;dx+=20) {
for (var dy=-20;dy<=20;dy+=20) {
selector += "[x='"+ (x + dx) +"'][y='"+ (y + dy) +"'],"
}
}
// cut off the final extraneous comma
selector = selector.substring(0, selector.length - 1);
d3.selectAll(selector)
.transition()
.style("fill", "black");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Or if you just want a cross without the centre as you describe in the question you could do this...
var w = 960,
h = 500,
z = 20,
x = w / z,
y = h / z;
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(d3.range(x * y))
.enter().append("rect")
.attr("width", z)
.attr("height", z)
.attr("clicked", false)
.attr('x', horizontalpos)
.attr('y', verticalpos)
.on("click", test)
.style("stroke", "rgb(6,120,155)")
.style("stroke-width", 2)
.style("fill", "rgb(255, 255, 255)")
function translate(d) {
return "translate(" + (d % x) * z + "," + Math.floor(d / x) * z + ")";
}
function verticalpos(d) {
return ((d % x) * z);
}
function horizontalpos(d) {
return (Math.floor(d / x) * z );
}
function test(d) {
x = parseInt(d3.select(this).attr("x"));
y = parseInt(d3.select(this).attr("y"));
var selector = ""
var deltas = [[-20, 0], [20, 0], [0, 20], [0, -20]];
for (var i=0;i < deltas.length;i++) {
selector += "[x='"+ (x + deltas[i][0]) +"'][y='"+ (y + deltas[i][1]) +"'],"
}
// cut off the final extraneous comma
selector = selector.substring(0, selector.length - 1);
d3.selectAll(selector)
.transition()
.style("fill", "black");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I tried to fix this problem using filter:
function test(d){
var x = d3.select(this);
var x1 = (parseInt(x.attr("x")) +20);
var x2 = (parseInt(x.attr("x")) -20);
var y1 = (parseInt(x.attr("y")) +20);
var y2 = (parseInt(x.attr("y")) -20);
var f = d3.selectAll("rect")[0].filter(function(d){
//left rect
if (d3.select(d).attr("x") == x1 && d3.select(d).attr("y") == parseInt(x.attr("y")))
return true;
//right rect
if (d3.select(d).attr("x") == x2 && d3.select(d).attr("y") == parseInt(x.attr("y")))
return true;
//bottom rect
if (d3.select(d).attr("y") == y1 && d3.select(d).attr("x") == parseInt(x.attr("x")))
return true;
//top rect
if (d3.select(d).attr("y") == y2 && d3.select(d).attr("x") == parseInt(x.attr("x")))
return true;
return false;
});
//select all filtered and make their fill black
d3.selectAll(f).transition().delay(100).style("fill", "black");
}
Working code here
Hope this helps!
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!
I have a d3.js barplot using some json data containing 12 elements. The data value I'm using for bar height is fpkm. I'm able to return that value as a callback to d3's data function- but only for half the elements.
My problem is that only the first half of the values are appearing in my barplot. I only get 6 rows corresponding to my first 6 values.
I made a fiddle here: http://jsfiddle.net/z9Mvt/
I can't seem to figure out why it's only using half the elements in my json.
Any help = appreciated.
html:
<div align='center' id="GECGplot" style='width:98%;text-align:center;'></plot>
and the js:
var gecgData= {"nodeName":"GECG","children":[{"nodeName":0,"nodeData":{"id":"643139","library_id":"SI_5589","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"1.1241","fpkm_conf_lo":"0.898502","fpkm_conf_hi":"1.34969","fpkm_status":"OK","fpkm_percentile_compendium":"8.33","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":1,"nodeData":{"id":"872561","library_id":"SI_5596","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"1.12666","fpkm_conf_lo":"0.871059","fpkm_conf_hi":"1.38226","fpkm_status":"OK","fpkm_percentile_compendium":"16.67","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":2,"nodeData":{"id":"1031623","library_id":"SI_5553","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"1.21305","fpkm_conf_lo":"0.949369","fpkm_conf_hi":"1.47674","fpkm_status":"OK","fpkm_percentile_compendium":"25.00","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":3,"nodeData":{"id":"248423","library_id":"SI_5486","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"1.98203","fpkm_conf_lo":"1.64888","fpkm_conf_hi":"2.31519","fpkm_status":"OK","fpkm_percentile_compendium":"33.33","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":4,"nodeData":{"id":"1039674","library_id":"SI_5554","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"2.24514","fpkm_conf_lo":"1.83333","fpkm_conf_hi":"2.65696","fpkm_status":"OK","fpkm_percentile_compendium":"41.67","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":5,"nodeData":{"id":"304849","library_id":"SI_5485","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"2.29868","fpkm_conf_lo":"2.02514","fpkm_conf_hi":"2.57221","fpkm_status":"OK","fpkm_percentile_compendium":"50.00","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":6,"nodeData":{"id":"417495","library_id":"SI_5484","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"2.61196","fpkm_conf_lo":"2.28949","fpkm_conf_hi":"2.93442","fpkm_status":"OK","fpkm_percentile_compendium":"58.33","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":7,"nodeData":{"id":"928522","library_id":"SI_5595","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"2.94397","fpkm_conf_lo":"2.61962","fpkm_conf_hi":"3.26832","fpkm_status":"OK","fpkm_percentile_compendium":"66.67","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":8,"nodeData":{"id":"622876","library_id":"SI_5552","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"3.27303","fpkm_conf_lo":"2.79509","fpkm_conf_hi":"3.75097","fpkm_status":"OK","fpkm_percentile_compendium":"75.00","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":9,"nodeData":{"id":"50230","library_id":"SI_5487","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"9.88611","fpkm_conf_lo":"8.6495","fpkm_conf_hi":"11.1227","fpkm_status":"OK","fpkm_percentile_compendium":"83.33","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":10,"nodeData":{"id":"816444","library_id":"SI_5594","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"15.1868","fpkm_conf_lo":"13.8218","fpkm_conf_hi":"16.5519","fpkm_status":"OK","fpkm_percentile_compendium":"91.67","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":11,"nodeData":{"id":"496931","library_id":"SI_5551","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"52.249","fpkm_conf_lo":"50.8217","fpkm_conf_hi":"53.6763","fpkm_status":"OK","fpkm_percentile_compendium":"100.00","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}}]}
;
//Width and height
// var w = $('#GECGplot').width();
var w = 700;
var h = 300;
var barPadding = 1;
var margin = {top: 40, right: 10, bottom: 20, left: 10};
var xScale = d3.scale.linear().
domain([0, 20]). // your data minimum and maximum
range([0, h]); // the pixels to map to, e.g., the width of the diagram.
//Create SVG element
var svg = d3.select("#GECGplot")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
// .data(dataset)
.data(function(d, i) {
return plotData[i].nodeData.fpkm;
})
.enter()
.append("rect")
.attr("x", function(d, i) {
// alert(plotData.length);
return i * (w / plotData.length);
})
.attr("y", function(d, i) {
alert(plotData[i].nodeData.fpkm);
return h - (plotData[i].nodeData.fpkm * 50); //Height minus data value
})
.attr("width", w / plotData.length - barPadding)
.attr("height", function(d, i) {
return plotData[i].nodeData.fpkm * 50; //Just the data value
})
.attr("fill", function(d, i) {
return "rgb(0, 0, " + (plotData[i].nodeData.fpkm * 50) + ")";
})
svg.selectAll("text")
.data(function(d, i) {
return plotData[i].nodeData.fpkm;
})
.enter()
.append("text")
.text(function(d, i) {
return plotData[i].nodeData.fpkm;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white")
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return i * (w / plotData.length) + (w / plotData.length - barPadding) / 2;
})
.attr("y", function(d, i) {
return h - (plotData[i].nodeData.fpkm * 50) + 14;
})
// alert(tableSchema);
Here you go. You bind the array "children" to the rectangle elements so you dont need the argument 'i' to access the value you need.
Also, I would recommend using the d3.scale.ordinal() for your x axis as opposed to calculating it explicitly from the data. Litte more flexible.
http://jsfiddle.net/Cef4D/
svg.selectAll("rect")
.data(plotData)
.enter().append("rect")
.attr("x", function(d, i) {return i * (w / plotData.length);})
.attr("y", function(d) {
return h - (d.nodeData.fpkm * 50); //Height minus data value
})
.attr("width", w / plotData.length - barPadding)
.attr("height", function(d, i) {
return d.nodeData.fpkm * 50; //Just the data value
})
.attr("fill", function(d, i) {
return "rgb(0, 0, " + (d.nodeData.fpkm * 50) + ")";
})