Search code examples
pythonbqplot

Bqplot rendering new bar at each step in animation instead of moving the original bar


I am trying to animate a bar chart in Bqplot. I would like to be able to update both the x and y values for the bars and have them adjust smoothly. The code below behaves as expected for 2 of the 3 bars in my chart, but the 3rd bar is redrawn completely at each timestep.

I was initially trying this with only 2 bars. The first bar behaved as expected, but the 2nd would be redrawn at each timestep. I added the 3rd bar to try solve the problem.

initialIndex = 0
idxSlider3 = IntSlider(min=0, max=20, step=1, 
    description='Index',value=initialIndex)

''' I want to update the bar chart with this function '''
def update_chart3(change):
    with bar3.hold_sync():
        bar3.x = [idxSlider3.value, 
             idxSlider3.value + 1, 
             idxSlider3.value + 2]
        bar3.y = [idxSlider3.value, idxSlider3.value, idxSlider3.value] 

idxSlider3.observe(update_chart3, 'value')  

fig3 = plt.figure(animation_duration=1000)

bar3 = plt.bar(x=[0, 1, 2], y=[1, 1, 0])

play_button3 = Play(min=0, max=20, step=1, interval=1000, initial_value=0)
jslink((play_button3, 'value'), (idxSlider3, 'value'))

VBox([HBox([idxSlider3, play_button3]), fig3])

Solution

  • Here is a way of making the 'bars' animate nicely in x. It can't be done with bars. Instead I've used line marks - which is a hack. But you get the flexibility. And I prefer using Bqplot in the object model (rather than the pyplot way).

    enter image description here

    import bqplot as bq
    from ipywidgets import *
    import numpy as np
    
    initialIndex = 0
    xBar=[0, 2, 4] 
    yBar=[1, 1, 0]
    zeroY = 0
    barWidth = 1
    
    idxSlider3 = IntSlider(min=0, max=20, step=1, 
        description='Index',value=initialIndex)
    
    ''' I want to update the bar chart with this function '''
    def update_chart3(change):
        xBar = [idxSlider3.value, 
             idxSlider3.value + 2, 
             idxSlider3.value + 4]
        np.random.rand()
        yBar = [idxSlider3.value + np.random.rand()*2, idxSlider3.value + np.random.rand()*2, idxSlider3.value + np.random.rand()*2] 
        xList, yList = getLineCoordsFromBarLike(xBar, yBar)
        lines.x = xList
        lines.y = yList
    
    idxSlider3.observe(update_chart3, 'value')  
    
    x_sc = bq.LinearScale()
    y_sc = bq.LinearScale()
    
    x_ax1 = bq.Axis(label='', scale=x_sc, num_ticks = 3)
    y_ax1 = bq.Axis(label='', scale=y_sc, orientation='vertical')
    
    def getLineCoordsFromBarLike(xBar, yBar):
        """ Convert of single list of bar coordinates to a 2D array of line coordinates that describe the rectangular shape
    
        xBar: 1D list of numbers for central x position of bar chart
        yBar: 1D list of numbers for amplitudes of bar chart  (must be same length as xBar)
    
        retrns x cordinates 2D array, y coordinates 2D array.   
        """
        xList= []
        yList= []
        for num, x in enumerate(xBar):
            y = yBar[num] 
            xList.append([x - barWidth/2, x - barWidth/2, x + barWidth/2, x + barWidth/2,])
            yList.append([zeroY, y, y, zeroY])
        x_ax1.tick_values = xBar 
        return xList, yList
    
    xList, yList = getLineCoordsFromBarLike(xBar, yBar)   
    
    lines = bq.Lines(x=xList, y=yList,
        scales={'x': x_sc, 'y': y_sc},
        colors=['black'], 
        display_legend=True, 
    #     tooltip=def_tt,
        stroke_width = .5,
         close_path = True,
         fill = 'inside',
        fill_colors = bq.colorschemes.CATEGORY10 * 10
        )
    
    margins = dict(top=10, bottom=40, left=50, right=30)
    fig3 = bq.Figure(marks=[lines], axes=[x_ax1, y_ax1], fig_margin=margins, animation_duration=1000)
    fig3.layout.width = '600px'
    fig3.layout.height = '600px'
    
    # def_tt = bq.Tooltip(fields=['name',], formats=['',],  labels=['id', ])
    play_button3 = Play(min=1, max=20, step=1, interval=1000, initial_value=0)
    jslink((play_button3, 'value'), (idxSlider3, 'value'))
    
    VBox([HBox([idxSlider3, play_button3]), fig3])