How do I get data from a rectangle in D3? - javascript

I have this mock up of something I'm working on at the moment :
http://jsfiddle.net/Qh9X5/5760/
Basically I've created 3 rectangles with 3 circles on top, in my own work there's around 80. What I want to do is compare these rectangles to check which ones have the same number.
I've worked with the nodes in force layout so normally I would go through them like so :
rectangles.forEach(function(e){
console.log(e.Number) // to get that rectangles number
var compareRect = rectangles.filter(function(n){ return n.Number === e.Number});
if(e.Connect === compareRect.Connect){
//colour corresponding circles green
}
else{
//colour corresponding circles red
}
});
I can't even log to console as this gives me undefined.
I want to get at the rectangles data so I can first compare their Number, then once the numbers are the same, check if the 'Connect' value is the same. If it is, then colour the circles on both the same numbered rectangles Green, if the 'Connect' value is different, then colour the circles Red.
Hope this makes sense, basically I can't get hold of the data for any of the rectangles I have created. Thanks for your time :)

Please note, that your variable rectangles is a d3 selection which is a two-dimensional array containing grouped references to your rects. You are, however, using Array.prototype.forEach() to iterate over the the array.
rectangles.forEach(function(e){
console.log(e.Number) // to get that rectangles number
});
While being technically and syntactically correct, this won't give you the expected behaviour. To iterate over your rectangles you should use selection.each(function) instead:
rectangles.each(function(e){
console.log(e.Number) // to get that rectangles number
});
See this working JSFiddle.

Related

Triangulating contours into 2d mesh with color data intact

I am using a Javascript library 'Tess2' to triangulate a series of contours.
https://github.com/memononen/tess2.js/blob/master/src/tess2.js
It generates a perfect 2d mesh of any shape consisting of multiple contours:
A contour consists of a series of points (in a negative winding order for solid fills, in a positive winding order for holes)
However, the resulting triangles output by the algorithm are no longer tied to a contour and its fill color.
How would I alter Tess2 (or any other javascript library that tesselates contours) to allow for the retention of color data in the resulting triangles?
I've tried looking everywhere and I cannot find a solution.
From what I've seen in the source code, the tessalation function contains a vertex indices in an returned object:
Tess2.tesselate = function(opts) {
...
return {
vertices: tess.vertices,
vertexIndices: tess.vertexIndices,
vertexCount: tess.vertexCount,
elements: tess.elements,
elementCount: tess.elementCount,
mesh: debug ? tess.mesh : undefined
};
You can create a new array with the colors for each vertex, and then use vertexIndices from the object to get a color of the vertex.
If you would like to have a color per face, you would just need to generate an array like above, which means putting the same vertex color for each vertex in a array. You would also like to wrap all of this data in some kind of convienent object or class.
[EDIT]
It turns out that the tesselation algorithm merges vertices in the same position, meaning that it reorganizes vertex array completely. There are a solution to explicitly not merge different contours with overlapping vertices:
Tess2.tesselate({ contours: yourContours, elementType: Tess2.BOUNDARY_CONTOURS });
That should preserve the original vertices, however not in an original order, use vertexIndices to get the original position of these.
After many failed attempts I finally got there.
I've been trying all this time to try and process a huge amount of contours all at once with a single tessellation pass.
I tried editing the tessellation library to make each half edge retain it original contour ID. I had several eureka moments when it finally seemed to work, only to be disappointed when I stress tested it and found it to be less than perfect.
But it turns out I've been incredibly daft...
All I had to do was group each contour with a particular fill, and then tesselate each group independently.
I didn't understand that for every interior contour fill, there would always be an opposite contour that effectively enclosed the outer contour loop.
For instance, to represent a red box with a blue box inside there would be 2 red contours and 1 blue. I thought it could be represented with only 1 blue contour and 1 red, where the blue would also represent the red contour's hole, and so processing each colour group of contours independently didn't make sense to me.
When I finally realised this, I figured it out.
I've published a solution on github but I'm not sure how much use it is to anyone:
https://github.com/hedgehog90/triangulate-contours-colour-example
I've included a pretty comprehensive exporter script for converting contours (including curves) into polygonal paths for Adobe Flash/Animate which might be useful for someone.
I will be writing an OBJ exporter on top of this shortly, so I can represent vector graphics in a 3D engine.

Can't reference data objects in d3js

I can't wrap my head around manipulating the data object in d3js. I'm planning to create a chart composed of horizontal bars to hold data elements. Each data element is a circle. I figured out how to insert circles into the different bars, but I'm stuck on how to equally space the circles in each bar. For example, if the width is 800 and there are 8 circles, the x attribute should be 100*i.
Here's a link to my project: https://plnkr.co/edit/fHrdJsItEqA5qc35iUxG?p=preview
I think the problem is how to reference the data object in this block of code. Anyways, I would like to equally space the circles using scaleBand which I defined as variable x earlier in my code:
var x = d3.scaleBand()
.range([0,width]);
I think the solution would look something like this: .attr("x",x.domain(data.map(function(d,i){return d[i]})); x.bandwidth(), but obviously data is not the right object.
Selecting each bar and inserting circles:
bar.selectAll("rect")
.data(function(d,i){console.log(data_group[i].values.length);return data_group[i].values})
.enter().append("circle")
.attr("class","circle")
.attr("width", width)
//.attr("x",) //how to equally space these circle elements???
.attr("height",20)
.attr("y", y.bandwidth())
console.log(y.bandwidth());
As always, I would really appreciate your help.
There are a number of issues with your code that are preventing it from working, including:
You aren't setting a domain for your x scale.
You are attempting to place <circle>s inside of <rect>s but you cannot nest shapes in SVGs. You should place both inside of a <g>.
A <circle>'s position is set using the cx and cy attributes (and you also need to provide it an r radius attribute).
To address your question, you will need to determine how you want your items laid out. Because you are referencing the index in your question, I will use that.
You are breaking your data into nested groups where each one has a values array. You are rendering a <circle> for each datum in that array, so you will want to determine the length of the longest values array.
var longest = data_group.reduce(function(acc, curr) {
return curr.values.length > acc ? curr.values.length : acc;
}, -Infinity);
Once you have the length of the longest values array, you can set the domain for your x scale.
You are using d3.scaleBand (d3.scalePoint would probably work better here), which is an ordinal scale. Ordinal scales work on discrete domains, which means that you will need to have a domain value for each possible input (the indices). For this, you will need to generate an array of the possible indices from 0 to longest-1.
var domainValues = d3.range(longest);
Now that you have the input domain values, you can set them for the x scale.
x.domain(domainValues);
Then, for each <circle>, you will set its cx value using the index of the circle in its group and the x scale.
.attr('cx', function(d,i) { return x(i); })
As I mentioned in the beginning, there are other errors in your code, so just fixing this won't get it running correctly, but it should push you in the right direction.

Why are the .selectAll() D3-created circles in my SVG said to be 1 element using .length?

I suspect that the answer lies somewhere between my ignorance of what kind of object I'm actually working and the inner darkness of D3 and/or SVG.
I can see 30 circles on the screen. So I go in the console like this.
var circles = d3.select("#svg1").selectAll("circle");
>undefined
circles;
>[Array[30]]...
circles.length;
>1
I know I'm targeting the right elements because executing .remove() on the set clears them from the screen. But what's up with the count?
var circles = d3.select("#svg1").selectAll("circle");
circles is an array of array, hence the length is 1. That is the length of the outer array. If you want to get the count of circles selected then you should use d3s built-in method size.
var count = d3.select("#svg1").selectAll("circle").size();
This will give you the expected result.

Setting pie chart colors using d3.js

I'm having issues setting pie slice colors using a d3.pieChart. Documentation and examples I've seen use the colors method in combination with an array of hex colors. However, setting this results in my pie chart being colored white (invisible) and every item in the legend becoming black.
I've tried using .colors with an array of five and an array of six colors but the issue persists. Could this be due to some issue with the slicesCap?
Code snippet below, but category10 burns my eyes so any advice on implementing a custom color set would be appreciated!
pie
.slicesCap(5)
.legend(dc.legend().gap(3))
.colors(d3.scale.category10())
Just passing an array of colour values doesn't work because the .colors() function is expecting a color scale like the one created by d3.scale.category10(). Scales are functions which take input data and return a value; in this case, the returned value is one of 10 colours.
For starters, you could try one of the other d3 built-in colour scales, which don't have as extreme contrast:
https://github.com/mbostock/d3/wiki/Ordinal-Scales#wiki-category20
If none of those suit, you can make your own scale function with
var colorScale = d3.scale.ordinal().range([/*array of hex values */]);
pie.colors(colorScale);
You can create the array yourself or use one of the colorBrewer functions.
If you want to specify particular colours for particular values (instead of just assigning the colours to values in the order they appear) you'll need to (a) specify the domain of the scale as an array of values that matches the order of the range array, and (b) create a helper function that passes in the correct property from your data:
var colorScale = d3.scale.ordinal().domain(["banana", "cherry", "blueberry"])
.range(["#eeff00", "#ff0022", "#2200ff"]);
pie.colors(function(d){ return colorScale(d.fruitType); });
You can assign colors to particular values like this.
var colorScale = d3.scale.ordinal()
.domain(["banana", "cherry", "blueberry"])
.range(["#eeff00", "#ff0022", "#2200ff"]);
pie.colors(colorScale.range())
.colorAccessor(function(d){ return colorScale.domain().indexOf(d.fruitType); });
This solution is a little bit hacky, but I couldn't get it to work using only chart.colorDomain, and calling a function in chart.colors seems to be throwing errors now.
Riffing off Tayden's answer, there is slightly different syntax for newer versions of d3 (i.e. "scaleOrdinal")
var colorScale = d3.scaleOrdinal()
.domain(["banana", "cherry", "blueberry"])
.range(["#eeff00", "#ff0022", "#2200ff"]);

How do you get a y-axis that starts at zero in graphael, if none of your values are zero?

I wish to create a graph whose y-axis starts at 0. None of my values are 0 though. How do i go about doing that in graphael ?
As an extension, how can I create a graph with a y-axis whose maximum value is, lets say, twice the maximum value in the provided data ?
I don't know if this is too late but I've been struggling with gRaphael myself and came across a solution that might work for you. There's no obvious or clean way to do this but I've found a decent hack for it.
You can add in another array of y values (the first being 0 and last being 2x your max value) and specify the color for this line to be transparent using the colors option.
I've created a fiddle so you can see what I mean: jsFiddle example
The first chart is what you currently have and the second shows the addition of the second set of y values.

Categories