Skip to content

Commit

Permalink
Make Dimension.label source of truth for Dimension identity (#6262)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr authored Jun 7, 2024
1 parent 4db648d commit 346be14
Show file tree
Hide file tree
Showing 21 changed files with 125 additions and 86 deletions.
10 changes: 5 additions & 5 deletions examples/user_guide/01-Annotating_Data.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,12 @@
"source": [
"### Dimension parameters\n",
"\n",
"``Dimensions`` are not just names, they are rich objects with numerous parameters that can be used to describe the space in which the data resides. Only two of these are considered *core* parameters that uniquely identify the dimension object; the rest are auxiliary metadata. The most important parameters are:\n",
"``Dimensions`` are not just names, they are rich objects with numerous parameters that can be used to describe the space in which the data resides. Only two of these are considered *core* parameters the dimension object; the rest are auxiliary metadata. The most important parameters are:\n",
"\n",
"<br>\n",
"<dl class=\"dl-horizontal\">\n",
" <dt>name</dt><dd>(core) A concise name for the dimension, which for convenient usage as a keyword argument should usually be a legal Python identifier.</dd>\n",
" <dt>label <dd>(core) A optional longer description of the dimension, which is convenient if you want the displayed label to contain arbitrary spaces, symbols, or unicode.</dd>\n",
" <dt>name</dt><dd>(core) A concise name for the dimension, which for convenient usage as a keyword argument should usually be a legal Python identifier. The name also corresponds to the name of the variable in the underlying data, e.g. when providing a dictionary of columns or a DataFrame.</dd>\n",
" <dt>label <dd>(core) A optional longer description of the dimension, which is convenient if you want the displayed label to contain arbitrary spaces, symbols, or unicode. By default this is identical to the name but if provided this value uniquely identifies the dimension.</dd>\n",
" <dt>range <dd>The minimum and maximum allowable values for the dimension, for error checking and generating widgets when needed.</dd>\n",
" <dt>soft_range <dd>Suggested minimum and maximum values within the allowed range, used to specify a useful portion of the range for widgets and animations.</dd>\n",
" <dt>step <dd>Suggested interval for sampling a continuous range, if needed for a widget or animation.</dd>\n",
Expand All @@ -167,7 +167,7 @@
" ('height','Height above sea level'))\n",
"\n",
"distance = hv.Dimension('distance', label='Horizontal distance', unit='m')\n",
"height = hv.Dimension(('height','Height above sea level'), unit='m')\n",
"height = hv.Dimension(('height', 'Height above sea level'), unit='m')\n",
"with_unit = hv.Curve((xs, ys), distance, height)\n",
"\n",
"# (using + to compose elements is described in the next guide)\n",
Expand Down Expand Up @@ -254,5 +254,5 @@
}
},
"nbformat": 4,
"nbformat_minor": 2
"nbformat_minor": 4
}
28 changes: 27 additions & 1 deletion examples/user_guide/08-Tabular_Datasets.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,32 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"This is particularly useful when you're working with wide data where you have one column (or index) representing the shared x-values and multiple columns measuring some quantity, e.g. a set of stocks indexed by date. By providing a name and label for the value dimension of our `Curve` we can correctly label the dimension as the stock `Price` while referring to the underlying equity by name:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"stocks = pd.DataFrame({\n",
" 'AAPL': np.random.randn(100).cumsum() + 20,\n",
" 'MSFT': np.random.randn(100).cumsum() + 25,\n",
" 'IBM': np.random.randn(100).cumsum() + 10,\n",
"}, index=pd.date_range('2024-01-01', '2024-04-09'))\n",
"\n",
"hv.NdOverlay(\n",
" {col: hv.Curve(stocks, 'index', (col, 'Price')) for col in stocks.columns}, 'Ticker'\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Each Element merely provides a view into the underlying data.\n",
"\n",
"For columnar data, this approach is much more efficient than creating copies of the data for each Element, and allows for some advanced features like linked brushing in the [Bokeh backend](./Plotting_with_Bokeh.ipynb)."
]
},
Expand Down Expand Up @@ -591,5 +617,5 @@
}
},
"nbformat": 4,
"nbformat_minor": 1
"nbformat_minor": 4
}
2 changes: 1 addition & 1 deletion holoviews/core/dimension.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ def __eq__(self, other):
"Implements equals operator including sanitized comparison."

if isinstance(other, Dimension):
return self.spec == other.spec
return self.label == other.label

# For comparison to strings. Name may be sanitized.
return other in [self.name, self.label, util.dimension_sanitizer(self.name)]
Expand Down
2 changes: 1 addition & 1 deletion holoviews/core/pprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ def element_info(cls_or_slf, node, siblings, level, value_dims):
if len(node.kdims) >= 1:
info += cls_or_slf.tab + f"[{','.join(d.name for d in node.kdims)}]"
if value_dims and len(node.vdims) >= 1:
info += cls_or_slf.tab + f"({','.join(d.name for d in node.vdims)})"
info += cls_or_slf.tab + f"({','.join(d.label for d in node.vdims)})"
return level, [(level, info)]

@bothmethod
Expand Down
12 changes: 6 additions & 6 deletions holoviews/plotting/bokeh/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,10 +447,10 @@ def get_data(self, element, ranges, style):

def get_extents(self, element, ranges, range_type='combined', **kwargs):
ydim = element.get_dimension(1)
s0, s1 = ranges[ydim.name]['soft']
s0, s1 = ranges[ydim.label]['soft']
s0 = min(s0, 0) if isfinite(s0) else 0
s1 = max(s1, 0) if isfinite(s1) else 0
ranges[ydim.name]['soft'] = (s0, s1)
ranges[ydim.label]['soft'] = (s0, s1)
return super().get_extents(element, ranges, range_type)


Expand Down Expand Up @@ -888,8 +888,8 @@ def get_data(self, element, ranges, style):
stack_dim = element.get_dimension(1)
if stack_dim.values:
stack_order = stack_dim.values
elif stack_dim in ranges and ranges[stack_dim.name].get('factors'):
stack_order = ranges[stack_dim]['factors']
elif stack_dim in ranges and ranges[stack_dim.label].get('factors'):
stack_order = ranges[stack_dim.label]['factors']
else:
stack_order = element.dimension_values(1, False)
stack_order = list(stack_order)
Expand All @@ -904,7 +904,7 @@ def get_data(self, element, ranges, style):
color_index = (group_dim or stack_dim) if no_cidx else self.color_index
color_dim = element.get_dimension(color_index)
if color_dim:
self.color_index = color_dim.name
self.color_index = color_dim.label

# Define style information
width = style.get('bar_width', style.get('width', 1))
Expand Down Expand Up @@ -935,7 +935,7 @@ def get_data(self, element, ranges, style):
datatype=['dataframe', 'dictionary'])

width = abs(width)
y0, y1 = ranges.get(ydim.name, {'combined': (None, None)})['combined']
y0, y1 = ranges.get(ydim.label, {'combined': (None, None)})['combined']
if self.logy:
bottom = (ydim.range[0] or (0.01 if y1 > 0.01 else 10**(np.log10(y1)-2)))
else:
Expand Down
12 changes: 6 additions & 6 deletions holoviews/plotting/bokeh/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ def _axis_props(self, plots, subplots, element, ranges, pos, *, dim=None,
dims = [dim]
v0, v1 = dim.range
axis_label = str(dim)
specs = ((dim.name, dim.label, dim.unit),)
specs = ((dim.label, dim.unit),)
# For y-axes check if we explicitly passed in a dimension.
# This is used by certain plot types to create an axis from
# a synthetic dimension and exclusively supported for y-axes.
Expand All @@ -724,7 +724,7 @@ def _axis_props(self, plots, subplots, element, ranges, pos, *, dim=None,
for elrange in ranges.values()
])
axis_label = str(dim)
specs = ((dim.name, dim.label, dim.unit),)
specs = ((dim.label, dim.unit),)
else:
try:
l, b, r, t = self.get_extents(range_el, ranges, dimension=dim)
Expand Down Expand Up @@ -756,7 +756,7 @@ def _axis_props(self, plots, subplots, element, ranges, pos, *, dim=None,
if dims:
if not isinstance(dims, list):
dims = [dims]
specs = tuple((d.name, d.label, d.unit) for d in dims)
specs = tuple((d.label, d.unit) for d in dims)
else:
specs = None

Expand All @@ -773,7 +773,7 @@ def _axis_props(self, plots, subplots, element, ranges, pos, *, dim=None,
categorical = any(self.traverse(lambda plot: plot._categorical))
if self.subcoordinate_y:
categorical = False
elif dims is not None and any(dim.name in ranges and 'factors' in ranges[dim.name] for dim in dims):
elif dims is not None and any(dim.label in ranges and 'factors' in ranges[dim.label] for dim in dims):
categorical = True
else:
categorical = any(isinstance(v, (str, bytes)) for v in (v0, v1))
Expand Down Expand Up @@ -1663,8 +1663,8 @@ def get_aspect(self, xspan, yspan):
def _get_dimension_factors(self, element, ranges, dimension):
if dimension.values:
values = dimension.values
elif 'factors' in ranges.get(dimension.name, {}):
values = ranges[dimension.name]['factors']
elif 'factors' in ranges.get(dimension.label, {}):
values = ranges[dimension.label]['factors']
else:
values = element.dimension_values(dimension, False)
values = np.asarray(values)
Expand Down
4 changes: 2 additions & 2 deletions holoviews/plotting/bokeh/graphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ def _get_edge_paths(self, element, ranges):
"Expected %d, found %d paths." % (len(element), len(edges)))
elif self.directed:
xdim, ydim = element.nodes.kdims[:2]
x_range = ranges[xdim.name]['combined']
y_range = ranges[ydim.name]['combined']
x_range = ranges[xdim.label]['combined']
y_range = ranges[ydim.label]['combined']
arrow_len = np.hypot(y_range[1]-y_range[0], x_range[1]-x_range[0])*self.arrowhead_length
arrows = get_directed_graph_paths(element, arrow_len)
path_data['xs'] = [arr[:, 0] for arr in arrows]
Expand Down
16 changes: 8 additions & 8 deletions holoviews/plotting/bokeh/hex_tiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,16 +150,16 @@ class HexTilesPlot(ColorbarPlot):

def get_extents(self, element, ranges, range_type='combined', **kwargs):
xdim, ydim = element.kdims[:2]
ranges[xdim.name]['data'] = xdim.range
ranges[ydim.name]['data'] = ydim.range
ranges[xdim.label]['data'] = xdim.range
ranges[ydim.label]['data'] = ydim.range
xd = element.cdims.get(xdim.name)
if xd and xdim.name in ranges:
ranges[xdim.name]['hard'] = xd.range
ranges[xdim.name]['soft'] = max_range([xd.soft_range, ranges[xdim.name]['soft']])
if xd and xdim.label in ranges:
ranges[xdim.label]['hard'] = xd.range
ranges[xdim.label]['soft'] = max_range([xd.soft_range, ranges[xdim.label]['soft']])
yd = element.cdims.get(ydim.name)
if yd and ydim.name in ranges:
ranges[ydim.name]['hard'] = yd.range
ranges[ydim.name]['hard'] = max_range([yd.soft_range, ranges[ydim.name]['soft']])
if yd and ydim.label in ranges:
ranges[ydim.label]['hard'] = yd.range
ranges[ydim.label]['hard'] = max_range([yd.soft_range, ranges[ydim.label]['soft']])
return super().get_extents(element, ranges, range_type)

def _hover_opts(self, element):
Expand Down
4 changes: 2 additions & 2 deletions holoviews/plotting/bokeh/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,8 @@ def get_data(self, element, ranges, style):
data[dim_name] = values

factors = None
if cdim.name in ranges and 'factors' in ranges[cdim.name]:
factors = ranges[cdim.name]['factors']
if cdim.label in ranges and 'factors' in ranges[cdim.label]:
factors = ranges[cdim.label]['factors']
elif values.dtype.kind in 'SUO' and len(values):
if isinstance(values[0], np.ndarray):
values = np.concatenate(values)
Expand Down
4 changes: 2 additions & 2 deletions holoviews/plotting/bokeh/sankey.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,8 @@ def get_extents(self, element, ranges, range_type='combined', **kwargs):
return element.nodes.extents
xdim, ydim = element.nodes.kdims[:2]
xpad = .05 if self.label_index is None else 0.25
x0, x1 = ranges[xdim.name][range_type]
y0, y1 = ranges[ydim.name][range_type]
x0, x1 = ranges[xdim.label][range_type]
y0, y1 = ranges[ydim.label][range_type]
xdiff = (x1-x0)
ydiff = (y1-y0)

Expand Down
2 changes: 1 addition & 1 deletion holoviews/plotting/bokeh/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ def get_data(self, element, ranges, style):
groups = dict([((element.label,), element)])

if split_dim:
split_name = split_dim.dimension.name
split_name = split_dim.dimension.label
if split_name in ranges and not split_dim.ops and 'factors' in ranges[split_name]:
split_cats = ranges[split_name].get('factors')
elif split_dim:
Expand Down
28 changes: 14 additions & 14 deletions holoviews/plotting/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ def get_extents(self, element, ranges, range_type='combined', **kwargs):
kdims = element.kdims
# loop over start and end points of segments
# simultaneously in each dimension
for kdim0, kdim1 in zip([kdims[i].name for i in range(2)],
[kdims[i].name for i in range(2,4)]):
for kdim0, kdim1 in zip([kdims[i].label for i in range(2)],
[kdims[i].label for i in range(2,4)]):
new_range = {}
for kdim in [kdim0, kdim1]:
# for good measure, update ranges for both start and end kdim
Expand Down Expand Up @@ -85,10 +85,10 @@ def get_extents(self, element, ranges, range_type='combined', **kwargs):
opts = self.lookup_options(element, 'plot').options
if len(element.dimensions()) > 1 and 'spike_length' not in opts:
ydim = element.get_dimension(1)
s0, s1 = ranges[ydim.name]['soft']
s0, s1 = ranges[ydim.label]['soft']
s0 = min(s0, 0) if util.isfinite(s0) else 0
s1 = max(s1, 0) if util.isfinite(s1) else 0
ranges[ydim.name]['soft'] = (s0, s1)
ranges[ydim.label]['soft'] = (s0, s1)
proxy_dim = None
if 'spike_length' in opts or len(element.dimensions()) == 1:
proxy_dim = Dimension('proxy_dim')
Expand Down Expand Up @@ -118,12 +118,12 @@ class AreaMixin:

def get_extents(self, element, ranges, range_type='combined', **kwargs):
vdims = element.vdims[:2]
vdim = vdims[0].name
vdim = vdims[0].label
if len(vdims) > 1:
new_range = {}
for r in ranges[vdim]:
if r != 'values':
new_range[r] = util.max_range([ranges[vd.name][r] for vd in vdims])
new_range[r] = util.max_range([ranges[vd.label][r] for vd in vdims])
ranges[vdim] = new_range
else:
s0, s1 = ranges[vdim]['soft']
Expand Down Expand Up @@ -153,9 +153,9 @@ def get_extents(self, element, ranges, range_type='combined', **kwargs):
element = Bars(overlay.table(), kdims=element.kdims+overlay.kdims,
vdims=element.vdims)
for kd in overlay.kdims:
ranges[kd.name]['combined'] = overlay.range(kd)
ranges[kd.label]['combined'] = overlay.range(kd)

vdim = element.vdims[0].name
vdim = element.vdims[0].label
s0, s1 = ranges[vdim]['soft']
s0 = min(s0, 0) if util.isfinite(s0) else 0
s1 = max(s1, 0) if util.isfinite(s1) else 0
Expand Down Expand Up @@ -209,15 +209,15 @@ def _get_coords(self, element, ranges, as_string=True):
else:
if gdim.values:
gvals = gdim.values
elif ranges.get(gdim.name, {}).get('factors') is not None:
gvals = ranges[gdim.name]['factors']
elif ranges.get(gdim.label, {}).get('factors') is not None:
gvals = ranges[gdim.label]['factors']
else:
gvals = element.dimension_values(gdim, False)
gvals = np.asarray(gvals)
if xvals:
pass
elif ranges.get(xdim.name, {}).get('factors') is not None:
xvals = ranges[xdim.name]['factors']
elif ranges.get(xdim.label, {}).get('factors') is not None:
xvals = ranges[xdim.label]['factors']
else:
xvals = element.dimension_values(0, False)
xvals = np.asarray(xvals)
Expand All @@ -229,8 +229,8 @@ def _get_coords(self, element, ranges, as_string=True):
else:
if xvals:
pass
elif ranges.get(xdim.name, {}).get('factors') is not None:
xvals = ranges[xdim.name]['factors']
elif ranges.get(xdim.label, {}).get('factors') is not None:
xvals = ranges[xdim.label]['factors']
else:
xvals = element.dimension_values(0, False)
xvals = np.asarray(xvals)
Expand Down
Loading

0 comments on commit 346be14

Please sign in to comment.