holoviz / holoviews

With Holoviews, your data visualizes itself.
https://holoviews.org
BSD 3-Clause "New" or "Revised" License
2.71k stars 403 forks source link

Customize title_format #2948

Open Gordon90s opened 6 years ago

Gordon90s commented 6 years ago

Say kdims = 'a' in a HoloMap. In the title of the plot, the default is then in the form "a: [value of a]".

I would like to have something of the form "a: [value of a], b: [value of a]^2" in the title, who would I do that?

For example of this example:

import numpy as np
import holoviews as hv
hv.extension('bokeh')

frequencies = [0.5, 0.75, 1.0, 1.25]

def sine_curve(phase, freq):
    xvals = [0.1* i for i in range(100)]
    return hv.Curve((xvals, [np.sin(phase+freq*x) for x in xvals]))

curve_dict = {f:sine_curve(0,f) for f in frequencies}

hmap = hv.HoloMap(curve_dict, kdims='a')
hmap
jbednar commented 6 years ago

You can add arbitrary information into the title for each Curve if you want:

import numpy as np, pandas as pd, holoviews as hv
hv.extension('bokeh')

frequencies = [0.5, 0.75, 1.0, 1.25]

def sine_curve(phase, freq):
    xvals = [0.1* i for i in range(100)]
    return hv.Curve((xvals, [np.sin(phase+freq*x) for x in xvals])).relabel("b: {}".format(freq**2))

curve_dict = {f:sine_curve(0,f) for f in frequencies}

hv.HoloMap(curve_dict, kdims=['a'])

I'm not sure if that's precisely what you're after here, though.

Gordon90s commented 6 years ago

Kind of thanks. I would have liked to change it at the HoloMap level - or more generally at the layout level - and that did not work. Though I'm happy with the result.

(I would have liked to change pythagoras_viz in the following plot

import holoviews as hv
from holoviews import Image, HoloMap, DynamicMap, Polygons, Text
import numpy as np
from numpy import array
hv.extension('bokeh')
import bokeh.palettes as bp  # for color palettes

# Pythagoras a^2 + b^2 = c^2 visual proof
# define function that returns coordinates of rectangle and line
def rectangle(x=0, y=0, width=.05, height=.05):
    return array([(x, y), (x + width, y), (x + width, y + height), (x, y + height)])

def line(x=0, y=0, width=.05, height=.05):
    return array([(x, y), (x + width, y + height)])

# choose color for plotting
color_palette = 'Inferno'
col_sq_1 = bp.all_palettes[color_palette][256][20]
col_sq_2 = bp.all_palettes[color_palette][256][20]
col_sq_3 = bp.all_palettes[color_palette][256][20]
col_rect = bp.all_palettes[color_palette][256][145]

# options for plot styling
opts = {'Polygons': dict(toolbar=None, xaxis=None, yaxis=None, width=350, height=350, line_width=1.5)}

# left part of the visual proof, squares of area a^2 and b^2 + 4 triangles of area a*b/2
def square_left(a=1):
    # we set a + b = 100 then convert to a + b = 1
    a = a / 100.0
    b = 1.0 - a

    poly_1 = Polygons([rectangle(0, 0, a, b)]).options(color=col_rect) * Polygons([line(0, 0, a, b)])
    poly_2 = Polygons([rectangle(0, b, a, a)]).options(color=col_sq_1)
    poly_3 = Polygons([rectangle(a, 0, b, b)]).options(color=col_sq_2)
    poly_4 = Polygons([rectangle(a, b, b, a)]).options(color=col_rect) * Polygons([line(a, b, b, a)])
    text_1 = Text(a / 2, -0.05, 'a') * Text(-0.05, b / 2, 'b')
    text_11 = Text(a / 2, 1.05, 'a') * Text(1.05, b / 2, 'b')
    text_2 = Text(a + b / 2, -0.05, 'b') * Text(-0.05, b + a / 2, 'a')
    text_22 = Text(a + b / 2, 1.05, 'b') * Text(1.05, b + a / 2, 'a')
    text_3 = Text(a / 2, b + a / 2, 'a\u00b2').options(color='white') * Text(a + b / 2, b / 2, 'b\u00b2').options(
        color='white')
    text_4 = Text(a / 2.5, (b + 0.1 / a) / 2.5, 'c') * Text(a + (b - 0.082 / a) / 2, b + a / 2, 'c')
    output = (poly_1 * poly_2 * poly_3 * poly_4 * text_1 * text_11 * text_2 * text_22 * text_3 * text_4).options(opts)
    output = output.relabel("a\u00b2: {}".format(np.round((a)*100)**2), "b\u00b2: {}".format(np.round((1-a)*100)**2))
    return output

# right part of the visual proof, squares of area c^2 + 4 triangles of area a*b/2
def square_right(a=1):
    a = a / 100.0
    b = 1.0 - a

    poly_5 = Polygons([rectangle(0, 0, a + b, b + a)]).options(color=col_rect)
    poly_6 = Polygons([array([[a, 0], [a + b, a], [b, b + a], [0, b], [a, 0]])]).options(color=col_sq_3)
    text_1 = Text(a / 2, -0.05, 'a') * Text(-0.05, b / 2, 'b')
    text_11 = Text(b / 2, 1.05, 'b') * Text(1.05, a / 2, 'a')
    text_2 = Text(a + b / 2, -0.05, 'b') * Text(-0.05, b + a / 2, 'a')
    text_22 = Text(b + a / 2, 1.05, 'a') * Text(1.05, a + b / 2, 'b')
    text_3 = Text((b + a) / 2, (b + a) / 2, 'c\u00b2').options(color='white')
    output = (poly_5 * poly_6 * text_1 * text_11 * text_2 * text_22 * text_3).options(opts)
    output = output.relabel("c\u00b2: {}".format(np.round((a)*100)**2+np.round((1-a)*100)**2))
    return output

# create dynamic maps of square_left and square_right
dmap_left = DynamicMap(square_left, kdims=['a'])
dmap_right = DynamicMap(square_right, kdims=['a'])

# and plot together
range_a = (20, 80)

pythagoras_viz = (dmap_left.redim.range(a=range_a) + dmap_right.redim.range(a=range_a))
pythagoras_viz = pythagoras_viz.options(toolbar=None).redim.range(x=(-0.1, 1.1), y=(-0.1, 1.1))
pythagoras_viz = pythagoras_viz
pythagoras_viz

)

P.S.: By the way I'm surprised at the lack of fluidity in my plot. I guess it's because Bokeh renders pixelwize with no vector objects?

philippjfr commented 6 years ago

Cool demo, but indeed pretty slow. I'll do some profiling to figure out what's taking so long. One thing worth trying is to combine the text elements into an hv.Labels element.

philippjfr commented 6 years ago

There are a bunch of things we could improve to speed things up when plotting a lot of objects but you can also achieve a significant speedup by combining the polygons and labels as much as possible, here's my initial attempt:

# left part of the visual proof, squares of area a^2 and b^2 + 4 triangles of area a*b/2
def square_left(a=1):
    # we set a + b = 100 then convert to a + b = 1
    a = a / 100.0
    b = 1.0 - a
    poly_1 = Polygons([rectangle(a, b, b, a), rectangle(0, 0, a, b)]).options(color=col_rect)
    poly_2 = Polygons([rectangle(0, b, a, a), rectangle(a, 0, b, b)]).options(color=col_sq_1)
    poly_3 = Polygons([line(0, 0, a, b), line(a, b, b, a)])
    text_1 = [
        (a / 2, -0.05, 'a'), (-0.05, b / 2, 'b'),
        (a / 2, 1.05, 'a'), (1.05, b / 2, 'b'),
        (a + b / 2, -0.05, 'b'), (-0.05, b + a / 2, 'a'),
        (a + b / 2, 1.05, 'b'), (1.05, b + a / 2, 'a'),
        (a / 2.5, (b + 0.1 / a) / 2.5, 'c'),
        (a + (b - 0.082 / a) / 2, b + a / 2, 'c')
    ]
    text_2 = [
        (a / 2, b + a / 2, 'a\u00b2'), (a + b / 2, b / 2, 'b\u00b2')
    ]
    output = (poly_1 * poly_2 * poly_3 * hv.Labels(text_1) * hv.Labels(text_2).options(text_color='white')).options(opts)
    output = output.relabel("a\u00b2: {}".format(np.round((a)*100)**2), "b\u00b2: {}".format(np.round((1-a)*100)**2))
    return output

# right part of the visual proof, squares of area c^2 + 4 triangles of area a*b/2
def square_right(a=1):
    a = a / 100.0
    b = 1.0 - a

    poly_5 = Polygons([rectangle(0, 0, a + b, b + a)]).options(color=col_rect)
    poly_6 = Polygons([array([[a, 0], [a + b, a], [b, b + a], [0, b], [a, 0]])]).options(color=col_sq_3)
    text_1 = [
        (a / 2, -0.05, 'a'), (-0.05, b / 2, 'b'),
        (b / 2, 1.05, 'b'), (1.05, a / 2, 'a'),
        (a + b / 2, -0.05, 'b'), (-0.05, b + a / 2, 'a'),
        (b + a / 2, 1.05, 'a'), (1.05, a + b / 2, 'b')
    ]
    text_2 = Text((b + a) / 2, (b + a) / 2, 'c\u00b2').options(color='white')
    output = (poly_5 * poly_6 * hv.Labels(text_1) * text_2).options(opts)
    output = output.relabel("c\u00b2: {}".format(np.round((a)*100)**2+np.round((1-a)*100)**2))
    return output
Gordon90s commented 6 years ago

Didn't improve speed that much, but the code looks a lot nicer that way!

The labels are definitely the ones slowing down the graphic. Without them, everything runs smoothly. Maybe a HoloMap would have been a better choice with the labels? Anyways, I'm happy with this version now! :)

Gordon90s commented 6 years ago

Hmmm, well with another visualization I am not quite able to solve my issue with the proposed .relabel("b: {}".format(freq**2)) solution.

Is there any way to do it at the HoloMap level after all? Say something like

hv.HoloMap(curve_dict, kdims=['freq']).relabel("b: {}".format(freq**2))

?