I am not familiar with cytoscape or Java, so I do not know if there is a way to define a node size which depend on node's degree.
Currently I am using the following:
cytoscapeobj.set_style(
[
{
'selector':node,
'style': {
'font-family': 'helvetica',
'font-size': '20px',
'label': 'data(id)'
}
},
{
'selector': 'edge',
'style': {
'font-family': 'helvetica',
'font-size': '20px',
'width' : 'mapData(weight)' # it is actually not working
}
},
{
'selector': 'node[Degree>0]',
'style': {
'width': '100px',
'height': '100px'
}
},
{
'selector': 'node[Degree>1]',
'style': {
'width': '150px',
'height': '150px'
}
},
{
'selector': 'node[Degree>2]',
'style': {
'width': '200px',
'height': '200px'
}
}
]
)
I have thousands of nodes, some of them has degree 1 (most of them), then I have nodes with degree 2, 3, 4, 5 ,... 100, ...
It would be not easy to add a selector for each of them.
Do you know if there is an easier way to plot any node's degree?
You can define any required parameters in the node's data and then use style mappers to apply that data to style properties like width or any other. So your code is actually the right way to do that.
It doesn't work because mapData() and data() works in a different ways. While data() will just apply the data value to that property, as it does to label in your example, the mapData() requires additional parameters to be set.
Check this out:
width: mapData(weight, 0, 100, 1, 3)
In that case, mapData will take the value of data.weight and then check where is that value between 0 and 100 and proportionally set width to the according value between 1 and 3.
In my experience, I find it more convenient to use values, precalculated in the code. So I'd go with creating the data.width param and setting to the desired width and then just map the simple data() mapper to that value.
Related
My documents are like this:
{'start': 0, 'stop': 3, 'val': 3}
{'start': 2, 'stop': 4, 'val': 1}
{'start': 5, 'stop': 6, 'val': 4}
We can imagine that each document occupies the x-coordinates from 'start' to 'stop',
and has a certain value 'val' ('start' < 'stop' is guaranteed).
The goal is to plot a line showing the sum of these values 'val' from all the
documents which occupy an x-coordinate:
this graph online
In reality there are many documents with many different 'start' and 'stop' coordinates. Speed is important, so:
Is this possible to do with at most a couple of elastic search requests? how?
What I've tried:
With one elastic search request we can get the min_start, and max_stop coordinates. These will be the boundaries of x.
Then we divide the x-coordinates into N intervals, and in a loop for each interval we make an elastic search request: to filter out all the documents which lie completely outside of this interval, and do a sum aggregation of 'val'.
This approach takes too much time because there are N+1 requests, and if we want to have a line with higher precision, the time will increase linearly.
Code:
N = 300 # number of intervals along x
x = []
y = []
data = es.search(index='index_name',
body={
'aggs': {
'min_start': {'min': {'field': 'start'}},
'max_stop': {'max': {'field': 'stop'}}
}
})
min_x = data['aggregations']['min_start']['value']
max_x = data['aggregations']['max_stop']['value']
x_from = min_x
x_step = (max_x - min_x) / N
for _ in range(N):
x_to = x_from + x_step
data = es.search(
index='index_name',
body= {
'size': 0, # to not return any actual documents
'query': {
'bool': {
'should': [
# start is in the current x-interval:
{'bool': {'must': [
{'range': {'start': {'gte': x_from}}},
{'range': {'start': {'lte': x_to}}}
]}},
# stop is in the current x-interval:
{'bool': {'must': [
{'range': {'stop': {'gte': x_from}}},
{'range': {'stop': {'lte': x_to}}}
]}},
# current x-interval is inside start--stop
{'bool': {'must': [
{'range': {'start': {'lte': x_from}}},
{'range': {'stop': {'gte': x_to}}}
]}}
],
'minimum_should_match': 1 # at least 1 of these 3 conditions should match
}
},
'aggs': {
'vals_sum': {'sum': {'field': 'val'}}
}
}
)
# Append info to the lists:
x.append(x_from)
y.append(data['aggregations']['vals_sum']['value'])
# Next x-interval:
x_from = x_to
from matplotlib import pyplot as plt
plt.plot(x, y)
The right way to do this in one single query is to use the range field type (available since 5.2) instead of using two fields start and stop and reimplementing the same logic. Like this:
PUT test
{
"mappings": {
"properties": {
"range": {
"type": "integer_range"
},
"val": {
"type":"integer"
}
}
}
}
Your documents would look like this:
{
"range" : {
"gte" : 0,
"lt" : 3
},
"val" : 3
}
And then the query would simply leverage an histogram aggregation like this:
POST test/_search
{
"size": 0,
"aggs": {
"histo": {
"histogram": {
"field": "range",
"interval": 1
},
"aggs": {
"total": {
"sum": {
"field": "val"
}
}
}
}
}
}
And the results are as expected: 3, 3, 4, 1, 0, 4
I have been trying to make sense out of TimestampedGeoJson plugin from folium.
I want to draw lines that change their colour over time. At the moment, what I do is to completely redraw a line every time I need to change the color, with the massive overhead that entails.
Another issue is how to specify the time in the features. At the moment, I have this example:
import folium
from folium.plugins import TimestampedGeoJson
m = folium.Map(
location=[42.80491692, -4.62577249],
zoom_start=10
)
data = [
{
'coordinates': [
[-4.018876661, 43.11843382],
[-4.856537491, 42.82202193],
],
'dates': [
'2017-06-02T00:00:00',
'2017-06-02T00:10:00'
],
'color': 'red'
},
{
'coordinates': [
[-4.018876661, 43.11843382],
[-4.856537491, 42.82202193],
],
'dates': [
'2017-06-02T00:00:00',
'2017-06-02T00:20:00'
],
'color': 'blue'
},
]
features = [
{
'type': 'Feature',
'geometry': {
'type': 'LineString',
'coordinates': d['coordinates'],
},
'properties': {
'times': d['dates'],
'style': {
'color': d['color'],
'weight': d['weight'] if 'weight' in d else 5
}
}
}
for d in data
]
TimestampedGeoJson({
'type': 'FeatureCollection',
'features': features,
}, period='PT1M', add_last_point=True).add_to(m)
m.save('dynamic4.html')
To me, the first date does not make any sense, but apparently it is required because otherwise the browser will not draw anything.
So:
a) How can I change the style without redrawing the lines?
b) What does the time mean? How can I specify a consistent time sequence?
I will first try to address your questions individually and I'm putting a full solution of what I would do in the end. essentially:
change the TimestampedGeoJson _template variable to change the style_function and it will enable it to make style dinamic
make sure you have one timestep per coordinates in the TimestampedGeoJson data
For avoiding confusion try to not overlap features or have features missing data of in a certain timestep
I believe in your scenario you only have one feature but change colors in different timesteps
Addressing your questions:
a) How can I change the style without redrawing the lines?
I don't think it's possible from folium itself, it would be necessary to pass a style_function to TimestampedGeoJson which is not even a parameter on the class init at the moment. It seems to be hard to do that because you would need to translate a python style_function, to a javascript style_function.
There would be a simple work around. Inside the class definition of TimestampedGeoJson it uses a _template variable as a string template of the javascript code so you could potentially adapt this template however you want but using javascript.
class TimestampedGeoJson(MacroElement):
.... hidding lines to save space
_template = Template("""
{% macro script(this, kwargs) %}
.... hidding lines to save space
style: function (feature) {
return feature.properties.style;
},
onEachFeature: function(feature, layer) {
if (feature.properties.popup) {
layer.bindPopup(feature.properties.popup);
}
}
})
{% endmacro %}
""") # noqa
..... hidding lines to save space
So for changing the line color at every time step you could change this part of the template:
style: function (feature) {
return feature.properties.style;
},
by this : loop through an array of colors
style: function(feature) {
lastIdx=feature.properties.colors.length-1
currIdx=feature.properties.colors.indexOf(feature.properties.color);
if(currIdx==lastIdx){
feature.properties.color = feature.properties.colors[0]
}
else{
feature.properties.color =feature.properties.colors[currIdx+1]
}
return {color: feature.properties.color}
},
change it so that you update the color inside properties.style every timestep.
b) What does the time mean? How can I specify a consistent time sequence?
TimestampedGeoJson is using the library Leaflet.TimeDimension, so TimestampedGeoJson corresponds to L.TimeDimension.Layer.GeoJSON.
from that documentation you get
"coordTimes, times or linestringTimestamps: array of times that can be associated with a geometry (datestrings or ms). In the case of a LineString, it must have as many items as coordinates in the LineString."
so essentially to be consistent just make sure
1. for each feature the times size is the same as coordinates and
2. use a valid datestrings or ms format
3. if your dates are increasing by a constant set your period to that value
putting all together I mainly changed in your previous example:
1) added a _template variable with the new style_function and change the TimestampedGeoJson default template
2) changed the two features coordinates a bit to show that the two features you set were overlapping and some timesteps and at some timesteps just the first feature was defined and later just the second feature was defined so it get's confusing what is happening in each time step.
3) added a list of colors to loop through for each feature
from jinja2 import Template
_template = Template("""
{% macro script(this, kwargs) %}
L.Control.TimeDimensionCustom = L.Control.TimeDimension.extend({
_getDisplayDateFormat: function(date){
var newdate = new moment(date);
console.log(newdate)
return newdate.format("{{this.date_options}}");
}
});
{{this._parent.get_name()}}.timeDimension = L.timeDimension(
{
period: {{ this.period|tojson }},
}
);
var timeDimensionControl = new L.Control.TimeDimensionCustom(
{{ this.options|tojson }}
);
{{this._parent.get_name()}}.addControl(this.timeDimensionControl);
var geoJsonLayer = L.geoJson({{this.data}}, {
pointToLayer: function (feature, latLng) {
if (feature.properties.icon == 'marker') {
if(feature.properties.iconstyle){
return new L.Marker(latLng, {
icon: L.icon(feature.properties.iconstyle)});
}
//else
return new L.Marker(latLng);
}
if (feature.properties.icon == 'circle') {
if (feature.properties.iconstyle) {
return new L.circleMarker(latLng, feature.properties.iconstyle)
};
//else
return new L.circleMarker(latLng);
}
//else
return new L.Marker(latLng);
},
style: function(feature) {
lastIdx=feature.properties.colors.length-1
currIdx=feature.properties.colors.indexOf(feature.properties.color);
if(currIdx==lastIdx){
feature.properties.color = feature.properties.colors[currIdx+1]
}
else{
feature.properties.color =feature.properties.colors[currIdx+1]
}
return {color: feature.properties.color}
},
onEachFeature: function(feature, layer) {
if (feature.properties.popup) {
layer.bindPopup(feature.properties.popup);
}
}
})
var {{this.get_name()}} = L.timeDimension.layer.geoJson(
geoJsonLayer,
{
updateTimeDimension: true,
addlastPoint: {{ this.add_last_point|tojson }},
duration: {{ this.duration }},
}
).addTo({{this._parent.get_name()}});
{% endmacro %}
""")
import folium
from folium.plugins import TimestampedGeoJson
m = folium.Map(
location=[42.80491692, -4.62577249],
zoom_start=9
)
data = [
{
'coordinates': [
[-4.018876661, 43.11843382],
[-4.856537491, 42.82202193],
],
'dates': [
'2017-06-02T00:00:00',
'2017-06-02T00:10:00'
],
'color': 'brown',
'colors':["black","orange","pink"],
},
{
'coordinates': [
[-4.058876661, 43.11843382],
[-4.936537491, 42.82202193],
],
'dates': [
'2017-06-02T00:00:00',
'2017-06-02T00:10:00'
],
'color': 'blue',
'colors':["red","yellow","green"],
},
]
features = [
{
'type': 'Feature',
'geometry': {
'type': 'LineString',
'coordinates': d['coordinates'],
},
'properties': {
'times': d['dates'],
'color': d["color"],
'colors':d["colors"]
}
}
for d in data
]
t=TimestampedGeoJson({
'type': 'FeatureCollection',
'features': features,
}, period='PT10H', add_last_point=True)
t._template=_template
t.add_to(m)
m.save('original.html')
I have an Eve app publishing a simple read-only (GET) interface. It is interfacing a MongoDB collection called centroids, which has documents like:
[
{
"name":"kachina chasmata",
"location":{
"type":"Point",
"coordinates":[-116.65,-32.6]
},
"body":"ariel"
},
{
"name":"hokusai",
"location":{
"type":"Point",
"coordinates":[16.65,57.84]
},
"body":"mercury"
},
{
"name":"caƱas",
"location":{
"type":"Point",
"coordinates":[89.86,-31.188]
},
"body":"mars"
},
{
"name":"anseris cavus",
"location":{
"type":"Point",
"coordinates":[95.5,-29.708]
},
"body":"mars"
}
]
Currently, (Eve) settings declare a DOMAIN as follows:
crater = {
'hateoas': False,
'item_title': 'crater centroid',
'url': 'centroid/<regex("[\w]+"):body>/<regex("[\w ]+"):name>',
'datasource': {
'projection': {'name': 1, 'body': 1, 'location.coordinates': 1}
}
}
DOMAIN = {
'centroids': crater,
}
Which will successfully answer to requests of the form http://hostname/centroid/<body>/<name>. Inside MongoDB this represents a query like: db.centroids.find({body:<body>, name:<name>}).
What I would like to do also is to offer an endpoint for all the documents of a given body. I.e., a request to http://hostname/centroids/<body> would answer the list of all documents with body==<body>: db.centroids.find({body:<body>}).
How do I do that?
I gave a shot by including a list of rules to the DOMAIN key centroids (the name of the database collection) like below,
crater = {
...
}
body = {
'item_title': 'body craters',
'url': 'centroids/<regex("[\w]+"):body>'
}
DOMAIN = {
'centroids': [crater, body],
}
but didn't work...
AttributeError: 'list' object has no attribute 'setdefault'
Got it!
I was assuming the keys in the DOMAIN structure was directly related to the collection Eve was querying. That is true for the default settings, but it can be adjusted inside the resources datasource.
I figured that out while handling an analogous situation as that of the question: I wanted to have an endpoint hostname/bodies listing all the (unique) values for body in the centroids collection. To that, I needed to set an aggregation to it.
The following settings give me exactly that ;)
centroids = {
'item_title': 'centroid',
'url': 'centroid/<regex("[\w]+"):body>/<regex("[\w ]+"):name>',
'datasource': {
'source': 'centroids',
'projection': {'name': 1, 'body': 1, 'location.coordinates': 1}
}
}
bodies = {
'datasource': {
'source': 'centroids',
'aggregation': {
'pipeline': [
{"$group": {"_id": "$body"}},
]
},
}
}
DOMAIN = {
'centroids': centroids,
'bodies': bodies
}
The endpoint, for example, http://127.0.0.1:5000/centroid/mercury/hokusai give me the name, body, and coordinates of mercury/hokusai.
And the endpoint http://127.0.0.1:5000/bodies, the list of unique values for body in centroids.
Beautiful. Thumbs up to Eve!
I am using the python librairy for Adwords and I need to select the audiences that I want to link to a given ad group. I need to select audiences that are either remarketing and similar, custom intent or affinity.
How can I set the audiences when creating an ad group?
So after some testing here is how to do it:
create the ad group and get its id
add the audience using AdGroupCriterionService
Here is my code for the 3 types of audiences I wanted to use (self.client is initiating adwords.AdWordsClient.LoadFromStorage):
ad_group_criterion_service = self.client.GetService('AdGroupCriterionService', version='v201809')
audience_custom_affinity = {
'xsi_type': 'BiddableAdGroupCriterion',
'adGroupId': 'my_ad_group_id',
'criterion': {
'xsi_type': 'CriterionCustomAffinity',
'type': 'CUSTOM_AFFINITY',
'customAffinityId': 'my_audience_id'
}
}
audience_custom_intent = {
'xsi_type': 'BiddableAdGroupCriterion',
'adGroupId': 'my_ad_group_id',
'criterion': {
'xsi_type': 'CriterionCustomIntent',
'type': 'CUSTOM_INTENT',
'customIntentId': 'my_audience_id'
}
}
audience_remarketing = {
'xsi_type': 'BiddableAdGroupCriterion',
'adGroupId': 'my_ad_group_id',
'criterion': {
'xsi_type': 'CriterionUserList',
'type': 'USER_LIST',
'userListId': 'my_audience_id'
}
}
operations = [
{'operator': 'ADD',
'operand': audience_custom_affinity},
{ 'operator': 'ADD',
'operand': audience_custom_intent},
{'operator': 'ADD',
'operand': audience_remarketing}
]
ad_group_criterion_service.mutate(operations)
I've been trying to write my own Google Sheets wrapper, and it's been a frustrating experience so far. The thing I'm stuck on at the moment is how to get a symmetrical in / out format of sheet data.
Basically, I want to call values().get(), alter the resulting hash, and send that same hash back up to update().
I'm happy to write my own solution to process or coerce the output of values().get() to the structure that batchUpdate() needs, but I need the formatting information of each of the cells to do that.
batchUpdate() expects formatting information like this:
bod = {
'updateCells': {
'start': {
'sheetId': 0,
'rowIndex': 7,
'columnIndex': 0
},
'rows': [
{
'values': [
{
"userEnteredValue": {
'stringValue': 'LOL'
},
"userEnteredFormat": {
'backgroundColor': {
'red': .2,
'blue': .75,
'green': .75
}
}
},
{
"userEnteredValue": {
'stringValue': 'LOL2'
},
"userEnteredFormat": {
'backgroundColor': {
'red': .2,
'blue': 1,
'green': .75
}
}
},
{
"userEnteredValue": {
'stringValue': 'LOL3'
},
"userEnteredFormat": {
'backgroundColor': {
'red': .2,
'blue': 1,
'green': 1
}
}
}
]
}
],
'fields': 'userEnteredValue,userEnteredFormat.backgroundColor'
}
}
How I'm retrieving values currently looks something like this:
import requests
import json
from oauth2client.service_account import ServiceAccountCredentials
from apiclient.discovery import build
#Set up credentials object
auth_key_url = "<JSON CREDENTIALS FILE>"
file_contents = requests.get(auth_key_url).content
key_dict = json.loads(file_contents)
creds = ServiceAccountCredentials.from_json_keyfile_dict(key_dict, ['https://spreadsheets.google.com/feeds'])
#Now build the API object
discoveryUrl = "https://sheets.googleapis.com/$discovery/rest?version=v4"
gsheet = build('sheets', 'v4', discoveryServiceUrl=discoveryUrl, credentials=creds)
result = gsheet.spreadsheets().values().get(spreadsheetId="<A SHEET ID>", range="Sheet1!A1:ZZ").execute()
This produces "results", which is a dictionary with 2 keys, "range" and "values", and "values" is a list of lists of the values of the spreadsheet. These lists do not contain formatting data - just the values in the cells.
Can someone show me, in Python, how I can get cell value, background color, alignment, and other cell formatting information from spreadsheets().values() or from the spreadsheet?
The spreadsheets.values.get endpoint only returns the values. If you want a more complete picture of the spreadsheet (formatting, etc) then you need to use the spreadsheets.get endpoint:
https://developers.google.com/sheets/reference/rest/v4/spreadsheets/get
Make sure to pass either includeGridData=true or pass a value for the fields that includes sheets.data so that the cell data is returned. Pass a value in the range parameter to limit the results to only a specific range.