In R how to replicate highchart chart with highcharter package - r

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).

Related

How to define a heavy object in Matter JS

To explain better what I need, think about a tank and a can of soda.
No matter what the speed of that can of soda would be, the tank wouldn't move if it's hit.
But the tank should be able to move when force is applied to it.
Already opened an issue here with more details:
https://github.com/liabru/matter-js/issues/841
And I also made an example here for what I need.
var Engine = Matter.Engine,
Render = Matter.Render,
World = Matter.World,
Bodies = Matter.Bodies,
Body = Matter.Body;
var engine = Engine.create();
engine.positionIterations = 10;
engine.velocityIterations = 8;
var render = Render.create({
element: document.body,
engine: engine,
options: {
width: 800,
height: 400,
wireframes: false
}
});
engine.world.gravity.y = 0;
var topWall = Bodies.rectangle(400, 50, 720, 20, { isStatic: true });
var leftWall = Bodies.rectangle(50, 210, 20, 300, { isStatic: true });
var rightWall = Bodies.rectangle(750, 210, 20, 300, { isStatic: true });
var bottomWall = Bodies.rectangle(400, 350, 720, 20, { isStatic: true });
var ball = Bodies.circle(100, 200, 5, {
friction: 0.05,
frictionAir: 0.05,
density: 0.001,
restitution: 0.2
});
var bigBox = Matter.Bodies.rectangle(500, 200, 120, 120, {
inertia: Infinity,
frictionAir: 1,
friction: 1,
density: 0.1
});
World.add(engine.world, [topWall, leftWall, rightWall, bottomWall, ball, bigBox]);
Engine.run(engine);
Render.run(render);
// CMD + SHIFT + 7 to reload
$('.shoot').on('click', function () {
var speed = 0.02;
var angle = 0;
let vector = Matter.Vector.create(Math.cos(angle) * speed, Math.sin(angle) * speed);
Body.applyForce(ball, ball.position, vector);
});
https://codepen.io/dmerlea/pen/BaoQJYK

highcharter custom animation

I'm trying to add a custom animation using highcharter R package like in this example where I use a polar chart.
I'm able to do this using JS, but I can't translate the animation function (from ease repository) to highcharter.
Here is my R code:
# I've tried to created a function using `JS`:
easeOutBounce <- JS("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);
}")
library(tidyverse)
library(highcharter)
highchart() %>%
hc_chart(polar = T, type = "bar",
events = list(
render = JS("function() {
var chart = this,
middleElement = chart.middleElement;
if (middleElement) {
middleElement.destroy();
}
chart.middleElement = chart.renderer.circle(chart.plotSizeX / 2 + chart.plotLeft, chart.plotHeight / 2 + chart.plotTop, 20).attr({
zIndex: 3,
fill: '#ffffff'
}).add();
}")
)
) %>%
hc_title(text = "Athlete 1 vs Athlete 2") %>%
hc_xAxis(categories = c("Total Score", "Avg. Score", "Sum Score",
"Best Score"),
tickmarkPlacement = "on",
plotLines = list(
list(label = list(
rotation = 90))
)
) %>%
hc_yAxis(offset = 30) %>%
hc_series(
list(
pointPadding = 0,
groupPadding = 0,
name = "Athlete 1",
animatio = list(
duration = 1000,
easing = easeOutBounce
),
data = c(43000, 19000, 60000, 35000)
),
list(
pointPadding = 0,
groupPadding = 0,
name = "Athlete 2",
data = c(50000, 39000, 42000, 31000)
)
) %>%
hc_colors(c("firebrick", "steelblue")) %>%
hc_tooltip(
borderWidth = 0,
backgroundColor = 'none',
shadow = FALSE,
style = list(
fontSize = '16px'
),
headerFormat = '',
pointFormatter = JS("function() {
return this.y / 1000 + 'k'
}"),
positioner = JS("function(labelWidth, labelHeight) {
return {
x: (this.chart.plotSizeX - labelWidth) / 2 + this.chart.plotLeft,
y: (this.chart.plotSizeY - labelHeight) / 2 + this.chart.plotTop
};
}")
)
Thank you!
Animation doesn't work because you have a little typo in attached code. Please take a look on it:
animatio = list(
duration = 1000,
easing = easeOutBounce
),
Should be animation, not animatio. Please correct it, then animation should appear.

How to get coordinates from a R plotly figure

I have been struggling like mad to solve an apparently basic question.
Imagine you have a scatter plot, with say ... 10 markers.
I suppose this plot has been generated using plotly within a Shiny environment.
One can easily get the coordinates of these markers using the event_data("plotly_click") code.
Now imagine you do not need the coordinates of these markers, but the coordinates generated by a mouse click but precisely where no marker exists (for example because you would like to set a new marker exactly there, and you would like to re-use the information coming from that mouse click).
I cannot obtain such a behavior using onclick(), or whatever.
Any idea ?
You could add a D3 event listener to your plot
Plotly.d3.select('.plotly').on('click', function(d, i) {})
and then
retrieve the relative x and y values based on the click position (d3.event.layerX resp. layerY)
adjusting for the relative graph position (document.getElementsByClassName('bg')[0].attributes['x'])
and finally calculating the new values based on the axis ranges (myPlot.layout.xaxis.range[0])
The new x and y value are then pushed to the existing graph
Plotly.extendTraces(myPlot, {
x: [[x]],
y: [[y]]
}, [1]);
Complete R code
library("plotly")
library("htmlwidgets")
p <- plot_ly(x = c( -2, 0, 2 ),y = c( -2, 1, 2), type = 'scatter' ,mode = 'lines+markers') %>%
add_trace(x=c(-1,0.4,2),y=c(2, 0, -1),type='scatter',mode='lines+markers') %>%
layout(hovermode='closest')
javascript <- "
var myPlot = document.getElementsByClassName('plotly')[0];
Number.prototype.between = function (min, max) {
return this >= min && this <= max;
};
Plotly.d3.select('.plotly').on('click', function(d, i) {
var e = Plotly.d3.event;
var bg = document.getElementsByClassName('bg')[0];
var x = ((e.layerX - bg.attributes['x'].value + 4) / (bg.attributes['width'].value)) * (myPlot.layout.xaxis.range[1] - myPlot.layout.xaxis.range[0]) + myPlot.layout.xaxis.range[0];
var y =((e.layerY - bg.attributes['y'].value + 4) / (bg.attributes['height'].value)) * (myPlot.layout.yaxis.range[0] - myPlot.layout.yaxis.range[1]) + myPlot.layout.yaxis.range[1]
if (x.between(myPlot.layout.xaxis.range[0], myPlot.layout.xaxis.range[1]) &&
y.between(myPlot.layout.yaxis.range[0], myPlot.layout.yaxis.range[1])) {
Plotly.extendTraces(myPlot, {
x: [[x]],
y: [[y]]
}, [1]);
}
});"
p <- htmlwidgets::prependContent(p, onStaticRenderComplete(javascript), data=list(''))
p
Interactive Javascript example
var traces = [{
x: [1, 2, 3, 4],
y: [10, 15, 13, 17],
mode: 'markers',
type: 'scatter'
}];
traces.push({
x: [2, 3, 4, 5],
y: [16, 5, 11, 9],
mode: 'markers',
type: 'scatter'
});
traces.push({
x: [1, 2, 3, 4],
y: [12, 9, 15, 12],
mode: 'markers',
type: 'scatter'
});
traces.push({
x: [],
y: [],
mode: 'lines+markers',
type: 'scatter'
});
var myPlot = document.getElementById('myPlot')
Plotly.newPlot('myPlot', traces, {hovermode: 'closest'});
Number.prototype.between = function(min, max) {
return this >= min && this <= max;
};
Plotly.d3.select(".plotly").on('click', function(d, i) {
var e = Plotly.d3.event;
var bg = document.getElementsByClassName('bg')[0];
var x = ((e.layerX - bg.attributes['x'].value + 4) / (bg.attributes['width'].value)) * (myPlot.layout.xaxis.range[1] - myPlot.layout.xaxis.range[0]) + myPlot.layout.xaxis.range[0];
var y = ((e.layerY - bg.attributes['y'].value + 4) / (bg.attributes['height'].value)) * (myPlot.layout.yaxis.range[0] - myPlot.layout.yaxis.range[1]) + myPlot.layout.yaxis.range[1]
if (x.between(myPlot.layout.xaxis.range[0], myPlot.layout.xaxis.range[1]) &&
y.between(myPlot.layout.yaxis.range[0], myPlot.layout.yaxis.range[1])) {
Plotly.extendTraces(myPlot, {
x: [
[x]
],
y: [
[y]
]
}, [3]);
}
});
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<div id="myPlot" style="width:100%;height:100%"></div>
Shiny example
library(shiny)
library("plotly")
library("htmlwidgets")
ui <- fluidPage(
plotlyOutput("plot")
)
server <- function(input, output) {
javascript <- "
function(el, x){
Number.prototype.between = function (min, max) {
return this >= min && this <= max;
};
Plotly.d3.select('.plotly').on('click', function(d, i) {
var e = Plotly.d3.event;
var bg = document.getElementsByClassName('bg')[0];
var x = ((e.layerX - bg.attributes['x'].value + 4) / (bg.attributes['width'].value)) * (el.layout.xaxis.range[1] - el.layout.xaxis.range[0]) + el.layout.xaxis.range[0];
var y =((e.layerY - bg.attributes['y'].value + 4) / (bg.attributes['height'].value)) * (el.layout.yaxis.range[0] - el.layout.yaxis.range[1]) + el.layout.yaxis.range[1]
if (x.between(el.layout.xaxis.range[0], el.layout.xaxis.range[1]) && y.between(el.layout.yaxis.range[0], el.layout.yaxis.range[1])) {
Plotly.extendTraces(el, {
x: [[x]],
y: [[y]]
}, [1]);
}
});
}"
output$plot <- renderPlotly({
plot_ly(x = c( -2, 0, 2 ),y = c( -2, 1, 2), type = 'scatter' ,mode = 'lines+markers') %>%
add_trace(x=c(-1,0.4,2),y=c(2, 0, -1),type='scatter',mode='lines+markers') %>%
layout(hovermode='closest') %>% onRender(javascript)
})
}
shinyApp(ui = ui, server = server)
The solution by Maximilian does not work on Plotly.js versions later than 1.42.0. Trying to fetch
var bg = document.getElementsByClassName('bg')[0];
returns undefined. The solution works using version 1.41.3.
This answer is most likely more suited to be a comment but my reputation does not meet the minimum requirement of 50.

d3.js: why don't the columns in my column chart contain the full data?

I am using a slightly modified reusable column chart script from https://gist.github.com/llad/3766585
Works great. However, I was just trying to add some tooltips on hover, that would display additional data from the JSON file for each column, but the additional data is not there. I look in the inspector and just see the two values used to calculate x and y. Is there something obvious in the way the script is written that is not binding the full data set?
The script:
function columnChart() {
var margin = {top: 30, right: 10, bottom: 50, left: 50},
width = 420,
height = 420,
xRoundBands = 0.2,
xValue = function(d) { return d[0]; },
yValue = function(d) { return d[1]; },
xScale = d3.scale.ordinal(),
yScale = d3.scale.linear(),
xFormat = '',
yFormat = '',
yAxis = d3.svg.axis().scale(yScale).orient("left"),
xAxis = d3.svg.axis().scale(xScale);
function chart(selection) {
selection.each(function(data) {
// Convert data to standard representation greedily;
// this is needed for nondeterministic accessors.
data = data.map(function(d, i) {
return [xValue.call(data, d, i), yValue.call(data, d, i)];
});
// Update the x-scale.
xScale
.domain(data.map(function(d) { return d[0];} ))
.rangeRoundBands([0, width - margin.left - margin.right], xRoundBands);
// Update the y-scale.
yScale
.domain(d3.extent(data.map(function(d) { return d[1];} )))
.range([height - margin.top - margin.bottom, 0])
.nice();
// Select the svg element, if it exists.
var svg = d3.select(this).selectAll("svg").data([data]);
// Otherwise, create the skeletal chart.
var gEnter = svg.enter().append("svg").append("g");
gEnter.append("g").attr("class", "bars");
gEnter.append("g").attr("class", "y axis");
gEnter.append("g").attr("class", "x axis");
gEnter.append("g").attr("class", "x axis zero");
// Update the outer dimensions.
svg .attr("width", width)
.attr("height", height);
// Update the inner dimensions.
var g = svg.select("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Update the bars.
var bar = svg.select(".bars").selectAll(".bar").data(data);
bar.enter().append("rect");
bar.exit().remove();
bar.attr("class", function(d, i) { return d[1] < 0 ? "bar negative" : "bar positive"; })
.attr("x", function(d) { return X(d); })
.attr("y", function(d, i) { return d[1] < 0 ? Y0() : Y(d); })
.attr("width", xScale.rangeBand())
.attr("height", function(d, i) { return Math.abs( Y(d) - Y0() ); })
.on("click", function(d, i)
{
d3.selectAll('.bar').classed('fade', true);
d3.select(this).classed("sel", true).classed("fade", false);
})
.on("mouseover", function(d, i)
{
d3.select(this).classed("hover", true);
})
.on("mouseout", function(d, i)
{
d3.select(this).classed("hover", false);
});
// x axis at the bottom of the chart
g.select(".x.axis")
.attr("transform", "translate(0," + (height - margin.top - margin.bottom) + ")")
.call(xAxis.orient("bottom").tickFormat(xFormat));
// zero line
g.select(".x.axis.zero")
.attr("transform", "translate(0," + Y0() + ")")
.call(xAxis.tickFormat("").tickSize(0));
// Update the y-axis.
g.select(".y.axis")
.call(yAxis);
// Horizontal grid
g.insert("g", ".bars")
.attr("class", "grid horizontal")
.call(d3.svg.axis().scale(yScale)
.orient("left")
.tickSize(-(width-margin.left-margin.right), 0, 0)
.tickFormat("")
);
});
}
// The x-accessor for the path generator; xScale ∘ xValue.
function X(d) {
return xScale(d[0]);
}
function Y0() {
return yScale(0);
}
// The x-accessor for the path generator; yScale ∘ yValue.
function Y(d) {
return yScale(d[1]);
}
chart.margin = function(_) {
if (!arguments.length) return margin;
margin = _;
return chart;
};
chart.width = function(_) {
if (!arguments.length) return width;
width = _;
return chart;
};
chart.height = function(_) {
if (!arguments.length) return height;
height = _;
return chart;
};
chart.x = function(_) {
if (!arguments.length) return xValue;
xValue = _;
return chart;
};
chart.y = function(_) {
if (!arguments.length) return yValue;
yValue = _;
return chart;
};
chart.yTickFormat = function(_) {
if (!arguments.length) return yFormat;
yFormat = _;
return chart;
};
chart.xTickFormat = function(_) {
if (!arguments.length) return xFormat;
xFormat = _;
return chart;
};
return chart;
}
The initialization:
function renderGraph(view){
var chartWidth = mainWidth();
var chartHeight = 400;
var parseDate = d3.time.format("%Y-%m-%d").parse;
var xFormat = d3.time.format("%b %e");
var data = [];
var req = $.ajax({
url: '/data/column-data.json',
type: 'GET',
dataType: 'json',
success: function(response) {
data = response;
}
});
$.when(req).done(function() {
d3.select("#columnChart")
.datum(data.widgets)
.call(columnChart()
.width(chartWidth)
.height(chartHeight)
.x(function(d, i) { return parseDate(d[0]); })
.xTickFormat(xFormat)
.y(function(d, i) {
var yData;
if (view === 'thisView'){
yData = d[1];
}else if (view === 'thatView'){
yData = d[2];
}
return yData;
}));
});
}
and the data looks like this:
{ "widgets" : [
["2013-09-15", 1, 66622, 1, 3],
["2013-09-16", 0, 0, 0, 0],
["2013-09-17", 2, 76316, 2, 2],
["2013-09-18", 4, 291244, 8, 12],
["2013-09-19", 1, 74674, 2, 2],
["2013-09-20", 5, 287965, 7, 5],
["2013-09-21", 0, 0, 0, 0],
["2013-09-22", 0, 0, 0, 0],
["2013-09-23", 7, 459249, 15, 22],
["2013-09-24", 2, 317320, 1, 6],
["2013-09-25", 3, 100269, 3, 10],
["2013-09-26", 4, 181080, 8, 4],
["2013-09-27", 1, 38056, 1, 1],
["2013-09-28", 0, 0, 0, 0],
["2013-09-29", 0, 0, 0, 0],
["2013-09-30", 3, 449334, 2, 13],
["2013-10-01", 9, 403929, 5, 15],
["2013-10-02", 4, 222512, 7, 12],
["2013-10-03", 1, 196012, 3, 9],
["2013-10-04", 2, 391716, 2, 8],
["2013-10-05", 0, 0, 0, 0],
["2013-10-06", 0, 0, 0, 0],
["2013-10-07", 4, 260312, 8, 14],
["2013-10-08", 1, 34350, 1, 1],
["2013-10-09", 3, 179067, 9, 18],
["2013-10-10", 2, 124250, 8, 19],
["2013-10-11", 2, 381186, 4, 9],
["2013-10-12", 0, 0, 0, 0],
["2013-10-13", 0, 0, 0, 0],
["2013-10-14", 5, 393400, 11, 17]
]
}
The tooltip should display the full data for any one column. The web inspector shows an array of 2 for each column (I'm expecting an array of 5)
How do I modify the script to bind the full data for each column?
Thanks!
UPDATE:
I still haven't solved this, though #adam-pearce got very close. I've put together a couple jsbin's to help.
A bin for the original script is here: http://jsbin.com/EjugosA/2/edit
and the script with Adam's answer is here: http://jsbin.com/IPoDutA/1/edit
this just has the following commented out:
//data = data.map(function(d, i) {
// return [xValue.call(data, d, i), yValue.call(data, d, i)];
//});
You can see in the second that both x and y axis fail to render, though the full data set I wanted is bound to each bar. The web inspector shows the following error:
'undefined' is not a function (evaluating 'd.getMonth()')" which points to line 8410 in d3.v3.js, in d3_time_formats.
How to I modify this script to get the full data without breaking the axis?
data = data.map(function(d, i) {
return [xValue.call(data, d, i), yValue.call(data, d, i)];
});
Removes everything but the first two columns from data. Looking over the code, I think it would still work if you commented out those three lines.
In responce to your comment:
// x axis at the bottom of the chart
g.select(".x.axis")
.attr("transform", "translate(0," + (height - margin.top - margin.bottom) + ")")
.call(xAxis.orient("bottom"));//.tickFormat(xFormat));
You're trying to print strings as dates which doesn't work without parsing the strings first.

Changing bonsai code dynamically

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.

Resources