I have an data which show the performance of user and competency which is y and x axis respectively. Where its scaled from 1 to 10. I want to create an intersection point using d3js but as a beginner im having no idea where to start.
To create a dot at that point, you simply need to draw a new circle on the point of intersection. Here is a JSFiddle of a VERY simple graph with comments to make it easier to understand. The dots are drawn from data. The important piece for you is
.append("svg:circle")
.attr("cy", function(d) {
return y(d.y);
})
.attr("cx", function(d, i) {
return x(d.x);
})
.attr("r", 10)
which creates an svg circle, places it on the data point's x and y coordinates, and gives it a radius of 10. A more simplified version which might be easier to use for your application (because I don't know how your data is stored) is to feed in the data manually
.append("svg:circle")
.attr("cy", x(5))
.attr("cx", y(6))
.attr("r", 10)
This will create a single data point at 5,6 with a radius of 10. Note that this is using the scaling functions x() and y() to convert the data point 5,6 to pixel values
var x = d3.scale.linear()
.domain([0, 10])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 10])
.range([height, 0]);
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 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
I'm trying to prevent clipping at the top most part of my chart by increasing the domain on the yAxis like so:
mainHeight = 640;
yScale = d3.scale.linear()
.range([mainHeight, 0])
.domain(d3.extent([0, d3.max(data, function (d) {
return (d.total)+1000;
})]));
The idea is to get the max data for the yAxis and increase it by 1000.
The highest total is 14348 so with 1000 added on it creates 15348
However the top of the chart is still being clipped off and my axis hasn't increased to prevent the clipping. Even if I increase the number by 9999999999 it still doesn't happen.
The line is generated with:
var totalLine = d3.svg.line()
.interpolate('monotone')
.x(function (d) {
return xScale(d.date);
})
.y(function (d) {
return yScale(d.total);
});
Try throwing a .nice() at the end.
yScale = d3.scale.linear()
.range([mainHeight, 0])
.domain(d3.extent([0, d3.max(data, function (d) {
return (d.total)+1000;
})]))
.nice();
This will try to make the axis end on nice round numbers. You can read more here: https://github.com/d3/d3-3.x-api-reference/blob/master/Quantitative-Scales.md#linear_nice
Hope this helps.
Consider the following VND3 line graph: http://jsfiddle.net/tramtom/hfv68yan/
The graph plots the line ok, however most of my data is the static over long periods, and the two series will always have one at the top of the graphic and other on the bottom axis.
How to create relative minimum and maximums for the Y axis in a way that lines for the series be almost in the middle or at least spaced so that the lower valued series does not lie almost entirely on the x-axis.
Need to add 100 units below the minimum and 100 units above the maximum so the lines don't be at the top or bottom of the graphic.
I tried setting a domain and range values like in here http://jsfiddle.net/tramtom/hfv68yan/1/ but have no effect
var yScale = d3.scale.linear()
.domain([
d3.min(data, function (d) {
return d.y;
}) - 100,
d3.max(data, function (d) {
return d.y;
}) + 100
])
.range([
d3.min(data, function (d) {
return d.y;
}),
d3.max(data, function (d) {
return d.y;
})
]);
You need to force the axis range by adding .forceY([0,500]) to the chart instantiation.
This SO answer might be helpful. That should at least point you in the right direction.
I am trying to get brushing to work similar to this example, but with a grouped bar chart: http://bl.ocks.org/mbostock/1667367
I don't really have a good understanding of how brushing works (I haven't been able to find any good tutorials), so I'm a bit at a loss as to what is going wrong. I will try to include the relevant bits of code below. The chart is tracking the time to fix broken builds by day and then grouped by portfolio. So far the brush is created and the user can move and drag it, but the bars in the main chart are re-drawn oddly and the x axis is not updated at all. Any help you can give would be greatly appreciated. Thank you.
// x0 is the time scale on the X axis
var main_x0 = d3.scale.ordinal().rangeRoundBands([0, main_width-275], 0.2);
var mini_x0 = d3.scale.ordinal().rangeRoundBands([0, main_width-275], 0.2);
// x1 is the portfolio scale on the X axis
var main_x1 = d3.scale.ordinal();
var mini_x1 = d3.scale.ordinal();
// Define the X axis
var main_xAxis = d3.svg.axis()
.scale(main_x0)
.tickFormat(dateFormat)
.orient("bottom");
var mini_xAxis = d3.svg.axis()
.scale(mini_x0)
.tickFormat(dateFormat)
.orient("bottom");
After binding the data...
// define the axis domains
main_x0.domain(data.result.map( function(d) { return d.date; } )
.sort(d3.ascending));
mini_x0.domain(data.result.map( function(d) { return d.date; } )
.sort(d3.ascending));
main_x1.domain(data.result.map( function(d) { return d.portfolio; } )
.sort(d3.ascending))
.rangeRoundBands([0, main_x0.rangeBand() ], 0);
mini_x1.domain(data.result.map( function(d) { return d.portfolio; } )
.sort(d3.ascending))
.rangeRoundBands([0, main_x0.rangeBand() ], 0);
// Create brush for mini graph
var brush = d3.svg.brush()
.x(mini_x0)
.on("brush", brushed);
After adding the axis's, etc.
// Create the bars
var bar = main.selectAll(".bars")
.data(nested)
.enter().append("g")
.attr("class", function(d) { return d.key + "-group bar"; })
.attr("fill", function(d) { return color(d.key); } );
bar.selectAll("rect").append("rect")
.data(function(d) { return d.values; })
.enter().append("rect")
.attr("class", function(d) { return d.portfolio; })
.attr("transform", function(d) { return "translate(" + main_x0(d.date) + ",0)"; })
.attr("width", function(d) { return main_x1.rangeBand(); })
.attr("x", function(d) { return main_x1(d.portfolio); })
.attr("y", function(d) { return main_y(d.buildFixTime); })
.attr("height", function(d) { return main_height - main_y(d.buildFixTime); });
Here is the brush function (trying several different options)...
function brushed() {
main_x1.domain(brush.empty() ? mini_x1.domain() : brush.extent());
//main.select("rect")
//.attr("x", function(d) { return d.values; })
//.attr("width", function(d) { return d.values; });
bar.select("rect")
.attr("width", function(d) { return main_x1.rangeBand(); })
.attr("x", function(d) { return main_x1(d.portfolio); });
//.attr("y", function(d) { console.log(d); return main_y(d.buildFixTime); })
//.attr("height", function(d) { return main_height - main_y(d.buildFixTime); });
main.select(".x.axis").call(main_xAxis);
}
The problem comes from trying to use the brush to set the x-scale domain, when your x-scale is an ordinal scale. In other words, the expected domain of your x-axis is a list of categories, not a max-min numerical extent. So the problem is right at the top of the brushing function:
function brushed() {
main_x0.domain(brush.empty() ? mini_x0.domain() : brush.extent());
The domain set by brush.extent() is an array of two numbers, which then completely throws off your ordinal scale.
According to the wiki, if one of the scales attached to a brush function is an ordinal scale, the values returned by brush.extent() are values in the output range, not in the input domain. Ordinal scales don't have an invert() method to convert range values into domain values.
So, you have a few options on how to proceed:
You could re-do the whole graph using a linear time scale for your main x-axes instead of an ordinal scale. But then you have to write your own function to figure out the width of each day on that axis instead of being able to use .rangeBand().
You can create your own "invert" function to figure out which categorical values (dates on the mini_x0.domain) are included in the range returned by brush.extent(). Then you would have to both reset the main_x0.domain to only include those dates on the axis, and filter out your rectangles to only draw those rectangles.
Or you can leave the domain of main_x0. be, and change the range instead. By making the range of the graph larger, you space out the bars greater. In combination with a clipping path to cut off bars outside the plotting area, this has the effect of only showing a certain subset of bars, which is what you want anyway.
But what should the new range be? The range returned by brush.extent() is the beginning and end positions of the brushing rectangle. If you used these values as the range on the main graph, your entire graph would be squished down to just that width. That's the opposite of what you want. What you want is for the area of the graph that originally filled that width to be stretched to fill the entire plotting area.
So, if your original x range is from [0,100], and the brush covers the area [20,60], then you need a new range that satisfies these conditions:
the 20% mark of the new range width is at 0;
the 60% mark of the new range width is at 100.
Therefore,
the total width of the new range is ( (100-0) / (60-20) )*(100-0) = 250;
the start of the new range is at (0 - (20/100)*250) = -50;
the end of the new range is at (-50) + 250 = 200.
Now you could do all the algebra for figuring out this conversion yourself. But this is really just another type of scaling equation, so why not create a new scale function to convert between the old range and the zoomed-in range.
Specifically, we need a linear scale, with its output range set to be the actual range of the plotting area. Then set the domain according to the range of the brushed area that we want to stretch to cover the plotting area. Finally, we figure out the range of the ordinal scale by using the linear scale to figure out how far off the screen the original max and min values of the range would be. And from there, we-can resize the other ordinal scale and reposition all the rectangles.
In code:
//Initialization:
var main_xZoom = d3.scale.linear()
.range([0, main_width - 275])
.domain([0, main_width - 275]);
//Brushing function:
function brushed() {
var originalRange = main_xZoom.range();
main_xZoom.domain(brush.empty() ?
originalRange:
brush.extent() );
main_x0.rangeRoundBands( [
main_xZoom(originalRange[0]),
main_xZoom(originalRange[1])
], 0.2);
main_x1.rangeRoundBands([0, main_x0.rangeBand()], 0);
bar.selectAll("rect")
.attr("transform", function (d) {
return "translate(" + main_x0(d.date) + ",0)";
})
.attr("width", function (d) {
return main_x1.rangeBand();
})
.attr("x", function (d) {
return main_x1(d.portfolio);
});
main.select("g.x.axis").call(main_xAxis);
}
Working fiddle based on your simplified code (Note: you still need to set a clipping rectangle on the main plot):
http://fiddle.jshell.net/CjaD3/1/