Я хочу визуализировать многоугольники в d3.js, но на краю каждого многоугольника добавить значок корзины, чтобы можно было удалить сам многоугольник.
Затем следите за оставшимися полигонами.
<script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src = "http://d3js.org/d3.v3.min.js" charset = "utf-8"></script>
<script>
$(document).ready(function() {
var vis = d3.select("body").append("svg")
.attr("width", 1000)
.attr("height", 667);
var scaleX = d3.scale.linear()
.domain([-30, 30])
.range([0, 600]);
var scaleY = d3.scale.linear()
.domain([0, 50])
.range([500, 0]);
var data = [{
"code": "BR23",
"points": "405,100.5625,331,108.5625,300,120.5625,290,141.5625,283,158.5625,278,171.5625,289,209.5625,304,231.5625,332,255.5625,351,264.5625,383,268.5625,400,266.5625,440,266.5625,462,266.5625,510,257.5625,522,230.5625,530,210.5625,534,183.5625,524,163.5625,512,148.5625,490,134.5625,462,124.5625"
},
{
"code": "FR23",
"points": "702,164.5625,594,250.5625,689,324.5625,749,261.5625,770,206.5625"
}
];
vis.selectAll("polygon")
.data(data)
.enter().append("polygon")
.attr("points", function(d, i) {
return d.points
})
.attr("fill", "transparent")
.attr("stroke", "red")
.attr("stroke-width", 2)
.style("stroke-dasharray", ("15, 15")) // <== This line here!!
.on("click", function(d, i) {
console.info("d", d);
d3.select(this).style("fill", "blue");
});
vis.append('circle')
.attr('cx', 110)
.attr('cy', 110)
.attr('r', 20)
.attr('stroke', 'black')
.attr('fill', '#69a3b2')
.on("click", function(d, i) {
console.info("d", d);
});
});
</script>Вам нужно разработать границы фигур?
Как выровнять точки на внешних краях многоугольника для более толстой границы в svg?
последние jsfiddles



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Удалите первые два скрипта и используйте document.querySelectorAll, тогда все заработает.
Как сейчас написано, ваш ответ неясен. Пожалуйста, отредактируйте , чтобы добавить дополнительные сведения, которые помогут другим понять, как это отвечает на заданный вопрос. Вы можете найти больше информации о том, как писать хорошие ответы в справочном центре.
d3.js и jquery требуются для запуска сценария приложения. Удаление этих ресурсов сценария нарушит работу сценария. Кроме того, нет никаких проблем, связанных с выбором. Пожалуйста, исправьте свой ответ.
Нет, вам не нужно использовать jquery, тогда он работает. Я проверил ваш код.
Справедливо, но как насчет D3? И где бы вы использовали querySelectorAll()? Как это решает проблему OP с расположением значков? Пожалуйста, предоставьте работающий пример.
Как уже упоминалось, вам необходимо рассчитать координаты ограничивающей рамки для каждого полигона в вашем наборе данных.
Вероятно, будет удобнее добавить эти координаты непосредственно в массив данных, чтобы вы могли использовать эти данные в цепочке функций D3 для позиционирования.
Вот простая вспомогательная функция:
function getDataBounds(data) {
data.forEach((item, i) => {
let coords = item.points.split(",").map((val) => {
return +val;
});
let xAll = coords.filter((coord, i) => i % 2 === 0);
let yAll = coords.filter((coord, i) => i % 2 !== 0);
let xMin = Math.min(...xAll);
let xMax = Math.max(...xAll);
let yMin = Math.min(...yAll);
let yMax = Math.max(...yAll);
let bb = {
x: xMin,
y: yMin,
cx: xMin + (xMax - xMin) / 2,
cy: yMin + (yMax - yMin) / 2,
top: yMin,
bottom: yMax,
right: xMax,
left: xMin,
width: xMax - xMin,
height: yMax - yMin
};
data[i].bb = bb;
});
return data;
}
Он перебирает все точки многоугольника и вычисляет ограничивающую рамку в соответствии с экстремумами x и y.
Координаты центра тяжести будут:
cx: xMin + (xMax - xMin) / 2,
cy: yMin + (yMax - yMin) / 2,
Если ваши полигоны уже добавлены в DOM, вы также можете использовать getBBox().
Я бы рекомендовал создать значок в редакторе, таком как inkscape, включая фоновый круг. Таким образом, вам не нужно добавлять несколько элементов (имхо, выбор родительских узлов в d3 может быть немного «неинтуитивным» ...).
body{
background:#ccc;
}
svg{
color:red
}<h3>Icon Design</h3>
<svg viewBox = "0 0 640 720" width = "40" height = "40">
<circle cx = "50%" cy = "50%" r = "50%" fill = "white"/>
<path d = "M152.6 576v-378h-24.6v-36h112.8v-18h158.4v18h112.8v36h-24.6v378h-334.8zm36-36h262.8v-342h-262.8v342zm63.6-51.6h36v-239.4h-36v239.4zm99.6 0h36v-239.4h-36v239.4zm-163.2-290.4v342v-342z" fill = "currentColor"/>
</svg>
<h3>Reusable Icon asset</h3>
<svg width = "40" height = "40">><use href = "#icnTrash"/></svg>
<svg width = "40" height = "40">><use href = "#icnTrash" style = "color:green"/></svg>
<!-- hidden icon asset -->
<svg style = "width:0; height:0">
<symbol id = "icnTrash" viewBox = "0 0 640 720">
<circle cx = "50%" cy = "50%" r = "50%" fill = "white" />
<path d = "M152.6 576v-378h-24.6v-36h112.8v-18h158.4v18h112.8v36h-24.6v378h-334.8zm36-36h262.8v-342h-262.8v342zm63.6-51.6h36v-239.4h-36v239.4zm99.6 0h36v-239.4h-36v239.4zm-163.2-290.4v342v-342z" fill = "currentColor" />
</symbol>
</svg>$(document).ready(function () {
var vis = d3
.select("body")
.append("svg")
.attr("width", 1000)
.attr("height", 667);
var scaleX = d3.scale.linear().domain([-30, 30]).range([0, 600]);
var scaleY = d3.scale.linear().domain([0, 50]).range([500, 0]);
var data = [
{
code: "BR23",
points:
"405,100.5625,331,108.5625,300,120.5625,290,141.5625,283,158.5625,278,171.5625,289,209.5625,304,231.5625,332,255.5625,351,264.5625,383,268.5625,400,266.5625,440,266.5625,462,266.5625,510,257.5625,522,230.5625,530,210.5625,534,183.5625,524,163.5625,512,148.5625,490,134.5625,462,124.5625"
},
{
code: "FR23",
points: "702,164.5625,594,250.5625,689,324.5625,749,261.5625,770,206.5625"
}
];
/**
* calculate bounds for polygons
*
*/
function getDataBounds(data) {
data.forEach((item, i) => {
let coords = item.points.split(",").map((val) => {
return +val;
});
let xAll = coords.filter((coord, i) => i % 2 === 0);
let yAll = coords.filter((coord, i) => i % 2 !== 0);
let xMin = Math.min(...xAll);
let xMax = Math.max(...xAll);
let yMin = Math.min(...yAll);
let yMax = Math.max(...yAll);
let bb = {
x: xMin,
y: yMin,
cx: xMin + (xMax - xMin) / 2,
cy: yMin + (yMax - yMin) / 2,
top: yMin,
bottom: yMax,
right: xMax,
left: xMin,
width: xMax - xMin,
height: yMax - yMin
};
data[i].bb = bb;
});
return data;
}
// add boundaries to data array
data = getDataBounds(data);
let iconWidth = 40;
let iconHeight = 40;
vis
.selectAll("polygon")
.data(data)
.enter()
.append("polygon")
.attr("id", (d, i) => {
return "polygon" + d.code;
})
.attr("points", function (d, i) {
return d.points;
})
.attr("fill", "transparent")
.attr("stroke", "red")
.attr("stroke-width", 2)
.style("stroke-dasharray", "15, 15") // <== This line here!!
.on("click", function (d, i) {
d3.select(this).node().classList.toggle("selected");
//let btn = document.getElementById("btnPoly-" + d.code);
//btn.classList.toggle("hidden");
});
// add circles
vis
.selectAll("use")
.data(data)
.enter()
.append("use")
.attr("class", "btnTrash")
.attr("id", function (d, i) {
return "btnPoly-" + d.code;
})
.attr("width", iconWidth)
.attr("height", iconHeight)
.attr("href", "#icnTrash")
.attr("x", function (d, i) {
return d.bb.cx - iconWidth / 2;
})
.attr("y", function (d, i) {
return d.bb.top - iconHeight / 2 - 20;
})
.on("click", (d, i) => {
let poly = document.getElementById("polygon" + d.code);
let btnTrash = document.getElementById("btnPoly-" + d.code);
btnTrash.remove();
poly.remove();
});
});body{
background:#ccc;
}
.btnTrash{
color:red
}
.hidden{
opacity:0
}
.selected{
stroke: green
}<script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src = "http://d3js.org/d3.v3.min.js" charset = "utf-8"></script>
<!-- hidden icon asset -->
<svg style = "width:0; height:0">
<symbol id = "icnTrash" viewBox = "0 0 640 720">
<circle cx = "50%" cy = "50%" r = "50%" fill = "white" />
<path d = "M152.6 576v-378h-24.6v-36h112.8v-18h158.4v18h112.8v36h-24.6v378h-334.8zm36-36h262.8v-342h-262.8v342zm63.6-51.6h36v-239.4h-36v239.4zm99.6 0h36v-239.4h-36v239.4zm-163.2-290.4v342v-342z" fill = "currentColor" />
</symbol>
</svg>Как и в первом подходе, мы обновляем исходный объект данных. На этот раз мы ищем самую верхнюю точку контура многоугольника.
Мы получаем эту точку, сравнивая значения y каждой вершины многоугольника.
function getTopMostPoint(data) {
data.forEach((item, i) => {
let points = item.points.split(',');
let topY = 999999;
let pTop = {}
for(let p=0; p<points.length; p+=2){
let pt = {x:+points[p], y:+points[p+1]}
if (pt.y<topY){
pTop = pt;
topY = pt.y;
}
}
data[i].anchor = {
x: pTop.x,
y: pTop.y
};
});
return data;
}
var data = [
{
code: "BR23",
points:
"405,100.5625,331,108.5625,300,120.5625,290,141.5625,283,158.5625,278,171.5625,289,209.5625,304,231.5625,332,255.5625,351,264.5625,383,268.5625,400,266.5625,440,266.5625,462,266.5625,510,257.5625,522,230.5625,530,210.5625,534,183.5625,524,163.5625,512,148.5625,490,134.5625,462,124.5625"
},
{
code: "FR23",
points: "702,164.5625,594,250.5625,689,324.5625,749,261.5625,770,206.5625"
}
];
/**
* calculate anchor for polygons
* find topmost point on polygon outline
*
*/
function getTopMostPoint(data) {
data.forEach((item, i) => {
let points = item.points.split(',');
let topY = 999999;
let pTop = {}
for(let p=0; p<points.length; p+=2){
let pt = {x:+points[p], y:+points[p+1]}
if (pt.y<topY){
pTop = pt;
topY = pt.y;
}
}
data[i].anchor = {
x: pTop.x,
y: pTop.y
};
});
return data;
}
$(document).ready(function () {
var vis = d3
.select("body")
.append("svg")
.attr("width", 1000)
.attr("height", 667);
var scaleX = d3.scale.linear().domain([-30, 30]).range([0, 600]);
var scaleY = d3.scale.linear().domain([0, 50]).range([500, 0]);
// get topmost point
data = getTopMostPoint(data);
let iconWidth = 40;
let iconHeight = 40;
vis
.selectAll("polygon")
.data(data)
.enter()
.append("polygon")
.attr("id", (d, i) => {
return "polygon" + d.code;
})
.attr("points", function (d, i) {
return d.points;
})
.attr("fill", "transparent")
.attr("stroke", "red")
.attr("stroke-width", 2)
.style("stroke-dasharray", "15, 15") // <== This line here!!
.on("click", function (d, i) {
d3.select(this).node().classList.toggle("selected");
//let btn = document.getElementById("btnPoly-" + d.code);
//btn.classList.toggle("hidden");
});
// add circles
vis
.selectAll("use")
.data(data)
.enter()
.append("use")
.attr("class", "btnTrash")
.attr("id", function (d, i) {
return "btnPoly-" + d.code;
})
.attr("width", iconWidth)
.attr("height", iconHeight)
.attr("href", "#icnTrash")
.attr("x", function (d, i) {
return d.anchor.x - iconWidth / 2;
})
.attr("y", function (d, i) {
return d.anchor.y - iconHeight / 2 - 20;
})
.on("click", (d, i) => {
let poly = document.getElementById("polygon" + d.code);
let btnTrash = document.getElementById("btnPoly-" + d.code);
btnTrash.remove();
poly.remove();
});
});body{
background:#ccc;
}
.btnTrash{
color:red
}
.selected{
stroke: green
}<script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src = "http://d3js.org/d3.v3.min.js" charset = "utf-8"></script>
<!-- hidden icon asset -->
<svg style = "width:0; height:0">
<symbol id = "icnTrash" viewBox = "0 0 640 720">
<circle cx = "50%" cy = "50%" r = "50%" fill = "white" />
<path d = "M152.6 576v-378h-24.6v-36h112.8v-18h158.4v18h112.8v36h-24.6v378h-334.8zm36-36h262.8v-342h-262.8v342zm63.6-51.6h36v-239.4h-36v239.4zm99.6 0h36v-239.4h-36v239.4zm-163.2-290.4v342v-342z" fill = "currentColor" />
</symbol>
</svg>ваш код не отображает мусорный ящик на границе полигонов?
Не уверен ... они будут отображаться только при нажатии на полигон. Вы также можете отобразить их при первоначальном рендеринге. Просто удалите правило непрозрачности css.
да - они должны быть при первоначальном рендеринге - я думаю - а затем, если пользователь щелкнет корзину, он удалит многоугольник - я не думаю, что есть изменение css, чтобы заполнить синим цветом сам многоугольник - поскольку там будет изображение под d3.js
Взгляните на второй пример, основанный на getPointAtLength. В этом примере будут найдены привязки, которые всегда находятся на контуре полигонов. Кнопки всегда видны. Я добавил образец выбранного класса, который переключается при щелчке по многоугольнику.
@The Old County: глупый я! Нам не нужна функция getPointAtLength(), но мы можем использовать уже известные вершины многоугольника, что намного быстрее! Проверьте обновленный пример. Если вам нужен (неуклюжий) предыдущий пример, см. этот codepen
Возможность создать еще один вариант, в котором белый фон мусорной корзины больше похож на «значок местоположения», например, в форме капли слезы, в соответствии с исходным эталоном / дизайном.
@The Old County: зачем мне добавлять ссылку jsfiddle, если есть запущенные фрагменты? (всегда предпочтительнее).
Вот снова ссылки jsfiddle -- jsfiddle.net/n6bdavxk -- jsfiddle.net/5kqLftzy/1
Итак, из двух подходов - какой вы считаете лучшим и почему - я не был уверен, в чем разница - один использует расчет границы?
@The Old County: с точки зрения дизайна я бы предпочел второй подход, поскольку значок всегда будет «касаться» контура многоугольника. Первый подход может быть удобен, если вам нужно расположить кнопку удаления по центру. Я прошу прощения за то, что был немного резок в отношении jsfiddle. Просто лучше включить примеры во фрагменты SO, так как вы можете удалить внешние скрипки/или кодовые ручки и т. д. Это особенно верно, если отвечающий пользователь не создал эту скрипку (кроме того, я думаю, что jsfiddle должен пересмотреть свой пользовательский интерфейс - отзывчивый вид вроде s * кс =)
Все в порядке — я знаю, что это отстой — фрагменты в порядке — но мне нравится делать jsfiddle и пытаться сохранить бесплатную онлайн-ссылку — а также играть локально. Я просто не хотел ограничиваться определенной строкой как единственным способом увидеть это в сообщении в блоге. У тебя бесценные знания, лол.
это не объясняет, как установить другой объект svg или изображения на границу многоугольника вверху