Triggering tween conditionally on different attributes and running them in parallel

I am trying to trigger a stroke-dashoffset animation conditionally based on an existing stroke-dasharray animation. But when the stroke-dashoffset tween kicks in; the existing stroke-dasharray does not tween anymore. How can I run both of them in parallel? My original intention is to author the tween to kick off with #1 stroke-dasharray and keep on checking the interpolatedValue. When the interpolatedValue is > than 10% of total pathLength to trigger #2stroke-dashoffset without stopping the existing #1 stroke-dasharray. The following code starts with #1, triggers #2 AND stops #1; which I don’t want.

It is based on this

const path =
    bound
    .append("path")
    .classed('path', true)
    .datum(data)
    .attr("fill", "none")
    .attr("stroke", "white")
    .attr("stroke-width", 1.5)
    .attr("d", d3.line()
        .x(d => scaleX(d.date))
        .y(d => scaleY(d.close))
    )
    .transition()
    .duration(8000)
    .attrTween('stroke-dasharray', function() {
            const length = this.getTotalLength();
            return function(t) {
                const interpolatedValue = d3.interpolate(0, length)(t);
                return `0,${length}`, `${interpolatedValue},${length}`
            }
        }

    )
    .attrTween('stroke-dashoffset', function() {
        const length = this.getTotalLength();
        const lengthPct = length * 0.1;
        return function(t) {
            const interpolatedValue = d3.interpolate(0, length)(t);
            if (interpolatedValue > lengthPct) {
                // Trigger the stroke-dashoffset animation               
                const dashOffset = length * -1;
                d3.select(this)
                    .attr('stroke-dashoffset', dashOffset)
                    .transition()
                    .duration(8000)
                    .attrTween('stroke-dashoffset', function() {
                        return d3.interpolate(0, dashOffset);
                    })
            }
        }
    }) 

Thank you in advance
@Fil

I pasted the code in a notebook, added the missing pieces (scales, etc), and it seems to do something, but I’m not sure about your intent. Animation control / recifs | Observable

Thanks @Fil. If you take a look at the inspector, you will see, once strok-dashoffset kicks in stroke-dasharray twen does not happen any more. Following is the snapshot from the end of animation. I want stroke-daharray’s interpolation to continue and end at 3729.6455078 3729.6455078

image

I’ve tried to guess what you’re trying to obtain (visually), and updated the code to reflect my guess.

Note that in the life of a transition it is documented that “When the transition (…) starts, it interrupts the active transition of the same name on the same element”, which explains why, in your original code, the first transition is interrupted.

1 Like

@Fil thanks for this. But this is not what I am asking.

I am asking if I am able to trigger stroke-dashoffset at a certain stage of the existing stroke-dasharray tween (let’s suppose when stroke-dasharray tween is 50% done) how can I not have the existing stroke-dasharray tween interrupted at all, so that both the tween runs in parallel.

.

You mentioned - When the transition (…) starts, it interrupts the active transition of the same name on the same element. I initially thought that I would need two different named transitions on the same element because I hoped to have them execute as desired with two different names on the same element. I tried this which also did not work.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
    <div id="viz"></div>
</body>
<script>
const width = 1280;
const height = 600;

const main = d3.select('div')
    .style('position', 'relative');


//namespace
const svgns = 'http://www.w3.org/2000/svg'

main
    .append('svg')
    .attr('xmlns', svgns)
    .attr('viewBox', `0 0 ${width} ${height}`)
    .attr('id', 'svg')

const svg = d3.select('svg')

svg.append('rect')
    .attr('class', 'vBoxRect')
    .attr('width', `${width}`)
    .attr('height', `${height}`)       
    .attr('fill', 'none')
    .attr('stroke', 'red');


// //------------------------2. CREATE BOUND------------------------//    
const padding = {
    top: 70,
    bottom: 50,
    left: 70,
    right: 50
};

const boundHeight = height - padding.top - padding.bottom;
const boundWidth = width - padding.right - padding.left;

//create BOUND rect -- to be deleted later
svg.append('rect')
    .attr('class', 'boundRect')
    .attr('x', `${padding.left}`)
    .attr('y', `${padding.top}`)
    .attr('width', `${boundWidth}`)
    .attr('height', `${boundHeight}`)        
    .attr('fill', 'none')
    .attr('stroke', 'green')

//create bound element
const bound = svg.append('g')
    .attr('class', 'bound')
    .style('transform', `translate(${padding.left}px,${padding.top}px)`);

bound.append('g')
    .classed('vertical', true)
    .append('line')
    .attr('x1', '580')
    .attr('x2', '580')
    .attr('y1', '0')
    .attr('y2', '480')
    .attr('stroke', 'blue');

const element =
    bound.append('g')
    .classed('horizontal', true)
    .append('line')
    .attr('x1', '0')
    .attr('x2', '1160')
    .attr('y1', '240')
    .attr('y2', '240')
    .attr('stroke', 'red');

const tranOne = d3.transition()
    .duration(4000)
    .ease(d3.easeLinear);

element
    .transition(tranOne)
    // .duration(4000)
    // .ease(d3.easeLinear)
    .attrTween('stroke-dasharray', function() {
            const length = this.getTotalLength();
            return function(t) {
                const interpolatedValue = d3.interpolate(0, length)(t);
                return `0,${length}`, `${interpolatedValue},${length}`
            }
        }

    );
const tranTwo = d3.transition()
    .duration(4000)
    .ease(d3.easeLinear);

element
    .transition(tranTwo)
    // .duration(4000)
    // .ease(d3.easeLinear)
    .delay(function(a, i) {
        const element = this;
        const len = this.getTotalLength();
        const pct = 0.5;
        const pctPoint = element.getPointAtLength(len * pct);
        const pctStart = element.getPointAtLength(len * 0);
        const d = 1;
        const p = pctPoint.x; 
        const c = len;
        const b = pctStart.x;
        const t = ((p - b) / c) * d;
        return 4000 * t

    })
    .attrTween(
        'stroke-dashoffset',
        function(a, i) {
            const element = this;
            const len = this.getTotalLength();
            const dashOffset = len * -1;
            return function(t) {
                const interpolatedValue = d3.interpolate(0, dashOffset)(t);
                return interpolatedValue;
            }
        }
    );
</script>

</html>

I’ve updated Animation control / recifs | Observable with two functions that map the interval [0, 1] that corresponds to the full transition support ([0, 6000ms]) to each of the target intervals. I believe this supports what your drawing shows. I’m still not sure of what you’re trying to achieve visually, but hopefully gives you enough to draw a conclusion?

1 Like

This is awesome !!! @Fil many thanks, such a clever use of custom interpolator through scale linear.

1 Like