I have some circles I want to append to a radial time series chart to indicate key events. Equivalent block here.
static picture:
The code for the circles:
var eventCircles = g.selectAll('.eventCirc')
.data(eventData)
.enter()
.append('circle')
.attr('class','eventCirc')
.attr('cx', function(d) { return x(d.date); })
.attr('cy', function(d) { return y(0)})
.attr('r', 5)
.style('fill', "#003366");
The y(0) scale seems to work fine, because the units are both in pixels, but I can't figure out how to convert degrees to pixels for use with cx -- which is a required attribute for a circle.
The scales are set up as so:
var x = d3.scaleTime()
.range([0, fullCircle]);
var y = d3.scaleRadial()
.range([innerRadius, outerRadius]);
Question
How can I use d.date in conjunction with the x scale to give me a pixel coordinate for the cx attribute (and not simply a degree/radian)?
You need a bit of trigonometry here. Given the specific bl.ocks you linked, this is the math you need:
eventCircles.attr('cx', function(d) {
return y(d.Close) * -Math.sin(x(d.Date) + Math.PI)
})
.attr('cy', function(d) {
return y(d.Close) * Math.cos(x(d.Date) + Math.PI)
})
In the bl.coks you linked, Close is the y value and Date is the x value. Change them according to your data.
Here is the forked bl.ocks: https://blockbuilder.org/GerardoFurtado/16adc1bb5677adfa501b3a03b3637d75
Related
Suppose we wanted to make a list-like visual. Setting the y logic for the circles can be as simple as:
var data = [0,1,2,3,4,5,6,7,8,9];
var yScale = d3.scaleLinear()
.range([height,0])
.domain([0,9]);
svg.selectAll(null)
.data(data)
.enter()
.append('circle')
.attr('cy', function(d) { return yScale(d) })
.attr('cx', 100)
.attr('r', 10)
.style('fill', "#a6a6a6");
However, suppose we wanted to go for some style points and arrange the circles not in a blocky / tabular arrangement but rather arrange them about a circle or arc. I had this result in mind (only concerned with the outer circles):
While I think d3 does have trigonometric functions, I have never seen them used in pixel coordinates. I'd imagine the pseudo-code to be something like:
var semiCircleScale = d3.?????
.range([250 degrees, 110 degrees])
.domain([0,9]);
svg.selectAll(null)
.data(data)
.enter()
.append('circle')
.attr('cy', function(d) { return semiCircleScale(d) })
.attr('cx', 100)
.attr('r', 10)
.style('fill', "#a6a6a6");
Question
Is anyone familiar with using circle / arc scales for use with x,y logic for appending shapes? Or is there an easier/less-math-intensive way?
So the idea is to create 2 different path of arc and then calculate the circumference and place the circles along with.
d3.svg.arc()
.append("path")
.attr("d", arc1)
Here is a fiddle link with minimum code to establish the idea
https://jsfiddle.net/Dibyanshu/g03p6sxj/
I am trying to generate a graph based on some data from a CSV file.
My code:
<script>
var svg = d3.select("body").append("svg")
.attr("width", 1000)
.attr("height", 1000);
function render(data){
var circles = svg.selectAll("circle").data(data);
circles.enter().append("circle")
.attr("r", 1);
circles
.attr("cx", function (d){return d[' Monthly Term']})
.attr("cy", function (d){ return d[' Principal Balance']/1000});
circles.exit().remove();
}
d3.csv("{% static "data/Total.csv" %}" , type, function(myArray){
render(myArray);
myArray.forEach(function(d){
console.log(d[' Principal Payment'] + ", " + d[' Interest Payment'] + ", " + d[' Principal Balance'] + ", " +d[' Monthly Term']);
});
});
function type(d){
d.value = +d.value;
return d;
}
</script>
Everything "works" but the Y-axis seems reversed.
Not sure if you guys can see the inspection window but the Y-value should be decreasing as x value increases.
Any ideas?
In your console output, the Y value is decreasing as X value increases. In an SVG, the 0,0 location is top left. So a lower Y value is closer to the top of the screen. Try inverting the Y value:
.attr("cy", function (d){ return height - d[' Principal Balance']/1000});
As #genestd states in his answer, SVG y coordinates start at the top and increase in value moving down. Generally, you should be using a d3 scale to map your user space values to svg coordinate values. Looking at the classic bar chart example, you see these lines:
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
Here, the x scale goes from 0 to width and the y from height to 0. They are reversed from each other because of the very thing you are seeing, x increases going left to right while y increases going top to bottom. This .range call is mapping the SVG coordinates.
Later you'll see this line:
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
This part has now mapped the user space coordinates, saying they run from 0 to the maximum of our data.
You can then use the scales as functions when you plot your point.
In your code it might look like this:
var svg = d3.select("body").append("svg")
.attr("width", 1000)
.attr("height", 1000);
var x = d3.scaleLinear().range([0, 1000]),
y = d3.scaleLinear().range([1000, 0]);
function render(data){
x.domain([0, d3.max(data, function(d){
return d[' Monthly Term'];
});
y.domain([0, d3.max(data, function(d){
return d[' Principal Balance']/1000;
});
var circles = svg.selectAll("circle").data(data);
circles.enter().append("circle")
.attr("r", 1);
circles
.attr("cx", function (d){return x(d[' Monthly Term']); })
.attr("cy", function (d){ return y(d[' Principal Balance']/1000); });
...
I have been looking into this d3.js block Timeline with Zoom. However, I am not able to figure out how the zoom function is actually implemented. Could somebody help me understand?
Frankly, there is no zoom happening.
var brush = d3.svg.brush()
.x(x)
.on("brush", display);//this calls display function on brush event drag.
Inside display function.
minExtent = brush.extent()[0],//this give the brush extent min
maxExtent = brush.extent()[1],//this give the brush extent max
Based on the max and min of the brush filter the data:
visItems = items.filter(function(d) {return d.start < maxExtent && d.end > minExtent;});
Reset the domain with the brush's max and min.
x1.domain([minExtent, maxExtent]);
Select all rectangles in the upper area not having the brush associate data to the DOM.
update it with the new scale values
rects = itemRects.selectAll("rect")
.data(visItems, function(d) { return d.id; })
.attr("x", function(d) {return x1(d.start);})
.attr("width", function(d) {return x1(d.end) - x1(d.start);});
create any new rectangles if the data is present but DOM is not present.
rects.enter().append("rect")
.attr("class", function(d) {return "miniItem" + d.lane;})
.attr("x", function(d) {return x1(d.start);})
.attr("y", function(d) {return y1(d.lane) + 10;})
.attr("width", function(d) {return x1(d.end) - x1(d.start);})
.attr("height", function(d) {return .8 * y1(1);});
Remove all the rectangle outsside the brush extent or not in the filtered item list visItems
rects.exit().remove();
Exactly the same for labels as done for rectangles above.
Hope this clears all your doubts.
I'm not sure but I think this is just a trick with D3 scales.
What happens is that it gets the selection below (which is a projection a 100% of with from time 0 to time 100) and plots into a new scale from time 50 to time 80 with the same width.
This will make the scale change in a way that looks like you zoomed on that time moment in time.
I'm new to D3.js and using following example from D3.js to create a simple dashboard for one of my web application.
http://bl.ocks.org/NPashaP/96447623ef4d342ee09b
My requirement is to rotate top value labels of each bar vertically by 90 degrees.
I changed following method by adding "transform" attribute. Then the labels do not align properly.
//Create the frequency labels above the rectangles.
bars.append("text").text(function(d){ return d3.format(",")(d[1])})
.attr("x", function(d) { return x(d[0])+x.rangeBand()/2; })
.attr("y", function(d) { return y(d[1])-5; })
.attr("text-anchor", "middle")
.attr("transform", function(d) { return "rotate(-90)" });
I tried to find a solution for long time but couldn't. Links to my codes are given below.
https://jsfiddle.net/vajee555/7udmyj1k/
Can anybody please give me an idea how to archive this?
Thanks!
EDIT:
I have solved the problem here.
http://jsfiddle.net/vajee555/7udmyj1k/5/
Remember that when you rotate an element, the x and y coordinates are changed: they are no longer with respect to that of the chart, but with respect to the new rotated orientation of the element. Therefore, you will need to compute the x and y attributes differently.
By rotating -90deg, your x axis will be flipped to y, and the y will be flipped to -x:
I have made some small pixel adjustments to make it appear aesthetically pleasing, such as the +8 I have added to the y coordinate and the +5 I have added to the x coordinate, but the fine tuning is up to you.
// Create the frequency labels above the rectangles.
bars.append("text").text(function(d){ return d3.format(",")(d[1])})
.attr('transform', 'rotate(-90)')
.attr("y", function(d) { return x(d[0]) + x.rangeBand()/2 + 4; })
.attr("x", function(d) { return -y(d[1]) + 5; });
Also, change how the coordinates are calculated in the hG.update() function:
// transition the frequency labels location and change value.
bars.select("text").transition().duration(500)
.text(function(d){ return d3.format(",")(d[1])})
.attr("x", function(d) { return -y(d[1]) + 5; });
See working fiddle here: https://jsfiddle.net/teddyrised/7udmyj1k/2/
//Create the frequency labels above the rectangles.
bars.append("text").text(function(d){ return d3.format(",")(d[1])})
.attr("x", function(d) { return x(d[0])+x.rangeBand()/2; })
.attr("y", function(d) { return y(d[1])-5; })
.attr("text-anchor", "middle")
.attr("transform", "rotate(-90,0,0)" );
Change the last line as above.
I have a choropleth map of the united states showing total population. I would like to add a legend to the map showing the quantile range values.I’ve seen other similar questions about this topic but can’t seem to get it to work for my specific case. I know I need to include the color range or color domain but just not sure if this is the correct way. As of right now just one feature shows up in the legend, could it be that all the legend features are stacked on top of each other. How can I know for sure and how can I fix this.
//Define default colorbrewer scheme
var colorSchemeSelect = "Greens";
var colorScheme = colorbrewer[colorSchemeSelect];
//define default number of quantiles
var quantiles = 5;
//Define quantile scale to sort data values into buckets of color
var color = d3.scale.quantile()
.range(colorScheme[quantiles]);
d3.csv(data, function (data) {
color.domain([
d3.min(data, function (d) {
return d.value;
}),
d3.max(data, function (d
return d.value
})
]);
//legend
var legend = svg.selectAll('rect')
.data(color.domain().reverse())
.enter()
.append('rect')
.attr("x", width - 780)
.attr("y", function(d, i) {
return i * 20;
})
.attr("width", 10)
.attr("height", 10)
.style("fill", color);
The legend code that you're using would work perfectly well if you had an ordinal scale, where the domain is made up of discrete values that correlate to the range of colours on a one-to-one basis. But you're using a quantile scale, and so need a different approach.
For a d3 quantile scale, the domain is the list of all possible input values, and the range is a list of discrete output values. The domain list is sorted in ascending order and then divided into equal-sized groups, which are assigned to each output value from the range. The number of groups is determined by the number of output values.
With that in mind, in order to get one legend entry for each colour, you're going to need to use your colour scale's range, not the domain, as the data for your legend. Then you can use the quantileScale.invertExtent() method to find the minimum and maximum input values that are getting drawn with that colour.
Sample code, making each legend entry a <g> containing both the coloured rectangle and a text label showing the corresponding values.
var legend = svg.selectAll('g.legendEntry')
.data(color.range().reverse())
.enter()
.append('g').attr('class', 'legendEntry');
legend
.append('rect')
.attr("x", width - 780)
.attr("y", function(d, i) {
return i * 20;
})
.attr("width", 10)
.attr("height", 10)
.style("stroke", "black")
.style("stroke-width", 1)
.style("fill", function(d){return d;});
//the data objects are the fill colors
legend
.append('text')
.attr("x", width - 765) //leave 5 pixel space after the <rect>
.attr("y", function(d, i) {
return i * 20;
})
.attr("dy", "0.8em") //place text one line *below* the x,y point
.text(function(d,i) {
var extent = color.invertExtent(d);
//extent will be a two-element array, format it however you want:
var format = d3.format("0.2f");
return format(+extent[0]) + " - " + format(+extent[1]);
});