@@ -1806,66 +1806,211 @@ def import_from_json(self, json_filename_or_object):
1806
1806
return self .fig_dict
1807
1807
1808
1808
def import_from_csv (self , filename , delimiter = "," ):
1809
+ """
1810
+ Imports a CSV or TSV file and converts its contents into a fig_dict.
1811
+ - The input file must follow a specific format, as of 6/25/25, but this may be made more general in the future.
1812
+ * Lines 1–5 define config metadata (e.g., comments, datatype, axis labels).
1813
+ * Line 6 defines series names.
1814
+ * Data rows begin on line 9.
1815
+ * The data table portion of the file can be xyxy or xyyy data.
1816
+
1817
+ Args:
1818
+ filename (str): File path to the CSV or TSV file. If no extension is provided,
1819
+ ".csv" or ".tsv" is inferred based on the delimiter.
1820
+ delimiter (str, optional): Field separator character. Defaults to ",". Use "\\ t" for TSV files.
1821
+
1822
+ Returns:
1823
+ dict: The created fig_dict.
1824
+
1825
+ """
1826
+ import os
1827
+ import math
1828
+
1829
+ # Ensure correct file extension
1830
+ file_extension = os .path .splitext (filename )[1 ]
1831
+ if delimiter == "," and not file_extension : #for no extension present.
1832
+ filename += ".csv"
1833
+ elif delimiter == "\t " and not file_extension : #for no extension present.
1834
+ filename += ".tsv"
1835
+
1836
+ with open (filename , "r" , encoding = "utf-8" ) as file :
1837
+ file_content = file .read ().strip ()
1838
+
1839
+ arr = file_content .split ("\n " ) #separate the rows.
1840
+ if len (arr [- 1 ].strip ()) < 2 :
1841
+ arr = arr [:- 1 ] # Trim empty trailing line
1842
+
1843
+ # Extract metadata
1844
+ comments = arr [0 ].split (delimiter )[0 ].split (":" )[1 ].strip ()
1845
+ datatype = arr [1 ].split (delimiter )[0 ].split (":" )[1 ].strip ()
1846
+ chart_label = arr [2 ].split (delimiter )[0 ].split (":" )[1 ].strip ()
1847
+ x_label = arr [3 ].split (delimiter )[0 ].split (":" )[1 ].strip ()
1848
+ y_label = arr [4 ].split (delimiter )[0 ].split (":" )[1 ].strip ()
1849
+ series_names_array = [
1850
+ n .strip () for n in arr [5 ].split (":" )[1 ].split ('"' )[0 ].split (delimiter )
1851
+ if n .strip ()
1852
+ ]
1853
+
1854
+ raw_data = [row .split (delimiter ) for row in arr [8 :]]
1855
+ column_count = len (raw_data [0 ])
1856
+
1857
+ # Format detection
1858
+ series_columns_format = "xyyy" # assume xyyy as default
1859
+ if column_count >= 4 :
1860
+ last_row = raw_data [- 1 ]
1861
+ for i in range (1 , column_count , 2 ):
1862
+ # Get last row, with failsafe that handles rows that may
1863
+ # have missing delimiters or fewer columns than expected
1864
+ val = last_row [i ] if i < len (last_row ) else ""
1865
+ try :
1866
+ num = float (val )
1867
+ if math .isnan (num ):
1868
+ series_columns_format = "xyxy"
1869
+ break
1870
+ except (ValueError , TypeError ):
1871
+ series_columns_format = "xyxy"
1872
+ break
1873
+
1874
+ # Prepare fig_dict
1875
+ self .fig_dict ["comments" ] = comments
1876
+ self .fig_dict ["datatype" ] = datatype
1877
+ self .fig_dict ["layout" ]["title" ] = {"text" : chart_label }
1878
+ self .fig_dict ["layout" ]["xaxis" ]["title" ] = {"text" : x_label }
1879
+ self .fig_dict ["layout" ]["yaxis" ]["title" ] = {"text" : y_label }
1880
+
1881
+ #Create the series data sets.
1882
+ new_data = []
1883
+
1884
+ if series_columns_format == "xyyy" :
1885
+ parsed_data = [[float (val ) if val .strip () else None for val in row ] for row in raw_data ]
1886
+ for i in range (1 , column_count ):
1887
+ x_series = [row [0 ] for row in parsed_data if row [0 ] is not None ]
1888
+ y_series = [row [i ] for row in parsed_data if row [i ] is not None ]
1889
+ data_series_dict = {
1890
+ "name" : series_names_array [i - 1 ] if i - 1 < len (series_names_array ) else f"Series { i } " ,
1891
+ "x" : x_series ,
1892
+ "y" : y_series ,
1893
+ "uid" : str (i - 1 )
1894
+ }
1895
+ new_data .append (data_series_dict )
1896
+ else : # xyxy format
1897
+ for i in range (0 , column_count , 2 ):
1898
+ x_vals = []
1899
+ y_vals = []
1900
+ for row in raw_data :
1901
+ try :
1902
+ x = float (row [i ])
1903
+ y = float (row [i + 1 ])
1904
+ x_vals .append (x )
1905
+ y_vals .append (y )
1906
+ except (ValueError , IndexError ):
1907
+ continue
1908
+ series_number = i // 2
1909
+ data_series_dict = {
1910
+ "name" : series_names_array [series_number ] if series_number < len (series_names_array ) else f"Series { series_number + 1 } " ,
1911
+ "x" : x_vals ,
1912
+ "y" : y_vals ,
1913
+ "uid" : str (series_number )
1914
+ }
1915
+ new_data .append (data_series_dict )
1916
+ self .fig_dict ["data" ] = new_data
1917
+ return self .fig_dict
1918
+
1919
+ def export_to_csv (self , filename = None , delimiter = "," ,
1920
+ update_and_validate = True , validate = True ,
1921
+ simulate_all_series = True , remove_simulate_fields = False ,
1922
+ remove_equation_fields = False , remove_remaining_hints = False ):
1809
1923
"""
1810
- Imports a CSV or TSV file and converts its contents into a fig_dict.
1811
- - The input file must follow a specific format, as of 6/25/25, but this may be made more general in the future.
1812
- * Lines 1–5 define config metadata (e.g., comments, datatype, axis labels).
1813
- * Line 6 defines series names.
1814
- * Data rows begin on line 9.
1924
+ Serializes fig_dict into a CSV file with optional preprocessing.
1925
+ Returns the modified fig_dict like export_to_json_file.
1815
1926
1816
1927
Args:
1817
- filename (str): File path to the CSV or TSV file. If no extension is provided,
1818
- ".csv" or ".tsv" is inferred based on the delimiter.
1819
- delimiter (str, optional): Field separator character. Defaults to ",". Use "\\ t" for TSV files.
1928
+ filename (str, optional): Destination filename. Appends '.csv' if missing.
1929
+ delimiter (str): Field separator. Defaults to ','.
1930
+ update_and_validate (bool): Apply corrections before validation.
1931
+ validate (bool): Perform validation without corrections.
1932
+ simulate_all_series (bool): Simulate any series with simulate fields.
1933
+ remove_simulate_fields (bool): Remove 'simulate' fields if True.
1934
+ remove_equation_fields (bool): Remove 'equation' fields if True.
1935
+ remove_remaining_hints (bool): Remove developer hints if True.
1820
1936
1821
1937
Returns:
1822
- dict: The created fig_dict.
1823
-
1824
- """
1825
- import os
1826
- # Modify the filename based on the delimiter and existing extension
1827
- file_extension = os .path .splitext (filename )[1 ]
1828
- if delimiter == "," and not file_extension : # No extension present
1829
- filename += ".csv"
1830
- elif delimiter == "\t " and not file_extension : # No extension present
1831
- filename += ".tsv"
1832
- with open (filename , "r" , encoding = "utf-8" ) as file :
1833
- file_content = file .read ().strip ()
1834
- # Separate rows
1835
- arr = file_content .split ("\n " )
1836
- # Count number of columns
1837
- number_of_columns = len (arr [5 ].split (delimiter ))
1838
- # Extract config information
1839
- comments = arr [0 ].split (delimiter )[0 ].split (":" )[1 ].strip ()
1840
- datatype = arr [1 ].split (delimiter )[0 ].split (":" )[1 ].strip ()
1841
- chart_label = arr [2 ].split (delimiter )[0 ].split (":" )[1 ].strip ()
1842
- x_label = arr [3 ].split (delimiter )[0 ].split (":" )[1 ].strip ()
1843
- y_label = arr [4 ].split (delimiter )[0 ].split (":" )[1 ].strip ()
1844
- # Extract series names
1845
- series_names_array = [
1846
- n .strip ()
1847
- for n in arr [5 ].split (":" )[1 ].split ('"' )[0 ].split (delimiter )
1848
- if n .strip ()
1938
+ dict: The fig_dict after processing.
1939
+ """
1940
+ import copy
1941
+ original_fig_dict = copy .deepcopy (self .fig_dict )
1942
+
1943
+ if simulate_all_series :
1944
+ self .fig_dict = simulate_as_needed_in_fig_dict (self .fig_dict )
1945
+ if remove_simulate_fields :
1946
+ self .fig_dict = clean_json_fig_dict (self .fig_dict , fields_to_update = ['simulate' ])
1947
+ if remove_equation_fields :
1948
+ self .fig_dict = clean_json_fig_dict (self .fig_dict , fields_to_update = ['equation' ])
1949
+ if remove_remaining_hints :
1950
+ self .remove_hints ()
1951
+ if update_and_validate :
1952
+ self .update_and_validate_JSONGrapher_record ()
1953
+ elif validate :
1954
+ self .validate_JSONGrapher_record ()
1955
+
1956
+ fig_dict = self .fig_dict
1957
+ comments = fig_dict .get ("comments" , "" )
1958
+ datatype = fig_dict .get ("datatype" , "" )
1959
+ graph_title = fig_dict .get ("layout" , {}).get ("title" , {}).get ("text" , "" )
1960
+ x_label = fig_dict .get ("layout" , {}).get ("xaxis" , {}).get ("title" , {}).get ("text" , "" )
1961
+ y_label = fig_dict .get ("layout" , {}).get ("yaxis" , {}).get ("title" , {}).get ("text" , "" )
1962
+ data_sets = fig_dict .get ("data" , [])
1963
+
1964
+ series_names = delimiter .join (ds .get ("name" , "" ) for ds in data_sets )
1965
+
1966
+ lines = [
1967
+ f"comments: { comments } " ,
1968
+ f"datatype: { datatype } " ,
1969
+ f"graph_title: { graph_title } " ,
1970
+ f"x_label: { x_label } " ,
1971
+ f"y_label: { y_label } " ,
1972
+ f"series_names:{ delimiter } { series_names } "
1849
1973
]
1850
- # Extract data
1851
- data = [[float (str_val ) for str_val in row .split (delimiter )] for row in arr [8 :]]
1852
- self .fig_dict ["comments" ] = comments
1853
- self .fig_dict ["datatype" ] = datatype
1854
- self .fig_dict ["layout" ]["title" ] = {"text" : chart_label }
1855
- self .fig_dict ["layout" ]["xaxis" ]["title" ] = {"text" : x_label }
1856
- self .fig_dict ["layout" ]["yaxis" ]["title" ] = {"text" : y_label }
1857
- # Create series datasets
1858
- new_data = []
1859
- for index , series_name in enumerate (series_names_array ):
1860
- data_series_dict = {}
1861
- data_series_dict ["name" ] = series_name
1862
- data_series_dict ["x" ] = [row [0 ] for row in data ]
1863
- data_series_dict ["y" ] = [row [index + 1 ] for row in data ]
1864
- data_series_dict ["uid" ] = str (index )
1865
- new_data .append (data_series_dict )
1866
- self .fig_dict ["data" ] = new_data
1867
- self .fig_dict = self .fig_dict
1868
- return self .fig_dict
1974
+
1975
+ all_x = [ds .get ("x" , []) for ds in data_sets ]
1976
+ is_xyyy = bool (all_x ) and all (x == all_x [0 ] for x in all_x )
1977
+
1978
+ if is_xyyy :
1979
+ y_headers = [f"y_{ i + 1 } " for i in range (len (data_sets ))]
1980
+ lines .append (delimiter .join (["x_values" ] + y_headers ))
1981
+ for i in range (len (all_x [0 ])):
1982
+ row = [str (all_x [0 ][i ])]
1983
+ for ds in data_sets :
1984
+ y_vals = ds .get ("y" , [])
1985
+ row .append (str (y_vals [i ]) if i < len (y_vals ) else "" )
1986
+ lines .append (delimiter .join (row ))
1987
+ else :
1988
+ headers = [f"x_{ i + 1 } { delimiter } y_{ i + 1 } " for i in range (len (data_sets ))]
1989
+ lines .append (delimiter .join (headers ))
1990
+ max_len = max ((len (ds .get ("x" , [])) for ds in data_sets ), default = 0 )
1991
+ for i in range (max_len ):
1992
+ row_cells = []
1993
+ for ds in data_sets :
1994
+ x_vals = ds .get ("x" , [])
1995
+ y_vals = ds .get ("y" , [])
1996
+ xv = str (x_vals [i ]) if i < len (x_vals ) else ""
1997
+ yv = str (y_vals [i ]) if i < len (y_vals ) else ""
1998
+ row_cells .extend ([xv , yv ])
1999
+ lines .append (delimiter .join (row_cells ))
2000
+
2001
+ csv_string = "\r \n " .join (lines ) + "\r \n "
2002
+ out_filename = filename if filename else "mergedJSONGrapherRecord.csv"
2003
+
2004
+ if len (out_filename ) > 0 :
2005
+ if '.csv' not in out_filename .lower ():
2006
+ out_filename += ".csv"
2007
+ with open (out_filename , 'w' , encoding = 'utf-8' ) as f :
2008
+ f .write (csv_string )
2009
+
2010
+ modified_fig_dict = self .fig_dict
2011
+ self .fig_dict = original_fig_dict
2012
+ return modified_fig_dict
2013
+
1869
2014
1870
2015
def set_datatype (self , datatype ):
1871
2016
"""
0 commit comments