holoviz / param

Param: Make your Python code clearer and more reliable by declaring Parameters
https://param.holoviz.org
BSD 3-Clause "New" or "Revised" License
433 stars 74 forks source link

Provide better ansi color codes. __doc__ strings are hard to read and not very "modern". #570

Open MarcSkovMadsen opened 2 years ago

MarcSkovMadsen commented 2 years ago

The param __doc__ string is powerful. But it is hard to read due to the colors chosen.

The blue on a black background is especially hard to read. But also the red can also be hard to read. And the green just does not look modern.

As a consequence I am not really finding my self using the __doc__ string. Which makes it harder to use the HoloViz ecosystem. Especially hvplot and HoloViews where a lot of needed information is in the __doc__ string and not on their web sites or something you can find via intellisense in your editor.

Can you read the blue below? šŸ˜„

image

from panel.template.fast.list import FastListTemplate
import param
import panel as pn
import ansiconv

ACCENT_COLOR = "#0072B5"
pn.state.sizing_mode="stretch_width"

class ClassificationPlot(param.Parameterized):
    """The ClassificationPlot plots the output of a classification, i.e. the *labels*
    and their *score*.
    """
    output_json = param.List(doc="""
    The ouput of the classification""")
    options = param.Dict(constant=True, doc="""
    The configuration used to construct the plot""")
    color = param.Color(ACCENT_COLOR, doc="""
    The colors of the bars""")
    theme = param.Selector(default="default", objects=["default", "dark"], doc="""
    The theme, i.e. 'default' or 'dark'. This is automatically set from pn.state.session_args""")

plot = ClassificationPlot()
def to_html(txt):
    html = ansiconv.to_html(txt)
    css = ansiconv.base_css()

    html = f"""
    <style>{css}</style>
    <pre class="ansi_fore ansi_back">{html}</pre>
    """
    return pn.Column(pn.pane.HTML(html, width=1200, sizing_mode="fixed"), height=500, scroll=True)

card = pn.layout.Card(
    to_html(plot.__doc__), header="ClassificationPlot", sizing_mode="stretch_width", collapsed=False
)

FastListTemplate(
    title="ClassificationPlot", main=[card], accent_base_color=ACCENT_COLOR, header_background=ACCENT_COLOR
).servable()

Additional Context

We should be able to get a lot of inspiration from Rich. That output is very readable.

image

MarcSkovMadsen commented 2 years ago

Proof of Concept

Ok. I guess the colors chosen have been chosen for a light background? I would still say they are not good.

This below is not great. But for me its better.

image

image

from panel.template.fast.list import FastListTemplate
import param
import panel as pn
import ansiconv

ACCENT_COLOR = "#0072B5"
pn.state.sizing_mode="stretch_width"

class ClassificationPlot(param.Parameterized):
    """The ClassificationPlot plots the output of a classification, i.e. the *labels*
    and their *score*.
    """
    output_json = param.List(doc="""
    The ouput of the classification""")
    options = param.Dict(constant=True, doc="""
    The configuration used to construct the plot""")
    color = param.Color(ACCENT_COLOR, doc="""
    The colors of the bars""")
    theme = param.Selector(default="default", objects=["default", "dark"], doc="""
    The theme, i.e. 'default' or 'dark'. This is automatically set from pn.state.session_args""")

plot = ClassificationPlot()

def get_css(background="#000000", color="#FFFFFF", red="#FF0000", green="#00FF00", blue="#0000FF", cyan="#00FFFF"):
    return f"""
.ansi_fore {{ color: {color}; }}
.ansi_back {{ background-color: {background}; }}
.ansi1 {{ font-weight: bold; }}
.ansi3 {{ font-weight: italic; }}
.ansi4 {{ text-decoration: underline; }}
.ansi9 {{ text-decoration: line-through; }}
.ansi30 {{ color: {background}; }}
.ansi31 {{ color: {red}; }}
.ansi32 {{ color: {green}; }}
.ansi33 {{ color: #FFFF00; }}
.ansi34 {{ color: {blue}; }}
.ansi35 {{ color: #FF00FF; }}
.ansi36 {{ color: {cyan}; }}
.ansi37 {{ color: {color}; }}
.ansi40 {{ background-color: {background}; }}
.ansi41 {{ background-color: {red}; }}
.ansi42 {{ background-color: {green}; }}
.ansi43 {{ background-color: #FFFF00; }}
.ansi44 {{ background-color: {blue}; }}
.ansi45 {{ background-color: #FF00FF; }}
.ansi46 {{ background-color: {cyan}; }}
.ansi47 {{ background-color: {color}; }}
"""

def to_html(txt, theme):
    html = ansiconv.to_html(txt)
    css = ansiconv.base_css()
    if theme=="dark":
        css = get_css(red="#ff8b8b", green="#8bFF8b", blue="#66b3ff")
    else:
        css = get_css(background="#FFFFFF", color="#000000", red="#8E0500", green="#19AF22", blue="#00008b", cyan="#17625F")

    html = f"""
    <style>{css}</style>
    <pre class="ansi_fore ansi_back">{html}</pre>
    """
    print(html)
    return pn.Column(pn.pane.HTML(html, width=1400, sizing_mode="fixed"), height=500, scroll=True)

try:
    theme = pn.state.session_args.get("theme")[0].decode()
except:
    theme = "default"
card = pn.layout.Card(
    to_html(plot.__doc__, theme=theme), header="ClassificationPlot", sizing_mode="stretch_width", collapsed=False
)

FastListTemplate(
    title="ClassificationPlot", main=[card], accent_base_color=ACCENT_COLOR, header_background=ACCENT_COLOR
).servable()
jbednar commented 2 years ago

Yes, the colors were originally chosen for visibility in a Jupyter notebook, defaulting to a white background. I'd be happy to accept a PR to update the colors in param/ipython.py#L32 to values that work well on both light and dark backgrounds. Unfortunately we can't even tell if a terminal supports ANSI, let alone what the background color might be (https://github.com/holoviz/param/pull/421).

To address the other issues you raise,

I think moving forward on either or both of these fronts would be a great help to users.

maximlt commented 2 years ago

Might be a separate discussion, but I'd also be happy if the Parameters were ordered alphabetically by default, the help of Parameterized classes with lots of Parameters are difficult to parse.

MarcSkovMadsen commented 2 years ago

Agree on alphabetic. I have a function that reorders the output from pn.Param. It's so much less strain on your brain when parameters are sorted alphabetically.

maximlt commented 2 years ago

(Was editing my message but posting here now since you've added a comment Marc!)

+1 on improving the help!

Look how rich highlights alternating rows, I quite like that.

And as a comment here, I think that when I look at the help of a Parameterized object I most often go after a docstring (either I know there's a parameter and forgot its name or I'm looking for one that does what I want), and then maybe I have a look at the table. Somehow I almost never remember what the acronyms in the Mode column mean.

MarcSkovMadsen commented 2 years ago

+1 on moving the docstrings above the table. It is also most often the docstrings I am searching for. The table is nice to have.

One argument is also that if you are new to the framework seeing that table can be overwhelming. It takes time to understand and is not something you see in a normal docstring.

jbednar commented 2 years ago

Here's something from the web that suggests why we need to put the contents of hv.help on each gallery page:

https://stackoverflow.com/questions/70009010/customizing-hvplot-box

MarcSkovMadsen commented 2 years ago

I would also say the parameters are described in 3 places. That is too much. Personally I would prefer to just remove the initial ones.

image

(Solarized Theme)

maximlt commented 2 years ago

Agree. But we need to keep the init signature entry, since users can initialize a Parameterized class with non-param parameters:

class P(param.Parameterized):
    x = param.Number(default=10)

    def __init__(self, y, **params):
        x = y * 2
        super().__init__(x=x, **params)

I have the feeling the Parameter docstrings section could integrate all the info that is in the table above, which could then be removed entirely. It's already common usage to add the type and the default value next to the parameter name. image