2017-03-02 93 views
2

我有一個美國縣地圖,應該在動畫中顯示14天內各縣的水量。我需要以紅色(小於50毫米),綠色(大於49毫米和小於100毫米)和藍色(大於100毫米)顯示顏色。我已經改編自麥克·博斯托克和Rich多諾霍以下代碼:如何根據數據值將特定顏色應用於D3.js地圖?

<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 

上面的代碼適用 「等值線」 的色彩使用填充-opacity。只有不同色調的鋼藍顏色。但我需要應用綠色,藍色和紅色。

感謝任何幫助。

回答

4

與使用css設置所有要素的顏色,然後將線性比例的不透明度值應用於每個要素相比,您可以直接使用比例(D3比例範圍接受顏色)輸出顏色。然後,不要設置填充不透明度,只需設置填充。

例如:

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); }); 
 
    
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

只要確保你的CSS依然不指定鋼藍色。

您也可以使用十六進制顏色代碼或指定多個步驟:

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); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

雖然,你可能需要一個臨界規模,如果你想爲每個值的明確步驟:

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); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

範圍內還有一個比閾值範圍更多的元素。設想一個單一的閾值,它將有一個值超過一個值小於。

+0

感謝您的迴應,安德魯。我嘗試過: var color = d3.scale.linear() .domain([0,160,325] //這些是來自csv文件的最小值,平均值,最大值 .range [「red」,「green」 , 「藍色」]); //幹,中,溼顏色 我無法使其訪問實際的csv文件來讀取每日值並根據讀取的值應用顏色。因此,點擊按鈕時不會生成動畫。我希望我可以附上我的csv文件,但不知道如何在這個論壇。 – user2770113

+0

需要澄清一些細節。原始腳本是否根據csv值成功修改了縣的透明度?如果沒有任何動畫,您是否可以在繪製它們時最初對縣進行着色? –

+0

是的。原始腳本在單擊播放按鈕並且透明膠片沒問題時進行動畫製作。此外,初始顏色設置爲顯示第1天的數據。所以原始腳本正在工作。但我的老闆想要顏色,我認爲它類似於「熱圖」,在那裏你可以看到隨着時間的推移,哪些縣有幹/溼天的趨勢,因此這些地區的農民可以決定是否使用肥料和其他做法需要做。 – user2770113