D3 зум v3 против v5 - PullRequest
       43

D3 зум v3 против v5

0 голосов
/ 16 февраля 2019

У меня проблемы с переводом примера D3 с поведением увеличения с v3 на v5.Мой код основан на этом примере: https://bl.ocks.org/mbostock/2206340 от Mike Bostock.Я использую реагировать и получаю эти ошибки: «d3.zoom (...). Перевод не является функцией» и «d3.zoom (...). Масштаб не является функцией»).Я посмотрел в документации, но не смог найти масштаб или перевести только scaleBy и translateTo и translateBy.Я не могу понять, как это сделать в любом случае.

componentDidMount() {
    this.drawChart();
}

drawChart = () => {
    var width = window.innerWidth * 0.66,
        height = window.innerHeight * 0.7,
        centered,
        world_id;

    window.addEventListener("resize", function() {
        width = window.innerWidth * 0.66;
        height = window.innerHeight * 0.7;
    });

    var tooltip = d3
        .select("#container")
        .append("div")
        .attr("class", "tooltip hidden");

    var projection = d3
        .geoMercator()
        .scale(100)
        .translate([width / 2, height / 1.5]);

    var path = d3.geoPath().projection(projection);

    var zoom = d3
        .zoom()
        .translate(projection.translate())
        .scale(projection.scale())
        .scaleExtent([height * 0.197, 3 * height])
        .on("zoom", zoomed);

    var svg = d3
        .select("#container")
        .append("svg")
        .attr("width", width)
        .attr("class", "map card shadow")
        .attr("height", height);

    var g = svg.append("g").call(zoom);

    g.append("rect")
        .attr("class", "background")
        .attr("width", width)
        .attr("height", height);

    var world_id = data2;
    var world = data;
    console.log(world);

    var rawCountries = topojson.feature(world, world.objects.countries)
            .features,
        neighbors = topojson.neighbors(world.objects.countries.geometries);

    console.log(rawCountries);
    console.log(neighbors);
    var countries = [];

    // Splice(remove) random pieces
    rawCountries.splice(145, 1);
    rawCountries.splice(38, 1);

    rawCountries.map(country => {
        //console.log(parseInt(country.id) !== 010)
        // Filter out Antartica and Kosovo
        if (parseInt(country.id) !== parseInt("010")) {
            countries.push(country);
        } else {
            console.log(country.id);
        }
    });

    console.log(countries);

    g.append("g")
        .attr("id", "countries")
        .selectAll(".country")
        .data(countries)
        .enter()
        .insert("path", ".graticule")
        .attr("class", "country")
        .attr("d", path)
        .attr("data-name", function(d) {
            return d.id;
        })
        .on("click", clicked)
        .on("mousemove", function(d, i) {
            var mouse = d3.mouse(svg.node()).map(function(d) {
                return parseInt(d);
            });

            tooltip
                .classed("hidden", false)
                .attr(
                    "style",
                    "left:" + mouse[0] + "px;top:" + (mouse[1] - 50) + "px"
                )
                .html(getCountryName(d.id));
        })
        .on("mouseout", function(d, i) {
            tooltip.classed("hidden", true);
        });

    function getCountryName(id) {
        var country = world_id.filter(
            country => parseInt(country.iso_n3) == parseInt(id)
        );
        console.log(country[0].name);
        console.log(id);
        return country[0].name;
    }

    function updateCountry(d) {
        console.log(world_id);

        var country = world_id.filter(
            country => parseInt(country.iso_n3) == parseInt(d.id)
        );
        console.log(country[0].name);
        var iso_a2;
        if (country[0].name === "Kosovo") {
            iso_a2 = "xk";
        } else {
            iso_a2 = country[0].iso_a2.toLowerCase();
        }

        // Remove any current data
        $("#countryName").empty();
        $("#countryFlag").empty();

        $("#countryName").text(country[0].name);

        var src = "svg/" + iso_a2 + ".svg";
        var img = "<img id='flag' class='flag' src=" + src + " />";
        $("#countryFlag").append(img);
    }

    // Remove country when deselected
    function removeCountry() {
        $("#countryName").empty();
        $("#countryFlag").empty();
    }

    // When clicked on a country
    function clicked(d) {
        if (d && centered !== d) {
            centered = d;

            updateCountry(d);
        } else {
            centered = null;
            removeCountry();
        }

        g.selectAll("path").classed(
            "active",
            centered &&
                function(d) {
                    return d === centered;
                }
        );

        console.log("Clicked");
        console.log(d);
        console.log(d);

        var centroid = path.centroid(d),
            translate = projection.translate();

        console.log(translate);
        console.log(centroid);

        projection.translate([
            translate[0] - centroid[0] + width / 2,
            translate[1] - centroid[1] + height / 2
        ]);

        zoom.translate(projection.translate());

        g.selectAll("path")
            .transition()
            .duration(700)
            .attr("d", path);
    }

    // D3 zoomed
    function zoomed() {
        console.log("zoomed");
        projection.translate(d3.event.translate).scale(d3.event.scale);
        g.selectAll("path").attr("d", path);
    }
};

render() {
    return (
        <div className="container-fluid bg">
            <div class="row">
                <div className="col-12">
                    <h2 className="header text-center p-3 mb-5">
                        Project 2 - World value survey
                    </h2>
                </div>
            </div>
            <div className="row mx-auto">
                <div className="col-md-8">
                    <div id="container" class="mx-auto" />
                </div>
                <div className="col-md-4">
                    <div id="countryInfo" className="card">
                        <h2 id="countryName" className="p-3 text-center" />
                        <div id="countryFlag" className="mx-auto" />
                    </div>
                </div>
            </div>
        </div>
    );
}

1 Ответ

0 голосов
/ 16 февраля 2019

Я не буду вдаваться в различия между v3 и v5 отчасти потому, что прошло достаточно много времени, и я забыл многие детали и детали относительно того, как v3 отличался.Вместо этого я просто посмотрю, как реализовать этот пример с v5. Этот ответ потребует адаптации для негеографических случаев - в этом случае географическая проекция выполняет визуальное масштабирование.

В вашем примере масштабирование отслеживает состояние масштабирования, чтобыустановить проекцию правильно.Масштабирование не устанавливает преобразование ни к одному элементу SVG, вместо этого проекция репроецирует объекты при каждом увеличении (или щелчке).

Итак, чтобы начать работу с d3v5, после того, как мы вызовем масштабирование нашего выбора, мыможно установить масштаб выбранного элемента с помощью:

selection.call(zoom.transform, transformObject);

Если базовый объект преобразования:

d3.zoomIdentity 

d3.zoomIdentity имеет масштаб (k), равный 1, перевод x (x)) и значения y (y), равные 0. В прототип идентичности встроены некоторые методы, поэтому простой объект не подойдет, но мы можем использовать идентичность, чтобы установить новые значения для k, x и y:

var transform = d3.zoomIdentity;
transform.x = projection.translate()[0]
transform.y = projection.translate()[1]
transform.k = projection.scale()

Это очень похоже на пример, но вместо того, чтобы предоставлять значения для самого поведения масштабирования, мы создаем объект, который описывает состояние масштабирования.Теперь мы можем использовать selection.call(zoom.transform, transform), чтобы применить преобразование.Это будет:

  • установить преобразование масштабирования на предоставленные значения
  • вызвать событие масштабирования

В нашей функции масштабирования мы хотим получить обновленное масштабированиепреобразовать, применить его к проекции, а затем перерисовать наши пути:

function zoomed() {
  // Get the new zoom transform
  transform = d3.event.transform;
  // Apply the new transform to the projection
  projection.translate([transform.x,transform.y]).scale(transform.k);
  // Redraw the features based on the updaed projection:
  g.selectAll("path").attr("d", path);
}

Примечание - d3.event.translate и d3.event.scale ничего не вернут в d3v5 - теперь это x, y,k свойств d3.event.transform

Без функции щелчка мы можем получить this , что напрямую адаптировано из примера в вопросе. Функция щелчка не включена, но вы все равно можете панорамировать .

Если мы хотим включить функцию щелчка по центру, как оригинал, мы можем обновить наш объект преобразования с помощью нового translate ивызвать масштабирование:

function clicked(d) {
  var centroid = path.centroid(d),
      translate = projection.translate();
  // Update the translate as before:
  projection.translate([
    translate[0] - centroid[0] + width / 2,
    translate[1] - centroid[1] + height / 2
  ]);
  // Update the transform object:
  transform.x = projection.translate()[0];
  transform.y = projection.translate()[1];
  // Apply the transform object:
  g.call(zoom.transform, transform);

}

По аналогии с версией v3 - но применяя преобразование масштабирования (как мы это делали изначально), мы запускаем событие масштабирования, поэтому нам не нужно обновлять путь как частьфункции щелчка.

Все вместе, что может выглядеть как this .


Есть детали, которые я не включил, переход по щелчку.Поскольку мы запускаем функцию масштабирования как при щелчке, так и при масштабировании, если мы включили переход, панорамирование также будет переходным, и панорамирование вызывает слишком много событий масштабирования, чтобы переходы выполнялись по желанию.У нас есть один вариант - запускать переход, только если исходным событием был щелчок.Эта модификация может выглядеть так:

function zoomed() {
  // Was the event a click?
  var event = d3.event.sourceEvent ? d3.event.sourceEvent.type : null;
  // Get the new zoom transform
  transform = d3.event.transform;
  // Apply the new transform to the projection
  projection.translate([transform.x,transform.y]).scale(transform.k);
  // Redraw the features based on the updaed projection:
  (event == "click") ? g.selectAll("path").transition().attr("d",path) : g.selectAll("path").attr("d", path);
}
...