Cartojs4 - markers with multivalues donut chart - mapping

I would like to create a marker as we can see in the Carto SalesQuest product.
I made a cluster map from my CARTOjs 4 following this article provided by CARTO
https://carto.com/blog/inside/tile-aggregation/
I can play with CARTOCSS to style my layer but I am wondering what would be the best practices / methodology to achieve such an example : see below
Multivalues donut chart, cluster markers as seen in CARTO Salesquest
My own version of clusters with CARTO JS 4
My code look like this (hidden API KEY)
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0" />
<meta charset="utf-8" />
<!-- Include Carto.js -->
<script src="https://cartodb-libs.global.ssl.fastly.net/carto.js/v4.0.2/carto.min.js"></script>
<!-- Include Leaflet -->
<script src="https://unpkg.com/leaflet#1.2.0/dist/leaflet.js"></script>
<link href="https://unpkg.com/leaflet#1.2.0/dist/leaflet.css" rel="stylesheet" />
<style>
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
const map = L.map('map').setView([30, 0], 3);
L.tileLayer('http://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png', {
maxZoom: 18
}).addTo(map);
// define client
const client = new carto.Client({
apiKey: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX',
username: 'anagraph-clement'
});
// define source of data using a SQL query
const source = new carto.source.SQL(`
select *, 1 as count from pochesfils_carto
`);
// Aggregation option
const aggregation = new carto.layer.Aggregation({
threshold: 5,
resolution: 32,
placement: carto.layer.Aggregation.placement.SAMPLE,
columns: {
total_agg: {
aggregateFunction: carto.layer.Aggregation.operation.SUM,
aggregatedColumn: "count"
}
}
});
// define CartoCSS code to style data on map
const style = new carto.style.CartoCSS(`
#layer {
marker-fill: ramp([total_agg], (#3288bd, #99d594, #e6f598, #ffffbf, #fee08b, #fc8d59, #d53e4f), quantiles);
marker-width: ramp([total_agg], 8 ,40 , quantiles);
marker-line-color: ramp([total_agg], (#5F4690, #1D6996, #38A6A5, #0F8554, #73AF48, #EDAD08, #E17C05, #CC503E, #94346E, #6F4070, #666666), (5,10,20,30,50,66,75,100,150), "=", category);
marker-line-width: 5;
marker-line-opacity: 0.75;
}
#layer::labels {
text-name: [total_agg];
text-face-name: 'DejaVu Sans Book';
text-size: 8;
text-fill: #FFFFFF;
text-label-position-tolerance: 0;
text-halo-radius: 1;
text-halo-fill: black;
text-allow-overlap: true;
text-placement: point;
text-placement-type: dummy;
}
`);
// create CARTO layer from source and style variables
// and defining the interactivity of columns
// when featureOver and featureClick events are executed
const cartolayer = new carto.layer.Layer(source, style, { aggregation });
// add CARTO layer to the client
client.addLayer(cartolayer);
// get tile from client and add them to the map object
client.getLeafletLayer().addTo(map);
</script>
</body>
</html>
Thak you for letting me know if you have any hint on this type of marker creation. (svg ? d3.js ? chart.js ? CARTOCSS + TurboCARTO ? etc...)

There's not much room to improve your styles, the donut chart is needing de-aggregated data which you don't have in an aggregated visualization. And on top of that, CartoCSS is not capable of rendering easily the "donut parts" because that would very likely involve quite a lot of trigonometry computation to set angles to start/stop the different parts of the chart. SalesQuest uses a leaflet plugin and traditional marker rendering directly in the browser loading GeoJSON data, instead of using CARTO Maps API. So to achieve that visualization you need to render the proper data using SQL API and then use pure Leaflet coding to render it as markers.
A quick google search gave me at least this and this to inspire yourself.

Related

Apply Multiple Filters

Dispite the tutorial here https://konvajs.org/docs/filters/Multiple_Filters.html of using multiple filters, I have been unsuccessful in applying multiple to an image in a NodeJS space.
Here is an example of the "fun" I went through trying to get it to work:
(Konva, layer, imageNode) => {
console.log('Callback');
imageNode.cache();
// imageNode.filters([
// Konva.Filters.Brighten,
// Konva.Filters.HSL,
// ]);
imageNode.filters([Konva.Filters.Posterize]);
imageNode.levels(0.8); // between 0 and 1
// imageNode.brightness(0.8);
// imageNode.contrast(0.8);
// imageNode.saturation(0.8);
// imageNode.cache();
// imageNode.filters([
// // Konva.Filters.Brighten,
// Konva.Filters.HSL,
// ]);
// imageNode.hue(0.5);
// imageNode.saturation(0.8);
// imageNode.luminance(0.85);
imageNode.cache();
As you can see, I'm trying to use a mix of brightness, contrast, saturation, hue, lumiance; however some work, some don't, and it all doesn't seem very reliable and if any work, only one ever does.
So how do I for example, brighten, and saturate an image?
Example Image I would like to "bring into the light" by adjusting different factors of it.
My end goal is something like this:
Update #1
No Luck, I cannot even get brightness to work on a small 50x35 image:
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/konva#8.3.12/konva.min.js"></script>
<meta charset="utf-8" />
<title>Konva Image Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
var layer = new Konva.Layer();
// main API:
var imageObj = new Image();
imageObj.onload = function () {
var imageNode = new Konva.Image({
x: 0,
y: 0,
image: imageObj,
width: 50, // 2048 / 2,
height: 35, // 1440 / 2,
});
imageNode.cache({ pixelRatio: 1})
imageNode.filters([
Konva.Filters.Brighten,
// Konva.Filters.HSL,
// Konva.Filters.Invert
]);
// imageNode.filters([Konva.Filters.Posterize]);
// imageNode.levels(0.2); // between 0 and 1
imageNode.brightness(0.8);
// imageNode.contrast(0.8);
// imageNode.saturation(0.8);
// imageNode.cache({ pixelRatio: 1})
// imageNode.filters([
// // Konva.Filters.Brighten,
// Konva.Filters.HSL,
// ]);
// imageNode.hue(0.5);
// imageNode.saturation(0.8);
// imageNode.luminance(0.85);
// imageNode.cache({ pixelRatio: 1})
layer.add(imageNode);
stage.add(layer);
};
imageObj.src = 'C:/Users/<User>/Downloads/Small.png';
</script>
</body>
</html>
Update #2
Ok this seems to be firstly an issue with doing this off a local html file, as not even taking the code from https://konvajs.org/docs/filters/Brighten.html and swapping in an absolute path to a file on my machine, works.
Seems to be an issue with editing images on a local HTML file, something stops them actually taking the changes.

Disable Chrome's pull-to-refresh on iPhone

I am implementing a drawing app on my site and trying to prevent overscroll while the user draws on the canvas. Despite trying several reported solutions, I cannot disable Chrome's pull-to-refresh.
According to https://developers.google.com/web/updates/2017/11/overscroll-behavior, the following one line of css should do the trick..yet pull-to-refresh and an annoying user experience persists. Any ideas?
<!DOCTYPE html>
<html>
<style type="text/css">
body {
/* Disables pull-to-refresh but allows overscroll glow effects. */
overscroll-behavior-y: contain;
}
</style>
<body>
<h1>Simple Site</h1>
</body>
<script type="text/javascript">
</script>
</html>
I had the same problem. I found that CSS property only works on chrome-android.
Finally, I successfully prevent pull-to-refresh on chrome-ios through the following:
<script>
function preventPullToRefresh(element) {
var prevent = false;
document.querySelector(element).addEventListener('touchstart', function(e){
if (e.touches.length !== 1) { return; }
var scrollY = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
prevent = (scrollY === 0);
});
document.querySelector(element).addEventListener('touchmove', function(e){
if (prevent) {
prevent = false;
e.preventDefault();
}
});
}
preventPullToRefresh('#id') // pass #id or html tag into the method
</script>
For newer version of Chrome v75.0.3770.103 on IOS
preventDefault()
does no longer disable pull-to-refresh.
Instead, you can add in
{passive:false}
as additional option into the event listener.
E.g.
window.addEventListener("touchstart", eventListener, {passive:false});
In newer version of chrome in IOS preventDefault(); is no longer disables pull to refresh.
For latest, you can just add inobounce js cdn to your header of the page you want to disable pull to refresh. This will do the magic.
<script src="https://cdnjs.cloudflare.com/ajax/libs/inobounce/0.2.0/inobounce.js"></script>
The only thing that worked for me was iNoBounce.
Example React snippet:
import 'inobounce'
...
<div style={{
height: windowHeight,
WebkitOverflowScrolling: 'touch',
overflowY: 'auto' }}
>Content goes here</div>

p:chart bar's ChartSeries not synchronized

I've been working on PrimeFaces Bar chart model,and its getting on my nerves now,I just can't get how its setting chart series values.What expected result should be that One Open for say abc and one closed for xyz, But what It shows one open and one closed for xyz,ABC is Ignored. If I change the position of
barModel.addSeries(close);
barModel.addSeries(open);
to
barModel.addSeries(open);
barModel.addSeries(close);
Then It shows One open and one closed for only abc,Not xyz.
here's my getBarModel() first,
public BarChartModel getBarModel() {
barModel=new BarChartModel();
ChartSeries open = new ChartSeries();
open.setLabel("OPEN");
ChartSeries close = new ChartSeries();
close.setLabel("Close");
open.set("abc", 1);
close.set("xyz", 1);
barModel.addSeries(close);
barModel.addSeries(open);
barModel.setMouseoverHighlight(false);
barModel.setShowPointLabels(false);
barModel.setTitle("Incident_application");
barModel.setLegendPosition("ne");
barModel.setShowDatatip(false);
barModel.setShowPointLabels(true);
barModel.setExtender("chartExtender");
barModel.setSeriesColors("A2CC39,1B75BA");
Axis xAxis = barModel.getAxis(AxisType.X);
xAxis.setLabel("Applications");
barModel.setAnimate(true);
Axis yAxis = barModel.getAxis(AxisType.Y);
yAxis.setLabel("No. of Incident");
yAxis.setMin(0);
yAxis.setMax(20);
yAxis.setTickInterval("10");
return barModel;
}
and here is xhtml
<h:outputScript library="primefaces" name="jquery/jquery.js" target="head" />
<h:outputScript library="primefaces" name="jquery/jquery-plugins.js" target="head" />
<style type="text/css">
table.jqplot-table-legend {
border: none;
font-size: x-large;
}
div.jqplot-table-legend-swatch
{
width: 2vw;
height: 2vh;
}
div.jqplot-gridData{
display:none;
}
div.jqplot-xaxis-tick{
font-weight:bold !important;
}
.jqplot-axis.jqplot-xaxis{
font-weight:bold;
}
.jqplot-point-label {
font-size: 300%;
color: black;
font-weight: bold;
}
.jqplot-target{
color:black !important;
}
</style>
<script>
function chartExtender() {
this.cfg.grid = {
background: 'transparent',
gridLineColor: '#303030',
drawBorder: false,
};
}
</script>
<h:form id="barChart">
<p:chart id="bar" type="bar" model="#{incidentBarChartController.barModel}" />
<p:poll interval="10" global="false" update="barChart"/>
</h:form>
</h:body>
Snapshots of expected and Actual results are added below.
Expected Result
Actual Result
Edit
After adding
open.set("abc", 1);
open.set("xyz",0);
close.set("xyz", 1);
close.set("abc", 0);
I still get weird results i.e.
1 open and 1 closed for xyz and 0 open and 0 closed for xyz.
The only solution is that you as a developer make sure all series contains all keys and populate the keys which you do not have and make sure all keys in all series are in the same order.
The reason for this is that only you know the real order (that is why a LinkedHashMap is used in the series). jQplot or PrimeFaces cannot know this. If e.g. the first series misses a key and that series is used as the 'primary' to iterate over, where is the missing key going to put. And if e.g. at position 6, both have a key that is not in the other set, which one should be taken first? The jqPlot or PrimeFaces cannot know. All reasons that you as a developer should make sure it is explicit by putting all keys in all series.

Using drag and drop in phonegap installed

I'm using simple HTML code to work on a drag and drop function as part of my IOS app. This piece of code works perfectly in the browser however when I copy it into my xcode file the image won't drag.
<!DOCTYPE HTML>
<html>
<head>
<style type="text/css">
#div1 {width:350px;height:70px;padding:10px;border:1px solid #aaaaaa;}
</style>
<script>
function allowDrop(ev)
{
ev.preventDefault();
}
function drag(ev)
{
ev.dataTransfer.setData("Text",ev.target.id);
}
function drop(ev)
{
ev.preventDefault();
var data=ev.dataTransfer.getData("Text");
ev.target.appendChild(document.getElementById(data));
}
</script>
</head>
<body>
<p>Drag the image into the rectangle:</p>
<div id="div1" ondrop="drop(event)" ondragover="allowDrop(event)"></div>
<br>
<img id="drag1" src="images/face.png" draggable="true" ondragstart="drag(event)" width="336" height="69">
</body>
</html>
Reading over Apple's webview capabilities document states that you have to set a CSS property for this to work.
From the docs:
Making an Element Draggable
WebKit provides automatic support to let users drag common items, such as images, links
and selected text. You can extend this support to include specific elements on an HTML
page. For example, you could mark a particular div or span tag as draggable.
To mark an arbitrary element as draggable, add the -webkit-user-drag attribute
(previously -khtml-user-drag) to the style definition of the element. Because it is a
cascading style sheet (CSS) attribute, you can include it as part of a style definition
or as an inline style attribute on the element tag. The values for this attribute are
listed in Table 1.
So standard draggables will work out of the box, but other elements like div or span require the -webkit-user-drag attribute to be appended.
Example:
#drag1 { -webkit-user-drag: element; }
Source: https://developer.apple.com/library/mac/documentation/AppleApplications/Conceptual/SafariJSProgTopics/Tasks/DragAndDrop.html
This code is what worked in the end.
<script>
var nodeList = document.getElementsByClassName('contents');
for(var i=0;i<nodeList.length;i++) {
var obj = nodeList[i];
obj.addEventListener('touchmove', function(event) {
var touch = event.targetTouches[0];
// Place element where the finger is
event.target.style.left = touch.pageX + 'px';
event.target.style.top = touch.pageY + 'px';
event.preventDefault();
});
}
</script>

Easeljs addEventListener do not work when bitmap added to stage

I do not understand, why eventlistener (copied from demo) does not work, If I add an bitmap to stage. It seems that bitmap causes problem, because if I add another circle etc. the click works fine.
See example:
<!DOCTYPE html>
<html>
<head>
<title>EaselJS demo: Simple animation</title>
<link href="../_shared/demo.css" rel="stylesheet" type="text/css">
<script src="http://code.createjs.com/easeljs-0.7.0.min.js"></script>
<script>
var stage, circle;
var counter = 0;
var ticounter = 0
var images = []
var mytext = 'kk';
var lepakko;
var mx = 0;
function init() {
stage = new createjs.Stage("demoCanvas");
var circle = new createjs.Shape();
circle.graphics.beginFill("red").drawCircle(0, 0, 50);
circle.x = 500;
circle.y = 500;
stage.addChild(circle);
stage.update();
lepakko = new createjs.Bitmap("halloween-bat.png");
//Click works, if line below is commented out, why?
//stage.addChild(lepakko);
circle.addEventListener("click",circle_event);
stage.update();
}
function circle_event(event) {
alert("clicked");
};
</script>
</head>
<body onLoad="init();">
<canvas id="demoCanvas" width="700" height="700">
alternate content
</canvas>
</body>
</html>
The click should not work by default. EaselJS library needs explicit enabling of the mouseover event. You need to add:
stage.enableMouseOver(20);
after creating the stage. To change the cursor to a pointer when it's over the object there is a property in EaselJS called cursor:
// doesn't work, even if a function is decleared outside
// circle.addEventListener("mouseover", function() { document.body.style.cursor = "pointer"; });
// this works
circle.cursor = "pointer";
Method enableMouseOver is documented on EaselJS website. Do note that listening to mouseover and other events in EaselJS is a lot more demanding for a web browser.

Resources