22
33import panel as pn
44import param
5- from narwhals .dependencies import is_into_dataframe , is_pandas_dataframe , is_polars_dataframe
5+ from narwhals .dependencies import is_into_dataframe
6+ from narwhals .dependencies import is_pandas_dataframe
7+ from narwhals .dependencies import is_polars_dataframe
68from narwhals .typing import IntoDataFrame
79
810logger = logging .getLogger (__name__ )
@@ -24,15 +26,35 @@ class CopyButton(pn.custom.JSComponent):
2426 value = param .Parameter (doc = """A String or DataFrame. Or a callback, Parameter or Parameterized object providing such types.""" )
2527 button = pn .custom .Child (constant = True , doc = """An optional custom Button or ButtonIcon to use.""" )
2628
29+ decimal_separator = param .Selector (
30+ default = None ,
31+ objects = [None , "." , "," ],
32+ doc = """The decimal symbol used when transforming a DataFrame. If not provided set to the decimal symbol of the client.""" ,
33+ )
34+ index = param .Boolean (default = False , doc = """Whether to include the index when copying a Pandas DataFrame.""" )
35+
2736 _DEFAULT_BUTTON = pn .widgets .ButtonIcon (description = "Copy to clip board." , icon = "copy" , active_icon = "check" , toggle_duration = 1500 )
2837 _data = param .Parameter (doc = """The value to be transferred to the clip board.""" )
2938
30- _rename = {"value" : None }
39+ _rename = {"value" : None , "index" : None }
3140 _esm = """
41+ function getDecimalSeparator(locale) {
42+ const numberWithDecimalSeparator = 1.1;
43+ return Intl.NumberFormat(locale)
44+ .formatToParts(numberWithDecimalSeparator)
45+ .find(part => part.type === 'decimal')
46+ .value;
47+ }
48+
3249 export function render({ model, el }) {
3350 const button = model.get_child("button")
3451 el.appendChild(button)
3552
53+ if (model.decimal_separator === null) {
54+ model.decimal_separator = getDecimalSeparator();
55+ console.log("set")
56+ }
57+
3658 model.on("_data", (e)=>{
3759 navigator.clipboard.writeText(model._data).then(function() { }, function(err) {
3860 console.error('Could not write to clipboard: ', err);
@@ -52,20 +74,27 @@ def _get_new_button(cls):
5274
5375 @param .depends ("button.clicks" , watch = True )
5476 def _handle_clicks (self ):
55- self ._data = self ._transform_value (self .value )
77+ self ._data = self ._transform_value (self .value , decimal_separator = self . decimal_separator )
5678
5779 @staticmethod
58- def _transform_frame (value : IntoDataFrame ) -> str :
80+ def _transform_frame (value : IntoDataFrame , index = False , decimal_separator = None ) -> str :
81+ if decimal_separator not in ["." , "," ]:
82+ decimal_separator = "."
83+
5984 if is_pandas_dataframe (value ):
60- return value .to_csv (sep = "\t " )
85+ return value .to_csv (sep = "\t " , decimal = decimal_separator , index = index )
6186 if is_polars_dataframe (value ):
87+ # Polars does not support ",": https://github.com/pola-rs/polars/issues/19963
88+ # This assumes pandas and pyarrow is installed
89+ if decimal_separator == "," :
90+ return value .to_pandas ().to_csv (sep = "\t " , decimal = decimal_separator , index = False )
6291 return value .write_csv (separator = "\t " )
6392
6493 msg = f"Value of type '{ type (value )} is not supported yet."
6594 raise ValueError (msg )
6695
6796 @classmethod
68- def _transform_value (cls , value , transform_func = None ) -> str :
97+ def _transform_value (cls , value , transform_func = None , index = False , decimal_separator = None ) -> str :
6998 if isinstance (value , param .Parameterized ):
7099 if hasattr (value , "value" ):
71100 return cls ._transform_frame (value .value )
@@ -81,7 +110,7 @@ def _transform_value(cls, value, transform_func=None) -> str:
81110 if isinstance (value , str ):
82111 return value
83112 if is_into_dataframe (value ):
84- return cls ._transform_frame (value )
113+ return cls ._transform_frame (value , index = index , decimal_separator = decimal_separator )
85114
86115 msg = f"Value of type '{ type (value )} is not supported yet."
87116 raise ValueError (msg )
0 commit comments