Skip to content

Instantly share code, notes, and snippets.

@stla
Last active May 28, 2019 14:12
Show Gist options
  • Save stla/33d5339f4ac1c72aa9a5745611334808 to your computer and use it in GitHub Desktop.
Save stla/33d5339f4ac1c72aa9a5745611334808 to your computer and use it in GitHub Desktop.
amcharts 4 grouped bar chart for Shiny
var groupedBarChartBinding = new Shiny.InputBinding();
$.extend(groupedBarChartBinding, {
find: function(scope) {
return $(scope).find(".amGroupedBarChart");
},
getValue: function(el) {
return $(el).data("data");
},
getType: function(el) {
return "dataframe";
},
subscribe: function(el, callback) {
$(el).on("change.groupedBarChartBinding", function(e) {
callback();
});
},
unsubscribe: function(el) {
$(el).off(".groupedBarChartBinding");
},
initialize: function(el) {
var id = el.getAttribute("id");
var $el = $(el);
var data = $el.data("data");
var dataCopy = $el.data("data");
var categoryField = $el.data("categoryfield");
var valueFields = $el.data("valuefields");
var minValue = $el.data("min");
var maxValue = $el.data("max");
var colors = $el.data("colors");
var theme = $el.data("theme");
var valueNames = $el.data("valuenames");
var categoryName = $el.data("categoryname");
var legend = $el.data("legend");
var showValueLabels = $el.data("showvaluelabels");
console.log(showValueLabels);
var categoryAxisTitle = $el.data("categoryaxistitle");
var valueAxisTitle = $el.data("valueaxistitle");
var draggable = $el.data("draggable");
var ndecimals = $el.data("ndecimals");
var numberFormat = "#.";
for (var d = 0; d < ndecimals; d++) {
numberFormat = numberFormat + "#";
}
var categoryAxisTitleFontSize = $el.data("categoryaxistitlefontsize") + "px";
var valueAxisTitleFontSize = $el.data("valueaxistitlefontsize") + "px";
var categoryAxisTitleColor = $el.data("categoryaxistitlecolor");
var valueAxisTitleColor = $el.data("valueaxistitlecolor");
// set theme
if (theme !== null) {
if(theme === "dataviz") {
am4core.useTheme(am4themes_dataviz);
}else if(theme === "material"){
am4core.useTheme(am4themes_material);
}else if(theme === "kelly"){
am4core.useTheme(am4themes_kelly);
}else if(theme === "dark"){
am4core.useTheme(am4themes_dark);
}else if(theme === "frozen"){
am4core.useTheme(am4themes_frozen);
}else if(theme === "moonrisekingdom"){
am4core.useTheme(am4themes_moonrisekingdom);
}else if(theme === "spiritedaway"){
am4core.useTheme(am4themes_spiritedaway);
}
}
am4core.useTheme(am4themes_animated);
// initialize chart
var chart = am4core.create(id, am4charts.XYChart);
chart.hiddenState.properties.opacity = 0; // this makes initial fade in effect
chart.data = data;
chart.padding(40, 40, 40, 40);
chart.maskBullets = false; // allow bullets to go out of plot area
// Create axes
var categoryAxis = chart.yAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = categoryField;
categoryAxis.numberFormatter.numberFormat = numberFormat;
categoryAxis.renderer.inversed = true;
categoryAxis.renderer.grid.template.location = 0;
categoryAxis.renderer.cellStartLocation = 0.1;
categoryAxis.renderer.cellEndLocation = 0.9;
categoryAxis.title.text = categoryAxisTitle;
categoryAxis.title.fontWeight = "bold";
categoryAxis.title.fontSize = categoryAxisTitleFontSize;
categoryAxis.title.setFill(categoryAxisTitleColor);
var valueAxis = chart.xAxes.push(new am4charts.ValueAxis());
valueAxis.renderer.opposite = true;
valueAxis.strictMinMax = true;
valueAxis.min = minValue;
valueAxis.max = maxValue;
if (valueAxisTitle !== null) {
valueAxis.title.text = valueAxisTitle;
valueAxis.title.fontWeight = "bold";
valueAxis.title.fontSize = valueAxisTitleFontSize;
valueAxis.title.setFill(valueAxisTitleColor);
}
function handleDrag(event) {
var dataItem = event.target.dataItem;
// convert coordinate to value
var value = valueAxis.xToValue(event.target.pixelX);
// set new value
dataItem.valueX = value;
// make column hover
dataItem.column.isHover = true;
// hide tooltip not to interrupt
dataItem.column.hideTooltip(0);
// make bullet hovered (as it might hide if mouse moves away)
event.target.isHover = true;
}
// Create series
function createSeries(field, name, barColor, drag) {
var series = chart.series.push(new am4charts.ColumnSeries());
series.dataFields.valueX = field;
series.dataFields.categoryY = categoryField;
series.name = name;
series.sequencedInterpolation = true;
// tooltip text color and background color, and scaling
var tooltip = series.tooltip;
tooltip.getFillFromObject = false;
tooltip.background.fill = am4core.color("#101010");
tooltip.autoTextColor = false;
tooltip.label.fill = am4core.color("#FFFFFF");
tooltip.properties.scale = 0.6;
var valueLabel = series.bullets.push(new am4charts.LabelBullet());
valueLabel.label.text = "{valueX}";
valueLabel.label.horizontalCenter = "left";
valueLabel.label.dx = 10;
valueLabel.label.hideOversized = false;
valueLabel.label.truncate = false;
if (showValueLabels) {
var categoryLabel = series.bullets.push(new am4charts.LabelBullet());
categoryLabel.label.text = "{name}";
categoryLabel.label.horizontalCenter = "right";
categoryLabel.label.dx = -10;
categoryLabel.label.fill = am4core.color("#fff");
categoryLabel.label.hideOversized = false;
categoryLabel.label.truncate = false;
}
// column template
var columnTemplate = series.columns.template;
columnTemplate.tooltipText = "[font-style:italic]{name}[/]: [bold]{valueX}[/]";
//columnTemplate.tooltipHTML =
// "<div style='font-size:9px'>" + "{name}" + ": " + "<b>{valueX}</b>" + "</div>";
columnTemplate.height = am4core.percent(100);
columnTemplate.column.cornerRadiusBottomRight = 8;
columnTemplate.column.cornerRadiusTopRight = 8;
columnTemplate.column.fillOpacity = 0.9;
columnTemplate.tooltipX = 0; // otherwise will point to middle of the column
// hover state
var columnHoverState = columnTemplate.column.states.create("hover");
columnHoverState.properties.fillOpacity = 1;
// you can change any property on hover state and it will be animated
columnHoverState.properties.cornerRadiusBottomRight = 35;
columnHoverState.properties.cornerRadiusTopRight = 35;
// color
if (barColor !== false) {
// columnTemplate.adapter.add("fill", (fill, target) => {
// return barColor;
// });
columnTemplate.fill = barColor;
columnTemplate.stroke = "#cccccc";
columnTemplate.strokeWidth = 1;
}
if (drag) {
// series bullet
var bullet = series.bullets.create();
bullet.stroke = am4core.color("#ffffff");
bullet.strokeWidth = 1;
bullet.opacity = 0; // initially invisible
bullet.defaultState.properties.opacity = 0;
// resize cursor when over
bullet.cursorOverStyle = am4core.MouseCursorStyle.horizontalResize;
bullet.draggable = true;
// create hover state
var hoverState = bullet.states.create("hover");
hoverState.properties.opacity = 1; // visible when hovered
// add circle sprite to bullet
var circle = bullet.createChild(am4core.Circle);
circle.radius = 5;
// dragging
// while dragging
bullet.events.on("drag", event => {
handleDrag(event);
});
bullet.events.on("dragstop", event => {
handleDrag(event);
var dataItem = event.target.dataItem;
dataItem.column.isHover = false;
event.target.isHover = false;
dataCopy[dataItem.index][field] = dataItem.values.valueX.value;
Shiny.setInputValue(id + ":dataframe", dataCopy);
Shiny.setInputValue(id + "_change", {
index: dataItem.index,
field: field,
category: dataItem.categoryY,
value: dataItem.values.valueX.value
});
});
// bullet color
if (barColor !== false) {
bullet.adapter.add("fill", (fill, target) => {
return barColor;
});
}
// show bullet when hovered
columnTemplate.events.on("over", event => {
var dataItem = event.target.dataItem;
var itemBullet = dataItem.bullets.getKey(bullet.uid);
itemBullet.isHover = true;
});
// hide bullet when mouse is out
columnTemplate.events.on("out", event => {
var dataItem = event.target.dataItem;
var itemBullet = dataItem.bullets.getKey(bullet.uid);
itemBullet.isHover = false;
});
// start dragging bullet even if we hit on column not just a bullet, this will make it more friendly for touch devices
columnTemplate.events.on("down", event => {
var dataItem = event.target.dataItem;
var itemBullet = dataItem.bullets.getKey(bullet.uid);
itemBullet.dragStart(event.pointer);
});
// when columns position changes, adjust minY/maxY of bullets so that we could only dragg horizontally
columnTemplate.events.on("positionchanged", event => {
var dataItem = event.target.dataItem;
var itemBullet = dataItem.bullets.getKey(bullet.uid);
var column = dataItem.column;
itemBullet.minY = column.pixelY + column.pixelHeight / 2;
itemBullet.maxY = itemBullet.minY;
itemBullet.minX = 0;
itemBullet.maxX = chart.seriesContainer.pixelWidth;
});
}
}
for (var i = 0; i < valueFields.length; i++) {
var color = colors === "auto" ? null : colors[i];
createSeries(valueFields[i], valueNames[i], color, draggable[i]);
}
// Add legend
if (legend) {
chart.legend = new am4charts.Legend();
chart.legend.useDefaultMarker = false;
console.log(chart.legend.markers);
var markerTemplate = chart.legend.markers.template;
console.log(markerTemplate);
markerTemplate.height = 10;
// marker.cornerRadius(12, 12, 12, 12);
markerTemplate.strokeWidth = 1;
markerTemplate.strokeOpacity = 1;
// markerTemplate.stroke = am4core.color("#000000"); no effect
}
}
});
Shiny.inputBindings.register(groupedBarChartBinding);
library(shiny)
library(jsonlite)
registerInputHandler("dataframe", function(data, ...) {
fromJSON(toJSON(data, auto_unbox = TRUE))
}, force = TRUE)
groupedBarChartInput <-
function(inputId, width = "100%", height = "400px",
data, categoryField, valueFields,
minValue, maxValue,
ndecimals = 0,
colors = NULL,
theme = NULL,
backgroundColor = ifelse(identical(theme,"dark"),
"#30303d", "#ffffff"),
legend = TRUE,
categoryLabel = categoryField,
valueLabels = valueFields,
showValueLabels = !legend,
categoryAxisTitle = categoryLabel,
valueAxisTitle = NULL,
categoryAxisTitleFontSize = 22,
valueAxisTitleFontSize = 22,
categoryAxisTitleColor = "indigo",
valueAxisTitleColor = "indigo",
draggable = rep(FALSE, length(valueFields))){
if(!is.null(theme)){
theme <- match.arg(theme, c("dataviz","material","kelly","dark",
"frozen","moonrisekingdom","spiritedaway"))
}
tags$div(
id = inputId, class = "amGroupedBarChart",
style =
sprintf("width: %s; height: %s; background-color: %s;",
width, height, backgroundColor),
`data-data` = as.character(toJSON(data)),
`data-categoryfield` = categoryField,
`data-valuefields` = as.character(toJSON(valueFields)),
`data-min` = minValue,
`data-max` = maxValue,
`data-ndecimals` = ndecimals,
`data-colors` = ifelse(is.null(colors), "auto",
as.character(toJSON(colors))),
`data-theme` = theme,
`data-valuenames` = as.character(toJSON(valueLabels)),
`data-showvaluelabels` = ifelse(showValueLabels, "true", "false"),
`data-categoryname` = categoryLabel,
`data-legend` = ifelse(legend, "true", "false"),
`data-categoryaxistitle` = categoryAxisTitle,
`data-valueaxistitle` = valueAxisTitle,
`data-draggable` = as.character(toJSON(draggable)),
`data-categoryaxistitlefontsize` = categoryAxisTitleFontSize,
`data-valueaxistitlefontsize` = valueAxisTitleFontSize,
`data-categoryaxistitlecolor` = categoryAxisTitleColor,
`data-valueaxistitlecolor` = valueAxisTitleColor)
}
set.seed(666)
dat <- data.frame(
year = rpois(5, 2010),
income = rpois(5, 25),
expenses = rpois(5, 20)
)
ui <- fluidPage(
tags$head(
tags$script(src = "http://www.amcharts.com/lib/4/core.js"),
tags$script(src = "http://www.amcharts.com/lib/4/charts.js"),
tags$script(src = "http://www.amcharts.com/lib/4/themes/dataviz.js"),
tags$script(src = "http://www.amcharts.com/lib/4/themes/material.js"),
tags$script(src = "http://www.amcharts.com/lib/4/themes/kelly.js"),
tags$script(src = "http://www.amcharts.com/lib/4/themes/dark.js"),
tags$script(src = "http://www.amcharts.com/lib/4/themes/frozen.js"),
tags$script(src = "http://www.amcharts.com/lib/4/themes/moonrisekingdom.js"),
tags$script(src = "http://www.amcharts.com/lib/4/themes/spiritedaway.js"),
tags$script(src = "http://www.amcharts.com/lib/4/themes/animated.js"),
tags$script(src = "groupedBarChartBinding.js")
),
fluidRow(
column(8,
groupedBarChartInput("mybarchart", data = dat[order(dat$year),],
categoryField = "year",
valueFields = c("income", "expenses"),
categoryLabel = "Year",
valueLabels = c("Income", "Expenses"),
valueAxisTitle = "Income and expenses",
minValue = 0, maxValue = 35,
draggable = c(FALSE, TRUE),
colors = c("darkmagenta","darkred"),
theme = "dark")),
column(4,
tags$label("Data:"),
verbatimTextOutput("data"),
br(),
tags$label("Change:"),
verbatimTextOutput("change"))
)
)
server <- function(input, output){
output[["data"]] <- renderPrint({
input[["mybarchart"]]
})
output[["change"]] <- renderPrint({ input[["mybarchart_change"]] })
}
shinyApp(ui, server)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment