Нарисуйте стрелку внутри карты данных, используя d3. js - PullRequest
2 голосов
/ 10 февраля 2020

Я новичок в d3. js, я пытаюсь создать собственную карту svg.

С некоторой ссылкой я сделал пользовательскую карту, которая показана ниже.

Arrow pointed at the bottom left of the page

Здесь приведен фрагмент кода для вышеприведенного вывода.

https://jsfiddle.net/9kbp4h6j/

"use strict"

var svg = d3.select("body").append("svg").append("g").attr("transform", "translate(100,50)")

svg.append("svg:defs")
  .append("svg:marker")
  .attr("id", "arrow")
  .attr("refX", 2)
  .attr("refY", 6)
  .attr("markerWidth", 13)
  .attr("markerHeight", 13)
  .attr("orient", "auto")
  .append("svg:path")
  .attr("d", "M2,2 L2,11 L10,6 L2,2");


var line = d3.svg.line()
  .x(function (point) {
    return point.lx;
  })
  .y(function (point) {
    return point.ly;
  });

function lineData(d) {
  // i'm assuming here that supplied datum 
  // is a link between 'source' and 'target'
  var points = [{
      lx: d.source.x,
      ly: d.source.y
    },
    {
      lx: d.target.x,
      ly: d.target.y
    }
  ];
  return line(points);
}

var path = svg.append("path")
  .data([{
    source: {
      x: 0,
      y: 0
    },
    target: {
      x: 80,
      y: 80
    }
  }])
  .attr("class", "line")
  //.style("marker-end", "url(#arrow)")
  .attr("d", lineData);
//var arrow = svg.append("svg:path")
//.attr("d", "M2,2 L2,11 L10,6 L2,2");


console.log(d3.svg.symbol())

var arrow = svg.append("svg:path")
  .attr("d", d3.svg.symbol().type("triangle-down")(10, 1));


arrow.transition()
  .duration(2000)
  .ease("linear")
  .attrTween("transform", translateAlong(path.node()))
//.each("end", transition);


// Returns an attrTween for translating along the specified path element.
function translateAlong(path) {
  var l = path.getTotalLength();
  var ps = path.getPointAtLength(0);
  var pe = path.getPointAtLength(l);
  var angl = Math.atan2(pe.y - ps.y, pe.x - ps.x) * (180 / Math.PI) - 90;
  var rot_tran = "rotate(" + angl + ")";
  return function (d, i, a) {
    console.log(d);

    return function (t) {
      var p = path.getPointAtLength(t * l);
      return "translate(" + p.x + "," + p.y + ") " + rot_tran;
    };
  };
}

var totalLength = path.node().getTotalLength();

path
  .attr("stroke-dasharray", totalLength + " " + totalLength)
  .attr("stroke-dashoffset", totalLength)
  .transition()
  .duration(2000)
  .ease("linear")
  .attr("stroke-dashoffset", 0);


var bubble_map = new Datamap({
  element: document.getElementById('canada'),
  scope: 'canada',
  geographyConfig: {
    popupOnHover: true,
    highlightOnHover: true,
    borderColor: '#444',
    borderWidth: 0.5,
    dataUrl: 'https://rawgit.com/Anujarya300/bubble_maps/master/data/geography-data/canada.topo.json'
    //dataJson: topoJsonData
  },
  fills: {
    'MAJOR': '#306596',
    'MEDIUM': '#0fa0fa',
    'MINOR': '#bada55',
    defaultFill: '#dddddd'
  },
  data: {
    'JH': {
      fillKey: 'MINOR'
    },
    'MH': {
      fillKey: 'MINOR'
    }
  },
  setProjection: function (element) {
    var projection = d3.geo.mercator()
      .center([-106.3468, 68.1304]) // always in [East Latitude, North Longitude]
      .scale(250)
      .translate([element.offsetWidth / 2, element.offsetHeight / 2]);

    var path = d3.geo.path().projection(projection);
    return {
      path: path,
      projection: projection
    };
  }
});

let bubbles = [{
    centered: "MB",
    fillKey: "MAJOR",
    radius: 8,
    state: "Manitoba"
  },
  {
    centered: "AB",
    fillKey: "MAJOR",
    radius: 8,
    state: "Alberta"
  },
  {
    centered: "NT",
    fillKey: "MAJOR",
    radius: 8,
    state: "Northwest Territories"
  },
  {
    centered: "NU",
    fillKey: "MEDIUM",
    radius: 8,
    state: "Nunavut"
  },
  {
    centered: "BC   ",
    fillKey: "MEDIUM",
    radius: 8,
    state: "British Columbia"
  },
  {
    centered: "QC",
    fillKey: "MINOR",
    radius: 8,
    state: "Québec"
  },
  {
    centered: "NB",
    fillKey: "MINOR",
    radius: 8,
    state: "New Brunswick"
  }

]
// // ISO ID code for city or <state></state>
setTimeout(() => { // only start drawing bubbles on the map when map has rendered completely.
  bubble_map.bubbles(bubbles, {
    popupTemplate: function (geo, data) {
      return `<div class="hoverinfo">city: ${data.state}, Slums: ${data.radius}%</div>`;
    }
  });
}, 1000);
.line {
  stroke: blue;
  stroke-width: 1.5px;
  fill: white;
}

circle {
  fill: red;
}
#marker {
 stroke: black;
 fill: black;
}
<!DOCTYPE html>
<html>
  <meta charset="utf-8">
  <body>
    <script src="http://d3js.org/d3.v3.min.js"></script>
    <script src="http://d3js.org/topojson.v1.min.js"></script>
    <script src="https://rawgit.com/Anujarya300/bubble_maps/master/data/geography-data/datamaps.none.js"></script>
    <div id="canada" style="height: 600px; width: 900px;"></div>
  </body>
</html>

У меня есть маркер, прикрепленный к телу, но фактический результат, который мне нужен,

  1. Стрелка должна начинаться с пузырька, показанного на изображении
  2. Она должна заканчиваться на некоторых случайных направлениях, чтобы можно было добавить всплывающее окно шаблона для описания фактического местоположения.

Итак, фактический результат, который мне нужен, должен выглядеть примерно так:

Arrow pointed out from each and every location

Любая помощь приветствуется.

Ответы [ 2 ]

3 голосов
/ 12 февраля 2020

Линии могут быть индивидуально настроены путем включения в данные 2 полей: arrowDirectionAngle и arrowLineLength.

"use strict"

var line = d3.svg.line()
    .x(function (point) {
        return point.lx;
    })
    .y(function (point) {
        return point.ly;
    });

function lineData(d) {
    // i'm assuming here that supplied datum 
    // is a link between 'source' and 'target'
    var points = [{
        lx: d.source.x,
        ly: d.source.y
    },
    {
        lx: d.target.x,
        ly: d.target.y
    }
    ];
    return line(points);
}

// Returns an attrTween for translating along the specified path element.
function translateAlong(path) {
    var l = path.getTotalLength();
    var ps = path.getPointAtLength(0);
    var pe = path.getPointAtLength(l);
    var angl = Math.atan2(pe.y - ps.y, pe.x - ps.x) * (180 / Math.PI) - 90;
    var rot_tran = "rotate(" + angl + ")";
    return function (d, i, a) {
        //console.log(d);

        return function (t) {
            var p = path.getPointAtLength(t * l);
            return "translate(" + p.x + "," + p.y + ") " + rot_tran;
        };
    };
}

var bubble_map = new Datamap({
    element: document.getElementById('canada'),
    scope: 'canada',
    geographyConfig: {
        popupOnHover: true,
        highlightOnHover: true,
        borderColor: '#444',
        borderWidth: 0.5,
        dataUrl: 'https://rawgit.com/Anujarya300/bubble_maps/master/data/geography-data/canada.topo.json'
        //dataJson: topoJsonData
    },
    fills: {
        'MAJOR': '#306596',
        'MEDIUM': '#0fa0fa',
        'MINOR': '#bada55',
        defaultFill: '#dddddd'
    },
    data: {
        'JH': {
            fillKey: 'MINOR'
        },
        'MH': {
            fillKey: 'MINOR'
        }
    },
    setProjection: function (element) {
        var projection = d3.geo.mercator()
            .center([-106.3468, 68.1304]) // always in [East Latitude, North Longitude]
            .scale(250)
            .translate([element.offsetWidth / 2, element.offsetHeight / 2]);

        var path = d3.geo.path().projection(projection);
        return {
            path: path,
            projection: projection
        };
    }
});

let bubbles = [{
    centered: "MB",
    fillKey: "MAJOR",
    radius: 8,
    state: "Manitoba",
    arrowDirectionAngle: 90,
    arrowLineLength: 120
},
{
    centered: "AB",
    fillKey: "MAJOR",
    radius: 8,
    state: "Alberta",
    arrowDirectionAngle: 90,
    arrowLineLength: 100
},
{
    centered: "NT",
    fillKey: "MAJOR",
    radius: 8,
    state: "Northwest Territories",
    arrowDirectionAngle: 180,
    arrowLineLength: 130
},
{
    centered: "NU",
    fillKey: "MEDIUM",
    radius: 8,
    state: "Nunavut",
    arrowDirectionAngle: -25,
    arrowLineLength: 80
},
{
    centered: "BC   ",
    fillKey: "MEDIUM",
    radius: 8,
    state: "British Columbia",
    arrowDirectionAngle: 125,
    arrowLineLength: 65
},
{
    centered: "QC",
    fillKey: "MINOR",
    radius: 8,
    state: "Québec",
    arrowDirectionAngle: -25,
    arrowLineLength: 70
},
{
    centered: "NB",
    fillKey: "MINOR",
    radius: 8,
    state: "New Brunswick",
    arrowDirectionAngle: 65,
    arrowLineLength: 50
}

]


function renderArrows(targetElementId) {
    let svgRoot = d3.select("#" + targetElementId).select("svg");

    svgRoot.append("svg:defs")
        .append("svg:marker")
        .attr("id", "arrow")
        .attr("refX", 2)
        .attr("refY", 6)
        .attr("markerWidth", 13)
        .attr("markerHeight", 13)
        .attr("orient", "auto")
        .append("svg:path")
        .attr("d", "M2,2 L2,11 L10,6 L2,2");

    let linesGroup = svgRoot.append("g");

    linesGroup.attr("class", "lines");

    let bubbleElements = svgRoot.selectAll(".datamaps-bubble")[0];

    bubbleElements.forEach(function (bubbleElement) {
        let xPosition = bubbleElement.cx.baseVal.value;
        let yPosition = bubbleElement.cy.baseVal.value;
        let datum = d3.select(bubbleElement).datum();

        let degree = datum.arrowDirectionAngle;
        let radius = datum.arrowLineLength;
        let theta = degree * Math.PI / 180;

        let path = linesGroup.append("path")
            .data([{
                source: {
                    x: xPosition,
                    y: yPosition
                },
                target: {
                    x: xPosition + radius * Math.cos(theta),
                    y: yPosition + radius * Math.sin(theta)
                }
            }])
            .style("stroke", "blue")
            .style("stroke-width", "1.5px")
            .style("fill", "white")
            //.style("marker-end", "url(#arrow)")
            .attr("d", lineData);


        let arrow = svgRoot.append("svg:path")
            .attr("d", d3.svg.symbol().type("triangle-down")(10, 1));


        arrow.transition()
            .duration(2000)
            .ease("linear")
            .attrTween("transform", translateAlong(path.node()))

        var totalLength = path.node().getTotalLength();

        path
            .attr("stroke-dasharray", totalLength + " " + totalLength)
            .attr("stroke-dashoffset", totalLength)
            .transition()
            .duration(2000)
            .ease("linear")
            .attr("stroke-dashoffset", 0);

    });
}

// // ISO ID code for city or <state></state>
setTimeout(() => { // only start drawing bubbles on the map when map has rendered completely.
    bubble_map.bubbles(bubbles, {
        popupTemplate: function (geo, data) {
            return `<div class="hoverinfo">city: ${data.state}, Slums: ${data.radius}%</div>`;
        }
    });

    renderArrows("canada");

}, 1000);
.line {
    stroke: blue;
    stroke-width: 1.5px;
    fill: white;
}

circle {
    fill: red;
}

#marker {
    stroke: black;
    fill: black;
}
<!DOCTYPE html>
<html>
  <meta charset="utf-8">
  <body>
    <script src="http://d3js.org/d3.v3.min.js"></script>
    <script src="http://d3js.org/topojson.v1.min.js"></script>
    <script src="https://rawgit.com/Anujarya300/bubble_maps/master/data/geography-data/datamaps.none.js"></script>
    <div id="canada" style="height: 600px; width: 900px;"></div>
  </body>
</html>
1 голос
/ 12 февраля 2020

Линии добавляются отдельно, используя линию пути и стрелку, и, используя событие мыши, вы можете манипулировать расположением пути, основываясь на центре наведенного пузыря, также для всплывающей подсказки вы можете использовать d3.select(), чтобы получить div из всплывающая подсказка и измените ее атрибут innerHTML для отображения сообщения.

Вот рабочее решение:

var svg = d3.select("body").append("svg").append("g").attr("transform", "translate(100,50)")

      svg.append("svg:defs")
        .append("svg:marker")
        .attr("id", "arrow")	
        .attr("refX", 2)
        .attr("refY", 6)
        .attr("markerWidth", 13)
        .attr("markerHeight", 13)
        .attr("orient", "auto")
        .append("svg:path")
        .attr("d", "M2,2 L2,11 L10,6 L2,2");


      var line = d3.svg.line()
                  .x( function(point) { return point.lx; })
                  .y( function(point) { return point.ly; });

      function lineData(d){
        // i'm assuming here that supplied datum 
        // is a link between 'source' and 'target'
        var points = [
            {lx: d.source.x, ly: d.source.y},
            {lx: d.target.x, ly: d.target.y}
        ];
        return line(points);
      }


  // Returns an attrTween for translating along the specified path element.
  function translateAlong(path) {
    var l = path.getTotalLength();
    var ps = path.getPointAtLength(0);
    var pe = path.getPointAtLength(l);
    var angl = Math.atan2(pe.y - ps.y, pe.x - ps.x) * (180 / Math.PI) - 90;
    var rot_tran = "rotate(" + angl + ")";
    return function(d, i, a) {
      return function(t) {
        var p = path.getPointAtLength(t * l);
        if(t < 0.111) {
          return '';
        }
        return "translate(" + p.x + "," + p.y + ") " + rot_tran;
      };
    };
  }




    var bubble_map = new Datamap({
        element: document.getElementById('canada'),
        scope: 'canada',
        geographyConfig: {
            popupOnHover: true,
            highlightOnHover: true,
            borderColor: '#444',
            borderWidth: 0.5,
            dataUrl: 'https://rawgit.com/Anujarya300/bubble_maps/master/data/geography-data/canada.topo.json'
            //dataJson: topoJsonData
        },
        fills: {
            'MAJOR': '#306596',
            'MEDIUM': '#0fa0fa',
            'MINOR': '#bada55',
            defaultFill: '#dddddd'
        },
        data: {
            'JH': { fillKey: 'MINOR' },
            'MH': { fillKey: 'MINOR' }
        },
        setProjection: function (element) {
              var projection = d3.geo.mercator()
            .center([-106.3468, 68.1304]) // always in [East Latitude, North Longitude]
            .scale(250)
            .translate([element.offsetWidth / 2, element.offsetHeight / 2]);

            var path = d3.geo.path().projection(projection);
            return { path: path, projection: projection };
        }
    });

    let bubbles = [
        {
            centered: "MB",
            fillKey: "MAJOR",
            radius: 8,
            state: "Manitoba"
        },
        {
            centered: "AB",
            fillKey: "MAJOR",
            radius: 8,
            state: "Alberta"
        },
        {
            centered: "NT",
            fillKey: "MAJOR",
            radius: 8,
            state: "Northwest Territories"
        },
        {
            centered: "NU",
            fillKey: "MEDIUM",
            radius: 8,
            state: "Nunavut"
        },
        {
            centered: "BC   ",
            fillKey: "MEDIUM",
            radius: 8,
            state: "British Columbia"
        },
        {
            centered: "QC",
            fillKey: "MINOR",
            radius: 8,
            state: "Québec"
        },
        {
            centered: "NB",
            fillKey: "MINOR",
            radius: 8,
            state: "New Brunswick"
        }

    ];
    // ISO ID code for city or <state></state>
    setTimeout(() => { // only start drawing bubbles on the map when map has rendered completely.
        bubble_map.bubbles(bubbles, {
            popupTemplate: function (geo, data) {
                return ``;
                // return `<div class="hoverinfo">city: ${data.state}, Slums: ${data.radius}%</div>`;
            }
        });
        const line_data = [{source: {x:0, y:0}, target: {x:100, y:100}}];
        var path = d3.select('.datamap').append("path")
          .data(line_data)
          .attr("class", "line")
                .attr("d", lineData);

        var arrow = d3.select('.datamap').append("svg:path")
          .attr('d', null)
          .attr('class', 'tri');
    }, 1000);

    setTimeout(() => { // only start drawing bubbles on the map when map has rendered completely.
        const svg = d3.select('.datamap');
        var circles = d3.selectAll('circle');
        
        var state_data = d3.selectAll('circle').data();


        circles.on("mouseover", function(d, i) {
          if(!document.querySelectorAll(".active").length) {
            let x = circles[0][i].cx.baseVal.value;
            let y = circles[0][i].cy.baseVal.value+8;
            
            state_info = state_data[i];

            const line_data = [{source: {x, y}, target: {x , y : y + 100}}];
            if(i === 2) {
              line_data[0].source.x = line_data[0].source.x - 8;
              line_data[0].source.y = line_data[0].source.y - 8;
              line_data[0].target.y = line_data[0].source.y;
              line_data[0].target.x = line_data[0].source.x - 150;
            }
            if(i === 3) {
              line_data[0].source.x = line_data[0].source.x + 8;
              line_data[0].source.y = line_data[0].source.y - 8;
              line_data[0].target.y = line_data[0].source.y;
              line_data[0].target.x = line_data[0].source.x + 100;
            }

            if(i === 4) {
              line_data[0].source.x = line_data[0].source.x - 8;
              line_data[0].source.y = line_data[0].source.y - 8;
              line_data[0].target.y = line_data[0].source.y + 50;
              line_data[0].target.x = line_data[0].source.x / 2;
            }

            if(i === 5) {
              line_data[0].source.x = line_data[0].source.x + 8;
              line_data[0].source.y = line_data[0].source.y - 8;
              line_data[0].target.y = line_data[0].source.y;
              line_data[0].target.x = line_data[0].source.x  + 100;
            }

            var path = d3.select('path.line');
            path.data(line_data)
              .attr("class", "line")
              .attr("d", lineData);

            var arrow = d3.select('.tri');
            
            arrow.attr("d", d3.svg.symbol().type("triangle-down")(10,1));
            arrow.interrupt();
            arrow.transition()
              .duration(2000)
              .ease("linear")
              .attr("class", "tri")
              .attrTween("transform", translateAlong(path.node()))
              .each("end", () => {
                d3.select('.datamaps-hoverover')
                .style('display','block')
                .style('left', (line_data[0].target.x + 10) + "px")
                .style('top', (line_data[0].target.y + 10) +"px")
                .html('<div class="hoverinfo"><strong> this is from the custom tooltip: city: ' + state_info.state + ', Slum: ' + state_info.radius + '%</strong></div>');
              });

              var totalLength = path.node().getTotalLength();
              path.interrupt();
              path
                .attr("stroke-dasharray", totalLength + " " + totalLength)
                .attr("stroke-dashoffset", totalLength)
                .transition()
                  .duration(2000)        
                  .ease("linear")
                  .attr("stroke-dashoffset", 0);
            
            }
        });

        circles.on("mouseout", function(d, i) {
          d3.select('path.line').attr('d', null);
          d3.select('path.tri').attr('d', null);
          d3.select('.datamaps-hoverover')
            .style('display','none')
            .html('');
        })

      }, 1500);
.line {
  stroke: blue !important;
  fill: blue
}

.tri {
  stroke: blue !important;
  fill: blue
}

circle {
  fill: red;
}

#marker {
  stroke: black;
  fill: black;
}
<!DOCTYPE html>
<meta charset="utf-8">
<html>
  <head>
    <script src="https://d3js.org/d3.v3.min.js"></script>
    <script src="https://d3js.org/topojson.v1.min.js"></script>
    <script src="https://rawgit.com/Anujarya300/bubble_maps/master/data/geography-data/datamaps.none.js"></script>
  </head>
  <body>
    <div id="canada" style="height: 600px; width: 900px;"></div>
  </body>
</html>
...