rCharts + dimple | Interactive Legend

There are plans in rCharts future to provide support for partial layouts similar to slidify dev branch to provide templates in convenient places to customize your rChart creation. So For now, there are some options to add script/html to customize your chart. A good reference example comes from dimplejs new interactive legends example. In this case a small bit of javascript is added to the base chart to hide/show scatter points when the corresponding legend item is clicked.

Currently, I think the best method of adding script is through use of slidify. Since slidify is authored by the same author as rCharts, they play very nicely together. To run this reproducible example on your own, just download this .Rmd file and slidify it.

If you look at the code, you can see that I grab the data and create an rChart as normal. Then I simply take advantage of markdown's script rCharts afterScript support to copy/paste the script inside of the <script>...</script> tags. I only made one change to the script, since the rCharts dimple implementation uses l rather than mylegend to refer to the legend.

Other options to customize would be to create a custom html template that adds the script. For an example of this option, see this rCharts d3 horizon chart tutorial. If you plan to use the html inline, you could use myrChart$html() and then append the script before including in your final page.

Get Our Data

data <- read.delim(
  "http://pmsi-alignalytics.github.io/dimple/data/example_data.tsv"
)
### eliminate . to avoid confusion in javascript
colnames(data) <- gsub("[.]","",colnames(data))

Draw Our Chart

require(rCharts)
d1 <- dPlot(
  SalesValue~Price,
  groups = c("SKU", "Channel", "Owner"),
  data = subset(data, Date == "01/12/2012"),
  type = "bubble",
  height = 380,
  width = 590,
  bounds = list(x=60, y=30, width=420, height=310),
  xlab = "I am a changed x", #example of a custom x label
  ylab = "I am a changed y"
)
d1$xAxis( type = "addMeasureAxis" )
d1$yAxis( type = "addMeasureAxis" )
d1$legend(
  x = 530,
  y = 100,
  width = 60,
  height = 300,
  horizontalAlign = "right"
)
d1$setTemplate(
  afterScript = 
'<script>

          myChart.axes.filter(function(ax){return ax.position == "x"})[0].titleShape.text(opts.xlab)
          myChart.axes.filter(function(ax){return ax.position == "y"})[0].titleShape.text(opts.ylab)

        // This is a critical step.  By doing this we orphan the legend. This
        // means it will not respond to graph updates.  Without this the legend
        // will redraw when the chart refreshes removing the unchecked item and
        // also dropping the events we define below.
        myChart.legends = [];

        // This block simply adds the legend title. I put it into a d3 data
        // object to split it onto 2 lines.  This technique works with any
        // number of lines, it isn\'t dimple specific.
        svg.selectAll("title_text")
          .data(["Click legend to","show/hide owners:"])
          .enter()
          .append("text")
            .attr("x", 499)
            .attr("y", function (d, i) { return 90 + i * 14; })
            .style("font-family", "sans-serif")
            .style("font-size", "10px")
            .style("color", "Black")
            .text(function (d) { return d; });

        // Get a unique list of Owner values to use when filtering
        var filterValues = dimple.getUniqueValues(data, "Owner");
        // Get all the rectangles from our now orphaned legend
        l.shapes.selectAll("rect")
          // Add a click event to each rectangle
          .on("click", function (e) {
            // This indicates whether the item is already visible or not
            var hide = false;
            var newFilters = [];
            // If the filters contain the clicked shape hide it
            filterValues.forEach(function (f) {
              if (f === e.aggField.slice(-1)[0]) {
                hide = true;
              } else {
                newFilters.push(f);
              }
            });
            // Hide the shape or show it
            if (hide) {
              d3.select(this).style("opacity", 0.2);
            } else {
              newFilters.push(e.aggField.slice(-1)[0]);
              d3.select(this).style("opacity", 0.8);
            }
            // Update the filters
            filterValues = newFilters;
            // Filter the data
            myChart.data = dimple.filterData(data, "Owner", filterValues);
            // Passing a duration parameter makes the chart animate. Without
            // it there is no transition
            myChart.draw(800);

          myChart.axes.filter(function(ax){return ax.position == "x"})[0].titleShape.text(opts.xlab)
          myChart.axes.filter(function(ax){return ax.position == "y"})[0].titleShape.text(opts.ylab)
            });
  </script>'
)
d1

Now Add Script to Make Legend Interactive

Although you won't know it unless you look at the source, I duplicate the script so that it shows in the tutorial. This is unnecessary unless you want the script to be visible on the rendered html page.

<script>
        // This is a critical step.  By doing this we orphan the legend. This
        // means it will not respond to graph updates.  Without this the legend
        // will redraw when the chart refreshes removing the unchecked item and
        // also dropping the events we define below.
        myChart.legends = [];

        // This block simply adds the legend title. I put it into a d3 data
        // object to split it onto 2 lines.  This technique works with any
        // number of lines, it isn't dimple specific.
        svg.selectAll("title_text")
          .data(["Click legend to","show/hide owners:"])
          .enter()
          .append("text")
            .attr("x", 499)
            .attr("y", function (d, i) { return 90 + i * 14; })
            .style("font-family", "sans-serif")
            .style("font-size", "10px")
            .style("color", "Black")
            .text(function (d) { return d; });

        // Get a unique list of Owner values to use when filtering
        var filterValues = dimple.getUniqueValues(data, "Owner");
        // Get all the rectangles from our now orphaned legend
        l.shapes.selectAll("rect")
          // Add a click event to each rectangle
          .on("click", function (e) {
            // This indicates whether the item is already visible or not
            var hide = false;
            var newFilters = [];
            // If the filters contain the clicked shape hide it
            filterValues.forEach(function (f) {
              if (f === e.aggField.slice(-1)[0]) {
                hide = true;
              } else {
                newFilters.push(f);
              }
            });
            // Hide the shape or show it
            if (hide) {
              d3.select(this).style("opacity", 0.2);
            } else {
              newFilters.push(e.aggField.slice(-1)[0]);
              d3.select(this).style("opacity", 0.8);
            }
            // Update the filters
            filterValues = newFilters;
            // Filter the data
            myChart.data = dimple.filterData(data, "Owner", filterValues);
            // Passing a duration parameter makes the chart animate. Without
            // it there is no transition
            myChart.draw(800);

            myChart.axes.filter(function(ax){return ax.position == "x"})[0].titleShape.text  (opts.xlab)
            myChart.axes.filter(function(ax){return ax.position == "y"})[0].titleShape.text(opts.ylab)
            });            
          });
  </script>

Thanks