¿Cuál es una forma eficaz de llevar un elemento SVG a la parte superior del orden z, utilizando la biblioteca D3?
Mi escenario específico es un gráfico circular que resalta (agregando un stroke
al path
) cuando el mouse está sobre una pieza determinada. El bloque de código para generar mi gráfico se encuentra a continuación:
svg.selectAll("path")
.data(d)
.enter().append("path")
.attr("d", arc)
.attr("class", "arc")
.attr("fill", function(d) { return color(d.name); })
.attr("stroke", "#fff")
.attr("stroke-width", 0)
.on("mouseover", function(d) {
d3.select(this)
.attr("stroke-width", 2)
.classed("top", true);
//.style("z-index", 1);
})
.on("mouseout", function(d) {
d3.select(this)
.attr("stroke-width", 0)
.classed("top", false);
//.style("z-index", -1);
});
Probé algunas opciones, pero hasta ahora no tuve suerte. Usar style("z-index")
y llamar a classed
ambos no funcionó.
La clase "superior" se define de la siguiente manera en mi CSS:
.top {
fill: red;
z-index: 100;
}
La fill
declaración está ahí para asegurarme de que sabía que se estaba encendiendo / apagando correctamente. Está.
Escuché que usar sort
es una opción, pero no tengo claro cómo se implementaría para llevar el elemento "seleccionado" a la parte superior.
ACTUALIZAR:
Arreglé mi situación particular con el siguiente código, que agrega un nuevo arco al SVG en el mouseover
evento para mostrar un punto culminante.
svg.selectAll("path")
.data(d)
.enter().append("path")
.attr("d", arc)
.attr("class", "arc")
.style("fill", function(d) { return color(d.name); })
.style("stroke", "#fff")
.style("stroke-width", 0)
.on("mouseover", function(d) {
svg.append("path")
.attr("d", d3.select(this).attr("d"))
.attr("id", "arcSelection")
.style("fill", "none")
.style("stroke", "#fff")
.style("stroke-width", 2);
})
.on("mouseout", function(d) {
d3.select("#arcSelection").remove();
});
fuente
a.id
yd.id
. La solución es comparar directamentea
yd
.<g>
.svg:use
elemento puede apuntar dinámicamente al elemento elevado. Consulte stackoverflow.com/a/6289809/292085 para obtener una solución.Como se explica en las otras respuestas, SVG no tiene una noción de índice z. En cambio, el orden de los elementos en el documento determina el orden en el dibujo.
Además de reordenar los elementos manualmente, existe otra forma para determinadas situaciones:
Al trabajar con D3, a menudo tiene ciertos tipos de elementos que siempre deben dibujarse encima de otros tipos de elementos .
Por ejemplo, al diseñar gráficos, los enlaces siempre deben colocarse debajo de los nodos. De manera más general, algunos elementos de fondo generalmente deben colocarse debajo de todo lo demás, mientras que algunos resaltados y superposiciones deben colocarse arriba.
Si tiene este tipo de situación, descubrí que la mejor manera de hacerlo es crear elementos de grupo principal para esos grupos de elementos. En SVG, puede usar el
g
elemento para eso. Por ejemplo, si tiene enlaces que siempre deben colocarse debajo de los nodos, haga lo siguiente:svg.append("g").attr("id", "links") svg.append("g").attr("id", "nodes")
Ahora, cuando pinte sus enlaces y nodos, seleccione lo siguiente (los selectores que comienzan con
#
referencia al ID del elemento):svg.select("#links").selectAll(".link") // add data, attach elements and so on svg.select("#nodes").selectAll(".node") // add data, attach elements and so on
Ahora, todos los enlaces siempre se agregarán estructuralmente antes de todos los elementos del nodo. Por lo tanto, el SVG mostrará todos los enlaces debajo de todos los nodos, sin importar con qué frecuencia y en qué orden agregue o elimine elementos. Por supuesto, todos los elementos del mismo tipo (es decir, dentro del mismo contenedor) aún estarán sujetos al orden en que se agregaron.
fuente
Dado que SVG no tiene índice Z pero usa el orden de los elementos DOM, puede traerlo al frente de la siguiente manera:
this.parentNode.appendChild(this);
Entonces puede, por ejemplo, hacer uso de insertBefore para volver a colocarlo
mouseout
. Sin embargo, esto requiere que pueda apuntar al nodo hermano que su elemento debe insertarse antes.DEMO: Eche un vistazo a este JSFiddle
fuente
:hover
estilo. Tampoco creo que la función ìnsert al final sea necesaria para este caso de uso.insert
función de d3 probablemente no sea la mejor manera de hacerlo, porque crea un nuevo elemento. Solo queremos mover un elemento existente, por lo tanto, lo usoinsertBefore
en este ejemplo.SVG no tiene índice z. El orden Z está dictado por el orden de los elementos DOM de SVG en su contenedor.
Por lo que pude ver (y lo he intentado un par de veces en el pasado), D3 no proporciona métodos para separar y volver a unir un solo elemento para llevarlo al frente o lo que sea.
Existe un
.order()
método que reorganiza los nodos para que coincidan con el orden en que aparecen en la selección. En su caso, debe traer un solo elemento al frente. Entonces, técnicamente, podría recurrir a la selección con el elemento deseado al frente (o al final, no puedo recordar cuál es el más alto) y luego llamarloorder()
.O bien, puede omitir d3 para esta tarea y usar JS simple (o jQuery) para volver a insertar ese único elemento DOM.
fuente
.style('pointer-events', 'none')
a la ruta superpuesta. Desafortunadamente, esta propiedad no hace nada en IE, pero aún así ....La respuesta simple es utilizar métodos de ordenación d3. Además de d3.select ('g'). Order (), hay .lower () y .raise () en la versión 4. Esto cambia la apariencia de sus elementos. Consulte los documentos para obtener más información: https://github.com/d3/d3/blob/master/API.md#selections-d3-selection
fuente
Implementé la solución de futurend en mi código y funcionó, pero con la gran cantidad de elementos que estaba usando, fue muy lento. Aquí está el método alternativo con jQuery que funcionó más rápido para mi visualización particular. Se basa en los svgs que desea que tengan una clase en común (en mi ejemplo, la clase se indica en mi conjunto de datos como d.key). En mi código hay una
<g>
con la clase "ubicaciones" que contiene todos los SVG que estoy reorganizando..on("mouseover", function(d) { var pts = $("." + d.key).detach(); $(".locations").append(pts); });
Entonces, cuando pasa el mouse sobre un punto de datos en particular, el código encuentra todos los demás puntos de datos con elementos DOM de SVG con esa clase en particular. Luego, separa y vuelve a insertar los elementos SVG DOM asociados con esos puntos de datos.
fuente
Quería ampliar lo que respondió @ notan3xit en lugar de escribir una respuesta completamente nueva (pero no tengo suficiente reputación).
Otra forma de resolver el problema del orden de los elementos es usar 'insertar' en lugar de 'agregar' al dibujar. De esa manera, las rutas siempre se colocarán juntas antes de los otros elementos svg (esto supone que su código ya hace enter () para los enlaces antes de enter () para los otros elementos svg).
d3 insert api: https://github.com/mbostock/d3/wiki/Selections#insert
fuente
Me tomó años encontrar cómo modificar el orden Z en un SVG existente. Realmente lo necesitaba en el contexto de d3.brush con comportamiento de información sobre herramientas. Para que las dos funciones funcionen bien juntas ( http://wrobstory.github.io/2013/11/D3-brush-and-tooltip.html ), necesita que d3.brush sea el primero en el orden Z (Primero se dibuja en el lienzo, luego se cubre con el resto de los elementos SVG) y capturará todos los eventos del mouse, sin importar lo que esté encima (con índices Z más altos).
La mayoría de los comentarios del foro dicen que debes agregar el d3.brush primero en tu código, luego tu código de "dibujo" SVG. Pero para mí no fue posible porque cargué un archivo SVG externo. Puede agregar fácilmente el pincel en cualquier momento y modificar el orden Z más adelante con:
d3.select("svg").insert("g", ":first-child");
En el contexto de una configuración de d3.brush, se verá así:
brush = d3.svg.brush() .x(d3.scale.identity().domain([1, width-1])) .y(d3.scale.identity().domain([1, height-1])) .clamp([true,true]) .on("brush", function() { var extent = d3.event.target.extent(); ... }); d3.select("svg").insert("g", ":first-child"); .attr("class", "brush") .call(brush);
API de función insert () de d3.js: https://github.com/mbostock/d3/wiki/Selections#insert
¡Espero que esto ayude!
fuente
Versión 1
En teoría, lo siguiente debería funcionar bien.
El código CSS:
path:hover { stroke: #fff; stroke-width : 2; }
Este código CSS agregará un trazo a la ruta seleccionada.
El código JS:
svg.selectAll("path").on("mouseover", function(d) { this.parentNode.appendChild(this); });
Este código JS primero elimina la ruta del árbol DOM y luego lo agrega como el último hijo de su padre. Esto asegura que la ruta se dibuje encima de todos los demás hijos del mismo padre.
En la práctica, este código funciona bien en Chrome, pero se rompe en otros navegadores. Lo probé en Firefox 20 en mi máquina Linux Mint y no pude hacerlo funcionar. De alguna manera, Firefox no activa los
:hover
estilos y no he encontrado una manera de solucionarlo.Versión 2
Entonces se me ocurrió una alternativa. Puede ser un poco 'sucio', pero al menos funciona y no requiere recorrer todos los elementos (como algunas de las otras respuestas).
El código CSS:
path.hover { stroke: #fff; stroke-width : 2; }
En lugar de usar el
:hover
pseudoelector, uso una.hover
claseEl código JS:
svg.selectAll(".path") .on("mouseover", function(d) { d3.select(this).classed('hover', true); this.parentNode.appendChild(this); }) .on("mouseout", function(d) { d3.select(this).classed('hover', false); })
Al pasar el mouse, agrego la
.hover
clase a mi ruta. Al mouseout, lo elimino. Como en el primer caso, el código también elimina la ruta del árbol DOM y luego la agrega como el último hijo de su padre.fuente
Puede hacer esto al pasar el mouse por encima. Puede colocarlo en la parte superior.
d3.selection.prototype.bringElementAsTopLayer = function() { return this.each(function(){ this.parentNode.appendChild(this); }); }; d3.selection.prototype.pushElementAsBackLayer = function() { return this.each(function() { var firstChild = this.parentNode.firstChild; if (firstChild) { this.parentNode.insertBefore(this, firstChild); } });
};
nodes.on("mouseover",function(){ d3.select(this).bringElementAsTopLayer(); });
Si quieres empujar hacia atrás
nodes.on("mouseout",function(){ d3.select(this).pushElementAsBackLayer(); });
fuente