http://jsfiddle.net/pPMqQ/146/
I'm developing a bubble chart application that is a derivative of a force chart to provide a little bit of movement.
Here is some of the code. As I create the svg - I also set up the viewBox - which I think could be used to adjust the size of the chart - but I've not been able to adjust the ratios properly to get the correct scaling.
I've also added here the code that adds the nodes - as the node size is calculated - its using a scale variable to affect the size of the orbs.
var svg = d3.select(selector)
.append("svg")
.attr("class", "bubblechart")
.attr("width", parseInt(w + margin.left + margin.right,10))
.attr("height", parseInt(h + margin.top + margin.bottom,10))
.attr('viewBox', "0 0 "+parseInt(w + margin.left + margin.right,10)+" "+parseInt(h + margin.top + margin.bottom,10))
.attr('perserveAspectRatio', "xMinYMid")
.append("g")
.attr("transform", "translate(0,0)");
methods.force = d3.layout.force()
.charge(1000)
.gravity(100)
.size([methods.width, methods.height])
var bubbleholder = svg.append("g")
.attr("class", "bubbleholder")
var bubbles = bubbleholder.append("g")
.attr("class", "bubbles")
var labelbubble = bubbleholder.append("g")
.attr("class", "labelbubble")
// Enter
nodes.enter()
.append("circle")
.attr("class", "node")
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("r", 1)
.style("fill", function (d) { return methods.fill(d.label); })
.call(methods.force.drag);
// Update
nodes
.transition()
.delay(300)
.duration(1000)
.attr("r", function (d) { return d.radius*scale; })
// Exit
nodes.exit()
.transition()
.duration(250)
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("r", 1)
.remove();
I have the remaining issues
scaling is an issue
I've set the width/heights via data attributes - currently I have a scaling variable set to adjust the size of the orbs depending on the width of the chart. I would like to find a more scientific way of ensuring the chart is resized accordingly and also that the elements always remain central (don't become obscured).
ensuring the smaller elements are on top of the big elements
I've also noticed that small objects may randomly fall underneath larger orbs, is there a way to organize the rendering of the orbs dependant on size, so bigger elements always sit at the bottom layer.
I've solved the second problem by sorting the data via value.
http://jsfiddle.net/pPMqQ/149/
data = data.sort(function(a, b) {return b.value - a.value})
Currently I have a scale variable depending on the width of the chart - var scale = methods.width*.005;
but its not very scientific per say
If the chart is 150 width
http://jsfiddle.net/pPMqQ/150/
the chart renders - but the bubbles no longer fit in the space.
the chart at 250 px
http://jsfiddle.net/pPMqQ/151/
Related
I am trying to convert a bubble chart from d3v3 to v4. Running into x,y,d missing variables?
In this version -- a rect is applied to the svg - and then a circle is cut -- so its like an inverse bubble chart.
I am keen to work out a set radius for the chart as a maxium -- if it should act like a score between 0 and 100? What kind of math to apply that a max radius has been reached to signify that the value is very big?
I also tried to have the svg mask adapt - if the browser or its container changed size -- ideally would want it to response during the change - rather than resizeEnd
//version 3
https://jsfiddle.net/8ag1vf6e/1/
//current version 4
https://jsfiddle.net/d56g9r0y/
// filters go in defs element
var defs = innversebubble.append("defs");
var mask = defs.append("mask")
.attr("id", "myMask");
mask.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.style("fill", "white")
.style("opacity", 1);
var invisiblebubble = mask.append("circle")
.data(data);
//create a fixed bubble first
invisiblebubble
.attr("cx", "50%")
.attr("cy", "50%")
.attr("r", function(d) {
return d.value - 20;
});
//now mask the fixed circle
var masker = defs.append(function() {
return mask.node().cloneNode(true)
})
.attr("id", "myMaskForPointer")
.select("rect")
.style("fill", "white")
.style("opacity", 1);
invisiblebubble
.attr("r", 10);
//apply the rest of the chart elements
var rect = innversebubble
.attr("class", "series")
.append("g")
.attr("transform", "translate(0,0)")
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", "100%")
.attr("height", "100%")
.attr("mask", "url(#myMask)")
.style("fill", backcolor)
.style("opacity", backopacity);
//animate this circle
invisiblebubble
.attr("cx", "50%")
.attr("cy", "50%")
.transition()
.duration(1800)
.attr("r", 10)
.transition()
.duration(900)
.attr("r", function(d) {
return d.value;
});
latest jsfiddle - 15th June -- needs fixing
https://jsfiddle.net/xmrtahns/
"I am keen to work out a set radius for the chart as a maxium -- if it should act like a score between 0 and 100? What kind of math to apply that a max radius has been reached to signify that the value is very big?
I also tried to have the svg mask adapt - if the browser or its container changed size -- ideally would want it to response during the change - rather than resizeEnd"
I've fixed the conversion and the data source - but still need issues to resolve.
var backcolor = $this.data("color");
var backopacity = $this.data("opacity");
var width = $this.data("width");
var height = $this.data("height");
var data = [{
"label": $this.data("label-name"),
"centralLabel": $this.data("central-label"),
"xPer": $this.data("displace-left"),
"yPer": $this.data("displace-top"),
"value": $this.data("bubble-value")
}];
http://jsfiddle.net/hLymw8et/2/
--I am keen to work out a set radius for the chart as a maximum -- if it should act like a score between 0 and 100?
--What kind of math to apply that a max radius has been reached to signify that the value is very big?
--I also tried to have the svg mask adapt - if the browser or its container changed size -- ideally would want it to response during the change - rather than resizeEnd –
I am creating a mapping application in d3 and want to tie some text to the top right corner of my view port. Additionally, I want the text to remain in the top right corner while I zoom and pan across the application.I think I can solve my problem by figuring out how to get the coordinates of the top right corner of my view. Knowing this information would allow me to then set the coordinates of my text element. I've tried manually setting the dimensions of the containing svg element and then moving the text to that location but interestingly this didn't work. I was hoping to be able to find the coordinates programatically rather than setting coordinates manually. How can I do this in d3/javascript?
EDIT:
My code is a modification of this code by Andy Barefoot: https://codepen.io/nb123456/pen/zLdqvM
My own zooming and panning code has essentially remained the same as the above example:
function zoomed() {
t = d3
.event
.transform
;
countriesGroup
.attr("transform","translate(" + [t.x, t.y] + ")scale(" + t.k + ")")
;
}
I'm trying to append the text at the very bottom of the code:
countriesGroup.append("text")
.attr("transform", "translate(" How do I get top right coordinates? ")")
.style("fill", "#ff0000")
.attr("font-size", "50px")
.text("This is a test");
My idea is to be able to get the top right coordinates of the view port through the code rather than setting it manually and then have the coordinates of the text update as the user zooms or pans.
To keep something in place while zooming and panning you could invert the zoom:
point == invertZoom(applyZoom(point))
This isn't particularly efficient, as we are using two operations to get to the original number. The zoom is applied here:
countriesGroup
.attr("transform","translate(" + [t.x, t.y] + ")scale(" + t.k + ")");
While the inversion would need to look something like:
text.attr("x", d3.zoom.transform.invert(point)[0])
.attr("y", d3.zoom.transform.invert(point)[1])
.attr("font-size", baseFontSize / d3.zoom.transform.k);
Where point and base font size are the original anchor point and font size. This means storing that data somewhere. In the example below I assign it as a datum to the text element:
var width = 500;
var height = 200;
var data = d3.range(100).map(function() {
return {x:Math.random()*width,y:Math.random()*height}
})
var zoom = d3.zoom()
.on("zoom",zoomed);
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height)
.call(zoom);
var g = svg.append("g")
var circles = g.selectAll()
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 5)
.attr("fill","steelblue")
var text = g.append("text")
.datum({x: width-10, y: 20, fontSize: 12})
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.style("text-anchor","end")
.attr("font-size",function(d) { return d.fontSize; })
.text("This is a test");
function zoomed() {
g.attr("transform", d3.event.transform);
var d = text.datum();
var p = d3.event.transform.invert([d.x,d.y]);
var x1 = p[0];
var y1 = p[1];
text.attr("x",x1)
.attr("y",y1)
.attr("font-size", d.fontSize / d3.event.transform.k)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Better Solution
The above is the solution to the approach you seem to be looking for. But the end result is best achieved by a different method. As I mention in my comment, the above approach goes through extra steps that can be avoided. There can also be some size/clarity changes in the text when zooming (quickly) using the above method
As noted above, you are applying the zoom here:
countriesGroup
.attr("transform","translate(" + [t.x, t.y] + ")scale(" + t.k + ")")
The zoom transform is applied only to countriesGroup, if your label happens to be in a different g (and not a child of countriesGroup), it won't be scaled or panned.
We wouldn't need to apply and invert the zoom, and we wouldn't need to update the position or font size of the text at all.
var width = 500;
var height = 200;
var data = d3.range(100).map(function() {
return {x:Math.random()*width,y:Math.random()*height}
})
var zoom = d3.zoom()
.on("zoom",zoomed);
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height)
.call(zoom);
var g = svg.append("g");
var g2 = svg.append("g"); // order does matter in layering
var circles = g.selectAll()
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 5)
.attr("fill","steelblue")
// position once and leave it alone:
var text = g2.append("text")
.attr("x", width - 10)
.attr("y", 20 )
.style("text-anchor","end")
.attr("font-size", 12)
.text("This is a test");
function zoomed() {
// apply the zoom to the g that has zoomable content:
g.attr("transform", d3.event.transform);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
(EDIT: viewBox and preserveAspectRatio are great, but aren't rendering properly in IE9, so I can't use that.)
I've been working on resizing this heatmap (here it is on a block). By selecting the width of a div, appending an SVG to it, and setting my SVG's width to that of the div, I've been able to make the heatmap responsive.
Nevertheless, I've run issues when I've tried to update the heatmap on resize. Here's the resize function:
d3.select(window).on("resize", resize);
function resize(){
marginHM = {top:35, right:0, bottom:50, left:30};
divWidthHM = parseInt(d3.select("#chartHM").style("width"));
widthHM = divWidthHM-marginHM.left-marginHM.right;
gridSize = Math.floor(widthHM/24);
legendElementWidth = gridSize*2.665;
heightHM = (9*gridSize)-marginHM.top-marginHM.bottom;
svgWidthHM = widthHM + marginHM.left+marginHM.right;
svgHeightHM = heightHM+marginHM.top+marginHM.bottom;
svgHM.select("svg").style("width", (svgWidthHM+20) + "px")
.style("height", (svgHeightHM+30) + "px");
dayLabels.attr("y", function (d, i){ return i*gridSize;})
.attr("transform", "translate(-6," + gridSize/1.5+")");
timeLabels.attr("x", function(d,i) {return i * gridSize;})
.attr("transform", "translate(" + gridSize/2+", -6)");
cards.attr("x", function(d) {return d.hour *gridSize; })
.attr("y", function(d) {return d.day * gridSize; })
.attr("width", gridSize)
.attr("height", gridSize);
d3.selectAll("rect.HMLegend")
.attr("x", function(d, i){ return legendElementWidth * i;})
.attr("y", 7.2*gridSize)
.attr("width", legendElementWidth)
.attr("height", gridSize/2);
d3.selectAll("text.HMLegendText")
.attr("x", function(d, i){ return legendElementWidth *i;})
.attr("y", 8*gridSize);
}
If you open the heatmap and drag the window to a wider size than it initially loaded in, you'll find that the heatmap grows, but the SVG's dimensions are cut off at the initial width; I find this really strange, considering I update the width in the resize function. You can decrease the width from its original value, and the map will size down, but you can't increase it above the original value of the width.
Additionally, I run into issues with the legend bar and legend labels. The the legend labels don't shift on window resize, and when you click back and forth between USA and New York, the legend colours often move off screen or show duplicates. Have been staring at this all evening, and am getting to the point where everything's blurring into one nonsensical line of code. Would really appreciate someone weighing in on exactly what it is I'm messing up!
Using D3.js Is there an option to overlay one bar over another?
I need two independent columns to indicate start and end time (x axis) against a count (y axis). I want BOTH columns to be overlaid over each other (similar start time e.g x1=8:45am, x2=10:05, and same end times. y1=90, y2=108), both columns will have transparency around 0.5, so each column can be seen over the common time/count range.
A similar example using Highcharts:
http://jsfiddle.net/gopinaghr/q8Udk/
// This page provides an example in highcharts
{ I need same for D3}
I need to create a chart where
Column width dependant on (end_time - start_time)
Column x origin is dependant on start time
Column height dependant on y value
The columns need opacity less than 1.
d3.js requires you to explicitly place your bars at coordinates, so, you can put your bars where ever you like:
// create a group for your overlapped bars
var g = svg.selectAll(".bars")
.data(data)
.enter().append("g")
// place the first bar
g.append("rect")
.attr("class", "bar1")
.attr("x", function(d) {
return x(d.letter) + 10; // center it
})
.attr("width", x.rangeBand() - 20) // make it slimmer
.attr("y", function(d) {
return y(d.col1);
})
.attr("height", function(d) {
return height - y(d.col1);
});
// place the second bar on top of it
g.append("rect")
.attr("class", "bar2")
.attr("x", function(d) {
return x(d.letter);
})
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.col2);
})
.attr("height", function(d) {
return height - y(d.col2);
});
Here's a quick example.
EDITS
To add in time, you have to make a number of changes.
Set up a time formatter, to parse your date/times out of your file:
// say date/times are local 20160622 15:00
var timeFormatter = d3.time.format("%Y%m%d %H:%M")
Set up a time x-scale for your axis:
// x scale showing 1 day - 06/22/2015
var x = d3.time.scale()
.range([0,width])
.domain([timeFormatter.parse("20150621 00:00"), timeFormatter.parse("20150622 00:00")])
And when you draw the rect, with width is the number of pixels from endTime to startTime:
g.append("rect")
.attr("class", "bar1")
.attr("x", function(d) {
return x(d.startTime1); // start at startTime
})
.attr("width", function(d,i){
return x(d.endTime1) - x(d.startTime1); // pixel width from end to start
})
.attr("y", function(d) {
return y(d.col1);
})
.attr("height", function(d) {
return height - y(d.col1);
});
Example here.
I'm trying to get a multi-line date based chart to pan nicely across the X date axis and I simply cannot figure out what the problem is.
I have the zoom behaviour set up in the code but it's just not performing as expected. If you click on a point in a line and scroll it appears to be scrolling the axis, if it click on the labels in the axis it also scrolls but the actual visualisation of data doesn't scroll.
var zoom = d3.behavior.zoom()
.x(x)
.scaleExtent([1, 1])
.on("zoom", function () {
svg.select(".x.axis").call(xAxis);
svg.select(".lines").call(xAxis);
});
svg.call(zoom);
Also if you click directly on the back ground the mouse event doesn't seem to make it's way to the control at all.
I read a few examples on this and each seem to take a vastly different approach which I've tried but none have worked for my chart.
There are possibly a number of issues that exist as barriers to getting this working so I thought the best way to illustrate the problem was in a JsFiddle.
D3 Chart Panning Fiddle
What I'm trying to achieve is when there is a lot of data to visualise the chart can adapt to the data set and allow the data to extend beyond the bounds of the chart.
Currently clicking on the background does not allow panning because you have applied zoom behavior to the g element not to the svg.
var svg = d3.select('#host')
.data(plotData)
.append("svg")
.attr("id", "history-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.call(zoom);
Right now on zoom you have updated x and y axes but not the visualization. So you have update the lines and circles also like this.
var zoom = d3.behavior.zoom()
.x(x)
.scaleExtent([1, 1])
.on("zoom", function () {
svg.select(".x.axis").call(xAxis);
svg.select(".lines").call(xAxis);
svg.selectAll("path.lines")
.attr("d", function(d) { return line(d.values); });
svg.selectAll("circle")
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.value); });
});
Since you are panning the map you will have to use clip path for restricting visualization from moving outside the chart
var clip = svg.append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
Apply clip path to the g elment which contains lines and cicrles.
var attribute = svg.selectAll(".attribute")
.data(plotData)
.enter().append("svg:g")
.attr("clip-path", "url(#clip)")
.attr("class", "attribute");