Estoy tratando de evitar repetir el código para ingresar y actualizar las selecciones en d3. He usado con éxito la merge para obtener la selección correcta de todos los elementos que están presentes con los nuevos datos (después de haber usado append para agregar los nuevos). Ahora quiero establecer algunos atributos como r , cy y cx . Puedo hacer eso en la selección fusionada, pero si los coloco detrás de una transition , los elementos que recién aparecen se animan desde 0,0 y quiero que aparezcan en el lugar correcto.
Lo que espero es alguna forma de escribir todas las líneas de configuración de atributos en una función y pasar una vez en la selección enter () y la próxima vez pasar en la fusión transicional para que solo tenga que escribir las líneas para configurar cx una vez. Esto es lo que ya he seguido por mi conjetura en pseudocódigo
const valueCircles = this.chartGroup.selectAll('.valueCircle') .data(values); valueCircles.enter() .append('circle') .attr('stroke-width', '1px') .attr('stroke', '#ffffff') .attr('opacity', '1') .attr('class', 'dots valueCircle') .merge(valueCircles) .transition() .duration(100) .attr('r', (d) => { if (dy < 0.5 ) { return 5; } else { return 15; } }) .attr('cx', (d) => { return timescale(new Date(dx)); }) .attr('cy', (d) => { return valueScale(dy) })Esto es lo que creo que quiero lograr.
function setTheDynamicValues(aSelection) { aSelection .attr('cx', (d) => { return timescale(new Date(dx)); }) .attr('cy', (d) => { return valueScale(dy) }) } function updateGraph(newData) { const valueCircles = this.chartGroup.selectAll('.valueCircle') .data(newData); setTheDynamicValues(valueCircles.enter()); setTheDynamicValues(valueCircles.transition().duration(100)); }Otra forma de describir esto sería hacer que la duración de la transición sea 0 para los elementos entrantes y 100 para los elementos existentes, de modo que los nuevos aparezcan correctos y los existentes tengan una transición visible.
En primer lugar, las selecciones D3 son inmutables: esa merge tuya no está haciendo nada. Este sería el patrón adecuado:
//here, pay attention to "let" instead of const let valueCircles = this.chartGroup.selectAll('.valueCircle') .data(values); //our enter selection is called "valuesCirclesEnter" const valueCirclesEnter = valueCircles.enter() .append('circle') etc... //now, you merge the selections valueCircles = valueCirclesEnter.merge(valueCircles); A partir de ese momento, valueCircles contiene tanto los elementos de actualización como los de entrada.
Ahora, volviendo a tu pregunta:
De hecho, un D3 idiomático tiene muchas repeticiones, y ese es un punto en el que mucha gente se queja del D3. Pero podemos evitar algunas repeticiones con métodos como selection.call . Además, dado que desea una transición para la selección de actualización pero ninguna para la selección de entrada, simplemente puede soltar la merge .
Aquí hay un ejemplo básico, usando un poco de su código:
const data1 = [{ x: 100, y: 100 }, { x: 230, y: 20 }]; const data2 = [{ x: 10, y: 10 }, { x: 50, y: 120 }, { x: 190, y: 100 }, { x: 140, y: 30 }, { x: 270, y: 140 }]; const svg = d3.select("svg"); draw(data1); setTimeout(() => draw(data2), 1000); function draw(values) { const valueCircles = svg.selectAll('.valueCircle') .data(values); valueCircles.enter() .append('circle') .attr("class", "valueCircle") .attr("r", 10) .call(positionCircles); valueCircles.transition() .duration(1000) .call(positionCircles) }; function positionCircles(selection) { selection.attr("cx", d => dx) .attr("cy", d => dy) } <script src="https://d3js.org/d3.v7.min.js"></script> <svg></svg> En el fragmento anterior, estoy posicionando las selecciones de entrada y actualización usando una función llamada positionCircles ; sin embargo, mientras que para la selección de entrada paso una selección simple, para la selección de actualización paso una selección de transición. Pero la función es la misma:
function positionCircles(selection) { selection.attr("cx", d => dx) .attr("cy", d => dy) }