Реализация нескольких фильтров в одном наборе данных в d3.js - PullRequest
0 голосов
/ 15 ноября 2018

Я надеялся получить несколько советов о том, как реализовать несколько фильтров ползунка диапазона для одного и того же набора данных (но двух разных атрибутов) на имеющейся у меня карте d3 / leaflet.

Я бы хотел использовать ползунок диапазона для фильтрации точек на карте по дате и еще один ползунок диапазона для фильтрации точек на карте по «размеру». В настоящее время способ, которым я это настроил, предназначен только для фильтрации по дате. По сути, когда создается экземпляр ползунка времени, он передает значение непрозрачности из функции, которая может динамически устанавливать непрозрачность точки равной нулю, отфильтровывая их. Вот основной код:

//Build time-slider in HTML
<div  id="sliderContainer"> 
<input id="timeslide-leaflet" type="range" min="0" max="25" value="0" step="1" /><br>

//Define years for time slider
var inputValue = null;
var years = ["1993","1994","","","" ];

        //load in data
        SFData.forEach(function(d) {
        var coords = d.geometry.coordinates
        console.log(coords)
        d.latLong = new L.LatLng(coords[1],
                                coords[0]);

    })

    //draw map circles
    var feature = mapG.selectAll("circle")
        .data(SFData)
        .enter().append("circle")
        .attr("class", 'features')

    //if time-slider moved, call update function
    d3.select("#timeslide-leaflet").on("input", function() {        
        update(+this.value);   
    });

    //Update function sets feature's opacity and can filter by making invisible
    function update(value) {
        document.getElementById("range-leaflet").innerHTML=years[value];
        inputValue = years[value];

        d3.selectAll(".features")
            .attr("opacity", dateMatch)       
}


    function dateMatch(data, value) {

    //do some internal calculations based on my data

    if (yearInt <= inputValueInt && yearExpirationInt >= inputValueInt) {

        return ".7";
    } else {
        return "0"; //return opacity 0 for data that should be filtered out 
    };
}

Я хотел бы иметь возможность фильтровать одни и те же данные по другому атрибуту (размеру) аналогичным образом. Я могу легко создать другой ползунок времени - но как мне передать другое возвращаемое значение непрозрачности объектам карты .attr ("непрозрачность")? По сути, я мог бы написать аналогичную функцию для dateMatch (), которая фильтрует по размеру, я не уверен, как бы передать возвращаемое значение непрозрачности обратно объектам карты, поскольку dateMatch () уже передает значение.

Спасибо!

1 Ответ

0 голосов
/ 15 ноября 2018

Создайте функцию, которая связывает непрозрачность

d3.selectAll(".features")
    .attr("opacity", calcOpacity);

function calcOpacity(d) {
    var opacity = dateOpacity(d, 1);
    return c2Opacity(d, opacity);
}

function dateOpacity(d, opacity) {
    if (opacity === 0.0) return opacity;
    // calcs
    return (yearInt <= inputValueInt && yearExpirationInt >= inputValueInt) ? 0.7 : 0.0;
}

function c2Opacity(d, opacity) {
    if (opacity === 0.0) return opacity;
    // calcs
    return Math.random() < 0.5 ? 0.7 : 0.0;
}

Редактировать

Я удалил редактирование этого ответа DiamondJoe12 и вставил фрагмент, использующий 2 ползункачтобы отфильтровать круги на графике.

Ползунок предоставляет необходимое значение year и size, я не вижу причины использовать массив поиска.

Наименьшее значение ползунка используется в качестве часового для передачи всех данных по этому конкретному фильтру.

Во фрагменте кода показано svg с 500 кружками разных размеров и разными данными года.

var svgWidth = 500, svgHeight = 500;

var svg = d3.select("body")
  .append("svg")
    .attr("width", svgWidth)
    .attr("height", svgHeight);

var color = d3.scaleOrdinal(d3.schemeCategory10);
var x = d3.scaleLinear().domain([-0.1, 1.1]).range([0, svgWidth]);
var y = d3.scaleLinear().domain([-0.1, 1.1]).range([0, svgHeight]);

var data = d3.range(500).map(i => ({x:Math.random(), y:Math.random(),
    year: 1993 + Math.floor(Math.random()*26),
    size: 4 + Math.floor(Math.random()*7) }));

svg.selectAll(".features")
    .data(data)
  .enter()
  .append("circle")
    .attr("class", "features")
    .attr("cx", d => x(d.x))
    .attr("cy", d => y(d.y))
    .attr("r", d => d.size)
    .attr("fill", (d, i) => color(i));

//if time-slider moved, call update function
d3.select("#timeslide-leaflet").on("input", function() {
    update();
});

d3.select("#sizeslide-leaflet").on("input", function() {
    update();
});

update();

//Update function sets feature's opacity and can filter by making invisible
function update() {
    var year = +document.getElementById("timeslide-leaflet").value;
    year = year === 1992 ? 0 : year;
    document.getElementById("time-leaflet").innerHTML = year === 0 ? "All" : year;

    var size = +document.getElementById("sizeslide-leaflet").value;
    size = size === 3 ? 0 : size;
    document.getElementById("size-leaflet").innerHTML = size === 0 ? "All" : size;

    svg.selectAll(".features")
      .transition()
      .duration(200)
        .attr("opacity", d => calcOpacity(d, year, size) );
}

function calcOpacity(d, year, size) {
    var opacity = dateOpacity(d, year, 1);
    return sizeOpacity(d, size, opacity);
}

function dateOpacity(d, year, opacity) {
    if (opacity === 0.0) return opacity;
    if (year === 0)  return opacity; // All pass
    return (d.year === year) ? 0.7 : 0.0;
}

function sizeOpacity(d, size, opacity) {
    if (opacity === 0.0) return opacity;
    if (size === 0)  return opacity; // All pass
    return d.size === size ? 0.7 : 0.0;
}
.features {
stroke:black;
stroke-width:0.5;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="sliderContainer"> 
<input id="timeslide-leaflet" type="range" min="1992" max="2018" value="1992" step="1" /><br/>
<input id="sizeslide-leaflet" type="range" min="3" max="10" value="3" step="1" />
</div>
<div>
<div>Year: <span id="time-leaflet"></span></div>
<div>Size: <span id="size-leaflet"></span></div>
</div>
...