Skip to content

Instantly share code, notes, and snippets.

@timelyportfolio
Last active August 29, 2015 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timelyportfolio/fe16f2df171c9e6f2f1e to your computer and use it in GitHub Desktop.
Save timelyportfolio/fe16f2df171c9e6f2f1e to your computer and use it in GitHub Desktop.
rCharts Hijacks Dimple's Tooltip

In an email conversation, someone asked:

How might I delete some lines in the default dimplejs tooltip?

This question comes up because the user felt like the dimplejs tooltip duplicated info that already appeared in the legend. This hack will require install_github("timelyportfolio/rCharts@dimple_v2.0.0") [2014-07-02] until the newest dimple version and functionality gets pulled into the primary rCharts repo.

The code below will hijack dimplejs getTooltipText with rCharts and allow us to change what text we see. If you look closely at the result, you should notice that even though we specify a z it does not appear and for effect we added a worthless custom line. This all shows up here.

require(rCharts)

d1 <- dPlot(
  x= "x",
  y = "y",
  groups = "grp",
  data = data.frame(
    x = LETTERS[1:4],
    y = runif(4,1,100),
    grp = rep(1:2,2)
  ),
  z = "y",  #throw this in as another test
  type = "bubble",
  xAxis = list(orderRule = "x"),
  zAxis = list(type = "addMeasureAxis"),
  #colorAxis = list(type="addColorAxis",colorSeries="grp"),
  legend = list(x = 60, y = 10, width = 700, height = 20)
)
#hijack dimple's tooltip function
#so we can still inherit dimple's tooltip functionality
#and not have to completely rebuild a tooltip
#so copy dimple getTooltipText code and then comment out 
#what we do not want

d1$chart("series[0].getTooltipText" = "#!
function (e) {
         var rows = [];
         // Add the series categories
         if (this.categoryFields !== null && this.categoryFields !== undefined && this.categoryFields.length !== 0) {
         this.categoryFields.forEach(function (c, i) {
         if (c !== null && c !== undefined && e.aggField[i] !== null && e.aggField[i] !== undefined) {
         // If the category name and value match don't display the category name
                        rows.push(c + (e.aggField[i] !== c ? ': ' + e.aggField[i] : ''));
                    }
                }, this);
            }

            if (this.x) {
                this.x._getTooltipText(rows, e);
            }
            if (this.y) {
                this.y._getTooltipText(rows, e);
            }
            if (this.z) {
            //    this.z._getTooltipText(rows, e);
            }
            if (this.c) {
            //    this.c._getTooltipText(rows, e);
            }

            debugger;
            //as an example of something custom
            rows.push('something custom here');

            // Get distinct text rows to deal with cases where 2 axes have the same dimensionality
            return rows; //rows.filter(function (elem, pos) { return rows.indexOf(elem) === pos; });
        }          
!#")

d1
require(rCharts)
d1 <- dPlot(
x= "x",
y = "y",
groups = "grp",
data = data.frame(
x = LETTERS[1:4],
y = runif(4,1,100),
grp = rep(1:2,2)
),
z = "y", #throw this in as another test
type = "bubble",
xAxis = list(orderRule = "x"),
zAxis = list(type = "addMeasureAxis"),
#colorAxis = list(type="addColorAxis",colorSeries="grp"),
legend = list(x = 60, y = 10, width = 700, height = 20)
)
#hijack dimple's tooltip function
#so we can still inherit dimple's tooltip functionality
#and not have to completely rebuild a tooltip
#so copy dimple getTooltipText code and then comment out
#what we do not want
d1$chart("series[0].getTooltipText" = "#!
function (e) {
var rows = [];
// Add the series categories
if (this.categoryFields !== null && this.categoryFields !== undefined && this.categoryFields.length !== 0) {
this.categoryFields.forEach(function (c, i) {
if (c !== null && c !== undefined && e.aggField[i] !== null && e.aggField[i] !== undefined) {
// If the category name and value match don't display the category name
rows.push(c + (e.aggField[i] !== c ? ': ' + e.aggField[i] : ''));
}
}, this);
}
if (this.x) {
this.x._getTooltipText(rows, e);
}
if (this.y) {
this.y._getTooltipText(rows, e);
}
if (this.z) {
// this.z._getTooltipText(rows, e);
}
if (this.c) {
// this.c._getTooltipText(rows, e);
}
debugger;
//as an example of something custom
rows.push('something custom here');
// Get distinct text rows to deal with cases where 2 axes have the same dimensionality
return rows; //rows.filter(function (elem, pos) { return rows.indexOf(elem) === pos; });
}
!#")
d1
<!doctype HTML>
<meta charset = 'utf-8'>
<html>
<head>
<script src='http://d3js.org/d3.v3.min.js' type='text/javascript'></script>
<script src='http://dimplejs.org/dist/dimple.v2.0.0.min.js' type='text/javascript'></script>
<style>
.rChart {
display: block;
margin-left: auto;
margin-right: auto;
width: 800px;
height: 400px;
}
</style>
</head>
<body >
<div id = 'chart223c523f12cd' class = 'rChart dimple'></div>
<script type="text/javascript">
var chart223c523f12cd = (function() {
var opts = {
"dom": "chart223c523f12cd",
"width": 800,
"height": 400,
"xAxis": {
"type": "addCategoryAxis",
"showPercent": false,
"orderRule": "x"
},
"yAxis": {
"type": "addMeasureAxis",
"showPercent": false
},
"zAxis": {
"type": "addMeasureAxis"
},
"defaultColors": [],
"layers": [],
"legend": {
"x": 60,
"y": 10,
"width": 700,
"height": 20
},
"x": "x",
"y": "y",
"groups": "grp",
"z": "y",
"type": "bubble",
"id": "chart223c523f12cd"
},
data = [{"x":"A","y":76.2302330867387,"grp":1},{"x":"B","y":85.7265882596839,"grp":2},{"x":"C","y":61.678814667277,"grp":1},{"x":"D","y":28.7233419870026,"grp":2}];
var svg = dimple.newSvg("#" + opts.id, opts.width, opts.height);
//data = dimple.filterData(data, "Owner", ["Aperture", "Black Mesa"])
var myChart = new dimple.chart(svg, data);
if (opts.bounds) {
myChart.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) { myChart.noFormats = opts.noFormats; };
//for markimekko and addAxis also have third parameter measure
//so need to evaluate if measure provided
//function to build axes
function buildAxis(position,layer){
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.overrideMin) axis.overrideMin = axisopts.overrideMin;
if (axisopts.overrideMax) axis.overrideMax = axisopts.overrideMax;
if (axisopts.overrideMax) axis.overrideMax = axisopts.overrideMax;
if (axisopts.inputFormat) axis.dateParseFormat = axisopts.inputFormat;
if (axisopts.outputFormat) axis.tickFormat = axisopts.outputFormat;
return axis;
};
var c = null;
if(d3.keys(opts.colorAxis).length > 0) {
c = myChart[opts.colorAxis.type](opts.colorAxis.colorSeries,opts.colorAxis.palette) ;
if(opts.colorAxis.outputFormat){
c.tickFormat = opts.colorAxis.outputFormat;
}
}
//allow manipulation of default colors to use with dimple
if(opts.defaultColors.length) {
//need to adjust this if not handed an array of arrays
//depends on which toJSON used in R
if (opts.defaultColors[0].length) {
opts.defaultColors = opts.defaultColors[0];
}
if (typeof(opts.defaultColors) == "function") {
//assume this is a d3 scale
//for now loop through first 20 but need a better way to handle
defaultColorsArray = [];
for (var n=0;n<20;n++) {
defaultColorsArray.push(opts.defaultColors(n));
};
opts.defaultColors = defaultColorsArray;
}
opts.defaultColors.forEach(function(d,i) {
opts.defaultColors[i] = new dimple.color(d);
})
myChart.defaultColors = opts.defaultColors;
}
//do series
//set up a function since same for each
//as of now we have x,y,groups,data,type in opts for primary layer
//and other layers reside in opts.layers
function buildSeries(layer, hidden){
//inherit from primary layer if not intentionally changed or xAxis, yAxis, zAxis null
if (!layer.xAxis) layer.xAxis = opts.xAxis;
if (!layer.yAxis) layer.yAxis = opts.yAxis;
if (!layer.zAxis) layer.zAxis = opts.zAxis;
var x = buildAxis("x", layer);
x.hidden = hidden;
var y = buildAxis("y", layer);
y.hidden = hidden;
//z for bubbles
var z = null;
if (!(typeof(layer.zAxis) === 'undefined') && layer.zAxis.type){
z = buildAxis("z", layer);
};
//here think I need to evaluate group and if missing do null
//as the group argument
//if provided need to use groups from layer
var s = new dimple.series(myChart, null, x, y, z, c, dimple.plot[layer.type], dimple.aggregateMethod.avg, dimple.plot[layer.type].stacked);
//as of v1.1.4 dimple can use different dataset for each series
if(layer.data){
//convert to an array of objects
var tempdata;
//avoid lodash for now
datakeys = d3.keys(layer.data)
tempdata = layer.data[datakeys[1]].map(function(d,i){
var tempobj = {}
datakeys.forEach(function(key){
tempobj[key] = layer.data[key][i]
})
return tempobj
})
s.data = tempdata;
}
if(layer.hasOwnProperty("groups")) {
s.categoryFields = (typeof layer.groups === "object") ? layer.groups : [layer.groups];
//series offers an aggregate method that we will also need to check if available
//options available are avg, count, max, min, sum
}
if (!(typeof(layer.aggregate) === 'undefined')) {
s.aggregate = eval(layer.aggregate);
}
if (!(typeof(layer.lineWeight) === 'undefined')) {
s.lineWeight = layer.lineWeight;
}
if (!(typeof(layer.barGap) === 'undefined')) {
s.barGap = layer.barGap;
}
if (!(typeof(layer.interpolation) === 'undefined')) {
s.interpolation = layer.interpolation;
}
/* if (!(typeof(layer.eventHandler) === 'undefined')) {
layer.eventHandler = (layer.eventHandler.length === "undefined") ? layer.eventHandler : [layer.eventHandler];
layer.eventHandler.forEach(function(evt){
s.addEventHandler(evt.event, eval(evt.handler))
})
}*/
myChart.series.push(s);
/*placeholder fix domain of primary scale for new series data
//not working right now but something like this
//for now just use overrideMin and overrideMax from rCharts
for( var i = 0; i<2; i++) {
if (!myChart.axes[i].overrideMin) {
myChart.series[0]._axisBounds(i==0?"x":"y").min = myChart.series[0]._axisBounds(i==0?"x":"y").min < s._axisBounds(i==0?"x":"y").min ? myChart.series[0]._axisBounds(i==0?"x":"y").min : s._axisBounds(i==0?"x":"y").min;
}
if (!myChart.axes[i].overrideMax) {
myChart.series[0]._axisBounds(i==0?"x":"y")._max = myChart.series[0]._axisBounds(i==0?"x":"y").max > 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 s;
};
buildSeries(opts, false);
if (opts.layers.length > 0) {
opts.layers.forEach(function(layer){
buildSeries(layer, true);
})
}
//unsure if this is best but if legend is provided (not empty) then evaluate
if(d3.keys(opts.legend).length > 0) {
var l =myChart.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) {
myChart.setStoryboard(opts.storyboard);
};
//catch all for other options
//these can be provided by dMyChart$chart( ... )
myChart.series[0].getTooltipText =
function (e) {
var rows = [];
// Add the series categories
if (this.categoryFields !== null && this.categoryFields !== undefined && this.categoryFields.length > 0) {
this.categoryFields.forEach(function (c, i) {
if (c !== null && c !== undefined && e.aggField[i] !== null && e.aggField[i] !== undefined) {
// If the category name and value match don't display the category name
rows.push(c + (e.aggField[i] !== c ? ': ' + e.aggField[i] : ''));
}
}, this);
}
if (this.x) {
this.x._getTooltipText(rows, e);
}
if (this.y) {
this.y._getTooltipText(rows, e);
}
if (this.z) {
// this.z._getTooltipText(rows, e);
}
if (this.c) {
// this.c._getTooltipText(rows, e);
}
//debugger;
//as an example of something custom
rows.push('something custom here');
// Get distinct text rows to deal with cases where 2 axes have the same dimensionality
return rows; //rows.filter(function (elem, pos) { return rows.indexOf(elem) === pos; });
}
;
myChart.draw();
return myChart;
})();
</script>
<script></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment