如何根据数据值将特定颜色应用于D3.js贴图?

时间:2017-03-02 03:33:21

标签: d3.js

我有一张美国县地图,应该在动画中显示14天内各县的水量。我需要显示红色(小于50毫米),绿色(大于49毫米且小于100毫米)和蓝色(大于100毫米)的颜色。我改编自Mike Bostock和Rich Donohue以下代码:

<style>
.county {
    fill:steelblue;
    stroke: #fff; /*White*/
    stroke-width: .5px;
}

#play, #clock {
    position: absolute;
    /*top: 15px;*/
}

#play {
    /*left: 15px;*/
    left: 160px;
    top: 140px;
}

#clock {
    left: 220px;
    top: 148px;
}

<button id="play">Play</button>
<span id="clock">Day</span>
<h1 style="text-align:center">14-Day Water Yield By County</h1>

<div id="svgDiv1" style="text-align:center">
<svg width="960" height="600" stroke-linejoin="round" stroke-linecap="round">
    <defs>
        <filter id="blur">
            <feGaussianBlur stdDeviation="5"></feGaussianBlur>
        </filter>
    </defs>
</svg>

<script>

//globals
var width, height, projection, path, group, graticule, svg, defs, attributeArray = [], currentAttribute = 0, playing = false;

function init() {

    setMap();
    animateMap();

}

function setMap() {

    svg = d3.select("svg");

    defs = svg.select("defs");

    path = d3.geoPath();

    d3.json("/topo/us-10m.v1.json", function (error, us) {
        if (error) throw error;

        defs.append("path")
            .attr("id", "nation")
            .attr("d", path(topojson.feature(us, us.objects.counties)));

        svg.append("use")
            .attr("xlink:href", "#nation")
            .attr("fill-opacity", 0.2)
            .attr("filter", "url(#blur)");

        svg.append("use")
            .attr("xlink:href", "#nation")
            .attr("fill", "#fff");

        svg.append("path")
            .attr("fill", "none")
            .attr("stroke", "#777")
            .attr("stroke-width", 0.70)
            .attr("d", path(topojson.mesh(us, us.objects.counties, function (a, b) { return a !== b; })));
    });

    loadData();  // let's load our data next
}

function loadData() {
    queue()   // queue function loads all external data files asynchronously
      .defer(d3.json, "/topo/us-10m.v1.json")  // our geometries
      .defer(d3.csv, "/data/wtryld.csv")  // and associated data in csv file
      .await(processData);   // once all files are loaded, call the processData function passing the loaded objects as arguments
}

function processData(error, us, countyData) {
    // function accepts any errors from the queue function as first argument, then
    // each data object in the order of chained defer() methods above
    if (error) throw error;

    //Get values from geojson
    var conus = topojson.feature(us, us.objects.counties); // store the path in variable for ease

    //Get values from csv file
    for (var i in conus.features) {    // for each geometry object
        for (var j in countyData) {  // for each row in the CSV
            if (conus.features[i].id == countyData[j].id) {  // if they match
                for (var k in countyData[i]) {   // for each column in the a row within the CSV
                    if (k != 'id' && k != 'County') {  // select only number of days as column headings
                        if (attributeArray.indexOf(k) == -1) {
                            attributeArray.push(k);  // add new column headings to our array for later
                        }
                        conus.features[i].properties[k] = Number(countyData[j][k])  // add each CSV column key/value to geometry object
                    }
                }
                break;  // stop looking through the CSV since we made our match
            }
        }
    }
    d3.select('#clock').html(attributeArray[currentAttribute]);  // populate the clock initially with the current day
    drawMap(conus);  // let's mug the map now with our newly populated data object
}

//Sort function; can specify multiple columns to sort: propSort("STATE", "COUNTY");
function propSort(props) {
    if (!props instanceof Array) props = props.split(",");
    return function sort(a, b) {
        var p;
        a = a.properties;
        b = b.properties;
        for (var i = 0; i < props.length; i++) {
            p = props[i];
            if (typeof a[p] === "undefined") return -1;
            if (a[p] < b[p]) return -1;
            if (a[p] > b[p]) return 1;
        }
        return 0;
    };
}

function drawMap(conus) {

    svg.selectAll(".feature")   // select country objects (which don't exist yet)
      .data(conus.features)   // bind data to these non-existent objects
      .enter().append("path") // prepare data to be appended to paths
      .attr("class", "county") // give them a class for styling and access later
      .attr("id", function (d) { return d.properties.id; }, true)  // give each a unique id for access later
      .attr("d", path); // create them using the svg path generator defined above

    var dataRange = getDataRange(); // get the min/max values from the current day's range of data values
    d3.selectAll('.county')  // select all the counties
        .attr('fill-opacity', function (d) {
            return getColor(d.properties[attributeArray[currentAttribute]], dataRange);  // give them an opacity value based on their current value
        });
}

function sequenceMap() {
    var dataRange = getDataRange(); // get the min/max values from the current year's range of data values
    d3.selectAll('.county').transition()  //select all the counties and prepare for a transition to new values
      .duration(300)  // give it a smooth time period for the transition
      .attr('fill-opacity', function (d) {
          return getColor(d.properties[attributeArray[currentAttribute]], dataRange);  // the end color value
      })
}

function getColor(valueIn, valuesIn) {
    // create a linear scale
    var color = d3.scale.linear()
      .domain([valuesIn[0], valuesIn[1]])  // input uses min and max values
      .range([.3, 1]);   // output for opacity between .3 and 1 %

    return color(valueIn);  // return that number to the caller
}

function getDataRange() {
    // function loops through all the data values from the current data attribute
    // and returns the min and max values

    var min = Infinity, max = -Infinity;
    d3.selectAll('.county')
        .each(function (d, i) {
          var currentValue = d.properties[attributeArray[currentAttribute]];
          if (currentValue <= min && currentValue != -99 && currentValue != 'undefined') {
              min = currentValue;
          }
          if (currentValue >= max && currentValue != -99 && currentValue != 'undefined') {
              max = currentValue;
          }
      });
    return [min, max];
}

function animateMap() {

    var timer;  // create timer object
    d3.select('#play')
      .on('click', function () {  // when user clicks the play button
          if (playing == false) {  // if the map is currently playing
              timer = setInterval(function () {   // set a JS interval
                  if (currentAttribute < attributeArray.length - 1) {
                      currentAttribute += 1;  // increment the current attribute counter
                  } else {
                      currentAttribute = 0;  // or reset it to zero
                  }
                  sequenceMap();  // update the representation of the map
                  d3.select('#clock').html(attributeArray[currentAttribute]);  // update the clock
              }, 2000);

              d3.select(this).html('Stop');  // change the button label to stop
              playing = true;   // change the status of the animation
          } else {    // else if is currently playing
              clearInterval(timer);   // stop the animation by clearing the interval
              d3.select(this).html('Play');   // change the button label to play
              playing = false;   // change the status again
          }
      });
}


window.onload = init();  // magic starts here

上述代码通过使用填充不透明度来应用“等值线”颜色。只有不同色调的钢蓝色。但我需要应用绿色,蓝色和红色。

感谢任何帮助。

1 个答案:

答案 0 :(得分:5)

您可以直接使用刻度输出颜色(D3刻度范围接受颜色),而不是使用css设置所有要素的颜色,然后将线性刻度的不透明度值应用到每个要素。然后,不要设置填充不透明度,只需设置填充。

例如:

&#13;
&#13;
var color = d3.scale.linear()
  .domain([0, 9])  
  .range(["blue", "green"]); 
  
var svg = d3.select('body')
    .append('svg')
    .attr('width',500)
    .attr('height',200);
    
svg.selectAll('rect')
    .data(d3.range(10))
    .enter()
    .append('rect')
    .attr('x',function(d,i) { return i * 40; })
    .attr('y',30)
    .attr('width',30)
    .attr('height',30)
    .attr('fill',function(d,i) { return color(i); });
      
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
&#13;
&#13;
&#13;

请确保您的CSS尚未指定钢蓝色。

您也可以使用十六进制颜色代码或指定多个步骤:

&#13;
&#13;
var color = d3.scale.linear()
  .domain([0, 5, 9])  
  .range(["blue", "yellow", "green"]); 
  
var svg = d3.select('body')
    .append('svg')
    .attr('width',500)
    .attr('height',200);
    
svg.selectAll('rect')
    .data(d3.range(10))
    .enter()
    .append('rect')
    .attr('x',function(d,i) { return i * 40; })
    .attr('y',30)
    .attr('width',30)
    .attr('height',30)
    .attr('fill',function(d,i) { return color(i); });
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
&#13;
&#13;
&#13;

但是,如果您想为每个值采取明确的步骤,则可能需要阈值比例:

&#13;
&#13;
var color = d3.scale.threshold()
  .domain([2, 5, 9])  
  .range(["blue","yellow","green","orange"]); 
  
var svg = d3.select('body')
    .append('svg')
    .attr('width',500)
    .attr('height',200);
    
svg.selectAll('rect')
    .data(d3.range(10))
    .enter()
    .append('rect')
    .attr('x',function(d,i) { return i * 40; })
    .attr('y',30)
    .attr('width',30)
    .attr('height',30)
    .attr('fill',function(d,i) { return color(i); });
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
&#13;
&#13;
&#13;

范围内的元素多于阈值范围的域。想象一个单一的阈值,它将有一个值超过,一个值低于。