Search code examples
javascriptjqueryd3.jsleafletfolium

D3js legend color does not match to the map color javascript


I have a map already drawed. I would like to add a legend using d3.js. For example when filering by length, the map should show differents colors. Since a week, I couldn't achieve this task. My map color seem to be good but the legend does not match. Could anybody help me with my draw link function ? https://jsfiddle.net/aba2s/xbn9euh0/12/)

I think it's the error is about the legend function. Here is the function that change my map color Roads.eachLayer(function (layer) {layer.setStyle({fillColor: colorscale(layer.feature.properties.length)})});

function drawLinkLegend(dataset, colorscale, min, max) {
    // Show label
    linkLabel.style.display = 'block'

    var legendWidth = 100
        legendMargin = 10
        legendLength = document.getElementById('legend-links-container').offsetHeight - 2*legendMargin
        legendIntervals = Object.keys(colorscale).length
        legendScale = legendLength/legendIntervals

    // Add legend

    var legendSvg = d3.select('#legend-links-svg')
                .append('g')
                .attr("id", "linkLegendSvg");

    var bars = legendSvg.selectAll(".bars")
      //.data(d3.range(legendIntervals), function(d) { return d})
      .data(dataset)
      .enter().append("rect")
        .attr("class", "bars")
        .attr("x", 0)
        .attr("y", function(d, i) { return legendMargin + legendScale * (legendIntervals - i-1); })
        .attr("height", legendScale)
        .attr("width", legendWidth-50)
        .style("fill", function(d) { return colorscale(d) })

    // create a scale and axis for the legend
    var legendAxis = d3.scaleLinear()
        .domain([min, max])
        .range([legendLength, 0]);

    legendSvg.append("g")
         .attr("class", "legend axis")
         .attr("transform", "translate(" + (legendWidth - 50) + ", " + legendMargin + ")")
         .call(d3.axisRight().scale(legendAxis).ticks(10))
}

Solution

  • D3 expects your data array to represent the elements you are creating. It appears you are passing an array of all your features: but you want your scale to represent intervals. It looks like you have attempted this approach, but you haven't quite got it.

    We want to access the minimum and maximum values that will be provided to the scale. To do so we can use scale.domain() which returns an array containing the extent of the domain, the min and max values.

    We can then create a dataset that contains values between (and including) these two endpoints.

    Lastly, we can calculate their required height based on how high the visual scale is supposed to be by dividing the height of the visual scale by the number of values/intervals.

    Then we can supply this information to the enter/update/exit cycle. The enter/update/exit cycle expects one item in the data array for every element in the selection - hence why need to create a new dataset.

    Something like the following shold work:

    var dif = colorscale.domain()[1] - colorscale.domain()[0];
    var intervals = d3.range(20).map(function(d,i) {
        return dif * i / 20 + colorscale.domain()[0]
    }) 
    intervals.push(colorscale.domain()[1]);
    var intervalHeight = legendLength / intervals.length;
     
    var bars = legendSvg.selectAll(".bars")
      .data(intervals)
      .enter().append("rect")
        .attr("class", "bars")
        .attr("x", 0)
        .attr("y", function(d, i) { return Math.round((intervals.length - 1 - i)  * intervalHeight) + legendMargin; })
        .attr("height", intervalHeight)
        .attr("width", legendWidth-50)
        .style("fill", function(d, i) { return colorscale(d) })
    

    In troubleshooting your existing code, you can see you have too many elements in the DOM when representing the scale. Also, Object.keys(colorscale).length won't produce information useful for generating intervals - the keys of the scale are not dependent on the data.

    eg