Rails, Highchart maps - adding custom data - ruby-on-rails-4

I need some basic assistance with a Highmap (via Highcharts) I am trying to put in my Rails 4 app. I suspect I have some fundamental misunderstanding of it but can't find any clear guidance.
See a simple fiddle taken from the documentation, here
http://jsfiddle.net/SimonWalsh/zpdc1btu/
What I ultimately need to do is provide membership numbers for each country so that it will be displayed much the same as the population density is in this map.
I know I need to provide my data and the means to join it to the map data in
series : [{
data : data,
mapData: Highcharts.maps['custom/world'],
joinBy: ['iso-a2', 'code'],
name: 'Population density',
states: {
hover: {
color: '#BADA55'
}
}
}]
In this example, I am guessing that the data is being pulled from an external source and that the map data is the 'iso-a2' part of the array.
If this is the case, then why can't I supply this with my data....as an example see the added array with my data.....(just one example given for Denmark)
var mydata = [
{
"iso-a2": "dk",
"value": 30
},
]
and then do
series : [{
data : mydata,
mapData: Highcharts.maps['custom/world'],
joinBy: ['iso-a2', 'value'],
name: 'Population density',
states: {
hover: {
color: '#BADA55'
}
}
}]
This does not work.....any guidance at all (other than simply pointing me to docs would be greatly appreciated)

The joinBy specifies on which value you map a country with your data. With
joinBy: ['iso-a2', 'code']
you say that the 'iso-a2' value of the mapData should be equal to the 'code' value of your data. Therefore, your data must have this format:
var mydata = [
{
"code": "dk",
"value": 30
},
/* ... */
]

Related

Plotting multiple lines on a Cube.js line graph

Imagine a simple line graph plotting a person count (y-axis) against a custom time value (x-axis), as such:
Suppose you have another dimension, say specific groupings of people, how do you draw a separate line on this graph for each group?
You have to use the PivotConfig here an example I used in Angular
(EDIT) Here is the Query
Query = {
measures: ['Admissions.count'],
timeDimensions: [
{
dimension: 'Admissions.createdDate',
granularity: 'week',
dateRange: 'This quarter',
},
],
dimensions: ['Admissions.status'],
order: {
'Admissions.createdDate': 'asc',
},
}
(END EDIT)
PivotConfig = {
x: ['Admissions.createdDate.day'],
y: ['Admissions.status', 'measures'],
fillMissingDates: true,
joinDateRange: false,
}
Code to extract data from resultset :
let chartData = resultSet.series(this.PivotConfig).map(item => {
return {
label: item.title.split(',')[0], //title contains "ADMIS, COUNT"
data: item.series.map(({ value }) => value),
}
})
Result Object (not the one in the chart):
[{
"label": "ADMIS",
"data": [2,1,0,0,0,0,0]
},{
"label": "SORTIE",
"data": [2,1,0,0,0,0,0]
}]
Here is what the output looks like!
The chart renderer in the Developer Playground is meant to be quite simplistic; I'd recommend creating a dashboard app or using one of our frontend integrations in an existing project to gain complete control over chart rendering.

Why does geocoding let me choose an exact address and then once clicked show different city name?

This question concerns a variation on the Try It Yourself section on the Google Maps Place Autocomplete Address Sample, from which you can get a Fiddle with a demo API key.
The problem is I thought I had it working perfectly just now after spending two hours trying to get it to work the way I want. When I typed an address where I used to live, the autosuggest suggested my address, 82 hopkins rd, northfield, ct, then when I click the result it fills in my town as being Litchfield, CT, which is actually the larger town one town over, and also the same name of the County that Northfield is in. Google obviously knows about Northfield though it is a tiny town, because it shows the exact address I want but when I click it why would it show Litchfield? If I try some other towns, etc. I don't see this happening. I imagine there is no solution because when I tried to change:
{types: ['geocode']}); to {types: ['(cities)']}); and tried typing in Northfield, then it showed other states with the town Northfield but not Connecticut. So I guess my old town is too small for Google to care about but then why show it in the autosuggest? Strange...
// This example displays an address form, using the autocomplete feature
// of the Google Places API to help users fill in the information.
// This example requires the Places library. Include the libraries=places
// parameter when you first load the API. For example:
// <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places">
var placeSearch, autocomplete;
var componentForm = {
locality: 'short_name',
administrative_area_level_1: 'short_name',
};
function initAutocomplete() {
// Create the autocomplete object, restricting the search to geographical
// location types.
autocomplete = new google.maps.places.Autocomplete(
/** #type {!HTMLInputElement} */(document.getElementById('autocomplete')),
{types: ['geocode']});
// When the user selects an address from the dropdown, populate the address
// fields in the form.
autocomplete.addListener('place_changed', fillInAddress);
}
function fillInAddress() {
// Get the place details from the autocomplete object.
var place = autocomplete.getPlace();
for (var component in componentForm) {
document.getElementById(component).value = '';
document.getElementById(component).disabled = false;
}
// Get each component of the address from the place details
// and fill the corresponding field on the form.
for (var i = 0; i < place.address_components.length; i++) {
var addressType = place.address_components[i].types[0];
if (componentForm[addressType]) {
var val = place.address_components[i][componentForm[addressType]];
document.getElementById(addressType).value = val;
}
}lat = place.geometry.location.lat();
lng = place.geometry.location.lng();
document.getElementById('lat').value = lat;
document.getElementById('lon').value = lng;
}
// Bias the autocomplete object to the user's geographical location,
// as supplied by the browser's 'navigator.geolocation' object.
function geolocate() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var geolocation = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
var circle = new google.maps.Circle({
center: geolocation,
radius: position.coords.accuracy
});
autocomplete.setBounds(circle.getBounds());
});
}
}
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=NOTMYREALKEYHERE&libraries=places&callback=initAutocomplete"
async defer></script>
You're seeing these results because Northfield is not a city. According to Wikipedia, Northfield, CT is "an unincorporated village in the town of Litchfield, Litchfield County, Connecticut". However, you're in luck: The example that you're viewing is using the API output address_component, where formatted_address for your place is correctly listed as 82 Hopkins Rd, Northfield, CT 06778, USA.
From the "Geocoding Address Types section" in the Google Maps Javascript API documentation, there are a wide variety of types of geocoding result. Some, like locality, require incoporation; sublocality_level_1 through sublocality_level_5 refer to areas narrower than that.
Even without your Javascript, you can do a search:
https://maps.googleapis.com/maps/api/place/findplacefromtext/json?key=YOUR_KEY&inputtype=textquery&input=82+hopkins+rd,+northfield,+ct
...to get a place ID of ChIJZYpAVT2W54kRPnm9uVA3cpc, and then you can do a subsequent place search:
https://maps.googleapis.com/maps/api/place/details/json?key=YOUR_KEY&place_id=ChIJZYpAVT2W54kRPnm9uVA3cpc
...to reveal the different types of address component:
"address_components" : [
{
"long_name" : "82",
"short_name" : "82",
"types" : [ "street_number" ]
},
{
"long_name" : "Hopkins Road",
"short_name" : "Hopkins Rd",
"types" : [ "route" ]
},
{
"long_name" : "Northfield",
"short_name" : "Northfield",
"types" : [ "neighborhood", "political" ]
},
{
"long_name" : "Litchfield",
"short_name" : "Litchfield",
"types" : [ "locality", "political" ]
},
{
"long_name" : "Litchfield",
"short_name" : "Litchfield",
"types" : [ "administrative_area_level_3", "political" ]
},
{
"long_name" : "Litchfield County",
"short_name" : "Litchfield County",
"types" : [ "administrative_area_level_2", "political" ]
},
{
"long_name" : "Connecticut",
"short_name" : "CT",
"types" : [ "administrative_area_level_1", "political" ]
},
{
"long_name" : "United States",
"short_name" : "US",
"types" : [ "country", "political" ]
},
{
"long_name" : "06778",
"short_name" : "06778",
"types" : [ "postal_code" ]
}
To your point, this also gives a formatted_address of 82 Hopkins Rd, Northfield, CT 06778, USA, which is what you're looking for.
In the example you quoted, which is the "try it yourself" section on the Place Autocomplete Address Form sample, there are two separate processes happening: The Autocomplete searches for your address and suggests a formatted address, and then the place details call unpacks the structured pieces of the address as needed. However, as in the code comments:
<!-- Note: Selection of address components in this example is typical.
You may need to adjust it for the locations relevant to your app. See
https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete-addressform
-->
Thus, by tweaking the form as I have in this Fiddle, you can see that your formatted address includes Northfield, listed as a Neighborhood, even though the city is properly Litchfield. Note that this is also location-specific behavior, as "1600 Pennsylvania Avenue, Washington DC" returns a Neighborhood of "Northwest Washington" is omitted from the formatted address.
Thus, to get the behavior you want, just use the formatted_address, or consider expanding the address_components to provide the granularity you need.

"Total rows" in custom Power BI visualizations

I have a question about creating the custom visualization in Power BI.
I want to implement a "total row" functionality which is available in the built-in matrix visualization. The main concept is to automatically sum-up every value and group it by the rows. This is how it's looks like on the matrix visualization:
But, to be honest, I don't know how to achieve this. I try different things but I can't receive this grouped values in the dataViews.
I tried to analyze the built-in matrix.ts code but it's quite different that the custom visualizations code. I found the customizeQuery method which set the subtotalType property to the rows and columns - I tried to add this in my code but I don't see any difference in the dataViews (I don't found the grouped value).
Currently my capabilities.dataViewMappings is set like this:
dataViewMappings: [
{
conditions: [
{ 'Rows': { max: 3 } }
],
matrix: {
rows: {
for: { in: 'Rows' },
},
values: {
for: { in: 'Values' }
},
},
}
]
Does anyone know how we could achieve this "total row" functionality?
UPDATE 1
I already found the solution: when we implement the customizeQuery method (in the same way as the customizeQuery method in the matrix.ts code), and then add the reference to it in the powerbi.visuals.plugins.[visualisationName+visualisationAddDateEpoch].customizeQuery then it works as expected (I receive in the dataViews[0].matrix.row.root children elements that has the total values from row).
The only problem now is that I don't know exactly how to add correctly this reference to the customizeQuery method. For example the [visualisationName+visualisationAddDateEpoch] is Custom1451458639997, and I don't know what those number will be (I know only the name). I created the code in my visualisation constructor as below (and it's working):
constructor() {
var targetCustomizeQuery = this.constructor.customizeQuery;
var name = this.constructor.name;
for(pluginName in powerbi.visuals.plugins) {
var patt = new RegExp(name + "[0-9]{13}");
if(patt.test(pluginName)) {
powerbi.visuals.plugins[pluginName].customizeQuery = targetCustomizeQuery;
break;
}
}
}
But in my opinion this code is very dirty and inelegant. I want to improve it - what is the correct way to tell the Power BI that we implement the custom customizeQuery method and it should use it?
UPDATE 2
Code from update 1 works only with the Power BI in the web browser (web based). On the Power BI Desktop the customizeQuery method isn't invoked. What is the correct way to tell the Power BI to use our custom customizeQuery method? In the code from PowerBI-visuals repository using PowerBIVisualPlayground we could declare it in the plugin.ts file (in the same way like the matrix visual is done):
export let matrix: IVisualPlugin = {
name: 'matrix',
watermarkKey: 'matrix',
capabilities: capabilities.matrix,
create: () => new Matrix(),
customizeQuery: Matrix.customizeQuery,
getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => Matrix.getSortableRoles(),
};
But, in my opinion, from the Power BI Dev Tools we don't have access to add additional things to this part of code. Any ideas?
It seems you're missing the columns mapping in your capabilities. Take a look at the matrix capabilities (also copied for reference below) and as a first step adopt that structure initially. The matrix calculates the intersection of rows and columns so without the columns in capabilities its doubtful you'll get what you want.
Secondly, in the matrix dataview passed to Update you'll get a 'DataViewMatrixNode' with isSubtotal: true Take a look at the unit tests for matrix to see the structure.
dataViewMappings: [{
conditions: [
{ 'Rows': { max: 0 }, 'Columns': { max: 0 }, 'Values': { min: 1 } },
{ 'Rows': { min: 1 }, 'Columns': { min: 0 }, 'Values': { min: 0 } },
{ 'Rows': { min: 0 }, 'Columns': { min: 1 }, 'Values': { min: 0 } }
],
matrix: {
rows: {
for: { in: 'Rows' },
/* Explicitly override the server data reduction to make it appropriate for matrix. */
dataReductionAlgorithm: { window: { count: 500 } }
},
columns: {
for: { in: 'Columns' },
/* Explicitly override the server data reduction to make it appropriate for matrix. */
dataReductionAlgorithm: { top: { count: 100 } }
},
values: {
for: { in: 'Values' }
}
}
}],

How to search comma separated data in mongodb

I have movie database with different fields. the Genre field contains a comma separated string like :
{genre: 'Action, Adventure, Sci-Fi'}
I know I can use regular expression to find the matches. I also tried:
{'genre': {'$in': genre}}
the problem is the running time. it take lot of time to return a query result. the database has about 300K documents and I have done normal indexing over 'genre' field.
Would say use Map-Reduce to create a separate collection that stores the genre as an array with values coming from the split comma separated string, which you can then run the Map-Reduce job and administer queries on the output collection.
For example, I've created some sample documents to the foo collection:
db.foo.insert([
{genre: 'Action, Adventure, Sci-Fi'},
{genre: 'Thriller, Romantic'},
{genre: 'Comedy, Action'}
])
The following map/reduce operation will then produce the collection from which you can apply performant queries:
map = function() {
var array = this.genre.split(/\s*,\s*/);
emit(this._id, array);
}
reduce = function(key, values) {
return values;
}
result = db.runCommand({
"mapreduce" : "foo",
"map" : map,
"reduce" : reduce,
"out" : "foo_result"
});
Querying would be straightforward, leveraging the queries with an multi-key index on the value field:
db.foo_result.createIndex({"value": 1});
var genre = ['Action', 'Adventure'];
db.foo_result.find({'value': {'$in': genre}})
Output:
/* 0 */
{
"_id" : ObjectId("55842af93cab061ff5c618ce"),
"value" : [
"Action",
"Adventure",
"Sci-Fi"
]
}
/* 1 */
{
"_id" : ObjectId("55842af93cab061ff5c618d0"),
"value" : [
"Comedy",
"Action"
]
}
Well you cannot really do this efficiently so I'm glad you used the tag "performance" on your question.
If you want to do this with the "comma separated" data in a string in place you need to do this:
Either with a regex in general if it suits:
db.collection.find({ "genre": { "$regex": "Sci-Fi" } })
But not really efficient.
Or by JavaScript evaluation via $where:
db.collection.find(function() {
return (
this.genre.split(",")
.map(function(el) {
return el.replace(/^\s+/,"")
})
.indexOf("Sci-Fi") != -1;
)
})
Not really efficient and probably equal to above.
Or better yet and something that can use an index, the separate to an array and use a basic query:
{
"genre": [ "Action", "Adventure", "Sci-Fi" ]
}
With an index:
db.collection.ensureIndex({ "genre": 1 })
Then query:
db.collection.find({ "genre": "Sci-Fi" })
Which is when you do it that way it's that simple. And really efficient.
You make the choice.

Implement auto-complete feature using MongoDB search

I have a MongoDB collection of documents of the form
{
"id": 42,
"title": "candy can",
"description": "canada candy canteen",
"brand": "cannister candid",
"manufacturer": "candle canvas"
}
I need to implement auto-complete feature based on the input search term by matching in the fields except id. For example, if the input term is can, then I should return all matching words in the document as
{ hints: ["candy", "can", "canada", "canteen", ...]
I looked at this question but it didn't help. I also tried searching how to do regex search in multiple fields and extract matching tokens, or extracting matching tokens in a MongoDB text search but couldn't find any help.
tl;dr
There is no easy solution for what you want, since normal queries can't modify the fields they return. There is a solution (using the below mapReduce inline instead of doing an output to a collection), but except for very small databases, it is not possible to do this in realtime.
The problem
As written, a normal query can't really modify the fields it returns. But there are other problems. If you want to do a regex search in halfway decent time, you would have to index all fields, which would need a disproportional amount of RAM for that feature. If you wouldn't index all fields, a regex search would cause a collection scan, which means that every document would have to be loaded from disk, which would take too much time for autocompletion to be convenient. Furthermore, multiple simultaneous users requesting autocompletion would create considerable load on the backend.
The solution
The problem is quite similar to one I have already answered: We need to extract every word out of multiple fields, remove the stop words and save the remaining words together with a link to the respective document(s) the word was found in a collection. Now, for getting an autocompletion list, we simply query the indexed word list.
Step 1: Use a map/reduce job to extract the words
db.yourCollection.mapReduce(
// Map function
function() {
// We need to save this in a local var as per scoping problems
var document = this;
// You need to expand this according to your needs
var stopwords = ["the","this","and","or"];
for(var prop in document) {
// We are only interested in strings and explicitly not in _id
if(prop === "_id" || typeof document[prop] !== 'string') {
continue
}
(document[prop]).split(" ").forEach(
function(word){
// You might want to adjust this to your needs
var cleaned = word.replace(/[;,.]/g,"")
if(
// We neither want stopwords...
stopwords.indexOf(cleaned) > -1 ||
// ...nor string which would evaluate to numbers
!(isNaN(parseInt(cleaned))) ||
!(isNaN(parseFloat(cleaned)))
) {
return
}
emit(cleaned,document._id)
}
)
}
},
// Reduce function
function(k,v){
// Kind of ugly, but works.
// Improvements more than welcome!
var values = { 'documents': []};
v.forEach(
function(vs){
if(values.documents.indexOf(vs)>-1){
return
}
values.documents.push(vs)
}
)
return values
},
{
// We need this for two reasons...
finalize:
function(key,reducedValue){
// First, we ensure that each resulting document
// has the documents field in order to unify access
var finalValue = {documents:[]}
// Second, we ensure that each document is unique in said field
if(reducedValue.documents) {
// We filter the existing documents array
finalValue.documents = reducedValue.documents.filter(
function(item,pos,self){
// The default return value
var loc = -1;
for(var i=0;i<self.length;i++){
// We have to do it this way since indexOf only works with primitives
if(self[i].valueOf() === item.valueOf()){
// We have found the value of the current item...
loc = i;
//... so we are done for now
break
}
}
// If the location we found equals the position of item, they are equal
// If it isn't equal, we have a duplicate
return loc === pos;
}
);
} else {
finalValue.documents.push(reducedValue)
}
// We have sanitized our data, now we can return it
return finalValue
},
// Our result are written to a collection called "words"
out: "words"
}
)
Running this mapReduce against your example would result in db.words look like this:
{ "_id" : "can", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
{ "_id" : "canada", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
{ "_id" : "candid", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
{ "_id" : "candle", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
{ "_id" : "candy", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
{ "_id" : "cannister", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
{ "_id" : "canteen", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
{ "_id" : "canvas", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
Note that the individual words are the _id of the documents. The _id field is indexed automatically by MongoDB. Since indices are tried to be kept in RAM, we can do a few tricks to both speed up autocompletion and reduce the load put to the server.
Step 2: Query for autocompletion
For autocompletion, we only need the words, without the links to the documents.
Since the words are indexed, we use a covered query – a query answered only from the index, which usually resides in RAM.
To stick with your example, we would use the following query to get the candidates for autocompletion:
db.words.find({_id:/^can/},{_id:1})
which gives us the result
{ "_id" : "can" }
{ "_id" : "canada" }
{ "_id" : "candid" }
{ "_id" : "candle" }
{ "_id" : "candy" }
{ "_id" : "cannister" }
{ "_id" : "canteen" }
{ "_id" : "canvas" }
Using the .explain() method, we can verify that this query uses only the index.
{
"cursor" : "BtreeCursor _id_",
"isMultiKey" : false,
"n" : 8,
"nscannedObjects" : 0,
"nscanned" : 8,
"nscannedObjectsAllPlans" : 0,
"nscannedAllPlans" : 8,
"scanAndOrder" : false,
"indexOnly" : true,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"_id" : [
[
"can",
"cao"
],
[
/^can/,
/^can/
]
]
},
"server" : "32a63f87666f:27017",
"filterSet" : false
}
Note the indexOnly:true field.
Step 3: Query the actual document
Albeit we will have to do two queries to get the actual document, since we speed up the overall process, the user experience should be well enough.
Step 3.1: Get the document of the words collection
When the user selects a choice of the autocompletion, we have to query the complete document of words in order to find the documents where the word chosen for autocompletion originated from.
db.words.find({_id:"canteen"})
which would result in a document like this:
{ "_id" : "canteen", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
Step 3.2: Get the actual document
With that document, we can now either show a page with search results or, like in this case, redirect to the actual document which you can get by:
db.yourCollection.find({_id:ObjectId("553e435f20e6afc4b8aa0efb")})
Notes
While this approach may seem complicated at first (well, the mapReduce is a bit), it is actual pretty easy conceptually. Basically, you are trading real time results (which you won't have anyway unless you spend a lot of RAM) for speed. Imho, that's a good deal. In order to make the rather costly mapReduce phase more efficient, implementing Incremental mapReduce could be an approach – improving my admittedly hacked mapReduce might well be another.
Last but not least, this way is a rather ugly hack altogether. You might want to dig into elasticsearch or lucene. Those products imho are much, much more suited for what you want.
Thanks to #Markus solution, I came up with something similar with aggregations instead. Knowing that map-reduce are flagged as deprecated for later versions.
const { MongoDBNamespace, Collection } = require('mongodb')
//.replace(/(\b(\w{1,3})\b(\W|$))/g,'').split(/\s+/).join(' ')
const routine = `function (text) {
const stopwords = ['the', 'this', 'and', 'or', 'id']
text = text.replace(new RegExp('\\b(' + stopwords.join('|') + ')\\b', 'g'), '')
text = text.replace(/[;,.]/g, ' ').trim()
return text.toLowerCase()
}`
// If the pipeline includes the $out operator, aggregate() returns an empty cursor.
const agg = [
{
$match: {
a: true,
d: false,
},
},
{
$project: {
title: 1,
desc: 1,
},
},
{
$replaceWith: {
_id: '$_id',
text: {
$concat: ['$title', ' ', '$desc'],
},
},
},
{
$addFields: {
cleaned: {
$function: {
body: routine,
args: ['$text'],
lang: 'js',
},
},
},
},
{
$replaceWith: {
_id: '$_id',
text: {
$trim: {
input: '$cleaned',
},
},
},
},
{
$project: {
words: {
$split: ['$text', ' '],
},
qt: {
$const: 1,
},
},
},
{
$unwind: {
path: '$words',
includeArrayIndex: 'id',
preserveNullAndEmptyArrays: true,
},
},
{
$group: {
_id: '$words',
docs: {
$addToSet: '$_id',
},
weight: {
$sum: '$qt',
},
},
},
{
$sort: {
weight: -1,
},
},
{
$limit: 100,
},
{
$out: {
db: 'listings_db',
coll: 'words',
},
},
]
// Closure for db instance only
/**
*
* #param { MongoDBNamespace } db
*/
module.exports = function (db) {
/** #type { Collection } */
let collection
/**
* Runs the aggregation pipeline
* #return {Promise}
*/
this.refreshKeywords = async function () {
collection = db.collection('listing')
// .toArray() to trigger the aggregation
// it returns an empty curson so it's fine
return await collection.aggregate(agg).toArray()
}
}
Please check for very minimal changes for your convenience.