I am using data driven styling to style a Mapbox vector layer for a choropleth map. However, rather than getting the data strictly from the layer properties, I need to use statistic data from a separate object (pulled from our database). This separate object contains one statistic value for each polygon in the vector layer. The object maps the stat values to the vector layer polygons by a variable called "GEOID". In order to marry the polygon with its data, I would like to use a Mapbox expression to get the GEOID value from each polygon in the vector layer and pass this id to a separate function to get the statistic value for the polygon having this GEOID. Is this possible?
vectorLayer: {
id: "fooLayer",
type: "fill",
"source-layer": "foo-layer-dvf1ci",
paint: {
"fill-color": [
"rgba",
100,100,100,
this.getStatForDistrict(["get", "GEOID"])]
]
}
},
getStatForDistrict(districtId) {
console.log("districtId: " + districtId);
let alphaValue = fetchDataForThisDistrictFromDatabase(districtId)
return alphaValue;
},
I see that currently, I am passing ["get", "GEOID"] into the function getStatForDistrict when what I actually need is to pass the computed Mapbox expression.
More on Mapbox-GL's paint property: https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#paint-property
More on Mapbox-GL's expressions syntax: https://docs.mapbox.com/mapbox-gl-js/style-spec/expressions/
No, there is not any way to call arbitrary functions from within expressions, which look up data that doesn't exist within the feature in question.
There are several ways to make choropleths. I'd suggest you start by looking at the mapbox-choropleth library.
Related
I've got 2 pie charts with data like:
data: [
{diseaseType: 'Cancer', diseaseDetails: 'Lung cancer', quantity: 100},
{diseaseType: 'Diebetes', diseaseDetails: 'Unspecific', quantity: 650},
{diseaseType: 'Cancer', diseaseDetails: 'Breast cancer', quantity: 80}
]
i'm tying to get list of filters and able to remove them by user like (it's only controlled test code):
this.diseasePieChart.filters().splice(0, 1)
dc.renderAll()
it's updating first chart, but second (connected with first) not, it's stay like it was before remove filter.
Second chart i'm rendering like this:
self.diseasePieChart.on('filtered.monitor', function (chart) {
// create dimensions etc and render second chart
}
I also tried to do again crossfilter(data) after filter remove. When i'm calling dc.filterAll all filters are reset.
thanks for any help !
The correct entry points for changing filters are chart.filter() or chart.replaceFilter(), depending on whether you are trying to toggle individual items or change the entire array of filters at once.
As you found out, manipulating the array of filters inside the chart might affect the way the chart draws, but it won't convey the change to the crossfilter dimension and the other charts.
Note that as documented in the link above, the accepted type for the parameter for each of these functions is a little surprising:
The filter parameter can take one of these forms:
A single value: the value will be toggled (added if it is not present in the current filters, removed if it is present)
An array containing a single array of values ([[value,value,value]]): each value is toggled
When appropriate for the chart, a dc filter object such as
dc.filters.RangedFilter for the dc.coordinateGridMixin charts
dc.filters.TwoDimensionalFilter for the heat map
dc.filters.RangedTwoDimensionalFilter for the scatter plot
null: the filter will be reset using the resetFilterHandler
So if you want to get the array, remove an item, and then set it back, you could either:
var filters = chart.filters().slice(0); // copy the array of filters
filters.splice(0,1)
chart.replaceFilter([filters])
.redrawGroup();
or (using the toggle feature):
chart.filter(chart.filters()[0])
.redrawGroup();
Note that you usually want to redraw, not render, after changing a filter. This will allow the animated transitions to display, and is a little bit quicker.
Also, chart.redrawGroup is the same as dc.redrawAll() but it's a little safer in case you have more chart groups in the future.
I have implemented an app with multiple models and views but collections are a bit troublesome to understand. So far I have achieved my goals without the use of collections and now I am required to manipulate a set of models based on the attributes. And I'm pretty sure I need collections now.
I have the following structure(which is way simpler than the actual implementation):
app.Connector=Backbone.Model.extend({
line: //a d3 line object
source: //a d3 group
target: //a d3 group
// and some functions
});
app.Set=Backbone.Collections.extend({
model:app.Connector;
url:"/set" //what is the purpose of url?
});
var set=new app.Set();
//multiple connectors are initialized
Say I have a d3 object obj. How can I get a list/array of the Connectors that have obj as the target?
var filtered = set.filter(d=>d.get('target') == obj)
I find the Backbone get functions to be too verbose, so i like to transform the collection into json before filtering.
var filtered = _.filter(set.toJSON(),d=>d.target == obj)
I am using the pack layout for packing different no of equal sized circles. I have a group of clusters to be visualized. So I am calling pack function for each cluster of circles. In all the d3 examples the diameter is either calculated with the given size or fixed diameter. I would like to calculate it according to the no of circles to be packed. So how do I calculate the packing circle diameter?
is there any formula so that I can pack the circles without wasting the space.
If you truly don't care about relative sizing of the circles, then you could make your JSON file represent only the data you care about(say, names) and feed your packing function a dummy value that the 'value' accessor function is expecting.
For instance:
var circleChildren = [{
"value": 1
}, {
"value": 1
}, {
"value": 1
}, {
"value": 1
}];
would give you a JSON object that you can use as children for your packing function:
var circleInput = Object();
circleInput.children = circleChildren;
You can verify that in your console by running:
bubble.nodes(circleInput)
.filter(function (d) {
return !d.children; //we're flattening the 'parent-child' node structure
})
where bubble is your D3 packing bubble variable.
Here's a fiddle that demonstrates that. It may have some extra things but it implements what you're looking for. In addition, you can play around with the number of circles by adding more dummies in the JSON file, as well as changing the SVG container size in the diameter variable. Hope that helps!
EDIT: The size of your layout(in this case, a misnomer of the 'diameter' variable) directly determines the size and diameter of your circles within. At some point you have to assign the pack.size() or pack.radius() value in order for your circles to display within a layout(documentation ):
If size is specified, sets the available layout size to the specified two-element array of numbers representing x and y. If size is not specified, returns the current size, which defaults to 1×1.
Here you have several options:
If you want your circles to be 'dynamically' sized to your available element's width (that is, if you want them to cover up all the element width available) then I'd recommend you get your element's width beforehand, and then apply in your pack() function. The problem is then you have to think about resizing, etc.
If you want to keep the maximum sizing available, then you have to make your viz responsive. There's a really good question already in SO that deals with that.
I know this isn't the full solution but hopefully that points you in the right direction for what you're trying to do.
FURTHER EDIT:
All of a sudden, another idea came to mind. Kind of an implementation of my previous suggestion, but this would ensure you're using the maximum space available at the time for your circle drawing:
zone = d3.select("#myDiv");
myWidth = zone.style("width").substring(0, zone.style("width").length - 2);
circle is an array of four d3 circles.
circle
.attr( "cy", function(){ this.attr("cy") + 10*input_data.pitch });
This fails. How can I access the individual attributes in anonymous functions like above?
Inside your function, this is an Element per the W3C DOM API. So it’s just this.getAttribute("cy").
Two more things: you forgot to return a value. And since attribute values are strings, you’ll need to coerce them to number before you can add another number. Otherwise your number will get coerced to a string and then the two strings will be concatenated: "10" + 2 is "102", not 12.
So, like this:
circle.attr("cy", function() {
return +this.getAttribute("cy") + 10 * input_data.pitch;
});
All that said, it’s generally not idiomatic D3 to pull data back out of DOM attributes. (It’s slow and you have coercion and serialization problems since DOM attributes can only be strings.) So I would recommend looking for a way to do this based on data, and limit yourself to data-driven documents… not document-driven data!
The answer below on drawing a polygon works well for a single polygon. However, what if there are more than one polygon? Simply adding an additional polygon with points seems not to work even though using "select all" would seem to indicate that it would be OK to add a couple more polygons without much problem..
We have an array of polygons, each of which has an attribute Points which is an array of points.
The first array with polygon should obviously be mapped and the point arrays of each member processed as described. But how to spedify this two-level structure with d3?
Proper format for drawing polygon data in D3
The answer is simple and straightaway. Just pass the array of polygons as data to d3 selection.
In your case it seems that you are using an array of polygons which are composite objects, each having a key called 'Points'. I assume it looks something like this-
var arrayOfPolygons = [{
"name": "polygon 1",
"points":[
{"x":0.0, "y":25.0},
{"x":8.5,"y":23.4},
{"x":13.0,"y":21.0},
{"x":19.0,"y":15.5}
]
},
{
"name": "polygon 2",
"points":[
{"x":0.0, "y":50.0},
{"x":15.5,"y":23.4},
{"x":18.0,"y":30.0},
{"x":20.0,"y":16.5}
]
},
... etc.
];
You will just have to use d.Points instead of d when writing the equivalent map function, which can be written as follows-
vis.selectAll("polygon")
.data(arrayOfPolygons)
.enter().append("polygon")
.attr("points",function(d) {
return d.points.map(function(d) {
return [scaleX(d.x),scale(d.y)].join(",");
}).join(" ");
})
.attr("stroke","black")
.attr("stroke-width",2);
You can check the following working JSFiddle to verify.
EDIT- The same example as above with convex hull implementation for rendering complete polygons. http://jsfiddle.net/arunkjn/EpBCH/1/ Note the difference in polygon#4