How to access data object from a nested array - vega-lite

I am using ObservableHQ and vega lite API to do data visualizations and have faced a problem I can't figure out.
The problem is that, I would like to access data object from the following data structure,
Array
Array
Array
Item
Item
Array
As you can see in my bad drawing, I have a multidimensional array and would like to access a specific array from the main array. How can I do that using Vegalite API?
vl.markCircle({
thickness: 4,
bandSize: 2
})
.data(diff[0])
.encode(
vl.x().fieldQ("mins").scale({ domain: [-60, 60] }),
vl.color().fieldN('type').scale({ range: ['#636363', '#f03b20'] }),
)
.config({bandSize: 10})
.width(600)
.height(40)
.render()
Thank you,

Based on your comments, I’m assuming that you’re trying to automatically chart all of the nested arrays (separately), not just one of them. And based on your chart code, I’m assuming that your data looks sorta like this:
const diff = [
[
{ mins: 38, type: "Type B" },
{ mins: 30, type: "Type B" },
{ mins: 28, type: "Type A" },
…
],
[
{ mins: 20, type: "Type B" },
{ mins: 17, type: "Type A" },
{ mins: 19, type: "Type A" },
…
],
…
];
First, flatten all the arrays into one big array, and record which array each came from with a new array property on the item object, with flatMap. If each child array represents, say, a different city, or a different year, or a different person collecting the data, you could replace array: i with something more meaningful about the data.
const flat = diff.flatMap((arr, i) => arr.map((d) => ({ ...d, array: i })));
Then use Vega-Lite’s “faceting” (documentation, Observable tutorial and examples) to make split the chart into sections, one for each value of array: i, with shared scales. This just adds one line to your example:
vl
.markCircle({
thickness: 4,
bandSize: 2
})
.data(flat)
.encode(
vl.row().fieldN("array"), // this line is new
vl
.x()
.fieldQ("mins")
.scale({ domain: [-60, 60] }),
vl
.color()
.fieldN("type")
.scale({ range: ["#636363", "#f03b20"] })
)
.config({ bandSize: 10 })
.width(600)
.height(40)
.render()
Here’s an Observable notebook with examples of this working. As I show there at the bottom, you can also map over your array to make a totally separate chart for each nested array.

Related

Vega: transform array of values into accepted format

I'm using Vega for data visualization. Vega requires data which is shaped as an array of objects:
const accepted = [
{ num: 1, char: 'A' },
{ num: 2, char: 'B' },
{ num: 3, char: 'C' },
]
However, for sending data (e.g. via JSON), it is not efficient as the keys num and char are repeated multiple times. A more efficient way is sending the data as follows:
const efficient = {
num: [1, 2, 3 ],
char: ['A', 'B','C']
}
Right now I'm manually transforming the efficient structure above into the accepted structure before passing it into Vega. Is there any Vega transforms that I can use so taht I can pass efficient without having to transform it into accepted?
Use the flatten transform which should do what you need.
https://vega.github.io/vega/docs/transforms/flatten/
https://vega.github.io/vega-lite/docs/flatten.html

Intersept a FeatureCollection in MongoDB

I have a GeoJson filled with states from Austria and I want to do a query that gives me as output which certain states intercepts my polygon.
This is my query:
db.GeoAustria.find(
{
'features.geometry':{
$geoIntersects:{
$geometry:{
type: "Polygon",
coordinates: [
[
[
16.21685028076172,
48.007381433478855
],
[
16.24225616455078,
47.98716432210271
],
[
16.256675720214844,
48.00669234420252
],
[
16.21685028076172,
48.007381433478855
]
]
]
}
}
}
}
)
But it gives me all the features, including those that don't overlap the polygon...
Where is my mistake in this query?
Basic array match misunderstanding here. The input set is a single doc with 95 polygons in an array in a single FeatureCollection object. When you do a find() on such things, any individual geo that is an intersect will cause the entire doc to be returned as a match. This is exactly the same as:
> db.foo.insert({x:["A","B","C"]})
WriteResult({ "nInserted" : 1 })
> db.foo.find({x:"A"});
{ "_id" : ObjectId("5fb1845b08c09fb8dfe8d1c1"), "x" : [ "A", "B", "C" ] }
The whole doc is returned, not just element "A".
Let's assume that you might have more than one big doc in your collection. This pipeline yields the single target geometry for Baden (I tested it on your input set):
var Xcoords = [
[
[
16.21685028076172,
48.007381433478855
],
[
16.24225616455078,
47.98716432210271
],
[
16.256675720214844,
48.00669234420252
],
[
16.21685028076172,
48.007381433478855
]
]
];
var targ = {type: "Polygon", coordinates: Xcoords};
db.geo1.aggregate([
// First, eliminate any docs where the geometry array has zero intersects. In this
// context, features.geometry means "for each element of array features get the
// geometry field from the object there", almost like saying "features.?.geometry"
{$match: {"features.geometry": {$geoIntersects: {$geometry: targ}} }}
// Next, break up any passing docs of 95 geoms into 95 docs of 1 geom...
,{$unwind: "$features"}
// .. and run THE SAME $match as before to match just the one we are looking for.
// In this context, the array is gone and "features.geometry" means get JUST the
// object named geometry:
,{$match: {"features.geometry": {$geoIntersects: {$geometry: targ}} }}
]);
Beyond this, I might recommend breaking up that FeatureCollection into something that is both indexable (FeatureCollection is NOT indexable in MongoDB) and easier to deal with. For example, this little script run against your single-doc/many-polys design will convert it in 95 docs with extra info:
db.geo2.drop();
mainDoc = db.geo1.findOne(); // the one Austria doc
mainDoc['features'].forEach(function(oneFeature) {
var qq = {
country: "Austria",
crs: mainDoc['crs'],
properties: oneFeature['properties'],
geometry: oneFeature['geometry']
};
db.geo2.insert(qq);
});
db.geo2.aggregate([
{$match: {"geometry": {$geoIntersects: {$geometry: targ}} }}
]);
// yields same single doc output (Baden)
This allows ease of matching and filtering. For more on FeatureCollection vs. GeometryCollection see https://www.moschetti.org/rants/hurricane.html.

Chartist.js - data points not visible, issue with formatting json data

I am using Chartist.js to generate a chart dynamically using json data derived from multiple text inputs. The chart itself is generated with the proper labels series but no data points show, just the x and y axis. I've discovered that chartist needs an additional set of brackets '[ ]' around the data for it too work.
My json data looks like this:
{labels: ['07:00', '10:00', '12:00', '14:00'],
series: [333, 444, 322, 222]}
Chartist needs the series data to be written like this: series: [[333, 444, 322, 222]]
I am initiating Chartist like this:
new Chartist.Line('.ct-chart', data);
I need a bit of help in getting the extra brackets around the generated series data. I have searched for a method but all I can find are examples where the data is already set, not generated.
You need to split the series from your data, and assign them in their respective places.
var dataJson = {labels: ['07:00', '10:00', '12:00', '14:00'],
series: [333, 444, 322, 222]}
new Chartist.Line('.ct-chart', {
labels: dataJson.labels,
series: [dataJson.series]
}, {
fullWidth: true,
chartPadding: {
right: 40
}
});
Let me know if it worked.

import and use d3.parseCsv version 5

I'm trying to use d3.csv.parse(string) as documented https://d3-wiki.readthedocs.io/zh_CN/master/CSV/#parse
I've installed "d3": "5.12.0" but I find that with
import * as d3 from "d3";
d3.csv.parse
is undefined. What version of d3 do those docs cover?
After some searching I find https://github.com/d3/d3-dsv and d3.csvParse but this doesn't work as I'm expecting.
const c = "Name,Count,Total Area\nFoo,6.0,0.13\nBar,8.0,0.24"
d3.csvParse(c, data => console.log(data))
I was expecting an array of objects, but instead I just get the first object
{Count: "6.0", Name: "Foo", Total Area: "0.13"}
Any ideas what I'm doing wrong here,
Thanks.
OK, so it seems that d3.csvParse() doesn't behave like d3.csv() and the second function argument is not a callback
https://github.com/d3/d3-dsv/blob/v1.1.1/README.md#dsv_parse
console.log( d3.csvParse(c))
[0: {Name: "Foo", Count: "6.0", Total Area: "0.13"}
1: {Name: "Bar", Count: "8.0", Total Area: "0.24"}]
Sorry for the noise!

How do I parse a JSON file to bind data to a d3 choropleth map

I'm trying to take data in from a JSON file and link it to my geoJSON file to create a choropleth map with the county colours bound to the "amount" value but also I would like a corresponding "comment" value to be bound to a div for when I mouseover that county.
My code at http://bl.ocks.org/eoiny/6244102 will work to generate a choropleth map when my counties.json data is in the form:
"Carlow":3,"Cavan":4,"Clare":5,"Cork":3,
But things get tricky when I try to use the following form:
{
"id":"Carlow",
"amount":11,
"comment":"The figures for Carlow show a something." },
I can't get my head around how join the "id": "Carlow" from counties.json and "id": "Carlow" path created from ireland.json, while at the same time to have access to the other values in counties.json i.e. "amount" and "comment".
Apologies for my inarticulate question but if anyone could point me to an example or reference I could look up that would be great.
I would preprocess the data when it's loaded to make lookup easier in your quantize function. Basically, replace this: data = json; with this:
data = json.reduce(function(result, county) {
result[county.id] = county;
return result;
}, {});
and then in your quantize function, you get at the amounts like this:
function quantize(d) {
return "q" + Math.min(8, ~~(data[d.id].amount * 9 / 12)) + "-9";
}
What the preprocessing does is turn this array (easily accessed by index):
[{id: 'xyz', ...}, {id: 'pdq', ...}, ...]
into this object with county keys (easily accessed by county id):
{'xyz': {id: 'xyz', ...}, 'pdq': {id: 'pdq', ...}, ...}
Here's the working gist: http://bl.ocks.org/rwaldin/6244803