Question

I am trying to implement reusable charting with d3js, where chart type is changed based on user selection from drop-down menu.

My code here: http://tributary.io/inlet/8085642

GOAL: redraw new chart type in place of old chart when user selects type from the drop down menu.

I have everything set up as needed: can draw each chart on its own manually, the drop-down menu will console.log the name of the chart type I want to draw (line, area, candle), but obviously the goal is to do this programatically.

EDIT: eliminated aside from below, not related to original question above.

Was it helpful?

Solution

As Lars said, the problem isn't one of getting the reusable chart to work. (In fact, I wouldn't really call this a "reusable chart" in the sense of a customizable chart function to which you pass parameters; you just have multiple different functions.) But regardless: Your code works fine with the different options when you comment in the other function calls. All that is missing is the Javascript to trigger the call and some clean-up. I've got a discussion below for how to do that, but maybe update the title/question to "Calling alternate chart drawing function based on user selection" or some such.

There are three things you need:

  1. An event listener attached to the drop-down menu, so that code gets triggered when the selection gets changed,
  2. a way of deleting or hiding the current chart data, and
  3. a function to read the selected value and call the corresponding draw function.

You've already got the event listener here:

 d3.select("#drop-down").on("change", function () { 
    selected = this.value;
    console.log(selected);
 });

It's returning the value fine on the console, but instead you need to actually call the appropriate method, like this:

 d3.select("#drop-down").on("change", function () { 
    selected = this.value;
    if(selected == "line"){chartDraw.line()}
    else if(selected == "area"){chartDraw.area()}
    else if(selected == "candle"){chartDraw.candle()}
 });

If you have a lot of cases, you would probably want to re-write this as a switch statement.

However, this isn't quite what you want yet -- the new graph just gets drawn overtop of the old. What you need is a way to re-select the elements that are unique to each type of graph so you can remove them. To do this, you'll need to add a class to all the elements created within the chartDraw methods, something like .attr("class" "line alt-view"),or "candle alt-view", etc., then add one more line to your event handler function:

 d3.select("#drop-down").on("change", function () { 
    selected = this.value;
    d3.selectAll(".alt-view").remove();
    if(selected == "line"){chartDraw.line()}
    else if(selected == "area"){chartDraw.area()}
    else if(selected == "candle"){chartDraw.candle()}
 });

That creates a clean canvas for your re-draw (without deleting your axis and other constant elements).

That should get it working, but you can still make it more efficient, depending on how you expect it to be used. If you expect that users will be flipping back and forth between the different views (and don't expect the data to change in the meantime) you may want to just hide the alternate views instead of deleting and then re-drawing them.

Hiding is easy; just replace the .remove() above with .style("visibility", "hidden"). But you also need a way to test whether you've already drawn a specific chart view so you can just show it with .style("visibility", "visible") instead of re-drawing. My suggestion? Just create boolean variables and set them the first time you draw each chart, then check them in the chart-drawing method:

var areaDrawn=false, lineDrawn=false, candleDrawn=false; 

(or collect these all into an object: viewDrawn = {area:false, line:false, etc... and change the below code accordingly)

//inside the chartDraw object
   area: function (){
     if(areaDrawn){
        d3.selectAll(".area.alt-view").style("visibility", "visible");
     }
     else {  
      canvas.append("path")
        .datum(sample2)
        .attr("class", "area alt-view")
        .attr("d", area);

      areaDrawn=true;
     }
    }
//and similar for the other functions

There is one final thing I can't figure out: when you select something from the drop-down menu, the menu doesn't redraw right away with the selected value. The value changes, and if I tab away and come back the correct value is shown, but not right away. I'm not sure if this is something going on in Tributary or something to do with the way Chrome handles foreignObjects in SVG. Either way, it's something to test and perhaps consider creating the menu outside of the SVG (grouped together by a container div) for your production code.

Hope that all makes sense, I've tried to do it step-by-step,
--ABR

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top