Chartjs: display different average line while grouping - charts

Currently I'm working on chartjs and I found that is extremely fast to learn(at least for normal task).Currently I'm facing a problem: I was asked to display a grouped bar chart. like in figure.
grouped bar char
as you can see for the date 30-08-2016 there will be 3 distinct values for B,C,D and for 31-09-2016 same group of data but with different values.
I was asked also to add an average line for each different group in the chart
to look like this:
grouped bar chart with averages
I need to bind the start of one average line with the associated bar group.
I serached on internet but i couldn't find any example.Can you tell me if there is an example or give some suggestion? thanks in advance

this was my solution: i created a plugin and than tried to draw a line for each component of the chart. the code is quite messy (i'll try to clean)
// Define a plugin to provide average for different groups of data
Chart.plugins.register({
afterDatasetsDraw: function(chartInstance, easing) {
// To only draw at the end of animation, check for easing === 1
{
var ctx = chartInstance.chart.ctx;
var mapAverageLinePoints = {};
chartInstance.data.datasets.forEach(function (dataset, i) {
var meta = chartInstance.getDatasetMeta(i);
if (!meta.hidden) {
meta.data.forEach(function(element, index) {
var dataString = dataset.label;
var groupAverageLine = mapAverageLinePoints[dataString];
if(groupAverageLine==null)
{
groupAverageLine = [];
}
//store the point coordinate and the value
var linePoint =
{
x : position.x,
y : position.y,
value: dataset.data[index],
avg : 0
}
//adding the point to the array going to be stored in the map that group the point by the label
groupAverageLine.push(linePoint);
mapAverageLinePoints[dataString]=groupAverageLine;
}
);
}
});
for (var type in mapAverageLinePoints) {
var avgLinePoints = mapAverageLinePoints[type];
//NON E' in valore bensì rispecchia la sommatoria dei posY utilizzati nella rappresentazione
var totalYAxis=0;
var totale=0;
var labelNumero=0;
for(var k=0;k<avgLinePoints.length;k++)
{
var point = avgLinePoints[k];
totalYAxis+=point.y;
totale+=point.value;
//jump the first one
if(k>=1)
{
var prevPoint = avgLinePoints[k-1];
//k start from 0!!!!
var avgYAxis = (totalYAxis/(k+1));
var avg = (totale/(k+1));
// here i draw the line starting from the previous average
ctx.beginPath();
ctx.moveTo(point.x, avgYAxis);
ctx.strokeStyle = '#979797';
ctx.lineTo((prevPoint.x), prevPoint.avg);
ctx.stroke();
point.avg=avgYAxis;
//this one is for drawing a "o" where two segments collide
var fontSize = 12;
var fontStyle = 'normal';
var fontFamily = 'Helvetica Neue';
ctx.font = Chart.helpers.fontString(fontSize, fontStyle, fontFamily);
ctx.fillText("avg: "+(avg/range).toLocaleString() + rangeSuffix + ' €', point.x, point.y+(point.value>0?-30:+10));
}
else{
//for the first one only record the y as the avg
point.avg=point.y;
}
var fontSize = 10;
var fontStyle = 'normal';
var fontFamily = 'Helvetica Neue';
ctx.font = Chart.helpers.fontString(fontSize, fontStyle, fontFamily);
ctx.fillText("o", point.x, point.avg);
}
labelNumero=labelNumero+1;
}
}
}
});
the result is this one:
chart result

Related

How do I leave the clicked point highlighted in dygraphs?

I am using the selected shapes to draw a larger diamond shape on my graph. When a user clicks a point. I display the data in another div, but I want to leave the clicked point highlighted. In other words, I want to 'toggle' data behind the points on and off and the clicked points need to show if they are included in the dataset. I believe I have seen this somewhere but I cannot find it. Is there a 'standard' way of leaving a clicked point in the 'highlight' state when you mouse away after clicking?
Here is my code. The pointClickCallback is getting the data through ajax and displaying it in another div. That part works. I just want to leave the point highlighted so I know which points I have clicked on.
I also need the point to revert back to normal when I click a second time. This is a toggle that allows me to select and unselect points.
EDIT: I found the interaction model example but when I add it to my code I lose my pointClickCallback functionality. I saw the call to captureCanvas and the interaction model structure.
var g = new Dygraph(document.getElementById('rothmangraph'), lines, {
//showRangeSelector: true,
title: "Personal Wellness Index (PWI)",
labels: ['Date', 'Index'],
color: ['#006699'],
valueRange: [0, 101],
axisLabelFontSize: 12,
drawPoints: true,
gridLineColor: "#aaaaaa",
includeZero: true,
strokeWidth: 2,
rightGap: 20,
pointSize: 4,
highlightCircleSize: 8,
series : {
Index: {
drawHighlightPointCallback : Dygraph.Circles.DIAMOND
},
},
axes: {
y: {
pixelsPerLabel: 20,
},
x: {
valueFormatter: function(ms) {
return ' ' + strftime('%m/%d/%Y %r',new Date(ms)) + ' ';
},
axisLabelWidth: 60,
axisLabelFormatter: function(d, gran) {
return strftime('%m/%d %I:%M %p',new Date(d.getTime())) ;
}
}
},
underlayCallback: function (canvas, area, g) {
var warning = g.toDomCoords(0,41);
var critical = g.toDomCoords(0,66);
// set background color
canvas.fillStyle = graphCol;
canvas.fillRect(area.x, area.y, area.w, area.h);
// critical threshold line
canvas.fillStyle = "#cc0000";
canvas.fillRect(area.x,warning[1],area.w,2);
// warning threshold line
canvas.fillStyle = "#cccc00";
canvas.fillRect(area.x,critical[1],area.w,2);
},
pointClickCallback: function(e,point) {
var idx = point.idx;
var line = lines[idx];
var sqltime = strftime('%Y-%m-%d %H:%M:%S',new Date(line[0]));
var dispdate = strftime('%m/%d %r',new Date(line[0]));
_secureAjax({
url: '/ajax/getDataPoint',
data: {'patient_id': pid, "rdate": sqltime},
success: function (result) {
// parse and add row to table if not exists.
var data = JSON.parse(result);
var aid = data['id'];
var indexCol = "#a9cced"
if (line[1] <= 65) indexCol = "#ede1b7";
if (line[1] <= 40) indexCol = "#e5bfcc";
var headerinfo = '<th class="'+aid+'"><span class="showindex" style="background-color:'+indexCol+'">'+line[1]+'</span></th>';
var fixdate = dispdate.replace(' ','<br>');
var headerdate = '<th class="'+aid+'">'+fixdate+'</th>';
// skip if already exists
var found = false;
var whichone = false;
$('#headerdate tr th').each(function(idx, item) {
if (fixdate == $(this).html()) {
found = true;
whichone = idx;
}
});
if (!found) {
$.each(data, function (idx, item) {
$('#' + idx).append('<td class="'+aid+'" style="width:70px">' + item + '</td>');
});
$('#headerdate tr').append(headerdate);
$('#headerinfo tr').append(headerinfo);
} else {
$('tr').each(function() {
$('.'+aid).remove();
});
}
}
});
}
});
}

marker with polyline while dragging the marker using leaflet

Hi I have connection between marker with polyline like this Image .
I am attaching a sample here.
How Can I make drag possible that when I drag the the marker with polyline.
example , If I drag the marker 3 it should also update the polyline point and where ever I put the marker 3 polyline should connect with marker 3.
I need this type of drag event that can update the polyline also when dragging the marker.
I am using leaflet for this purpose but still unable to solve the dragging logic of marker with polyline.
Here is the sample code I am using
$http.get("db/getConnectionData.php").then(function (response) {
$scope.links1 = response.data.records;
// $scope.showArrow();
angular.forEach($scope.links1, function(value, i) {
var source_panoId = $scope.links1[i].s_panoId;
var dest_panoId = $scope.links1[i].d_panoId;
var sPanoID = $scope.links1[i].sourcePano_id;
var dPpanoID = $scope.links1[i].destPano_id;
angular.forEach($scope.panoramas, function(value, index) {
if($scope.panoramas[index].panoId == source_panoId){
if($scope.links.indexOf($scope.panoramas[index])== -1){
$scope.links.push($scope.panoramas[index]);
}
var SlatLang = $scope.panoramas[index].project_latLng ;
var SLatLngArr = SlatLang.split(",");
var Slat = parseFloat(SLatLngArr[0]);
var Slang = parseFloat(SLatLngArr[1]);
var polypoint1 = [Slat, Slang];
angular.forEach($scope.panoramas, function(value, index1) {
if($scope.panoramas[index1].panoId == dest_panoId){
if($scope.links.indexOf($scope.panoramas[index1])== -1){
$scope.links.push($scope.panoramas[index1]);
}
var DlatLang = $scope.panoramas[index1].project_latLng ;
var DLatLngArr = DlatLang.split(",");
var Dlat = parseFloat(DLatLngArr[0]);
var Dlang = parseFloat(DLatLngArr[1]);
var polypoint2 = [Dlat, Dlang];
// Draw seperate polyline for each connection
polyline = L.polyline([[Slat, Slang],[Dlat, Dlang]],
{
color: 'blue',
weight: 5,
opacity: .7,
}
).addTo(map);
$scope.polycoords.push(polyline);
}
});
}
});
Here is the code that I am using to make drag of marker with polyline
angular.forEach($scope.panoramas, function(value, index4){
$scope.markers[index4].on('dragstart', function(e){
var latlngs = polyline.getLatLngs(),
latlng = $scope.markers[index4].getLatLng();
for (var i = 0; i < latlngs.length; i++) {
if (latlng.equals(latlngs[i])) {
this.polylineLatlng = i;
}
}
});//dragstart
$scope.markers[index4].on('drag', function(e){
var latlngs = polyline.getLatLngs(),
latlng = $scope.markers[index4].getLatLng();
latlngs.splice(this.polylineLatlng, 1, latlng);
polyline.setLatLngs(latlngs);
});//drag
$scope.markers[index4].on('dragend', function(e){
delete this.polylineLatlng;
});//dragEnd
});
First, when creating the marker, remember to pass the draggable option as true, like this:
var marker = L.marker(latLng, { draggable: true });
Now, check which drag event you want to attach a listener to and then call the redraw function of the polyline inside the callback, like this:
// var polyline defined somewhere
marker.on('drag', function (e) {
polyline.redraw();
});
If this doesn't work, please provide sample code so we can work around with it.
Edit
You also need to change the coordinates of the polyline, otherwise redraw will do nothing. Check out this answer on SO and see if it fits your needs.
Edit 2
You're using an array of polylines while the answer just uses one polyline which has the array of coordinates, so in your case you need to use two loops to accomplish the same task. You can make this faster and maybe use an object as a lookup table to get the right polyline for each marker, for example, like this:
var table = {};
// ...
table[marker] = polyline;
Then later you can get the polyline used for each marker. But anyway, here's what I think would work in your case the way it is in the sample (it was a little hard to understand but I hope it works for you).
I don't know where you are putting the second part of your sample (the event handlers) but I assume it's not inside the double loop that is creating the polylines, right? So this is what I came up with:
marker.on('dragstart', function (e) {
var markerLatLng = marker.getLatLng();
this.polylineLatLngs = [];
for (var i = 0; i < $scope.polycoords.length; i++) {
var polyline = $scope.polycoords[i];
var latLngs = polyline.getLatLngs()
for (var j = 0; j < latLngs.length; j++) {
if (markerLatLng.equals(latLngs[j])) {
this.polylineLatLngs.push([i, j]);
}
}
}
});
marker.on('drag', function (e) {
for (var i = 0; i < this.polylineLatLngs.length; i++) {
var polyline = $scope.polycoords[this.polylineLatLngs[i][0]];
var latLngs = polyline.getLatLngs();
var markerLatLng = marker.getLatLng();
latLngs.splice(this.polylineLatLngs[i][1], 1, markerLatLng);
polyline.setLatLngs(latLngs);
}
});
I am getting this type of behavior. Please let me know how I can solve this .
Thank you for your time.
This is the polyline created by getting data from db or by making the connection between panorama.
This Image when I start dragging the marker 2 I got the result like this
This image when I dragged the marker 3.
This type of result I am getting using the source code you provided above.

How to remove L.rectangle(boxes[i])

I few days ago I implement a routingControl = L.Routing.control({...}) which works perfect for my needs. However I need for one of my customer also the RouteBoxer which I was also able to implement it. Now following my code I wants to remove the boxes from my map in order to draw new ones. However after 2 days trying to find a solution I've given up.
wideroad is a param that comes from a dropdown list 10,20,30 km etc.
function routeBoxer(wideroad) {
this.route = [];
this.waypoints = []; //Array for drawBoxes
this.wideroad = parseInt(wideroad); //Distance in km
this.routeArray = routingControl.getWaypoints();
for (var i=0; i<routeArray.length; i++) {
waypoints.push(routeArray[i].latLng.lng + ',' + routeArray[i].latLng.lat);
}
this.route = loadRoute(waypoints, this.drawRoute);
}; //End routeBoxer()
drawroute = function (route) {
route = new L.Polyline(L.PolylineUtil.decode(route)); // OSRM polyline decoding
boxes = L.RouteBoxer.box(route, this.wideroad);
var bounds = new L.LatLngBounds([]);
for (var i = 0; i < boxes.length; i++) {
**L.rectangle(boxes[i], {color: "#ff7800", weight: 1}).addTo(this.map);**
bounds.extend(boxes[i]);
}
console.log('drawRoute:',boxes);
this.map.fitBounds(bounds);
return route;
}; //End drawRoute()
loadRoute = function (waypoints) {
var url = '//router.project-osrm.org/route/v1/driving/';
var _this = this;
url += waypoints.join(';');
var jqxhr = $.ajax({
url: url,
data: {
overview: 'full',
steps: false,
//compression: false,
alternatives: false
},
dataType: 'json'
})
.done(function(data) {
_this.drawRoute(data.routes[0].geometry);
//console.log("loadRoute.done:",data);
})
.fail(function(data) {
//console.log("loadRoute.fail:",data);
});
}; //End loadRoute()
Well, my problem is now how to remove previously drawn boxes in order to draw new ones because of changing the wideroad using a dropdown list. Most of this code I got from the leaflet-routeboxer application.
Thanks in advance for your help...
You have to keep a reference to the rectangles so you can manipulate them (remove them) later. Note that neither Leaflet nor Leaflet-routeboxer will do this for you.
e.g.:
if (this._currentlyDisplayedRectangles) {
for (var i = 0; i < this._currentlyDisplayedRectangles.length; i++) {
this._currentlyDisplayedRectangles[i].remove();
}
} else {
this._currentlyDisplayedRectangles = [];
}
for (var i = 0; i < boxes.length; i++) {
var displayedRectangle = L.rectangle(boxes[i], {color: "#ff7800", weight: 1}).addTo(this.map);
bounds.extend(boxes[i]);
this._currentlyDisplayedRectangles.push(displayedRectangle);
}
If you don't store a reference to the L.rectangle() instance, you obviously won't be able to manipulate it later. This applies to other Leaflet layers as well - not storing explicit references to Leaflet layers is a usual pattern in Leaflet examples.

Leaflet Marker Cluster add weight to marker

I have a leaflet map and I am using the Leaflet.markerCluster plugin to cluster my markers. I have some markers that represent multiple points on the same location. Unfortunately when it gets clustered it only represents one single point. Is there a way to add a weight to each marker? So that the cluster sees it as more than one point?
Basically I am hoping for a clusterWeight property like the follwing:
var newMarker = L.marker(coordinates, {
icon: myIcon,
clusterWeight: 5
});
This propety does not exist however. Anyoneknow of a work around? Thanks!
First you will need to create a marker that supports custom properties. You can do this by extending the default L.Marker like so:
var weightMarker = L.Marker.extend({
options: {
customWeight: 0
}
});
Then you can make use of Leaflet.markercluster's iconCreateFunction to create a custom cluster marker, by changing what is displayed on the marker:
var markers = L.markerClusterGroup({
iconCreateFunction: function(cluster) {
// iterate all markers and count
var markers = cluster.getAllChildMarkers();
var weight = 0;
for (var i = 0; i < markers.length; i++) {
if(markers[i].options.hasOwnProperty("customWeight")){
weight += markers[i].options.customWeight;
}
}
var c = ' marker-cluster-';
if (weight < 10) {
c += 'small';
} else if (weight < 100) {
c += 'medium';
} else {
c += 'large';
}
// create the icon with the "weight" count, instead of marker count
return L.divIcon({
html: '<div><span>' + weight + '</span></div>',
className: 'marker-cluster' + c, iconSize: new L.Point(40, 40)
});
}
});
Demo: https://jsfiddle.net/chk1/0hq1t13t/

How to distinct two markers placed at the same coordinates in Nokia Here map

I have two markers placed at the same coordinates in a Nokia Here map.
The problem is I can access only at one marker. The other is below the first one.
Is there any options or something else to manage this case, in order to have access to all markers placed at the same coordinates ?
The only way to ensure that all markers are separately visible is to put the overlapping markers at slightly different locations. The best effect I have found is to use the clustering functionality down to zoom 15 and add a separate set of jittered markers to display at zoom 16+.
/**
* This is a H.clustering.ITheme which displays ordinary markers as
* noise points
*/
function SpiderifyTheme() {
var baseTheme = new H.clustering.DefaultTheme();
this.getClusterPresentation = function (cluster) {
var clusterIcon = baseTheme.getClusterPresentation(cluster).getIcon();
return new H.map.Marker(cluster.getPosition(), {
icon: clusterIcon,
min: cluster.getMinZoom(),
max: cluster.getMaxZoom()
});
};
this.getNoisePresentation = function (noisePoint) {
return new H.map.Marker(noisePoint.getPosition(), {
min: noisePoint.getMinZoom()
});
};
}
// dataPoints is an array of H.clustering.DataPoint elements
var len = dataPoints.length + 1;
var SCATTER = 0.0001; // When exploding a group this is the size of the ring.
var group = new H.map.Group();
var truncate = function(number, places) {
var shift = Math.pow(10, places);
return ((number * shift) | 0) / shift;
};
// Ensure that all markers are offset by a set amount.
dataPoints.forEach(function (dataPoint, index ) {
var jitteredPoint = {
lng: truncate(dataPoint.lng, 3) + SCATTER + (Math.cos(index) * SCATTER),
lat: truncate(dataPoint.lat, 3) + SCATTER + (Math.sin(index) * SCATTER)
},
marker = new H.map.Marker(jitteredPoint, {
min: 16}); // This needs to be one more than the max cluster level
group.addObject(marker);
});
var clusteredDataProvider = new H.clustering.Provider(dataPoints, {
clusteringOptions: {
eps: 32,
minWeight: 2
},
max : 15,
theme: new SpiderifyTheme()
});
// First add cluster for zooms 1-15
var clusteringLayer = new H.map.layer.ObjectLayer(clusteredDataProvider);
map.addLayer(clusteringLayer);
// Add the group for zooms 16+
map.addObject(group);
The result looks something like this - the first image shows clustered markers at low zoom. The second shows jittered markers at high zoom. You can alter the eps value of the clusterer to get other effects.