Joohun Lee Joohun Lee - 6 days ago 6
Javascript Question

Keep legend constant and only update chart in D3

my coding is to plot x axis with location, y axis with (value1,value2 or value3) and legend with types(high, medium,low). what I'm trying to do is to add menu with value1,2,3 and add legend with different types so if I change from either menu or click on legend, plot got updated with only selected data.
however, my code below is only able to create legend set as default type or clicked but not able to include all types. is there any way to include all types in legends constantly no matter what type is clicked and only update chart accordingly?
thank you,

<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960- margin.left - margin.right,
height = 900 - margin.top - margin.bottom,
radius = 3.5,
padding = 1,
xVar = "location",
cVar= " type";
default = "high";


// add the tooltip area to the webpage
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);


// force data to update when menu is changed
var menu = d3.select("#menu select")
.on("change", change);

// load data
d3.csv("sample.csv", function(error, data) {
formatted = data;

draw();
});


// set terms of transition that will take place
// when new indicator from menu or legend is chosen
function change() {
//remove old plot and data
var svg = d3.select("svg");
svg.transition().duration(100).remove();

//redraw new plot with new data
d3.transition()
.duration(750)
.each(draw)
}


function draw() {

// add the graph canvas to the body of the webpage

var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")


// setup x
var xValue = function(d) { return d[xVar];}, // data -> value
xScale = d3.scale.ordinal()
.rangeRoundBands([0,width],1), //value -> display
xMap = function(d) { return (xScale(xValue(d)) + Math.random()*10);}, // data -> display
xAxis = d3.svg.axis().scale(xScale).orient("bottom");

// setup y
var yVar = menu.property("value"),

yValue = function(d) { return d[yVar];}, // data -> value
yScale = d3.scale.linear().range([height, 0]), // value -> display
yMap = function(d) { return yScale(yValue(d));}, // data -> display
yAxis = d3.svg.axis().scale(yScale).orient("left");


// setup fill color
var cValue = function(d) { return d[cVar];},
color = d3.scale.category10();


// filter the unwanted data and plot with only chosen dataset.
data = formatted.filter(function(d, i)
{
if (d[cVar] == default)
{
return d;
}
});
data = formatted;
// change string (from CSV) into number format
data.forEach(function(d) {
d[xVar] = d[xVar];
d[yVar] = +d[yVar];
});


xScale.domain(data.sort(function(a, b) { return d3.ascending(a[xVar], b[xVar])})
.map(xValue) );
// don't want dots overlapping axis, so add in buffer to data domain
yScale.domain([d3.min(data, yValue)-1, d3.max(data, yValue)+1]);


// x-axis

svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text(xVar);


// y-axis

svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(yVar);

// draw dots
var dot = svg.selectAll(".dot")
.data(data)
.enter()
.append("circle")
.attr("class", "dot")
.attr("r", radius)
.attr("cx", xMap)
.attr("cy", yMap)
.style("fill", function(d) { return color(cValue(d));})
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(d[SN] + "<br/> (" + xValue(d)
+ ", " + yValue(d) + ")")
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});

// draw legend

var legend = svg.selectAll(".legend")
.data(color.domain().slice())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

// draw legend colored rectangles
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color)

.on("click", function (d){
default = d;
return change();
});

// draw legend text
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d;})
.on("click", function (d) {
default = d;
return change();
});

};

</script>
</body>


sample.csv

location type value1 value2 value3
A high 1 -2 -5
B medium 2 3 4
C low 4 1 2
C medium 6 3 4
A high 4 5 6
D low -1 3 2

Answer

I found a way to include all types in the legend. first, extract unique types from column "type" and save them in the "legend_keys" as array. second, instead of pre-define "default", set the first type in the "legend_keys" as a default. but next default will be set by the event on click out of legend.

d3.csv("sample.csv", function(error, data) {
  formatted = data;
  var nest = d3.nest()
    .key(function(d) { return d[cVar]; })
    .entries(formatted);

  console.log(nest);

  legend_keys = nest.map(function(o){return o.key});
  default = legend_keys[0];
  //console.log(legend_keys[0]);
  draw();
});

Finally, when define the legend, read "legend_keys" as data as below. By doing this, I can always keep all types in the legend.

 var legend = svg.selectAll(".legend")
      .data(legend_keys)
      .enter().append("g")
      .attr("class", "legend")
      .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; })
      .on("click", function (d){
        default = d;
        console.log(default);
        return change();
      });
Comments