How to add some text for a layer deckgl? - deck.gl

I am new with deckgl. I want to add some text to my layer (any type of layer) but I'm not able to
Here is my example:
const layer = new PointCloudLayer({
id: 'point-c-layer',
data: [{
position: [39.826168, 21.422510],
normal: [-1, 0, 0],
color: [204, 102, 0],
}],
radiusPixels: 20,
});
What I should add in data array to add my text to this layer?

Add the text to a separate TextLayer like this:
const text = new TextLayer({
id: 'text-layer',
data: [
{text: '#San Francisco', coordinates: [-122.425586, 37.775049]}
]
})
return (<DeckGL {...viewport} layers={[pointcloud, text]} />)

Related

Ramda - how to add new properties to nested object

I am trying to add new properties width and height to nested objects.
My data structure looks like this:
const graph = {
id: 'root',
children: [
{
id: 'n1'
},
{
id: 'n2'
}
]
};
I am trying to add unique width and height properties to each child based on id
I tried R.lensPath. Here you can check it in ramda editor:
const widthLens = R.curry((id, data) => R.lensPath([
'children',
R.findIndex(R.whereEq({ id }),
R.propOr([], 'children', data)),
'width',
]));
const setWidth = widthLens('n1', graph);
R.set(setWidth, '100', graph);
And this is working almost as it should but it is adding only width plus I need to iterate over all children and return the same object with new properties. It also looks overcomplicated so any suggestions are more than welcome. Thank you.
There are several different ways of approaching this. But one possibility is to use custom lens types. (This is quite different from Ori Drori's excellent answer, which simply uses Ramda's lensPath.)
Ramda (disclaimer: I'm one of the authors) only supplies only a few specific types of lenses -- one for simple properties, another for array indices, and a third for more complex object paths. But it allows you to build ones that you might need. And lenses are not designed only for simple object/array properties. Think of them instead as a framing of some set of your data, something you can focus on.
So we can write a lens which focuses on the array element with a specific id. There are decisions to make about how we handle missing ids. I'll choose here -- if the id is not found -- to return undefined for a get and to append to the end on a set, but there are reasonable alternatives one might explore.
In terms of implementation, there is nothing special about id, so I will do this based on a specific named property and specialize it to id in a separate function. We could write this:
const lensMatch = (propName) => (key) => lens (
find (propEq (propName, key)),
(val, arr, idx = findIndex (propEq (propName, key), arr)) =>
update(idx > -1 ? idx : length (arr), val, arr)
)
const lensId = lensMatch ('id')
It would work like this:
const lens42 = lensId (42)
const a = [{id: 17, x: 'a'}, {id: 42, x: 'b'}, {id: 99, x: 'c'}, {id: 57, x: 'd'}]
view (lens42, a) //=> {id: 42, x: 'b'}
set (lens42, {id: 42, x: 'z', foo: 'bar'}, a)
//=> [{id: 17, x: 'a'}, {id: 42, x: 'z', foo: 'bar'}, {id: 99, x: 'c'}, {id: 57, x: 'd'}]
over (lens42, assoc ('foo', 'qux'), a)
//=> [{id: 17, x: 'a'}, {id: 42, x: 'b', foo: 'qux'}, {id: 99, x: 'c'}, {id: 57, x: 'd'}]
But then we need to deal with our width and height properties. One very useful way to do this is to focus on an object with given particular properties, so that we get something like {width: 100, height: 200}, and we pass an object like this into set. It turns out to be quite elegant to write:
const lensProps = (props) => lens (pick (props), mergeLeft)
And we would use it like this:
const bdLens = lensProps (['b', 'd'])
const o = ({a: 1, b: 2, c: 3, d: 4, e: 5})
view (bdLens, o) //=> {b: 2, d: 4}
set (bdLens, {b: 42, d: 99}, o) //=> {a: 1, b: 42, c: 3, d: 99, e: 5}
over (bdLens, map (n => 10 * n), o) //=> {a: 1, b: 20, c: 3, d: 40, e : 5}
Combining these, we can develop a function to use like this: setDimensions ('n1', {width: 100, height: 200}, graph) We first write a lens to handle the id and our dimension:
const lensDimensions = (id) => compose (
lensProp ('children'),
lensId (id),
lensProps (['width', 'height'])
)
And then we call the setter of this lens via
const setDimensions = (id, dimensions, o) =>
set (lensDimensions (id), dimensions, o)
We can put this all together as
const lensMatch = (propName) => (key) => lens (
find (propEq (propName, key)),
(val, arr, idx = findIndex (propEq (propName, key), arr)) =>
update(idx > -1 ? idx : length (arr), val, arr)
)
const lensProps = (props) => lens (pick (props), mergeLeft)
const lensId = lensMatch ('id')
const lensDimensions = (id) => compose (
lensProp ('children'),
lensId (id),
lensProps (['width', 'height'])
)
const setDimensions = (id, dimensions, o) => set (lensDimensions (id), dimensions, o)
const graph = {id: 'root', children: [{id: 'n1'}, {id: 'n2'}]}
console .log (setDimensions ('n1', {width: 100, height: 200}, graph))
//=> {id: "root", children: [{ id: "n1", height: 200, width: 100}, {id: "n2"}]}
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script> const {find, propEq, findIndex, update, length, lens, pick, mergeLeft, compose, lensProp, set} = R </script>
This clearly involves more lines of code than does the answer from Ori Drori. But it creates the useful, reusable lens creators, lensMatch, lensId, and lensProps.
Note: This as is will fail if we try to work with unknown ids. I have a fix for it, but I don't have the time right now to dig into why it fails, probably something to do with the slightly unintuitive way lenses compose. If I find time soon, I'll dig back into it. But for the moment, we can simply change lensProps to
const lensProps = (props) => lens (compose (pick (props), defaultTo ({})), mergeLeft)
And then an unknown id will append to the end:
console .log (setDimensions ('n3', {width: 100, height: 200}, graph))
//=> {id: "root", children: [{id: "n1"}, {id: "n2"}, {id: "n3", width : 100, height : 200}]}
You can use R.over with R.mergeLeft to add the properties to the object at the index:
const { curry, lensPath, findIndex, whereEq, propOr, over, mergeLeft } = R;
const graph = {"id":"root","children":[{"id":"n1"},{"id":"n2"}]};
const widthLens = curry((id, data) => lensPath([
'children',
findIndex(whereEq({ id }), propOr([], 'children', data)),
]));
const setValues = widthLens('n1', graph);
const result = over(setValues, mergeLeft({ width: 100, height: 200 }), graph);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

How to zoom to bounds of arcs with deck.gl?

I've created some arcs using deck.gl. When you click on different points/polygons, different arcs appear between countries. When doing this, I want the map to zoom to the bounds of those arcs.
For clarity, here is an example: When clicking on Glasgow, I'd want to zoom to the arc shown (as tightly as possible):
It appears that with WebMercatorViewport, you can call fitBounds
(see: https://deck.gl/docs/api-reference/core/web-mercator-viewport#webmercatorviewport)
It's not clear to me how this gets used, though. I've tried to find examples, but have come up short. How can I add this to what I have?
Here is the code for the arcs:
fetch('countries.json')
.then(res => res.json())
.then(data => {
console.log('data',data)
const inFlowColors = [
[0, 55, 255]
];
const outFlowColors = [
[255, 200, 0]
];
const countyLayer = new deck.GeoJsonLayer({
id: 'geojson',
data: data,
stroked: true,
filled: true,
autoHighlight: true,
lineWidthScale: 20,
lineWidthMinPixels: 1,
pointRadiusMinPixels: 10,
opacity:.5,
getFillColor: () => [0, 0, 0],
getLineColor: () => [0,0,0],
getLineWidth: 1,
onClick: info => updateLayers(info.object),
pickable: true
});
const deckgl = new deck.DeckGL({
mapboxApiAccessToken: 'pk.eyJ1IjoidWJlcmRhdGEiLCJhIjoiY2pudzRtaWloMDAzcTN2bzN1aXdxZHB5bSJ9.2bkj3IiRC8wj3jLThvDGdA',
mapStyle: 'mapbox://styles/mapbox/light-v9',
initialViewState: {
longitude: -19.903283,
latitude: 36.371449,
zoom: 1.5,
maxZoom: 15,
pitch: 0,
bearing: 0
},
controller: true,
layers: []
});
updateLayers(
data.features.find(f => f.properties.name == 'United States' )
);
function updateLayers(selectedFeature) {
const {exports, centroid, top_exports, export_value} = selectedFeature.properties;
const arcs = Object.keys(exports).map(toId => {
const f = data.features[toId];
return {
source: centroid,
target: f.properties.centroid,
value: exports[toId],
top_exports: top_exports[toId],
export_value: export_value[toId]
};
});
arcs.forEach(a => {
a.vol = a.value;
});
const arcLayer = new deck.ArcLayer({
id: 'arc',
data: arcs,
getSourcePosition: d => d.source,
getTargetPosition: d => d.target,
getSourceColor: d => [0, 55, 255],
getTargetColor: d => [255, 200, 0],
getHeight: 0,
getWidth: d => d.vol
});
deckgl.setProps({
layers: [countyLayer, arcLayer]
});
}
});
Here it is as a Plunker:
https://plnkr.co/edit/4L7HUYuQFM19m9rI
I try to make it simple, starting from a raw implementation with ReactJs then try to translate into vanilla.
In ReactJS I will do something like that.
Import LinearInterpolator and WebMercatorViewport from react-map-gl:
import {LinearInterpolator, WebMercatorViewport} from 'react-map-gl';
Then I define an useEffect for viewport:
const [viewport, setViewport] = useState({
latitude: 37.7577,
longitude: -122.4376,
zoom: 11,
bearing: 0,
pitch: 0
});
Then I will define a layer to show:
const layerGeoJson = new GeoJsonLayer({
id: 'geojson',
data: someData,
...
pickable: true,
onClick: onClickGeoJson,
});
Now we need to define onClickGeoJson:
const onClickGeoJson = useCallback((event) => {
const feature = event.features[0];
const [minLng, minLat, maxLng, maxLat] = bbox(feature); // Turf.js
const viewportWebMercator = new WebMercatorViewport(viewport);
const {longitude, latitude, zoom} = viewport.fitBounds([[minLng, minLat], [maxLng, maxLat]], {
padding: 20
});
viewportWebMercator = {
...viewport,
longitude,
latitude,
zoom,
transitionInterpolator: new LinearInterpolator({
around: [event.offsetCenter.x, event.offsetCenter.y]
}),
transitionDuration: 1500,
};
setViewport(viewportWebMercator);
}, []);
First issue: in this way we are fitting on point or polygon clicked, but what you want is fitting arcs. I think the only way to overcome this kind of issue is to add a reference inside your polygon about bounds of arcs. You can precompute bounds for each feature and storage them inside your geojson (the elements clicked), or you can storage just a reference in feature.properties to point another object where you have your bounds (you can also compute them on the fly).
const dataWithComputeBounds = {
'firstPoint': bounds_arc_computed,
'secondPoint': bounds_arc_computed,
....
}
bounds_arc_computed need to be an object
bounds_arc_computed = {
minLng, minLat, maxLng, maxLat,
}
then on onClick function just take the reference
const { minLng, minLat, maxLng, maxLat} = dataWithComputedBounds[event.features[0].properties.reference];
const viewportWebMercator = new WebMercatorViewport(viewport);
...
Now just define our main element:
return (
<DeckGL
layers={[layerGeoJson]}
initialViewState={INITIAL_VIEW_STATE}
controller
>
<StaticMap
reuseMaps
mapStyle={mapStyle}
preventStyleDiffing
mapboxApiAccessToken={YOUR_TOKEN}
/>
</DeckGL>
);
At this point we are pretty close to what you already linked (https://codepen.io/vis-gl/pen/pKvrGP), but you need to use deckgl.setProps() onClick function instead of setViewport to change your viewport.
Does it make sense to you?

Google Sheet API => Sheet creation OK but can't set specific defaultFormat

I succeeded to create a sheet, but horizontal and vertical alignments are set to defaults, not the ones I tried to set.
Here is the code
spreadsheet_body = {
'properties': {
'title': str("sme_"+jour_heure.read()),
'defaultFormat': {
'horizontalAlignment': 'LEFT',
'verticalAlignment': 'MIDDLE'
}
}
}
print(spreadsheet_body)
# Création du fichier Google Sheet
request = service_sheet.spreadsheets().create(body=spreadsheet_body)
response = request.execute()
print (response)
My sheet is correctly created by vertical and horizontal alignments are not set.
print (spreadsheet_body)
prints:
{'properties': {'title': 'sme_18_04_2020\n', 'defaultFormat': {'horizontalAlignment': 'LEFT', 'verticalAlignment': 'MIDDLE'}}}
but
print (response)
prints:
{'spreadsheetId': '1F2o5F8B-efoAOkg-d3z_2tGcPhvlp8gtcvGOGkGvAGI', 'properties': {'title': 'sme_18_04_2020\n', 'locale': 'en_US', 'autoRecalc': 'ON_CHANGE', 'timeZone': 'Etc/GMT', 'defaultFormat': {'backgroundColor': {'red': 1, 'green': 1, 'blue': 1}, 'padding': {'top': 2, 'right': 3, 'bottom': 2, 'left': 3}, 'verticalAlignment': 'BOTTOM', 'wrapStrategy': 'OVERFLOW_CELL', 'textFormat': {'foregroundColor': {}, 'fontFamily': 'arial,sans,sans-serif', 'fontSize': 10, 'bold': False, 'italic': False, 'strikethrough': Fal ./..
Title is OK but verticalAlignment is not ...
What did I do wrong ?
i believe you want to create new sheet with required alignment, with your code everything is a string like "horizontalAlignment": "LEFT" anything between "" is string and line ends with ; instead of ,
try this for creating new sheet and alignment
function alignment(){
//change sheet name as per your requirement
var ss = SpreadsheetApp.getActiveSpreadsheet().insertSheet("yousheet");
var range = ss.getRange("A1:Z");
range.setHorizontalAlignment('left').setVerticalAlignment('middle');
}

Dynamic Flot graph - show hide series by clicking on legend text or box on graph

I am working on dynamic flot graph with 3 series. My need is to hide/show series when clicked on legend. I have seen different examples that will work fine for static graphs but for dynamic graph, even it works first time but when graph is updated with new data values then everything is displaying with default options. once I hide the series, I want it to be hided until I click again to show it.
Here is my code. Basically, I am fetching data from JSON and updating the flot graph dynamically in 10 sec intervals. so new data will be shown every 10 sec and this is where the series is showing back again.
<div id="placeholder4" style="width:1000px;height:300px;background:#C89175"></div>
<script type="text/javascript">
$(function() {
somePlot = null;
togglePlot = function(seriesIdx)
{
var someData = somePlot.getData();
someData[seriesIdx].lines.show = !someData[seriesIdx].lines.show;
somePlot.setData(someData);
somePlot.draw();
}
// Initilizaiton of Series and Counter
var i = 0;
var data_Total = [[], [], []];
// data_Total[0] : Stip Data
// data_Total[1] : Decline Data
// data_Total[2] : Volume Data
//Setting Options for Graph Display
var options = {
points: { show: true },
//legend: {toggle: true },
series: {
lines: { show: true }
},
legend: {
labelFormatter: function(label, series){
return ''+label+'';
}
},
grid: {backgroundColor: "#FCFCFC", labelMargin:12,hoverable: true,tickColor:"#AD5C5C" },
xaxis: { mode: "categories", show:true,color:"white",axisLabel:'Time Series' },
yaxis:{show:true,color:"white",min:0,max:10000,axisLabel:'Total/ Stip/ Decline'}
}
//Function that will be called recursively with specified Time Interval
function fetchData() {
//Function that will push data in to Series1 thru an ajax call
function getDPSStipData(series) {
var stipItem = [series.data[i][0], series.data[i][1]];
data_Total[0].push(stipItem);
}
$.ajax({
url: "./JSon/stipdpssec.json",
method: 'GET',
dataType: 'json',
success: getDPSStipData
});
//Function that will push data in to Series2 thru an ajax call
function getDPSDeclineData(series) {
var declineItem = [series.data[i][0], series.data[i][1]];
data_Total[1].push(declineItem);
}
$.ajax({
url: "./JSon/declinedpssec.json",
method: 'GET',
dataType: 'json',
success: getDPSDeclineData
});
//Function that will push data in to Series3 thru an ajax call
function getDPSTotalVolumeData(series) {
var totalVolItem = [series.data[i][0], series.data[i][1]];
data_Total[2].push(totalVolItem);
}
$.ajax({
url: "./JSon/totaldpssec.json",
method: 'GET',
dataType: 'json',
success: getDPSTotalVolumeData
});
//Moving forward the ticks if size > 10
if (data_Total[0].length > 10)
{
data_Total[0] = data_Total[0].splice(1,10);
data_Total[1] = data_Total[1].splice(1,10);
data_Total[2] = data_Total[2].splice(1,10);
}
// Plotting of Graph
//$.plot($("#placeholder4"), [{ data: data_Total[2], label: "TotalVolume"},{ data: data_Total[0], label: "Stip",yaxis:2 }, { data: data_Total[1], label: "Decline",yaxis:2 }], options);
somePlot=$.plot($("#placeholder4"), [{ data: data_Total[2], label: "TotalVolume",idx:0},{ data: data_Total[0], label: "Stip",color: "green",idx:1 }, { data: data_Total[1], label: "Decline",color:"red",idx:2 }], options);
i++;
}
//fetchData
setInterval(fetchData, 10000);
});
</script>
Here's a quick example I put together for you.
somePlot = null;
togglePlot = function(seriesIdx)
{
var someData = somePlot.getData();
someData[seriesIdx].lines.show = !someData[seriesIdx].lines.show;
somePlot.setData(someData);
somePlot.draw();
}
var data = [
{
label: 'foo',
color: 'red',
data: [[1, 300], [2, 300], [3, 300], [4, 300], [5, 300]],
idx: 0},
{
label: 'bar',
color: 'blue',
data: [[1, 800], [2, 600], [3, 400], [4, 200], [5, 0]],
idx: 1},
{
label: 'baz',
color: 'yellow',
data: [[1, 100], [2, 200], [3, 300], [4, 400], [5, 500]],
idx: 2},
{
label: 'dart',
color: 'green',
data: [[1, 500], [2, 350], [3, 400], [4, 700], [5, 50]],
idx: 3}
];
somePlot = $.plot($("#placeholder"), data, {
series: {
lines: {
show: true
}
},
legend: {
labelFormatter: function(label, series){
return ''+label+'';
}
}
});
You can provide legend clicks in a way that survives rerendering the graph like so:
HTML:
<div id=graph></div>
JS:
$('#graph').on('click', 'div.legend tr', function() {
var tr = $(this);
var index = tr.parent().find('tr').index(tr);
// Do something with the fact they clicked item (index)
});
There's nothing stored in the legend marking what each row represents, so you'll need to bring that info in from someplace else - all the above code does is get you the index of the legend item clicked.
For usability you should tell the user this is clickable:
CSS:
#graph div.legend tr {
cursor: pointer;
}

JavaScript InfoVis Toolkit Error loading JSON from external file using AJAX

I have the code snippet shown below in my application . What I am trying to accomplish is this: anytime a checkbox is clicked, the bar chart should be updated.
In my error console I'm getting:
"Error: TypeError: values is undefined Source file:hit.js Line: 11017.
I tried assigning the JSON directly in the function (var json = ...) and it works as it should but I can't get it to work by loading the JSON from the file. What am I doing wrong?
$(':checkbox').click(function () {
init();
function init()
{
var request = new XMLHttpRequest( );
request.open("GET", "GetGenes.php", false);
request.send(null);
var json = request.response;
console.log(request.response);
var barChart = new $jit.BarChart({
//id of the visualization container
injectInto: 'infovis',
//whether to add animations
animate: true,
//horizontal or vertical barcharts
orientation: 'horizontal',
//bars separation
barsOffset: 0.5,
//visualization offset
Margin: {
top: 5,
left: 5,
right: 5,
bottom: 5
},
//labels offset position
labelOffset:5,
//bars style
type:'stacked',
//whether to show the aggregation of the values
showAggregates:true,
//whether to show the labels for the bars
showLabels:true,
//label styles
Label: {
type: 'HTML', //Native or HTML
size: 9,
family: 'Arial',
color: 'black'
},
//tooltip options
Tips: {
enable: true,
onShow: function(tip, elem) {
tip.innerHTML = "<b>" + elem.name + "</b>: " + elem.value;
}
}
});
barChart.loadJSON(json);
}
})
genes.php contains:
echo "{'label': ['label 1', 'label 2', 'label 3', 'label 4'],'values': [{'label': 'date A','values': [20, 40, 15, 5]}, {'label': 'date B','values': [30, 10, 45, 10]}, {'label': 'date E','values': [38, 20, 35, 17]}, {'label': 'date F','values': [58, 10, 35, 32]}, {'label''date D','values': [55, 60, 34, 38]}, {'label': 'date C','values': [26, 40, 25, 40]}]};";
There is an error in your returned JSON :
'label''date D'
There is a missing colon there. Don´t know if that solves anything.