I'm throwing in some fairly simple code to build out some rectangles from a data file which is all working fine, however I'm trying to add in a transition for the rectangles.enter() something akin to .transition().duration(1000)
I've looked at using the .on() function prior to the transition, but no matter where I put it in the code either no change, or the whole graph disappears. Is it possible to add in a transition on the enter function, or do I need to work around to use d3.select
d3.json("data/buildings.json").then(function(data){
data.forEach(function(d){
d.height = +d.height;
});
console.log(data);
var svg = d3.select("#chart-area").append("svg")
.attr("width", 400)
.attr("height", 400);
var rectangles = svg.selectAll("rect")
.data(data);
rectangles.enter()
.append("rect")
.attr("x", function(d,i){
return (i * 50) + 25;
})
.attr("y", 25)
.attr("width", 40)
.attr("height",function(d){
return d.height;
})
.attr("fill", "grey")
})
The simple answer is no, for a transition you would need to define two states: the initial state and the final state of the animation. Using the enter - update - exit cycle of d3 you could end up with something like this:
the rectangles fly in from the center of the SVG changing their sizes and color in one smooth transition.
The enter phase sets the initial state of the transition, the update phase performs the changes during the transition to reach the final state. Exit is not really needed for this example. It would take care of removing nodes that no longer exist after the update phase.
There are plenty of good examples and a tutorial about the topic over at https://bl.ocks.org for further reading.
d3.json("data/buildings.json").then(function(data){
data.forEach(function(d){
d.height = +d.height;
});
console.log(data);
var width = 400;
var height = 400;
var svg = d3.select("#chart-area").append("svg")
.attr("width", width)
.attr("height", height);
var rectangles = svg.selectAll("rect")
.data(data);
var rectEnter = rectangles.enter()
.append('rect')
.attr('x', width/2)
.attr('y', height/2)
.attr('width', 1e-6)
.attr('height', 1e-6)
.attr('fill', 'red')
var rectUpdate = rectEnter.merge(rectangles)
rectUpdate.transition()
.duration(1500)
.attr('x', function(d,i) { return (i * 50) + 25 })
.attr('y', 25)
.attr('width', 40)
.attr('height', function(d) { return d.height })
.attr('fill', 'grey')
var rectExit = rectangles.exit().remove()
})
and the dataset buildings.json
[
{
"id": 1,
"height": 20
}, {
"id": 2,
"height": 40
}, {
"id": 3,
"height": 10
}
]
Related
I'm trying out this D3 heatmap example. I'd like to implement slightly more complex colouring logic - all values below 30 will simply have a red fill, and above 30 to follow the Blues colour gradient.
Related question, but this applies constant colours to different parts of a domain (e.g. red for values 0 to 50, blue for 50 to 100...) rather than flipping from a constant colour to a colour gradient. I could follow that answer and manually implement a domain of a single red and a sufficiently large set of blues from raw HTML colour codes, but I'd like a more elegant solution that implements the scale of blues for me, particularly because I won't know what the maximum value of possible range inputs will be.
I tried to modify the attr("fill") line to the following, but it won't work - it applies the red correctly, but the non-red squares just appear black, which tells me that the colour gradient isn't kicking in for some reason:
...
// Build color scale
var myColor = d3
.scaleSequential()
.interpolator(d3.interpolate)
.domain([30, 100]); //Doesn't matter if I set the domain to [0.001, 100] or [30, 100]
...
.style("fill", function (d) {
if (d.value < 30) {
return "red"
} else {
return myColor(d.value);
};
})
...
You need to specify a function to interpolate between colors. You've specified d3.interpolate, but you haven't given it any values to interpolate between, eg:
d3.interpolate("steelblue","yellow")
// Build color scale
var myColor = d3
.scaleSequential()
.interpolator(d3.interpolate("steelblue","yellow"))
.domain([30, 100]);
var data = d3.range(100)
d3.select("body")
.append("svg")
.attr("height", 300)
.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("width", 20)
.attr("height", 20)
.attr("x", (d,i) => i%10 * 22)
.attr("y", (d,i) => Math.floor(i/10) * 22)
.style("fill", function (d) {
if (d < 30) {
return "red"
} else {
return myColor(d);
};
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Or passed it a pre-built interpolator for different color schemes, eg:
d3.interpolateBlues
// Build color scale
var myColor = d3
.scaleSequential()
.interpolator(d3.interpolateBlues)
.domain([30, 100]);
var data = d3.range(100)
d3.select("body")
.append("svg")
.attr("height", 300)
.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("width", 20)
.attr("height", 20)
.attr("x", (d,i) => i%10 * 22)
.attr("y", (d,i) => Math.floor(i/10) * 22)
.style("fill", function (d) {
if (d < 30) {
return "red"
} else {
return myColor(d);
};
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
You can use d3.scaleSequential(interpoloator) instead of specifying the interpolator using the .interpoloator method:
// Build color scale
var myColor = d3
.scaleSequential(d3.interpolateBlues)
.domain([30, 100]);
var data = d3.range(100)
d3.select("body")
.append("svg")
.attr("height", 300)
.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("width", 20)
.attr("height", 20)
.attr("x", (d,i) => i%10 * 22)
.attr("y", (d,i) => Math.floor(i/10) * 22)
.style("fill", function (d) {
if (d < 30) {
return "red"
} else {
return myColor(d);
};
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
So I'm sort of new to Javascript and I am trying to create a histogram using d3.js. I've been following tutorials and examples of previously created histograms in d3 but cannot figure out how to make my rectangles appear.
My histogram currently contains 4 bins with the numbers 1, 2, 3 and 4 in each bin symbolizing a color attribute of each data point in my dataset. When I do console.log(d) in the .attr "x" function it will appear as an a kind of array with 4 different indices, each with the total number of data points in my dataset with that specific color. Now I'm trying to make that "array" into rectangles but my width and height functions aren't correct. If someone could explain what d.dx and d.y do any why they're wrong that would be helpful. I'm using d3.v3.min.js as my script src value
d3.csv("data.csv", function(data) {
var map = data.map(function (i) { return parseInt(i.color); })
var histogram = d3.layout.histogram()
.bins(4)(map)
var canvas = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 500);
var bars = canvas.selectAll(".bar")
.data(histogram)
.enter()
.append("g")
bars.append("rect")
.attr("x", function (d)
{
//console.log(d)
return d.x * 5; })
.attr("y", 0)
.attr("width",function(d) { return d.dx; })
.attr("height", function(d) { d.y; })
.attr("fill", "steelblue");
});
I updated your plunk as follows.
bars.append("rect")
.attr("x", function(d) { return d.x*100; })
.attr("y", 50)
.attr("height", function(d) { return d.y * 10;})
.attr("width", function(d) { return d.dx*50;})
.attr("fill", "steelblue")
.on("mouseout", function()
{
d3.select(this)
.attr("fill", "steelblue");
})
.on("mouseover", function()
{
d3.select(this)
.attr("fill", "orange");
});
Your code seems to work fine, only your elements are overlapping (also, d3 v4 was referenced instead of v3). What I did is:
multiply d.x by 50 to space the elements
multiplied d.dx by 50 to reduce the overlapping
As to your former questions:
d.x corresponds to the extent of a bin, in your case 0.75 (4 ranges make between 1 and 4 make 0.75: 1+(0.75*4)=4)
*d.y corresponds to the 'height' of a bin, i.e. the number of elements.
I'm trying to get an element with an appended image to transition using the d3.js library; I had successfully achieved this just using a plain circle that transitioned nicely around the screen but now that I've added a png the transition doesn't happen - the png does appear though when the page is refreshed, it just won't move like it did before! My code is below.. your help is appreciated!
<script>
var data = [60, 120, 40, 710, 560, 850];
var data1 = data[0];
var canvas = d3.select("body").append("svg")
.attr("width", 2000)
.attr("height", 2000);
var imgs = canvas.append("image")
.attr("xlink:href", "AWT-Bus.png")
.attr("x", "60")
.attr("y", "60")
.attr("width", "20")
.attr("height", "20")
.attr("cx", 50)
.attr("cy", 200)
.attr("r", 20)
;
imgs.transition()
.duration(data1*100)
.delay(2000)
.attr("cx", 200)
.transition()
.attr("cx", 50)
.attr("cy", 200)
.transition()
.attr("cx", 150)
.attr("cy", 300)
;
The attributes you are changing in your code (cx and cy) are applicable to circles which are described by the x and y co-ordinates of their center (cx and cy) plus the radius (r). This is why your circle example worked.
But images are described by their width, height and the x and y co-ordinates of the upper-left corner of the box (using x and y attributes as shown below).
Different svg elements have different attributes which describe their dimensions and their location on the page, so you need to be aware of the different attributes that each type of element has, perhaps using a reference such as https://developer.mozilla.org/en-US/docs/Web/SVG/Element. Then you can animate your svg element using transition as you have done in your code and changing the value of the appropriate attribute.
var canvas = d3.select("body").append("svg")
.attr("width", 2000)
.attr("height", 2000);
var imgs = canvas.append("image")
.attr("xlink:href", "AWT-Bus.png")
.attr("x", "60")
.attr("y", "60")
.attr("width", "20")
.attr("height", "20");
imgs.transition()
.duration(2000)
.delay(1000)
.attr("x", 200)
.transition()
.attr("x", 50)
.attr("y", 200)
.transition()
.attr("x", 150)
.attr("y", 300);
I'm trying to build out a simple color chart, as an introductory d3 exercise, and I'm already stuck.
I have the following:
var colors = ["#ffffcc","#c7e9b4","#7fcdbb","#41b6c4","#2c7fb8","#253494"];
var barHeight = 20,
barWidth = 20,
width = (barWidth + 5) * colors.length;
d3.select("body").selectAll("svg")
.data(colors)
.enter().append("rect")
.attr("class", "block")
.attr("width", barWidth)
.attr("height", barHeight - 1)
.text(function(d) { return d; })
.attr("fill", function(d) { return d; });
https://jsfiddle.net/xryamdkf/1/
The text works fine. I see the hex codes, but the height and width are definitely not respected, and I can't seem to set the color.
This works to set the color: .style("background", function(d) { return d; }) but I think that is the text background, not the rect fill.
What am I doing wrong here? How can I make 20x20 rectangles filled with color in d3?
As you are not giving any index and reference of colors array into your function the code will not understand from where to pick colors. try with below code it will help.
d3.select("body").selectAll("svg")
.data(colors).enter().append("rect")
.attr("class", "block")
.attr("width", barWidth)
.attr("height", barHeight - 1)
.text(function(d) {
return d;
})
.attr("fill", function(d,i) { return colors[i]; });
So, a few things. You should call data() on what will be an empty selection of the things you will be adding.
svg.selectAll("rect").data(colors)
.enter().append("rect")
The rect doesn't have a text property. There is an svg text node that shows text and you'll want to add it separately.
I hope this https://jsfiddle.net/xryamdkf/8/ gets you closer.
I am using CodeFlower built upon D3.js. I want to show an image as a background instead of arbitrary colors, and i successfully did that using SVG Patterns.
DEMO FIDDLE
// Enter any new nodes
this.node.enter().append('defs')
.append('pattern')
.attr('id', function(d) { return (d.id+"-icon-img");}) // just create a unique id (id comes from the json)
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', 80)
.attr('height', 80)
.append("svg:image")
.attr("xlink:xlink:href", function(d) { return (d.icon);}) // "icon" is my image url. It comes from json too. The double xlink:xlink is a necessary hack (first "xlink:" is lost...).
.attr("x", 0)
.attr("y", 0)
.attr("height", 80)
.attr("width", 80)
.attr("preserveAspectRatio", "xMinYMin slice");
this.node.enter().append('svg:circle')
.attr("class", "node CodeFlowerNode")
.classed('directory', function(d) { return (d._children || d.children) ? 1 : 0; })
.attr("r", function(d) { return d.children ? 3.5 : Math.pow(d.size, 2/5) || 1; })
.style("fill", function(d) { return ("url(#"+d.id+"-icon-img)");})
/* .style("fill", function color(d) {
return "hsl(" + parseInt(360 / total * d.id, 10) + ",90%,70%)";
})*/
.call(this.force.drag)
.on("click", this.click.bind(this))
.on("mouseover", this.mouseover.bind(this))
.on("mouseout", this.mouseout.bind(this));
The problem i am seeing is the image is not centrally aligned in the circle it is kind of tile layout composed of 4 images.
How can i make its position center and covering the circle nicely.
DEMO FIDDLE
You need to change the way you define your pattern. You should define it with respect to the element it is being applied to. So leave patternUnits at the default of objectBoundingBox, and set the width and height to 1.
Then you need to also set the patternContentUnits to objectBoundingBox also, and give the <image> the same size (width and height of 1).
this.node.enter().append('defs')
.append('pattern')
.attr('id', function(d) { return (d.id+"-icon");})
.attr('width', 1)
.attr('height', 1)
.attr('patternContentUnits', 'objectBoundingBox')
.append("svg:image")
.attr("xlink:xlink:href", function(d) { return (d.icon);})
.attr("height", 1)
.attr("width", 1)
.attr("preserveAspectRatio", "xMinYMin slice");
Demo fiddle here