Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DynamicMap Issue: AttributeError: unexpected attribute 'factors' to LinearColorMapper #5591

Open
MarcSkovMadsen opened this issue Jan 14, 2023 · 1 comment
Labels
type: bug Something isn't correct or isn't working
Milestone

Comments

@MarcSkovMadsen
Copy link
Collaborator

MarcSkovMadsen commented Jan 14, 2023

Gordonrix asked for help in https://discourse.holoviz.org/t/widget-that-switches-hvplot-points-color-value-between-categorical-and-numeric-type-columns-yields-attributeerror/4823.

His example is

import holoviews as hv
import pandas as pd
import numpy as np
import panel as pn
import hvplot.pandas
from holoviews.streams import Selection1D

data = {'X' : np.random.randn(100),
        'Y' : np.random.randn(100),
        'colorNum' : np.random.randint(0,10,100),
        'colorNum2' : np.random.randint(0,10,100),
        'colorCat' : list('ABCDEFGHIJ')*10}

df = pd.DataFrame(data)
color_by_select = pn.widgets.Select(name='color by', options=['colorNum', 'colorNum2', 'colorCat'], value='colorNum')

def points(ds, colorby):
    return ds.data.hvplot.points(x='X', y='Y', color=colorby)

pn.Column(color_by_select,
          hv.Dataset(df).apply(points, colorby=color_by_select)).servable()

When you select colorCat in the dropdown you get

 File "/home/jovyan/repos/private/panel/.venv/lib/python3.10/site-packages/holoviews/core/options.py", line 225, in __exit__
    raise AbbreviatedException(etype, value, traceback)
holoviews.core.options.AbbreviatedException: AttributeError: unexpected attribute 'factors' to LinearColorMapper, possible attributes are domain, high, high_color, js_event_callbacks, js_property_callbacks, low, low_color, name, nan_color, palette, subscribed_events, syncable or tags

Workaround

import holoviews as hv
import pandas as pd
import numpy as np
import panel as pn
import hvplot.pandas
from holoviews.streams import Selection1D

data = {'X' : np.random.randn(100),
        'Y' : np.random.randn(100),
        'colorNum' : np.random.randint(0,10,100),
        'colorNum2' : np.random.randint(0,10,100),
        'colorCat' : list('ABCDEFGHIJ')*10}

df = pd.DataFrame(data)

color_by_select = pn.widgets.Select(name='color by', options=['colorNum', 'colorNum2', 'colorCat'], value='colorNum')

def points(data, colorby):
    return data.hvplot.points(x='X', y='Y', c=colorby)

plot = pn.bind(points, df, color_by_select)

pn.Column(color_by_select, plot).servable()

This code works

image

But if I wrap the plot in hv.DynamicMap I see the same error.

I tried using a scatter plot instead. But it has the same issue.

@MarcSkovMadsen MarcSkovMadsen added the type: bug Something isn't correct or isn't working label Jan 14, 2023
@MarcSkovMadsen MarcSkovMadsen added this to the 1.15.4 milestone Jan 14, 2023
@Hoxbro Hoxbro modified the milestones: 1.15.4, 1.15.x Jan 14, 2023
@chrisbotica
Copy link

chrisbotica commented Mar 24, 2023

Big time user here (appreciate this project and the workarounds so far!)

Just curious, any updates to this bug? My two cents is that the default bokeh colormapper doesn't change after the plot init, so just forcing the colormapper to reset helps (see my workaround 1). Anyway, as @MarcSkovMadsen points out this doesn't work when wrapped in a hv.DynamicMap (which I need, to tie in with other streams), so here's two workarounds to use with dmaps:

Workaround 1

  • wrap points plot in a dmap & force the colormapper to reset using plot hooks
import holoviews as hv
import pandas as pd
import numpy as np
import panel as pn
from holoviews.streams import Selection1D
hv.extension('bokeh')


data = {
  'X': np.random.randn(100),
  'Y': np.random.randn(100),
  'colorNum': np.random.randint(0, 10, 100),
  'colorNum2': np.random.randint(0, 10, 100),
  'colorCat': list('ABCDEFGHIJ') * 10
}

df = pd.DataFrame(data)

color_by_select = pn.widgets.Select(
  name='color by',
  options=['colorNum', 'colorNum2', 'colorCat'],
  value='colorNum')

def hook(plot, element):
  print(plot.handles['color_color_mapper'])
  plot.handles['color_color_mapper'] = None

def points(colorby):
  return hv.Points(df, kdims=['X', 'Y'],
                   vdims=[colorby]).opts(color=colorby,
                                         colorbar=True,
                                         tools=['box_select'],
                                         hooks=[hook])

dmap = hv.DynamicMap(points, streams=dict(colorby=color_by_select.param.value))

def refresh_plot(colorby):
  dmap.reset() #whenever we change the colorby, reset the dmap entirely
  return dmap

plot = pn.bind(refresh_plot, color_by_select) # note that pn.bind is still needed.. 

pn.Column(color_by_select, plot).show()

Note that this works well, jut now I get a background js error: TypeError: h.slice is not a function. (in 'h.slice(i)', 'h.slice' is undefined). Nothing gets displayed to the command line, so if you can live with that, it works.

Workaround 2

  • Almost like having two plots: wrap points plot in two dmaps & basically let panel choose which one gets displayed
import holoviews as hv
import pandas as pd
import numpy as np
import panel as pn
from holoviews.streams import Selection1D
hv.extension('bokeh')

data = {
  'X': np.random.randn(100),
  'Y': np.random.randn(100),
  'colorNum': np.random.randint(0, 10, 100),
  'colorNum2': np.random.randint(0, 10, 100),
  'colorCat': list('ABCDEFGHIJ') * 10
}

df = pd.DataFrame(data)

color_by_select = pn.widgets.Select(
  name='color by',
  options=['colorNum', 'colorNum2', 'colorCat'],
  value='colorNum')

def points(colorby):
  return hv.Points(df, kdims=['X', 'Y'],
                   vdims=[colorby]).opts(color=colorby,
                                         colorbar=True,
                                         tools=['box_select'])

def points_continous(colorby):
  if colorby in ['colorCat']:  # if user changes color to categorical while continuous dmap is showing
    # return dummy plot so that there is no mistype error
    return hv.Points(pd.DataFrame({'X': [],'Y': [],'color': [] }), kdims=['X', 'Y'], vdims=['color']).opts(color='color')
  else:  # otherwise will return continuous plot:
    return points(colorby)

def points_categorical(colorby):
  if colorby in ['colorNum', 'colorNum2']:
    # return dummy plot
    return hv.Points(pd.DataFrame({'X': [],'Y': [],'color': [] }), kdims=['X', 'Y'], vdims=['color']).opts(color='color')
  else:
    return points(colorby)

# define our two maps
dmap_continuous = hv.DynamicMap(points_continous, streams=dict(colorby=color_by_select.param.value))
dmap_categorical = hv.DynamicMap(points_categorical, streams=dict(colorby=color_by_select.param.value))

# panels forces which dmap gets displayed in the plot, based on colorby
def refresh_plot(colorby):
  if colorby in ['colorNum', 'colorNum2']:
    dmap_continuous.reset()
    return dmap_continuous
  else:  # is categorical
    dmap_categorical.reset()
    return dmap_categorical

plot = pn.bind(refresh_plot, color_by_select)

pn.Column(color_by_select, plot).show()

# note that now we would have two selection streams that we could combine later..
# Cat_sel_stream = selection1D(source= dmap_categorical)
# Cont_sel_stream = selection1D(source= dmap_continuous)

This second one is a lot uglier and maybe less efficient, but I get no errors & can now use a dynamic map with other tie-ins

Curious to hear your thoughts!

Cheers,
Chris

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug Something isn't correct or isn't working
Projects
None yet
Development

No branches or pull requests

3 participants