Improve `FunctionTransformer` diagram representation

### Describe the workflow you want to enable

Currently, using multiple `FunctionTransformers` in a pipeline leads to an uninformative view:

```python
import pandas as pd
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer

df = pd.DataFrame([[1,2,3], [4,5,6]], columns=['one','two','three']) # sample data
def a(df): return df+1 # 1st transformer
def b(df): return df*10 # 2nd transformer

make_pipeline(FunctionTransformer(a), FunctionTransformer(b))
```

![image](https://github.com/scikit-learn/scikit-learn/assets/5570380/21382d09-82ad-4e14-8091-6e14ab9989e8)

I would like to see the name of the function being used in the visual blocks


### Describe your proposed solution

I would like to see something like this:

![image](https://github.com/scikit-learn/scikit-learn/assets/5570380/8c67e04a-6328-4afd-9ebc-a0e1da29ca57)

(or perhaps `Function(<name of function>)`  or `<name of function>()` or `FunctionTransformer_<name of function>`) 
 
A sample implementation might be look like this:

```python
from sklearn.preprocessing import FunctionTransformer
from sklearn.utils._estimator_html_repr import _VisualBlock
from functools import partial

class PrettyFunctionTransformer(FunctionTransformer):
    def _sk_visual_block_(self):
        return _VisualBlock(
            "single",
            self,
            names=self.func.func.__name__ if isinstance(self.func, partial) else self.func.__name__,
            name_details=str(self),
        )
```

### Describe alternatives you've considered, if relevant

_No response_

### Additional context

_No response_

COMMENT:
> I found the info icon tooltip actually shows "documentation of {name}" which in that case would be "documentation of func name" which I think is improper

Checking the [Developer API for HTML representation](https://scikit-learn.org/stable/developers/develop.html#developer-api-for-html-representation) it seems we could overwrite the `_doc_link_template` and `_doc_link_url_param_generator` methods for `FunctionTransformer`

> What about partial of partial, partial of partial of partial, vectorize of partial, etc.?

We can implement a recursive function for those edge cases, like so: 

<details>
<summary>sample implementation for `get_function_name`</summary>

```python
import numpy as np
import functools

def get_function_name(func):
    """
    Retrieves the name of a function, supporting `np.vectorize` and `functools.partial`, 
    including nested variations.
    """
    # Check if the function has a `__name__` attribute directly
    if hasattr(func, '__name__'):
        return func.__name__
    
    # Check for functools.partial
    if isinstance(func, functools.partial):
        return get_function_name(func.func)
    
    # Check for np.vectorize
    if isinstance(func, np.vectorize):
        return get_function_name(func.pyfunc)
    
    # Check if the function has a `__wrapped__` attribute (for other decorators)
    if hasattr(func, '__wrapped__'):
        return get_function_name(func.__wrapped__)
    
    # If all else fails, return a placeholder name or indication
    return "<unknown_function>"

# Example Usage:
def example_function(x):
    return x

partial_func = functools.partial(example_function, x=2)
vectorized_func = np.vectorize(example_function)
partial_vectorized_func = functools.partial(vectorized_func, x=2)

print(get_function_name(example_function))           # Output: example_function
print(get_function_name(partial_func))               # Output: example_function
print(get_function_name(vectorized_func))            # Output: example_function
print(get_function_name(partial_vectorized_func))    # Output: example_function
print(get_function_name(lambda x: x))    # Output: <lambda>
```

</details>

That will deal with the vast majority of functions and it has a fallback.

Labels: New Feature