Changing bonsai code dynamically - bonsaijs

I have this part of code
<script>
var xhr = new XMLHttpRequest();
xhr.open('POST', 'getNewUsers.php',false);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send();
var json;
if (xhr.readyState == 4 && xhr.status == 200) { // If file is loaded correctly.
json = JSON.parse(xhr.responseText);
alert(json.en);
}
else if(xhr.readyState == 4 && xhr.status != 200) { // En cas d'erreur !
alert( ' Une erreur est survenue !\n\nCode :' + xhr.status + '\nTexte : ' + xhr.statusText);
}
</script>
<script id="bs">
function Sector(x, y, radius, startAngle, endAngle) {
SpecialAttrPath.call(this, {
radius: 0,
startAngle: startAngle,
endAngle: endAngle
});
this.attr({
x: x,
y: y,
radius: radius,
startAngle: startAngle,
endAngle: endAngle
});
}
Sector.prototype = Object.create(SpecialAttrPath.prototype);
Sector.prototype._make = function() {
var attr = this._attributes,
radius = attr.radius,
startAngle = attr.startAngle,
endAngle = attr.endAngle;
var startX, startY, endX, endY;
var diffAngle = Math.abs(endAngle - startAngle);
this.startX = startX = radius * Math.cos(startAngle);
this.startY = startY = radius * Math.sin(startAngle);
if (diffAngle < Math.PI*2) {
endX = radius * Math.cos(endAngle);
endY = radius * Math.sin(endAngle);
} else { // angles differ by more than 2*PI: draw a full circle
endX = startX;
endY = startY - .0001;
}
this.endX = endX;
this.endY = endY;
this.radiusExtentX = radius * Math.cos(startAngle + (endAngle - startAngle)/2);
this.radiusExtentY = radius * Math.sin(startAngle + (endAngle - startAngle)/2);
return this.moveTo(0, 0)
.lineTo(startX, startY)
.arcTo(radius, radius, 0, (diffAngle < Math.PI) ? 0 : 1, 1, endX, endY)
.lineTo(0, 0);
};
Sector.prototype.getDimensions = function() {
var x = this.attr('x'),
y = this.attr('y'),
left = Math.min(x, x + this.startX, x + this.endX, x + this.radiusExtentX),
top = Math.min(y, y + this.startY, y + this.endY, y + this.radiusExtentY),
right = Math.max(x, x + this.startX, x + this.endX, x + this.radiusExtentX),
bottom = Math.max(y, y + this.startY, y + this.endY, y + this.radiusExtentY);
console.log(y, y + this.startY, y + this.endY, y + this.radiusExtentY)
return {
left: left,
top: top,
width: right - left,
height: bottom - top
};
};
PieChart.BASE_COLOR = color('red');
function PieChart(data) {
this.angle = 0;
this.labelY = 30;
this.kolor = PieChart.BASE_COLOR.clone();
var n = 0;
for (var i in data) {
this.slice(i, data[i], n++);
}
}
PieChart.prototype = {
slice: function(name, value, i) {
var start = this.angle,
end = start + (Math.PI*2) * value/100,
// Increase hue by .1 with each slice (max of 10 will work)
kolor = this.kolor = this.kolor.clone().hue(this.kolor.hue()+.1);
var s = new Sector(
400, 200, 150,
start,
end
);
var animDelay = (i * 200) + 'ms';
var label = this.label(name, value, kolor);
label.attr({ opacity: 0 });
s.stroke('#FFF', 3);
s.fill(kolor);
s.attr({
endAngle: start,
radius: 0
}).addTo(stage).on('mouseover', over).on('mouseout', out);
label.on('mouseover', over).on('mouseout', out);
function over() {
label.text.attr('fontWeight', 'bold');
label.animate('.2s', {
x: 40
});
s.animate('.2s', {
radius: 170,
fillColor: kolor.lighter(.1)
}, {
easing: 'sineOut'
});
}
function out() {
label.text.attr('fontWeight', '');
label.animate('.2s', {
x: 30
});
s.animate('.2s', {
radius: 150,
fillColor: kolor
});
}
s.animate('.4s', {
radius: 150,
startAngle: start,
endAngle: end
}, {
easing: 'sineOut',
delay: animDelay
});
label.animate('.4s', {
opacity: 1
}, { delay: animDelay });
this.angle = end;
},
label: function(name, v, fill) {
var g = new Group().attr({
x: 30,
y: this.labelY,
cursor: 'pointer'
});
var t = new Text(name + ' (' + v + '%)').addTo(g);
var r = new Rect(0, 0, 20, 20, 5).fill(fill).addTo(g);
t.attr({
x: 30,
y: 17,
textFillColor: 'black',
fontFamily: 'Arial',
fontSize: '14'
});
g.addTo(stage);
this.labelY += 30;
g.text = t;
return g;
}
};
new PieChart({
English: json.en,
French: 20,
German: 30,
Dutch: 5,
Spanish: 19,
Others: 18
})
</script>
The problem is I would like to change the pie dynamically using Json, in the demi it is shown with integers but here it is also an integer. This is the line that cause the problem for rendering the pie.
new PieChart({
English: json.en,

BonsaiJS is executed in a separate execution environment (mostly worker) and because of that it can't reach objects that were defined outside (in your case the json variable) of the BonsaiJS movie code (in your example <script id="bs">).
You can read about the special execution of BonsaiJS here: http://docs.bonsaijs.org/overview/Execution.html. If you want to pass data from your page to the Bonsai movie execution you can do the following:
Dynamically communicate through sendMessage with the execution context (described here: http://docs.bonsaijs.org/overview/Communication.html)
If you just have to pass data into your context once you can do that through bonsai.run(myNode, {myJson: json}); and access it from within your movie code through stage.options.myJson (documented on the bottom of http://docs.bonsaijs.org/overview/Execution.html).
You also have the third option to move the XMLHttpRequest code into the movie-code and do the request from there. Every client-side bonsai execution context (worker, iframe) does support that.

Related

In R how to replicate highchart chart with highcharter package

I need to replicate this chart bellow in my shiny app. But I am struggling to deal with the javascript part Any help would be amazing:
Clock Chart Highchart
This is the javascript code: how do I 'translate' this to R?
Any help/indication to deal with javascript in R would be amazing.
Many many tahnks guys
`/**
* Get the current time
*/
function getNow() {
var now = new Date();
return {
hours: now.getHours() + now.getMinutes() / 60,
minutes: now.getMinutes() * 12 / 60 + now.getSeconds() * 12 / 3600,
seconds: now.getSeconds() * 12 / 60
};
}
/**
* Pad numbers
*/
function pad(number, length) {
// Create an array of the remaining length + 1 and join it with 0's
return new Array((length || 2) + 1 - String(number).length).join(0) + number;
}
var now = getNow();
// Create the chart
Highcharts.chart('container', {
chart: {
type: 'gauge',
plotBackgroundColor: null,
plotBackgroundImage: null,
plotBorderWidth: 0,
plotShadow: false,
height: '80%'
},
credits: {
enabled: false
},
title: {
text: 'The Highcharts clock'
},
pane: {
background: [{
// default background
}, {
// reflex for supported browsers
backgroundColor: Highcharts.svg ? {
radialGradient: {
cx: 0.5,
cy: -0.4,
r: 1.9
},
stops: [
[0.5, 'rgba(255, 255, 255, 0.2)'],
[0.5, 'rgba(200, 200, 200, 0.2)']
]
} : null
}]
},
yAxis: {
labels: {
distance: -20
},
min: 0,
max: 12,
lineWidth: 0,
showFirstLabel: false,
minorTickInterval: 'auto',
minorTickWidth: 1,
minorTickLength: 5,
minorTickPosition: 'inside',
minorGridLineWidth: 0,
minorTickColor: '#666',
tickInterval: 1,
tickWidth: 2,
tickPosition: 'inside',
tickLength: 10,
tickColor: '#666',
title: {
text: 'Powered by<br/>Highcharts',
style: {
color: '#BBB',
fontWeight: 'normal',
fontSize: '8px',
lineHeight: '10px'
},
y: 10
}
},
tooltip: {
formatter: function () {
return this.series.chart.tooltipText;
}
},
series: [{
data: [{
id: 'hour',
y: now.hours,
dial: {
radius: '60%',
baseWidth: 4,
baseLength: '95%',
rearLength: 0
}
}, {
id: 'minute',
y: now.minutes,
dial: {
baseLength: '95%',
rearLength: 0
}
}, {
id: 'second',
y: now.seconds,
dial: {
radius: '100%',
baseWidth: 1,
rearLength: '20%'
}
}],
animation: false,
dataLabels: {
enabled: false
}
}]
},
// Move
function (chart) {
setInterval(function () {
now = getNow();
if (chart.axes) { // not destroyed
var hour = chart.get('hour'),
minute = chart.get('minute'),
second = chart.get('second'),
// run animation unless we're wrapping around from 59 to 0
animation = now.seconds === 0 ?
false : {
easing: 'easeOutBounce'
};
// Cache the tooltip text
chart.tooltipText =
pad(Math.floor(now.hours), 2) + ':' +
pad(Math.floor(now.minutes * 5), 2) + ':' +
pad(now.seconds * 5, 2);
hour.update(now.hours, true, animation);
minute.update(now.minutes, true, animation);
second.update(now.seconds, true, animation);
}
}, 1000);
});
/**
* Easing function from https://github.com/danro/easing-js/blob/master/easing.js
*/
Math.easeOutBounce = function (pos) {
if ((pos) < (1 / 2.75)) {
return (7.5625 * pos * pos);
}
if (pos < (2 / 2.75)) {
return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
}
if (pos < (2.5 / 2.75)) {
return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
}
return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
};`
This converts that JS into R/JS (you need to collect time in Javascript). I noticed odd vertical lines in the Viewer pane of RStudio when this runs, but these lines don't appear in my browser.
For most calls in JS for highcharter, the function or argument is identical in R. I used lubridate for the time functions in the R code. (Although, you could set the time to static values because the time isn't controlled by R code.)
After creating the graph, I used htmlwidgets::onRender to give add the animation so that it follows actual time.
If you run this without htmlwidgets, this is what you'll see. (Well, you'll see the time on the clock for your local time at the moment you render it.)
library(highcharter)
library(lubridate)
highchart() %>%
hc_chart(type = "gauge", plotBackgroundColor = NULL,
plotBackgroundImage = NULL, plotBorderWidth = 0,
plotShadow = F) %>%
hc_pane(
background = list(
backgroundColor = list(
radialGradient = list(cx = .5, cy = -.4, r = 1.9),
stops = list(
list(.5, "rgba(255, 255, 255, .2)"),
list(.5, "rgba(200, 200, 200, .2)"))
))) %>%
hc_tooltip(enabled = FALSE) %>%
hc_yAxis(
labels = list(distance = -20),
min = 0, max = 12, lineWidth = 0, showFirstLabel = F,
minorTickInterval = "auto", minorTickWidth = 1,
minorTickColor = "#666", tickColor = "#666",
minorTickPosition = "inside", minorGridLineWidth = 0,
tickInterval = 1, tickWidth = 2, tickPosition = "inside",
tickLength = 10) %>%
hc_add_series(
data = list(
list(id = "hour", y = hour(now()), dial = list(
radius = "60%", baseWidth = 4, baseLength = "95%", rearLength = 0)),
list(id = "minute", y = minute(now()), dial = list(
baseLength = "95%", rearLength = 0)),
list(id = "second", y = second(now()), dial = list(
radius = "100%", baseWidth = 1, rearLength = "20%"))),
dataLabels = list(enabled = F)) %>%
htmlwidgets::onRender("
function(el, x) {
chart = $('#' + el.id).highcharts()
$.extend($.easing, {
easeOutElastic: function (x, t, b, c, d) {
var s = 1.70158; var p = 0; var a = c;
if (t == 0) return b; if ((t /= d) == 1) return b+c;
if (!p) p = d*.3;
if (a < Math.abs(c)) { a = c; var s = p/4; }
else var s = p/(2 * Math.PI) * Math.asin (c/a);
return a * Math.pow(2, -10 * t) * Math.sin( (t * d - s) * (2 * Math.PI)/p) + c + b;
}
});
function getNow () {
var now = new Date();
return {
hours: now.getHours() + now.getMinutes() / 60,
minutes: now.getMinutes() * 12 / 60 + now.getSeconds() * 12 / 3600,
seconds: now.getSeconds() * 12 / 60
};
};
setInterval(function () {
var hour = chart.get('hour'),
minute = chart.get('minute'),
second = chart.get('second'),
now = getNow(),
/* run animation unless we're wrapping around from 59 to 0 */
animation = now.seconds == 0 ?
false : {easing: 'easeOutElastic'};
hour.update(now.hours, true, animation);
minute.update(now.minutes, true, animation);
second.update(now.seconds, true, animation);
}, 1000);
}")
In this JS, you'll see some deviation from the original code. I needed to define 'chart'. I did that using the same mechanism that is used to change any highcharter R object into it's HTML rendering: chart = $('#' + el.id).highcharts(). Since the function that sets the interval was originally part of creating the graph, it was an unnamed function. Since we're calling after we render the graph, I dropped that outer function(chart).

how to make custom class in fabric js using fabric.Textbox Class override?

I am using FabricJS version : 3.6.3
I want to make new FabricJS class called : Button
So that I have extend one class called Textbox from fabric js, which will Draw a Rectangle behind Text and it looking like a button.
But Problem is that, I can't set height to that Button because height is not allow in Texbox object.
I want to set Height and Width to Button object. Width is working Properly due to Textbox. it will also warp Text if width keep smaller then text width, and can be editable by double clicking on it. But only problem is that can't set Height to an object
it should be Text vertically center when Height is increase.
In short I want to make this kind of functionality in fabric js using object customization.
Expected Output :
but Actual Output :
Here Is my Code That Create button :
// fabric js custom button class
(function (fabric) {
"use strict";
// var fabric = global.fabric || (global.fabric = {});
fabric.Button = fabric.util.createClass(fabric.Textbox, {
type: "button",
stateProperties: fabric.Object.prototype.stateProperties.concat(
"buttonRx",
"buttonRy",
"buttonFill",
"buttonPadding",
"buttonStrokeColor",
"buttonStrokeWidth"
),
buttonRx: 0,
buttonRy: 0,
buttonFill: "#ffffff00",
buttonPadding: 0,
buttonHeight: 0,
buttonWidth: 0,
textAlign: "center",
buttonStrokeColor: "#000000",
buttonStrokeWidth: 0,
_dimensionAffectingProps: fabric.Text.prototype._dimensionAffectingProps.concat(
"width",
"fontSize"
),
cacheProperties: fabric.Object.prototype.cacheProperties.concat(
"buttonRx",
"buttonRy",
"buttonFill",
"buttonPadding",
"buttonStrokeColor",
"buttonStrokeWidth"
),
initialize: function (text, options) {
this.text = text;
this.callSuper("initialize", text, options);
/* this.on("scaling", function () {
console.log('scaling', this.getScaledHeight());
this.set({
height: this.getScaledHeight(),
scaleY: 1,
});
}); */
this._initRxRy();
},
_initRxRy: function () {
if (this.buttonRx && !this.buttonRy) {
this.buttonRy = this.buttonRx;
} else if (this.buttonRy && !this.buttonRx) {
this.buttonRx = this.buttonRy;
}
},
/* _setCenter(){
}, */
_render: function (ctx) {
// 1x1 case (used in spray brush) optimization was removed because
// with caching and higher zoom level this makes more damage than help
// this.width = this.width * this.scaleX;
// this.height = this.height * this.scaleY;
// (this.scaleX = 1), (this.scaleY = 1);
var rx = this.buttonRx ? Math.min(this.buttonRx, this.width / 2) : 0,
ry = this.buttonRy ? Math.min(this.buttonRy, this.height / 2) : 0,
w = this.width + this.buttonPadding,
h = this.height + this.buttonPadding,
x = -this.width / 2 - this.buttonPadding / 2,
y = -this.height / 2 - this.buttonPadding / 2,
isRounded = rx !== 0 || ry !== 0,
/* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */
k = 1 - 0.5522847498;
ctx.beginPath();
ctx.moveTo(x + rx, y);
ctx.lineTo(x + w - rx, y);
isRounded &&
ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry);
ctx.lineTo(x + w, y + h - ry);
isRounded &&
ctx.bezierCurveTo(
x + w,
y + h - k * ry,
x + w - k * rx,
y + h,
x + w - rx,
y + h
);
ctx.lineTo(x + rx, y + h);
isRounded &&
ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry);
ctx.lineTo(x, y + ry);
isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y);
ctx.closePath();
ctx.save();
if (this.buttonFill) {
ctx.fillStyle = this.buttonFill;
if (this.fillRule === "evenodd") {
ctx.fill("evenodd");
} else {
ctx.fill();
}
}
if (this.buttonStrokeWidth > 0) {
if (this.strokeUniform) {
ctx.scale(1 / this.scaleX, 1 / this.scaleY);
}
if (this.shadow && !this.shadow.affectStroke) {
this._removeShadow(ctx);
}
if (this.buttonStrokeColor) {
ctx.lineWidth = this.buttonStrokeWidth;
ctx.strokeStyle = this.buttonStrokeColor;
ctx.stroke();
} else {
ctx.lineWidth = this.buttonStrokeWidth;
ctx.stroke();
}
}
ctx.restore();
this.clearContextTop();
this._clearCache();
this.height = this.calcTextHeight();
this.saveState({ propertySet: "_dimensionAffectingProps" });
// this._renderPaintInOrder(ctx);
this._setTextStyles(ctx);
this._renderTextLinesBackground(ctx);
this._renderTextDecoration(ctx, "underline");
this._renderText(ctx);
this._renderTextDecoration(ctx, "overline");
this._renderTextDecoration(ctx, "linethrough");
this.initDimensions();
// this.callSuper('render', ctx);
},
toObject: function (propertiesToInclude) {
return this.callSuper(
"toObject",
[
"buttonRx",
"buttonRy",
"buttonFill",
"buttonPadding",
"buttonStrokeColor",
"buttonStrokeWidth",
"objectCaching",
].concat(propertiesToInclude)
);
},
});
fabric.Button.fromObject = function (object, callback) {
return fabric.Object._fromObject("Button", object, callback, "text");
};
})(fabric);
// fabric js class finish here
var canvas = [];
var cotainer = document.getElementById("canvas-container");
for (let i = 0; i < 1; i++) {
var width = 500,
height = 500;
var canvasEl = document.createElement("canvas");
canvasEl.id = "canvas-" + i;
cotainer.append(canvasEl);
var fabCanvas = new fabric.Canvas(canvasEl, {});
fabCanvas.setHeight(height);
fabCanvas.setWidth(width);
canvas.push(fabCanvas);
}
canvas.forEach((c) => {
var button = new fabric.Button("Click Me", {
text: "Click Me",
buttonStrokeColor: "#f00",
buttonStrokeWidth: 2,
width: 110,
fill: "#f00",
fontSize: 50,
width: 400,
buttonFill: "#42A5F5",
buttonRx: 15,
buttonRy: 15,
objectCaching: false,
fontFamily: "verdana",
});
c.add(button);
c.renderAll();
});
canvas{
border: 1px solid black
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.js"></script>
<div id="canvas-container">
</div>
The solution will be to set the scaleX and scaleY of the button text to 1 when you scale the Button Object and also set the font size of the text equal to its scale.
var tbox = new fabric.Button(v.textDisp, {
left: v.posX,
top: v.posY,
boxHeight: v.length // new create
});
fabric.Button = fabric.util.createClass(fabric.Textbox, {
type: "button",
stateProperties: fabric.Object.prototype.stateProperties.concat(
"buttonRx",
"buttonRy",
"buttonFill",
"buttonPadding",
"buttonStrokeColor",
"buttonStrokeWidth",
"boxHeight"
),
buttonRx: 0,
buttonRy: 0,
buttonPadding: 0,
buttonHeight: 0,
buttonWidth: 0,
buttonStrokeColor: "#000000",
buttonStrokeWidth: 0,
_dimensionAffectingProps: fabric.Text.prototype._dimensionAffectingProps.concat(
"width",
"fontSize"
),
cacheProperties: fabric.Object.prototype.cacheProperties.concat(
"buttonRx",
"buttonRy",
"buttonFill",
"buttonPadding",
"buttonStrokeColor",
"buttonStrokeWidth",
"boxHeight"
),
initialize: function (text, options) {
this.text = text;
this.callSuper("initialize", text, options);
/* this.on("scaling", function () {
console.log('scaling', this.getScaledHeight());
this.set({
height: this.getScaledHeight(),
scaleY: 1,
});
}); */
this._initRxRy();
},
_initRxRy: function () {
if (this.buttonRx && !this.buttonRy) {
this.buttonRy = this.buttonRx;
} else if (this.buttonRy && !this.buttonRx) {
this.buttonRx = this.buttonRy;
}
},
/* _setCenter(){
}, */
_render: function (ctx) {
// 1x1 case (used in spray brush) optimization was removed because
// with caching and higher zoom level this makes more damage than help
// this.width = this.width * this.scaleX;
// this.height = this.height * this.scaleY;
// (this.scaleX = 1), (this.scaleY = 1);
var rx = this.buttonRx ? Math.min(this.buttonRx, this.width / 2) : 0,
ry = this.buttonRy ? Math.min(this.buttonRy, this.height / 2) : 0,
w = this.width + this.buttonPadding,
h = this.height + this.buttonPadding,
x = -this.width / 2 - this.buttonPadding / 2,
y = -this.height / 2 - this.buttonPadding / 2,
hh = this.boxHeight * this.scaleY,
isRounded = rx !== 0 || ry !== 0,
k = 1 - 0.5522847498;
ctx.beginPath();
ctx.moveTo(x + rx, y);
ctx.lineTo(x + w - rx, y);
isRounded &&
ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry);
ctx.lineTo(x + w, y + hh - ry);
isRounded &&
ctx.bezierCurveTo(
x + w,
y + hh - k * ry,
x + w - k * rx,
y + hh,
x + w - rx,
y + hh
);
ctx.lineTo(x + rx, y + hh);
isRounded &&
ctx.bezierCurveTo(x + k * rx, y + hh, x, y + hh - k * ry, x, y + hh - ry);
ctx.lineTo(x, y + ry);
isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y);
ctx.closePath();
ctx.save();
if (this.buttonFill) {
ctx.fillStyle = this.buttonFill;
if (this.fillRule === "evenodd") {
ctx.fill("evenodd");
} else {
ctx.fill();
}
}
if (this.buttonStrokeWidth > 0) {
if (this.strokeUniform) {
ctx.scale(1 / this.scaleX, 1 / this.scaleY);
}
if (this.shadow && !this.shadow.affectStroke) {
this._removeShadow(ctx);
}
if (this.buttonStrokeColor) {
ctx.lineWidth = this.buttonStrokeWidth;
ctx.strokeStyle = this.buttonStrokeColor;
ctx.stroke();
} else {
ctx.lineWidth = this.buttonStrokeWidth;
ctx.stroke();
}
}
ctx.restore();
this.clearContextTop();
this._clearCache();
this.height = this.calcTextHeight();
this.saveState({ propertySet: "_dimensionAffectingProps" });
// this._renderPaintInOrder(ctx);
this._setTextStyles(ctx);
this._renderTextLinesBackground(ctx);
this._renderTextDecoration(ctx, "underline");
this._renderText(ctx);
this._renderTextDecoration(ctx, "overline");
this._renderTextDecoration(ctx, "linethrough");
this.initDimensions();
// this.callSuper('render', ctx);
},
toObject: function (propertiesToInclude) {
return this.callSuper(
"toObject",
[
"buttonRx",
"buttonRy",
"buttonFill",
"buttonPadding",
"buttonStrokeColor",
"buttonStrokeWidth",
"objectCaching",
"boxHeight"
].concat(propertiesToInclude)
);
},
});
After adding boxHeight, declare a variable as 'hh' instead of'h' in "_render" When drawing, change to'hh' instead of'h'

Leaflet-markercluster default icons missing

Default icons not showing for MarkerCluster plugin after using IconCreateFunction.
I want to use the default icons for the plugin but when using attached code I loose all the icons functions, I only get the numbers with no icons and if I activate the "childCount" I get one type of circle with the numbers offcenter within the icon. The markers has already been clustered and I want to add this value to the markercluster that is why I'm using the IconCreateFuncton so the numbers on the map shows correctly but I have lost all the icons and its beautiful functions... what is missing?
Result below using "var childCount"
$.getJSON("../test/test.geojson", function(json) {
geoLayer = L.geoJson(json, {
pointToLayer: function(feature, latlng) {
var log_p = feature.properties.log_p;
var marker;
if (log_p > 0){
marker = new L.shapeMarker(latlng, {radius: log_p*25, fillColor: '#2b83ba', fillOpacity: 0.5, color: '#000000', weight: 1, shape: 'circle'});
}
else {
marker = null
}
return marker;
},
onEachFeature: function(feature, layer) {
var popupText = "Amount per day: " + '<b>' + feature.properties.total + '</b>';
layer.bindPopup(popupText, {
closeButton: true,
offset: L.point(0, -20)
});
layer.on('click', function() {
layer.openPopup();
});
},
});
var markers = new L.MarkerClusterGroup({
iconCreateFunction: function(cluster) {
var children = cluster.getAllChildMarkers();
var sum = 0;
for (var i = 0; i < children.length; i++) {
sum += children[i].feature.properties.total;
}
/*
var childCount = cluster.getAllChildMarkers();
var c = ' marker-cluster-';
if (childCount < 10) {
c += 'small';
} else if (childCount < 500) {
c += 'medium';
} else {
c += 'large';
}
*/
return new L.DivIcon({ html: '<b>' + sum + '</b>', className: 'marker-cluster'/* + c */, iconSize: new L.Point(40, 40) });
}
});
markers.addLayer(geoLayer)
map.addLayer(markers);
});
Markercluster icons, styles and functions are lost
I manage to solve the problem, a few lines of code was missing. I added them to the original JavaScript code as follows.
$.getJSON("../test/test.geojson", function(json) {
geoLayer = L.geoJson(json, {
pointToLayer: function(feature, latlng) {
var log_p = feature.properties.log_p;
var marker;
if (log_p > 0) {
marker = new L.shapeMarker(latlng, {
radius: log_p * 25,
fillColor: '#2b83ba',
fillOpacity: 0.5,
color: '#000000',
weight: 1,
shape: 'circle'
});
} else {
marker = null
}
return marker;
},
onEachFeature: function(feature, layer) {
var popupText = "Amount per day: " + '<b>' + feature.properties.total + '</b>';
layer.bindPopup(popupText, {
closeButton: true,
offset: L.point(0, -20)
});
layer.on('click', function() {
layer.openPopup();
});
},
});
var clusters = new L.MarkerClusterGroup({
maxClusterRadius: 125,
iconCreateFunction: function(cluster) {
var children = cluster.getAllChildMarkers();
var sum = 0;
for (var i = 0; i < children.length; i++) {
sum += children[i].feature.properties.total;
}
var childCount = cluster.getChildCount()
var c = ' marker-cluster-';
if (childCount + sum <= 50) {
c += 'small';
} else if (childCount + sum <= 250) {
c += 'medium';
} else {
c += 'large';
}
return new L.DivIcon({
html: '<div><span>' + sum + '</span></div>',
className: 'marker-cluster marker-cluster-' + c,
iconSize: new L.Point(40, 40)
});
},
});
clusters.addLayer(geoLayer)
map.addLayer(clusters);
});

which part of the d3 initialization should be put into meteor autorun to make the charts reactive?

Just started learning meteor and d3 / crossfilter charting libraries.
Picked up some example code off the Web, and have it working in my local app.
I do have an empty this.autorun() function in my meteor client code, but have no idea what part of the lengthy d3 initialization and composition routine should be put into autorun, in order for these charts to react to the data changes.
I have tried to just put the Flights.find().fetch() inside the autorun, but in that case, the page never seem to finish loading.
Here is my entire meteor code:
if (Meteor.isClient) {
Template.dashboard.helpers({
});
Template.dashboard.events({
});
Template.dashboard.rendered = function(){
var flights = Flights.find().fetch();
if (!flights.length) return;
var crossData = crossfilter(flights);
// d3.csv(data, function(error, flights) {
// Various formatters.
var formatNumber = d3.format(",d"),
formatChange = d3.format("+,d"),
formatDate = d3.time.format("%B %d, %Y"),
formatTime = d3.time.format("%I:%M %p");
// A nest operator, for grouping the flight list.
var nestByDate = d3.nest()
.key(function(d) { return d3.time.day(d.date); });
// A little coercion, since the CSV is untyped.
flights.forEach(function(d, i) {
d.index = i;
d.date = parseDate(d.date);
d.delay = +d.delay;
d.distance = +d.distance;
});
// Create the crossfilter for the relevant dimensions and groups.
var flight = crossfilter(flights),
all = flight.groupAll(),
date = flight.dimension(function(d) { return d.date; }),
dates = date.group(d3.time.day),
hour = flight.dimension(function(d) { return d.date.getHours() + d.date.getMinutes() / 60; }),
hours = hour.group(Math.floor),
delay = flight.dimension(function(d) { return Math.max(-60, Math.min(149, d.delay)); }),
delays = delay.group(function(d) { return Math.floor(d / 10) * 10; }),
distance = flight.dimension(function(d) { return Math.min(1999, d.distance); }),
distances = distance.group(function(d) { return Math.floor(d / 50) * 50; });
var charts = [
barChart()
.dimension(hour)
.group(hours)
.x(d3.scale.linear()
.domain([0, 24])
.rangeRound([0, 10 * 24])),
barChart()
.dimension(delay)
.group(delays)
.x(d3.scale.linear()
.domain([-60, 150])
.rangeRound([0, 10 * 21])),
barChart()
.dimension(distance)
.group(distances)
.x(d3.scale.linear()
.domain([0, 2000])
.rangeRound([0, 10 * 40])),
barChart()
.dimension(date)
.group(dates)
.round(d3.time.day.round)
.x(d3.time.scale()
.domain([new Date(2001, 0, 1), new Date(2001, 3, 1)])
.rangeRound([0, 10 * 90]))
.filter([new Date(2001, 1, 1), new Date(2001, 2, 1)])
];
// Given our array of charts, which we assume are in the same order as the
// .chart elements in the DOM, bind the charts to the DOM and render them.
// We also listen to the chart's brush events to update the display.
var chart = d3.selectAll(".chart")
.data(charts)
.each(function(chart) { chart.on("brush", renderAll).on("brushend", renderAll); });
// Render the initial lists.
var list = d3.selectAll(".list")
.data([flightList]);
// Render the total.
d3.selectAll("#total")
.text(formatNumber(flight.size()));
renderAll();
// Renders the specified chart or list.
function render(method) {
d3.select(this).call(method);
}
// Whenever the brush moves, re-rendering everything.
function renderAll() {
chart.each(render);
list.each(render);
d3.select("#active").text(formatNumber(all.value()));
}
// Like d3.time.format, but faster.
function parseDate(d) {
return new Date(2001,
d.substring(0, 2) - 1,
d.substring(2, 4),
d.substring(4, 6),
d.substring(6, 8));
}
window.filter = function(filters) {
filters.forEach(function(d, i) { charts[i].filter(d); });
renderAll();
};
window.reset = function(i) {
charts[i].filter(null);
renderAll();
};
function flightList(div) {
var flightsByDate = nestByDate.entries(date.top(40));
div.each(function() {
var date = d3.select(this).selectAll(".date")
.data(flightsByDate, function(d) { return d.key; });
date.enter().append("div")
.attr("class", "date")
.append("div")
.attr("class", "day")
.text(function(d) { return formatDate(d.values[0].date); });
date.exit().remove();
var flight = date.order().selectAll(".flight")
.data(function(d) { return d.values; }, function(d) { return d.index; });
var flightEnter = flight.enter().append("div")
.attr("class", "flight");
flightEnter.append("div")
.attr("class", "time")
.text(function(d) { return formatTime(d.date); });
flightEnter.append("div")
.attr("class", "origin")
.text(function(d) { return d.origin; });
flightEnter.append("div")
.attr("class", "destination")
.text(function(d) { return d.destination; });
flightEnter.append("div")
.attr("class", "distance")
.text(function(d) { return formatNumber(d.distance) + " mi."; });
flightEnter.append("div")
.attr("class", "delay")
.classed("early", function(d) { return d.delay < 0; })
.text(function(d) { return formatChange(d.delay) + " min."; });
flight.exit().remove();
flight.order();
});
}
function barChart() {
if (!barChart.id) barChart.id = 0;
var margin = {top: 10, right: 10, bottom: 20, left: 10},
x,
y = d3.scale.linear().range([100, 0]),
id = barChart.id++,
axis = d3.svg.axis().orient("bottom"),
brush = d3.svg.brush(),
brushDirty,
dimension,
group,
round;
function chart(div) {
var width = x.range()[1],
height = y.range()[0];
y.domain([0, group.top(1)[0].value]);
div.each(function() {
var div = d3.select(this),
g = div.select("g");
// Create the skeletal chart.
if (g.empty()) {
div.select(".title").append("a")
.attr("href", "javascript:reset(" + id + ")")
.attr("class", "reset")
.text("reset")
.style("display", "none");
g = div.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
g.append("clipPath")
.attr("id", "clip-" + id)
.append("rect")
.attr("width", width)
.attr("height", height);
g.selectAll(".bar")
.data(["background", "foreground"])
.enter().append("path")
.attr("class", function(d) { return d + " bar"; })
.datum(group.all());
g.selectAll(".foreground.bar")
.attr("clip-path", "url(#clip-" + id + ")");
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(axis);
// Initialize the brush component with pretty resize handles.
var gBrush = g.append("g").attr("class", "brush").call(brush);
gBrush.selectAll("rect").attr("height", height);
gBrush.selectAll(".resize").append("path").attr("d", resizePath);
}
// Only redraw the brush if set externally.
if (brushDirty) {
brushDirty = false;
g.selectAll(".brush").call(brush);
div.select(".title a").style("display", brush.empty() ? "none" : null);
if (brush.empty()) {
g.selectAll("#clip-" + id + " rect")
.attr("x", 0)
.attr("width", width);
} else {
var extent = brush.extent();
g.selectAll("#clip-" + id + " rect")
.attr("x", x(extent[0]))
.attr("width", x(extent[1]) - x(extent[0]));
}
}
g.selectAll(".bar").attr("d", barPath);
});
function barPath(groups) {
var path = [],
i = -1,
n = groups.length,
d;
while (++i < n) {
d = groups[i];
path.push("M", x(d.key), ",", height, "V", y(d.value), "h9V", height);
}
return path.join("");
}
function resizePath(d) {
var e = +(d == "e"),
x = e ? 1 : -1,
y = height / 3;
return "M" + (.5 * x) + "," + y
+ "A6,6 0 0 " + e + " " + (6.5 * x) + "," + (y + 6)
+ "V" + (2 * y - 6)
+ "A6,6 0 0 " + e + " " + (.5 * x) + "," + (2 * y)
+ "Z"
+ "M" + (2.5 * x) + "," + (y + 8)
+ "V" + (2 * y - 8)
+ "M" + (4.5 * x) + "," + (y + 8)
+ "V" + (2 * y - 8);
}
}
brush.on("brushstart.chart", function() {
var div = d3.select(this.parentNode.parentNode.parentNode);
div.select(".title a").style("display", null);
});
brush.on("brush.chart", function() {
var g = d3.select(this.parentNode),
extent = brush.extent();
if (round) g.select(".brush")
.call(brush.extent(extent = extent.map(round)))
.selectAll(".resize")
.style("display", null);
g.select("#clip-" + id + " rect")
.attr("x", x(extent[0]))
.attr("width", x(extent[1]) - x(extent[0]));
dimension.filterRange(extent);
});
brush.on("brushend.chart", function() {
if (brush.empty()) {
var div = d3.select(this.parentNode.parentNode.parentNode);
div.select(".title a").style("display", "none");
div.select("#clip-" + id + " rect").attr("x", null).attr("width", "100%");
dimension.filterAll();
}
});
chart.margin = function(_) {
if (!arguments.length) return margin;
margin = _;
return chart;
};
chart.x = function(_) {
if (!arguments.length) return x;
x = _;
axis.scale(x);
brush.x(x);
return chart;
};
chart.y = function(_) {
if (!arguments.length) return y;
y = _;
return chart;
};
chart.dimension = function(_) {
if (!arguments.length) return dimension;
dimension = _;
return chart;
};
chart.filter = function(_) {
if (_) {
brush.extent(_);
dimension.filterRange(_);
} else {
brush.clear();
dimension.filterAll();
}
brushDirty = true;
return chart;
};
chart.group = function(_) {
if (!arguments.length) return group;
group = _;
return chart;
};
chart.round = function(_) {
if (!arguments.length) return round;
round = _;
return chart;
};
return d3.rebind(chart, brush, "on");
}
// });
this.autorun(function(){
})
}
}
if (Meteor.isServer) {
Meteor.startup(function () {
});
}
If this helps, here is my attempt at reproducing one of the d3 force layout examples with collision detection / custom gravity functions https://gist.github.com/gmlnchv/80dd206440cca39800b8. I'm using observe() to react to changes.

How can I get rotated rectangle corner points from side points

Say I have a set of four or more points that are on the perimeter of a rectangle, and that the rectangle is rotated by some unknown amount. I know that at least one point is on each side of the rectangle. One arbitrary side point is designated (0, 0), and the other points are the distance from this starting point. How can I get the non-rotated corner points of this rectangle?
assuming you're not trying to find a unique solution:
rotate your points around 0,0 until the top-most, bottom-most,
left-most, and right-most points are all different points
draw horizontal lines through the top-most and bottom-most, and vertical lines through the left-most and right-most
you're done
var points = [];
var bs = document.body.style;
var ds = document.documentElement.style;
bs.height = bs.width = ds.height = ds.width = "100%";
bs.border = bs.margin = bs.padding = 0;
var c = document.createElement("canvas");
c.style.display = "block";
c.addEventListener("mousedown", addPoint, false);
document.body.appendChild(c);
var ctx = c.getContext("2d");
var interval;
function addPoint(e) {
if (points.length >= 4) points = [];
points.push({
x: e.x - c.offsetLeft,
y: e.y - c.offsetTop
});
while (points.length > 4) points.shift();
redraw();
}
function rotateAround(a, b, r) {
d = {x:a.x - b.x, y:a.y - b.y};
return {
x: b.x + Math.cos(r) * d.x - Math.sin(r) * d.y,
y: b.y + Math.cos(r) * d.y + Math.sin(r) * d.x
}
}
function drawPoint(p) {
ctx.strokeStyle = "rgb(0,0,0)";
ctx.beginPath();
ctx.arc(p.x, p.y, 10, 0, 2 * Math.PI, true);
ctx.closePath();
ctx.stroke();
}
var last_few = [];
function redraw() {
if (interval) clearInterval(interval);
last_few = [];
c.width = window.innerWidth;
c.height = window.innerHeight;
ctx.clearRect(0, 0, c.width, c.height);
ctx.fillStyle = "rgb(200, 200, 200)";
ctx.font = "40px serif";
if (points.length < 4) {
ctx.fillText("click " + (4 - points.length) + " times", 20, 40);
points.forEach(drawPoint);
} else {
var average = {x:0, y:0};
points.forEach(function (p) {
average.x += p.x / 4;
average.y += p.y / 4;
});
var step = 0;
interval = setInterval(function () {
ctx.clearRect(0, 0, c.width, c.height);
ctx.fillText("click anywhere to start over", 20, 40);
last_few.forEach(function(r) {
ctx.strokeStyle = "rgb(200,255,200)";
ctx.save();
ctx.translate(average.x, average.y);
ctx.rotate((step -r.step) * Math.PI / 180);
ctx.strokeRect(r.lm - average.x, r.tm - average.y, (r.rm - r.lm), (r.bm - r.tm));
ctx.restore();
});
var tm = Infinity;
var bm = -Infinity;
var lm = Infinity;
var rm = -Infinity;
points.forEach(function (p) {
p = rotateAround(p, average, step * Math.PI / 180);
drawPoint(p);
tm = Math.min(p.y, tm);
bm = Math.max(p.y, bm);
lm = Math.min(p.x, lm);
rm = Math.max(p.x, rm);
});
if (points.every(function (p) {
p = rotateAround(p, average, step * Math.PI / 180);
return (p.x == lm) || (p.x == rm) || (p.y == tm) || (p.y == bm);
})) {
ctx.strokeStyle = "rgb(0,255,0)";
ctx.strokeRect(lm, tm, (rm - lm), (bm - tm));
last_few.push({tm:tm, bm:bm, lm:lm, rm:rm, step:step});
while(last_few.length > 30) last_few.shift();
} else {
ctx.strokeStyle = "rgb(255,0,0)";
ctx.strokeRect(lm, tm, (rm - lm), (bm - tm));
}
step++;
}, 30);
}
}
window.onresize = redraw;
redraw();

Resources