Skip to content

ArcLayer point-to-point animation #2531

Unanswered
hijiangtao asked this question in Q&A
ArcLayer point-to-point animation #2531
Dec 29, 2018 · 13 answers

Update: Enhancement about ArcLayer animation, see #2531 (comment)


Original Post (problem deprecated):

As mentioned in the title, I want to draw an animation with ArcLayer. As iuntroduced by deck.gl website, the Arc Layer renders raised arcs joining pairs of source and target points, specified as latitude/longitude coordinates, all arcs will be rendered at the same time, so can I animate it from source to target with some time latency?

By looking through the docs, I couldn't find a way to do that, however I found some documents may be useful for me, such as Animation and attribute-manager ,but I couldn't find concrete example of these usage.

However, I found some similar issues, like "How to animate ArcLayer arcs individually?" #633 It seems the developer want to draw different arcs at different time, and your team gave him an approach to do it with updateTriggers. I also need to animation the arc, but not different arcs. I want to draw arc with animation from source to target with some time, as I mentioned above.

I did some trying with his method. I look through the usage of updateTriggers and change the ArcLayer Demo codes with this property (updateTriggers method and getTargetPosition method):

_renderLayers() {
    const {strokeWidth = 2} = this.props;

    return [
      new ArcLayer({
        id: 'arc',
        data: this.state.arcs,
        getSourcePosition: d => d.source,
        getTargetPosition: d => [
          d.target[0] + Math.random()*2,
          d.target[1] + Math.random()*2,
        ],
        getSourceColor: d => (d.gain > 0 ? inFlowColors : outFlowColors)[d.quantile],
        getTargetColor: d => (d.gain > 0 ? outFlowColors : inFlowColors)[d.quantile],
        updateTriggers: {
          getTargetPosition: [time], // time is a variable that can be changed within a `setInterval` 
        },
        strokeWidth,
      }),
    ];
  }

However, I found it can only change arcs suddenly like this, not the animation I want.

image

Any ideas on it? Thanks very much.

Replies

13 suggested answers

background

Thanks for Yaryna Serkez's work on animating arcs:

The idea is pretty straightforward -- each arc has a gradient that varies from the actual color to its fully opaque equivalent. Changing the opacity point updates the length of the arc.

Though I need more time to understand the ideas of drawing animating arcs with help of colors and opactiy, I think the framework of this kind of AnimationArclayer is clear.

For normal ArcLayer, we can enhance it's interaction by providing a point-to-point (from source point to target point) animation.

Approaches

  • Way One: Animate the point-to-point externally by Layer itself.
import {ArcLayer} from 'deck.gl';

class ArcBrushingLayer extends ArcLayer {
  // custom shader with step function to create opacity gradient with colorA and colorB
  // More at https://thebookofshaders.com/05/
  getShaders() {
    return Object.assign({}, super.getShaders(), {
      inject: {
        'vs:#decl': `
         uniform float coef;
        `,
        'vs:#main-end': `
        if (coef > 0.0) {
          vec4 pct = vec4(segmentRatio);
          pct.a = step(coef, segmentRatio);
          vec4 colorA = instanceTargetColors;
          vec4 colorB = vec4(instanceTargetColors.r, instanceTargetColors.g, instanceTargetColors.b, 0.0);
          vec4 color = mix(colorA, colorB, pct) / 255.;
          vColor = color;
        }
                    `,
                    'fs:#main-start': `
        if (vColor.a == 0.0) discard;
                    `,
      },
    });
  }
  
  // overwrite draw fucntion
  draw(opts) {
    const {coef} = this.props;
    // add uniforms
    const uniforms = Object.assign({}, opts.uniforms, { coef });
    super.draw(Object.assign({}, opts, {uniforms}));
  }
}

And for deck.gl developers, we can provide the same API as Arclayer, except one more property called coef, to let them control the drawing process of each arc. The value should be assigned from 0 to 1, in Number type.

The usage can be formatted like this:

new ArcBrushingLayer({
   ... // other properties
   coef, // [0, 1]
});
  • Way Two: Animate the point-to-point internally by Layer itself.

The idea is pretty similar to the first approach, except we let Layer handle the drawing process by itself.

We should add a setInterval to the constructor, and provide a speed property to developers, rather than the pure progress rate coef:

constructor(props) {
	super(props);

	this.state = {
		coef: 0.001,
	};
      
    this.animationArcs();
}

animationArcs() {
	const {coef} = this.state;
	const = {speed} = this.props || 0.005;
	const animationInterval = setInterval(()=> {
        coef += 0.005;
        if (coef >= 1.0) {
          clearInterval(animationInterval);
        }
        this.setState({
          coef,
        })
      }, 10);
}

Last, we get coef from component's state, rather than its props.

0 replies

Hi @hijiangtao,

first of all thank you for providing your code! I tried to use the second way - seems to me way more beautiful - but I ran into a problem. Calling this.setState(...) in animationArcs() results in:

TypeError: Cannot read property 'changeFlags' of null
at AnimatedArcLayer.setChangeFlags (/Users/simons/dev/kapa/vis/node_modules/@deck.gl/core/dist/esm/lib/layer.js:1021:1)
at AnimatedArcLayer.setState (/Users/simons/dev/kapa/vis/node_modules/@deck.gl/core/dist/esm/lib/layer.js:288:1)
at /Users/simons/dev/kapa/vis/src/utils/AnimatedArcLayer.js:31:1

My AnimatedArcLayer looks like the following:

class AnimatedArcLayer extends ArcLayer {

    constructor(props) {
        super(props);
    
        this.state = {
            coef: props.coef,
        };
          
        this.animationArcs();
    }
    
    animationArcs() {
        let {coef} = this.state;
        const {speed} = this.props || 0.005;
        const animationInterval = setInterval(()=> {
            coef += speed;
            if (coef >= 1.0) {
              clearInterval(animationInterval);
            }
            this.setState({
              coef
            })
        }, 10);
    }
    
    // custom shader with step function to create opacity gradient with colorA and colorB
    // More at https://thebookofshaders.com/05/
    getShaders() {
        return Object.assign({}, super.getShaders(), {
            inject: {
                'vs:#decl': `
                uniform float coef;
                `,
                'vs:#main-end': `
                if (coef > 0.0) {
                vec4 pct = vec4(segmentRatio);
                pct.a = step(coef, segmentRatio);
                vec4 colorA = instanceTargetColors;
                vec4 colorB = vec4(instanceTargetColors.r, instanceTargetColors.g, instanceTargetColors.b, 0.0);
                vec4 color = mix(colorA, colorB, pct) / 255.;
                vColor = color;
                }
                            `,
                            'fs:#main-start': `
                if (vColor.a == 0.0) discard;
                            `,
            },
        });
    }
  
    // overwrite draw fucntion
    draw(opts) {
        const {coef} = this.props;
        // add uniforms
        const uniforms = Object.assign({}, opts.uniforms, { coef });
        super.draw(Object.assign({}, opts, {uniforms}));
    }
}

I also tried to bind this.animationArcs() in the constructor but this leads to:

TypeError: Cannot add property animationArcs, object is not extensible

Have you any idea on how to implement an AnimatedArcLayer in deck.gl@6.4.10? Any is help appreciated :D

0 replies

Hi, @stoney95 The base layer of deck.gl is not extensible so the second way is still a propose to be discussed here. And for the first question, I couldn't see the calling of changeFlags property in your code?

As the issue hasn't been accepted by deck.gl, I implemented it in this way, you may find some useful code here https://github.com/hijiangtao/glmaps/blob/master/src/layers/ArcLayer/animate.js#L31-L88 and here https://github.com/hijiangtao/glmaps/blob/master/src/layers/ArcLayer/index.js#L13-L22 (which is put the timer out of drawing layer itself)

0 replies

While I appreciate the detail, the text here got a bit long so I am trying to understand what effect you want to achieve. Is it mainly a question of wanting the arc endpoints to gradually move to the new positions, rather than "jump"? In that case you should just use the transitions prop, the ArcLayer props are "transition enabled"

I also tried to bind this.animationArcs() in the constructor but this leads to:

You can use the layer.state object if you need to add things to the layer. The layer itself is "disposable" so adding things on it usually would not make sense.

0 replies

@hijiangtao Thank you for your fast reply and also for providing your code!! I will submit an issue to your repo as i had to make two small fixes to get it running.

@ibgreen I'd like to achieve something similar to this: https://www.youtube.com/watch?v=6P4XQcnNTAo
I'm already using the transition, but this leads to an arc getting bigger with the endpoint "sliding" over the ground.

You can use the layer.state object if you need to add things to the layer

Don't quite get how to add methods using the layer-state. But I'm also pretty new to deck.gl and didn't get to extending layers so far.

0 replies

@stoney95 That's really great, hope to see your practice with deck.gl and learn from you.

And for @ibgreen 's mentioning - adding methods using the layer-state, I think you can just treat it as the internal state of React, they are pretty alike.

If you want to write your own layer in the future, I think this document may help you more or less https://deck.gl/#/documentation/developer-guide/writing-custom-layers/writing-your-own-layer?section=preparations

0 replies

Updated to deck.gl 7.3.4, and Yaryna Serkez's solution no longer works (no arcs rendering for me). Seems like breaking changes in luma.gl?

0 replies

@DarryQueen Hi, I mainly used getShaders API in solving my problems with deck.gl@^6.4.0, maybe you can check if it works in lower version deck.gl?

0 replies

I am wondering if anyone has solved the animation arc layer issue with v8.1 @hijiangtao

0 replies

I have solved after pulling quite a bit of hair. Not sure if it's the right solution since I have no clue about shader code but it seems to be working. Maybe it will help someone. Deckgl version 8.2.5.

    getShaders() {
      return Object.assign({}, super.getShaders(), {
        inject: {
          'vs:#decl': `uniform float coef;`,
          'vs:#main-end': `
            if (coef > 0.0) {
              vec4 pct = vec4(segmentRatio);
              pct.a = step(coef, segmentRatio);
              vec4 colorA = instanceTargetColors;
              vec4 colorB = vec4(instanceTargetColors.r, instanceTargetColors.g, instanceTargetColors.b, 0.0);
              vec4 color = mix(instanceSourceColors, colorB, pct.a);
              vColor = color;
              DECKGL_FILTER_COLOR(vColor, geometry);
            }
          `,
        },
      });
    }

    draw(opts) {
      const { coef } = this.props;

      this.state.model.setUniforms({ coef });;
      super.draw(opts);
    }
  }```
0 replies

I have solved after pulling quite a bit of hair. Not sure if it's the right solution since I have no clue about shader code but it seems to be working. Maybe it will help someone. Deckgl version 8.2.5.

    getShaders() {
      return Object.assign({}, super.getShaders(), {
        inject: {
          'vs:#decl': `uniform float coef;`,
          'vs:#main-end': `
            if (coef > 0.0) {
              vec4 pct = vec4(segmentRatio);
              pct.a = step(coef, segmentRatio);
              vec4 colorA = instanceTargetColors;
              vec4 colorB = vec4(instanceTargetColors.r, instanceTargetColors.g, instanceTargetColors.b, 0.0);
              vec4 color = mix(instanceSourceColors, colorB, pct.a);
              vColor = color;
              DECKGL_FILTER_COLOR(vColor, geometry);
            }
          `,
        },
      });
    }

    draw(opts) {
      const { coef } = this.props;

      this.state.model.setUniforms({ coef });;
      super.draw(opts);
    }
  }```

@Fl0rianFischer thank you for your code,this can work on deckgl 8.X version;
and if anyone want get a complete example, this document may help you more or less
https://observablehq.com/@pessimistress/deck-gl-tutorial-subclassing-a-layer

0 replies

@Diazhao @Fl0rianFischer I think we can always customize shaders via these APIs (as I mentioned above, in a very long time ago), what I supposed is to expose one explicit API to developers, which can let them draw animations easily, did you mean we can use such things already? :-)

0 replies

@hijiangtao my comment was more about improving on Yaryna Serkez's blog post on arc animation, since it doesn't work with recent versions of deck.gl. Most people trying to do this will stumble on this thread so I thought it might be a good idea to post it here.

I'd be very interested none the less to see a unified API in deck.gl for animating arcs and routes.

0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet
7 participants
Converted from issue