HTMLWidgets.widget({
name: "dimple",
type: "output",
initialize: function(el, width, height){
return {};
},
resize: function(el, width, height, instance) {
},
renderValue: function(el, x, instance){
// add title if provided
if (x.options.title){
if (x.options.title.html){
// if html provided then use innerHTML
// this is not robust and will need more checking on js or R side
el.innerHTML = x.options.title.html;
}
else if(x.options.title.text) {
// if just text then make it an h3
titlenode = document.createElement("h3")
titlenode.style["margin-top"] = 0;
titlenode.style["margin-bottom"] = 0;
titlenode.innerText = x.options.title.text;
el.appendChild(titlenode);
}
}
// make data in record or array of objects format
// using the HTMLWidgets helper function
x.data = HTMLWidgets.dataframeToD3(x.data);
el.widgetDimple = drawChart(x.options, x.data);
if(typeof x.options.tasks !== "undefined"){
if(!x.options.tasks.length){
x.options.tasks = [x.options.tasks]
}
x.options.tasks.map(function(task){
if(typeof task == "function"){
task.call(el)
}
})
}
function drawChart(opts, data){
var subCharts = [];
var c = null;
var assignedColors = {};
//handle RJSONIO box/unbox troubles
if( opts.defaultColors.length == 1 ) {
opts.defaultColors = opts.defaultColors[0];
}
//move this to top or make function since duplicated
//allow manipulation of default colors to use with dimple
if( opts.defaultColors.length || typeof(opts.defaultColors) == "function" ) {
var defaultColorsArray = [];
if (typeof(opts.defaultColors) == "function") {
//assume this is a d3 scale
//if there is a domain for the color scale given
//then we will need to assign colors with dimples assignColor
if( opts.defaultColors.domain().length > 0 ){
defaultColorsArray = opts.defaultColors.range();
opts.defaultColors.domain().forEach( function( d, i ) {
assignedColors[d] = new dimple.color(opts.defaultColors.range()[i])
})
} else {
for (var n=0;n s._axisBounds(i==0?"x":"y").max ? myChart.series[0]._axisBounds(i==0?"x":"y").max : s._axisBounds(i==0?"x":"y").max;
}
myChart.axes[i]._update();
}
*/
return myChart;
};
//function to build axes
function buildAxis(position, layer, myChart){
var axis;
var axisopts = opts[position+"Axis"];
if(axisopts.measure) {
axis = myChart[axisopts.type](position,layer[position],axisopts.measure);
} else {
axis = myChart[axisopts.type](position, layer[position]);
};
if(!(axisopts.type === "addPctAxis")) axis.showPercent = axisopts.showPercent;
if (axisopts.orderRule) axis.addOrderRule(axisopts.orderRule);
if (axisopts.grouporderRule) axis.addGroupOrderRule(axisopts.grouporderRule);
if (axisopts.inputFormat) axis.dateParseFormat = axisopts.inputFormat;
if (axisopts.outputFormat) axis.tickFormat = axisopts.outputFormat;
Object.keys(axisopts).filter(function(oky){
return [
"measure","type","orderRule","grouporderRule",
"outputFormat","inputFormat"
].indexOf(oky) < 0
}).forEach(function(oky){
axis[oky] = axisopts[oky]
})
return axis;
};
//if facet not provided for x or y make Dummy variable
//handle NULL facet
if (typeof opts.facet == "undefined") opts.facet = {}
opts.facet.x = opts.facet.x ? opts.facet.x : "Dummy"
opts.facet.y = opts.facet.y ? opts.facet.y : "Dummy"
if(opts.facet.x === "Dummy" || opts.facet.y === "Dummy") {
data.forEach(function(d){
d.Dummy = 1;
})
}
var rows = d3.set(data.map(function(d){return d[opts.facet.y]})).values();
var nrow = opts.facet.nrow ? opts.facet.nrow : rows.length;
var cols = d3.set(data.map(function(d){return d[opts.facet.x]})).values()
var ncol = opts.facet.ncol ? opts.facet.ncol : cols.length;
var tuples = d3.merge(rows.map(function(row,irow){return cols.map(function(col,icol){return {key:row + "~" + col, values: {"row":irow, "col":icol} }})}))
var svgGrid = d3.select("#" + el.id).append("svg")
.style("width", opts.width ? opts.width : "100%")
.style("height", opts.height ? opts.height : "90%" );
// .attr("transform", "translate(50,0)");
var grid = d3.layout.grid()
.rows( nrow )
.cols( ncol )
.size([ opts.width ? opts.width : el.getBoundingClientRect().width * 0.9, opts.height? opts.height : el.getBoundingClientRect().height * 0.9 - 30])
.bands();
grid(tuples);
tuples.forEach(function(cell,cellnum) {
var filteredData = dimple.filterData(data, opts.facet.x, cell.key.split('~')[1]);
filteredData = dimple.filterData(filteredData, opts.facet.y, cell.key.split('~')[0]);
// Draw a new chart which will go in the current shape
var subChart = new dimple.chart(svgGrid, filteredData);
if (tuples.length > 1){
// Position the chart inside the shape
subChart.height = grid.nodeSize()[1]
subChart.width = grid.nodeSize()[0]
if (opts.margins) {
subChart.setBounds(
parseFloat(cell.x) + opts.margins.left,
parseFloat(cell.y) + opts.margins.top,
subChart.width - opts.margins.right- opts.margins.left,
subChart.height - opts.margins.top - opts.margins.bottom
)
} else {
subChart.setBounds(
parseFloat(cell.x) + (30 * ( ncol == 1 ? 1.5 : 1 )),
parseFloat(cell.y) + (20 * ( nrow == 1 ? 1.5 : 2 )),
parseFloat(grid.nodeSize()[0]) - (30 * ( ncol == 1 ? 1.25 : 2 )),
parseFloat(grid.nodeSize()[1]) - (20 * ( nrow == 1 ? 3 : 2.5 ))
);
}
} else { //only one chart
if (opts.bounds) {
subChart.setBounds(opts.bounds.x, opts.bounds.y, opts.bounds.width, opts.bounds.height);//myChart.setBounds(80, 30, 480, 330);
}
}
//dimple allows use of custom CSS with noFormats
if(opts.noFormats) { subChart.noFormats = opts.noFormats; };
//need to fix later for better colorAxis support
if(d3.keys(opts.colorAxis).length > 0) {
c = subChart[opts.colorAxis.type](opts.colorAxis.colorSeries,opts.colorAxis.palette) ;
if(opts.colorAxis.outputFormat){
c.tickFormat = opts.colorAxis.outputFormat;
}
}
//add the colors from the array into the chart's defaultColors
if (typeof defaultColorsArray != "undefined"){
subChart.defaultColors = defaultColorsArray.map(function(d) {
return new dimple.color(d);
});
}
subChart._assignedColors = assignedColors;
subChart = buildSeries(opts, false, subChart, filteredData);
if (opts.layers.length > 0) {
opts.layers.forEach(function(layer){
subChart = buildSeries(layer, true, subChart, filteredData);
})
}
//unsure if this is best but if legend is provided (not empty) then evaluate
// also only want one legend so only set for cellnum = 0
if(d3.keys(opts.legend).length > 0 && cellnum == 0) {
var l =subChart.addLegend();
d3.keys(opts.legend).forEach(function(d){
l[d] = opts.legend[d];
});
}
//quick way to get this going but need to make this cleaner
if(opts.storyboard) {
subChart.setStoryboard(opts.storyboard);
};
//catch all for other options
//these can be provided by dMyChart$chart( ... )
//{{{ chart }}}
//add facet row and column in case we need later
subChart.facetposition = cell.values;
// undocumented hook to run last minute chart code
// allow functions using htmlwidgets::JS
if( typeof opts.chart !== "undefined" ) {
if( opts.chart.length == 0 ) opts.chart = [opts.chart]
opts.chart.forEach(function(c){
if(typeof c == "function"){
// function should return an altered chart
subChart = c.call(subChart);
}
})
}
subCharts.push(subChart);
})
subCharts.forEach(function(subChart) {
subChart.draw();
})
//get rid of all y for those not in column 1
//can easily customize this to only remove bits and pieces
if(opts.facet.removeAxes) {
["x","y","z"].forEach(function(position){
//work on axis scaling
//assume if remove then same scales for all charts
axisdomain = [];
subCharts.forEach(function(subChart){
subChart.axes.forEach(function(axis){
if (axis.position === position && !axis._hasCategories()){
axisdomain.push(axis._scale.domain())
}
})
});
axisdomain = d3.extent([].concat.apply([], axisdomain));
subCharts.forEach(function(subChart){
subChart.axes.forEach(function(axis){
if (axis.position === position && !axis._hasCategories()){
axis.overrideMin = axisdomain[0];
axis.overrideMax = axisdomain[1];
}
})
subChart.draw(null,true)
});
})
//evaluate which do not fall in column 1 or row 1 to remove
var xpos = d3.extent(subCharts,function(d){return d.x});
var ypos = d3.extent(subCharts,function(d){return d.y});
subCharts.filter(function(d){
return d.x!=xpos[0];
}).forEach(function(d){
d.series[0]._dropLineOrigin = function(){
return {"x" : xpos[0],"y" : ypos[1] + d._heightPixels()}
}
d.axes.forEach(function(axis){
if (axis.position === "y"){
//leave there for reference but set opacity 0
if (axis.shapes) axis.shapes.style("opacity",0);
if (axis.titleShape) axis.titleShape.style("opacity",0);
}
})
});
//now x for those not in row 1
subCharts.filter(function(d){
return d.y!=ypos[1];
}).forEach(function(d){
d.series[0]._dropLineOrigin = function(){
return {"x" : xpos[0],"y" : ypos[1] + d._heightPixels()}
}
d.axes.forEach(function(axis){
if (axis.position === "x"){
//leave there for reference but set opacity 0
if (axis.shapes) axis.shapes.style("opacity",0);
if (axis.titleShape) axis.titleShape.style("opacity",0);
}
})
});
}
return subCharts;
}
}
})