Skip to content

Commit ab39bb3

Browse files
authored
Merge pull request #72 from AdityaSavara/offset2d
export_to_csv added
2 parents 65c304d + 0d46d38 commit ab39bb3

File tree

3 files changed

+260
-487
lines changed

3 files changed

+260
-487
lines changed

JSONGrapher/JSONRecordCreator.py

Lines changed: 199 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1806,66 +1806,211 @@ def import_from_json(self, json_filename_or_object):
18061806
return self.fig_dict
18071807

18081808
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):
18091923
"""
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.
18151926
18161927
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.
18201936
18211937
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}"
18491973
]
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+
18692014

18702015
def set_datatype(self, datatype):
18712016
"""

JSONGrapher/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '4.6'
1+
__version__ = '4.7'

0 commit comments

Comments
 (0)