jsongrapher 3.8__py3-none-any.whl → 4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  import JSONGrapher.styles.layout_styles_library
3
3
  import JSONGrapher.styles.trace_styles_collection_library
4
+ import JSONGrapher.version
4
5
  #TODO: put an option to suppress warnings from JSONRecordCreator
5
6
 
6
7
 
@@ -607,6 +608,7 @@ class JSONGrapherRecord:
607
608
 
608
609
  self.fig_dict.update( {
609
610
  "comments": comments, # Top-level comments
611
+ "jsongrapher": "To plot this file, go to www.jsongrapher.com and drag this file into your browser, or use the python version of JSONGrapher. File created with python Version " + JSONGrapher.version.__version__,
610
612
  "datatype": datatype, # Top-level datatype (datatype)
611
613
  "data": data_objects_list if data_objects_list else [], # Data series list
612
614
  "layout": layout if layout else {
@@ -831,7 +833,8 @@ class JSONGrapherRecord:
831
833
  def evaluate_eqution_of_data_series_by_index(self, series_index, equation_dict = None, verbose=False):
832
834
  if equation_dict != None:
833
835
  self.fig_dict["data"][series_index]["equation"] = equation_dict
834
- self.fig_dict = evaluate_equation_for_data_series_by_index(data_series_index=data_series_dict, verbose=verbose) #implied return.
836
+ data_series_dict = self.fig_dict["data"][series_index]
837
+ self.fig_dict = evaluate_equation_for_data_series_by_index(fig_dict=self.fig_dict, data_series_index=data_series_dict, verbose=verbose) #implied return.
835
838
  return data_series_dict #Extra regular return
836
839
 
837
840
  #this function returns the current record.
@@ -842,11 +845,11 @@ class JSONGrapherRecord:
842
845
  return self.fig_dict
843
846
  #The update_and_validate function will clean for plotly.
844
847
  #TODO: the internal recommending "print_to_inspect" function should, by default, exclude printing the full dictionaries of the layout_style and the trace_collection_style.
845
- def print_to_inspect(self, update_and_validate=True, validate=True, remove_remaining_hints=False):
848
+ def print_to_inspect(self, update_and_validate=True, validate=True, clean_for_plotly = True, remove_remaining_hints=False):
846
849
  if remove_remaining_hints == True:
847
850
  self.remove_hints()
848
851
  if update_and_validate == True: #this will do some automatic 'corrections' during the validation.
849
- self.update_and_validate_JSONGrapher_record()
852
+ self.update_and_validate_JSONGrapher_record(clean_for_plotly=clean_for_plotly)
850
853
  elif validate: #this will validate without doing automatic updates.
851
854
  self.validate_JSONGrapher_record()
852
855
  print(json.dumps(self.fig_dict, indent=4))
@@ -896,14 +899,39 @@ class JSONGrapherRecord:
896
899
  #now, add the scaled data objects to the original one.
897
900
  #This is fairly easy using a list extend.
898
901
  self.fig_dict["data"].extend(scaled_fig_dict["data"])
899
-
900
-
901
-
902
+
902
903
  def import_from_dict(self, fig_dict):
903
904
  self.fig_dict = fig_dict
904
905
 
905
- def import_from_file(self, json_filename_or_object):
906
- self.import_from_json(json_filename_or_object)
906
+ def import_from_file(self, record_filename_or_object):
907
+ """
908
+ Determine the type of file or data and call the appropriate import function.
909
+
910
+ Args:
911
+ record_filename_or_object (str or dict): Filename of the CSV/TSV/JSON file or a dictionary object.
912
+
913
+ Returns:
914
+ dict: Processed JSON data.
915
+ """
916
+ import os # Moved inside the function
917
+
918
+ # If the input is a dictionary, process it as JSON
919
+ if isinstance(record_filename_or_object, dict):
920
+ result = self.import_from_json(record_filename_or_object)
921
+ else:
922
+ # Determine file extension
923
+ file_extension = os.path.splitext(record_filename_or_object)[1].lower()
924
+
925
+ if file_extension == ".csv":
926
+ result = self.import_from_csv(record_filename_or_object, delimiter=",")
927
+ elif file_extension == ".tsv":
928
+ result = self.import_from_csv(record_filename_or_object, delimiter="\t")
929
+ elif file_extension == ".json":
930
+ result = self.import_from_json(record_filename_or_object)
931
+ else:
932
+ raise ValueError("Unsupported file type. Please provide a CSV, TSV, or JSON file.")
933
+
934
+ return result
907
935
 
908
936
  #the json object can be a filename string or can be json object which is actually a dictionary.
909
937
  def import_from_json(self, json_filename_or_object):
@@ -929,6 +957,63 @@ class JSONGrapherRecord:
929
957
  self.fig_dict = json_filename_or_object
930
958
  return self.fig_dict
931
959
 
960
+ def import_from_csv(self, filename, delimiter=","):
961
+ """
962
+ Convert CSV file content into a JSON structure for Plotly.
963
+
964
+ Args:
965
+ filename (str): Path to the CSV file.
966
+ delimiter (str, optional): Delimiter used in CSV. Default is ",".
967
+ Use "\\t" for a tab-delimited file.
968
+
969
+ Returns:
970
+ dict: JSON representation of the CSV data.
971
+ """
972
+ import os
973
+ # Modify the filename based on the delimiter and existing extension
974
+ file_extension = os.path.splitext(filename)[1]
975
+ if delimiter == "," and not file_extension: # No extension present
976
+ filename += ".csv"
977
+ elif delimiter == "\t" and not file_extension: # No extension present
978
+ filename += ".tsv"
979
+ with open(filename, "r", encoding="utf-8") as file:
980
+ file_content = file.read().strip()
981
+ # Separate rows
982
+ arr = file_content.split("\n")
983
+ # Count number of columns
984
+ number_of_columns = len(arr[5].split(delimiter))
985
+ # Extract config information
986
+ comments = arr[0].split(delimiter)[0].split(":")[1].strip()
987
+ datatype = arr[1].split(delimiter)[0].split(":")[1].strip()
988
+ chart_label = arr[2].split(delimiter)[0].split(":")[1].strip()
989
+ x_label = arr[3].split(delimiter)[0].split(":")[1].strip()
990
+ y_label = arr[4].split(delimiter)[0].split(":")[1].strip()
991
+ # Extract series names
992
+ series_names_array = [
993
+ n.strip()
994
+ for n in arr[5].split(":")[1].split('"')[0].split(delimiter)
995
+ if n.strip()
996
+ ]
997
+ # Extract data
998
+ data = [[float(str_val) for str_val in row.split(delimiter)] for row in arr[8:]]
999
+ self.fig_dict["comments"] = comments
1000
+ self.fig_dict["datatype"] = datatype
1001
+ self.fig_dict["layout"]["title"] = {"text": chart_label}
1002
+ self.fig_dict["layout"]["xaxis"]["title"] = {"text": x_label}
1003
+ self.fig_dict["layout"]["yaxis"]["title"] = {"text": y_label}
1004
+ # Create series datasets
1005
+ new_data = []
1006
+ for index, series_name in enumerate(series_names_array):
1007
+ data_series_dict = {}
1008
+ data_series_dict["name"] = series_name
1009
+ data_series_dict["x"] = [row[0] for row in data]
1010
+ data_series_dict["y"] = [row[index + 1] for row in data]
1011
+ data_series_dict["uid"] = str(index)
1012
+ new_data.append(data_series_dict)
1013
+ self.fig_dict["data"] = new_data
1014
+ self.fig_dict = self.fig_dict
1015
+ return self.fig_dict
1016
+
932
1017
  def set_datatype(self, datatype):
933
1018
  """
934
1019
  Sets the datatype field used as the experiment type or schema identifier.
@@ -1047,7 +1132,7 @@ class JSONGrapherRecord:
1047
1132
  elif validate: #this will validate without doing automatic updates.
1048
1133
  self.validate_JSONGrapher_record()
1049
1134
 
1050
- # filepath: Optional, filename with path to save the JSON file.
1135
+ # filename with path to save the JSON file.
1051
1136
  if len(filename) > 0: #this means we will be writing to file.
1052
1137
  # Check if the filename has an extension and append `.json` if not
1053
1138
  if '.json' not in filename.lower():
@@ -1057,6 +1142,18 @@ class JSONGrapherRecord:
1057
1142
  json.dump(self.fig_dict, f, indent=4)
1058
1143
  return self.fig_dict
1059
1144
 
1145
+ def export_plotly_json(self, filename, plot_style = None, update_and_validate=True, simulate_all_series=True, evaluate_all_equations=True,adjust_implicit_data_ranges=True):
1146
+ fig = self.get_plotly_fig(plot_style=plot_style, update_and_validate=update_and_validate, simulate_all_series=simulate_all_series, evaluate_all_equations=evaluate_all_equations, adjust_implicit_data_ranges=adjust_implicit_data_ranges)
1147
+ plotly_json_string = fig.to_plotly_json()
1148
+ if len(filename) > 0: #this means we will be writing to file.
1149
+ # Check if the filename has an extension and append `.json` if not
1150
+ if '.json' not in filename.lower():
1151
+ filename += ".json"
1152
+ #Write to file using UTF-8 encoding.
1153
+ with open(filename, 'w', encoding='utf-8') as f:
1154
+ json.dump(plotly_json_string, f, indent=4)
1155
+ return plotly_json_string
1156
+
1060
1157
  #simulate all series will simulate any series as needed.
1061
1158
  def get_plotly_fig(self, plot_style=None, update_and_validate=True, simulate_all_series=True, evaluate_all_equations=True, adjust_implicit_data_ranges=True):
1062
1159
  """
@@ -1092,8 +1189,8 @@ class JSONGrapherRecord:
1092
1189
  self.apply_plot_style(plot_style=plot_style)
1093
1190
  #Now we clean out the fields and make a plotly object.
1094
1191
  if update_and_validate == True: #this will do some automatic 'corrections' during the validation.
1095
- self.update_and_validate_JSONGrapher_record() #this is the line that cleans "self.fig_dict"
1096
- self.fig_dict = clean_json_fig_dict(self.fig_dict, fields_to_update=['simulate', 'custom_units_chevrons', 'equation', 'trace_style', '3d_axes', 'bubble'])
1192
+ self.update_and_validate_JSONGrapher_record(clean_for_plotly=False) #We use the False argument here because the cleaning will be on the next line with beyond default arguments.
1193
+ self.fig_dict = clean_json_fig_dict(self.fig_dict, fields_to_update=['simulate', 'custom_units_chevrons', 'equation', 'trace_style', '3d_axes', 'bubble', 'superscripts'])
1097
1194
  fig = pio.from_json(json.dumps(self.fig_dict))
1098
1195
  #restore the original fig_dict.
1099
1196
  self.fig_dict = original_fig_dict
@@ -1390,8 +1487,8 @@ class JSONGrapherRecord:
1390
1487
  #Make some pointers to external functions, for convenience, so people can use syntax like record.function_name() if desired.
1391
1488
  def validate_JSONGrapher_record(self):
1392
1489
  validate_JSONGrapher_record(self)
1393
- def update_and_validate_JSONGrapher_record(self):
1394
- update_and_validate_JSONGrapher_record(self)
1490
+ def update_and_validate_JSONGrapher_record(self, clean_for_plotly=True):
1491
+ update_and_validate_JSONGrapher_record(self, clean_for_plotly=clean_for_plotly)
1395
1492
 
1396
1493
 
1397
1494
  # helper function to validate x axis and y axis labels.
@@ -2040,14 +2137,14 @@ def apply_trace_style_to_single_data_series(data_series, trace_styles_collection
2040
2137
  data_series["trace_style"] = trace_style_to_apply
2041
2138
  elif str(trace_style_to_apply) == str(''): #If we received an empty string for the trace_style_to apply (default JSONGrapher flow), we'll check in the data_series object.
2042
2139
  #first see if there is a trace_style in the data_series.
2043
- trace_style = data_series.get("trace_style", "")
2140
+ trace_style_to_apply = data_series.get("trace_style", "")
2044
2141
  #If it's "none", then we'll return the data series unchanged.
2045
2142
  #We consider it that for every trace_styles_collection, that "none" means to make no change.
2046
- if str(trace_style).lower() == "none":
2143
+ if str(trace_style_to_apply).lower() == "none":
2047
2144
  return data_series
2048
2145
  #if we find a dictionary, we will set the trace_style_to_apply to that, to ensure we skip other string checks to use the dictionary.
2049
- if isinstance(trace_style,dict):
2050
- trace_style_to_apply = trace_style
2146
+ if isinstance(trace_style_to_apply,dict):
2147
+ trace_style_to_apply = trace_style_to_apply
2051
2148
  #if the trace_style_to_apply is a string and we have not received a trace_styles collection, then we have nothing
2052
2149
  #to use, so will return the data_series unchanged.
2053
2150
  if type(trace_style_to_apply) == type("string"):
@@ -2060,6 +2157,16 @@ def apply_trace_style_to_single_data_series(data_series, trace_styles_collection
2060
2157
  if type(trace_style_to_apply) == type("string"):
2061
2158
  if (trace_style_to_apply.lower() == "nature") or (trace_style_to_apply.lower() == "science"):
2062
2159
  trace_style_to_apply = "default"
2160
+ #Because the 3D traces will not plot correctly unless recognized,
2161
+ #we have a hardcoded case for the situation that 3D dataset is received without plot style.
2162
+ if trace_styles_collection == "default":
2163
+ if trace_style_to_apply == "":
2164
+ if data_series.get("z", '') != '':
2165
+ trace_style_to_apply = "scatter3d"
2166
+ uid = data_series.get('uid', '')
2167
+ name = data_series.get("name", '')
2168
+ print("Warning: A dataseries was found with no trace_style but with a 'z' field. " , "uid: " , uid , " . " + "name:", name , " . The trace style for this dataseries is being set to scatter3d.")
2169
+
2063
2170
 
2064
2171
  #at this stage, should remove any existing formatting before applying new formatting.
2065
2172
  data_series = remove_trace_style_from_single_data_series(data_series)
@@ -2099,12 +2206,12 @@ def apply_trace_style_to_single_data_series(data_series, trace_styles_collection
2099
2206
  trace_style = data_series.get("trace_style", "")
2100
2207
  else:
2101
2208
  trace_style = trace_style_to_apply
2102
-
2103
2209
  if trace_style == "": #if the trace style is an empty string....
2104
2210
  trace_style = list(styles_collection_dict.keys())[0] #take the first trace_style name in the style_dict. In python 3.7 and later dictionary keys preserve ordering.
2105
2211
 
2106
2212
  #If a person adds "__colorscale" to the end of a trace_style, like "scatter_spline__rainbow" we will extract the colorscale and apply it to the plot.
2107
2213
  #This should be done before extracting the trace_style from the styles_available, because we need to split the string to break out the trace_style
2214
+ #Also should be initialized before determining the second half of colorscale_structure checks (which occurs after the trace_style application), since it affects that logic.
2108
2215
  colorscale = "" #initializing variable.
2109
2216
  if isinstance(trace_style, str): #check if it is a string type.
2110
2217
  if "__" in trace_style:
@@ -2928,24 +3035,61 @@ def get_fig_dict_ranges(fig_dict, skip_equations=False, skip_simulations=False):
2928
3035
 
2929
3036
  ### Start section of code with functions for cleaning fig_dicts for plotly compatibility ###
2930
3037
 
2931
- def update_title_field(fig_dict, depth=1, max_depth=10):
3038
+ def update_title_field(fig_dict_or_subdict, depth=1, max_depth=10):
2932
3039
  """ This function is intended to make JSONGrapher .json files compatible with the newer plotly recommended title field formatting
2933
3040
  which is necessary to do things like change the font, and also necessary for being able to convert a JSONGrapher json_dict to python plotly figure objects.
2934
3041
  Recursively checks for 'title' fields and converts them to dictionary format. """
2935
- if depth > max_depth or not isinstance(fig_dict, dict):
2936
- return fig_dict
3042
+ if depth > max_depth or not isinstance(fig_dict_or_subdict, dict):
3043
+ return fig_dict_or_subdict
2937
3044
 
2938
- for key, value in fig_dict.items():
2939
- if key == "title" and isinstance(value, str):
2940
- fig_dict[key] = {"text": value}
3045
+ for key, value in fig_dict_or_subdict.items():
3046
+ if key == "title" and isinstance(value, str): #This is for axes labels.
3047
+ fig_dict_or_subdict[key] = {"text": value}
2941
3048
  elif isinstance(value, dict): # Nested dictionary
2942
- fig_dict[key] = update_title_field(value, depth + 1, max_depth)
3049
+ fig_dict_or_subdict[key] = update_title_field(value, depth + 1, max_depth)
2943
3050
  elif isinstance(value, list): # Lists can contain nested dictionaries
2944
- fig_dict[key] = [update_title_field(item, depth + 1, max_depth) if isinstance(item, dict) else item for item in value]
2945
- return fig_dict
3051
+ fig_dict_or_subdict[key] = [update_title_field(item, depth + 1, max_depth) if isinstance(item, dict) else item for item in value]
3052
+ return fig_dict_or_subdict
3053
+
3054
+
2946
3055
 
2947
3056
 
3057
+ def update_superscripts_strings(fig_dict_or_subdict, depth=1, max_depth=10):
3058
+ """ This function is intended to make JSONGrapher .json files compatible with the newer plotly recommended title field formatting
3059
+ which is necessary to do things like change the font, and also necessary for being able to convert a JSONGrapher json_dict to python plotly figure objects.
3060
+ Recursively checks for 'title' fields and converts them to dictionary format. """
3061
+ if depth > max_depth or not isinstance(fig_dict_or_subdict, dict):
3062
+ return fig_dict_or_subdict
3063
+
3064
+ for key, value in fig_dict_or_subdict.items():
3065
+ if key == "title": #This is for axes labels and graph title.
3066
+ if "text" in fig_dict_or_subdict[key]:
3067
+ fig_dict_or_subdict[key]["text"] = replace_superscripts(fig_dict_or_subdict[key]["text"])
3068
+ if key == "data": #This is for the legend.
3069
+ for data_dict in fig_dict_or_subdict[key]:
3070
+ if "name" in data_dict:
3071
+ data_dict["name"] = replace_superscripts(data_dict["name"])
3072
+ elif isinstance(value, dict): # Nested dictionary
3073
+ fig_dict_or_subdict[key] = update_superscripts_strings(value, depth + 1, max_depth)
3074
+ elif isinstance(value, list): # Lists can contain nested dictionaries
3075
+ fig_dict_or_subdict[key] = [update_superscripts_strings(item, depth + 1, max_depth) if isinstance(item, dict) else item for item in value]
3076
+ return fig_dict_or_subdict
2948
3077
 
3078
+ #The below function was made with the help of copilot.
3079
+ def replace_superscripts(input_string):
3080
+ #Example usage: print(replace_superscripts("x^(2) + y**(-3) = z^(test)"))
3081
+ import re
3082
+ # Step 1: Wrap superscript expressions in <sup> tags
3083
+ output_string = re.sub(r'\^\((.*?)\)|\*\*\((.*?)\)',
3084
+ lambda m: f"<sup>{m.group(1) or m.group(2)}</sup>",
3085
+ input_string)
3086
+ # Step 2: Remove parentheses if the content is only digits
3087
+ output_string = re.sub(r'<sup>\((\d+)\)</sup>', r'<sup>\1</sup>', output_string)
3088
+ # Step 3: Remove parentheses if the content is a negative number (- followed by digits)
3089
+ # Step 4: Remove parentheses if the superscript is a single letter
3090
+ output_string = re.sub(r'<sup>\((\w)\)</sup>', r'<sup>\1</sup>', output_string)
3091
+ output_string = re.sub(r'<sup>\(-(\d+)\)</sup>', r'<sup>-\1</sup>', output_string)
3092
+ return output_string
2949
3093
 
2950
3094
 
2951
3095
  def convert_to_3d_layout(layout):
@@ -3089,6 +3233,7 @@ def clean_json_fig_dict(json_fig_dict, fields_to_update=None):
3089
3233
  because one would not want to do that by mistake before simulation is performed.
3090
3234
  This function can also remove the 'equation' field from data series. However, that is not the default behavior
3091
3235
  because one would not want to do that by mistake before the equation is evaluated.
3236
+ The "superscripts" option is not normally used until right before plotting because that will affect unit conversions.
3092
3237
  """
3093
3238
  if fields_to_update is None: # should not initialize mutable objects in arguments line, so doing here.
3094
3239
  fields_to_update = ["title_field", "extraInformation", "nested_comments"]
@@ -3112,6 +3257,8 @@ def clean_json_fig_dict(json_fig_dict, fields_to_update=None):
3112
3257
  fig_dict = remove_trace_style_field(fig_dict)
3113
3258
  if "3d_axes" in fields_to_update: #This is for 3D plots
3114
3259
  fig_dict = update_3d_axes(fig_dict)
3260
+ if "superscripts" in fields_to_update:
3261
+ fig_dict = update_superscripts_strings(fig_dict)
3115
3262
 
3116
3263
  return fig_dict
3117
3264
 
JSONGrapher/__init__.py CHANGED
@@ -1,3 +1,4 @@
1
1
  #Try to do things a bit like https://github.com/python/cpython/blob/master/Lib/collections/__init__.py
2
2
  #Except instead of putting things here directly in __init__, we'll import them so they are accessible by importing the module.
3
3
  from JSONGrapher.JSONRecordCreator import *
4
+ from JSONGrapher.version import *
@@ -446,6 +446,8 @@ def split_at_first_delimiter(string, delimter=" "):
446
446
  #I would still expect the optimzed code to be an order of magnitude faster. So it may be worth finding the slow steps.
447
447
  #One possibility might be to use "re.compile()"
448
448
  def evaluate_equation_dict(equation_dict, verbose=False):
449
+ import copy
450
+ equation_dict = copy.deepcopy(equation_dict) # Create a deep copy to prevent unintended modifications
449
451
  #First a block of code to extract the x_points needed
450
452
  # Extract each dictionary key as a local variable
451
453
  equation_string = equation_dict['equation_string']
@@ -479,21 +481,28 @@ def evaluate_equation_dict(equation_dict, verbose=False):
479
481
 
480
482
  #Start of block to check for any custom units and add them to the ureg if necessary.
481
483
  custom_units_list = []
484
+ #helper function to clean custom units brackets. In future, could be made more general rather than hardcoded as angle brackets.
485
+ def clean_brackets(string):
486
+ return string.replace("<", "").replace(">", "")
487
+
482
488
  for constant_entry_key in independent_variables_dict.keys():
483
489
  independent_variables_string = independent_variables_dict[constant_entry_key]
484
490
  custom_units_extracted = extract_tagged_strings(independent_variables_string)
491
+ independent_variables_dict[constant_entry_key] = clean_brackets(independent_variables_dict[constant_entry_key])
485
492
  for custom_unit in custom_units_extracted: #this will be skipped if the list is empty.
486
493
  ureg.define(f"{custom_unit} = [custom]") #use "[custom]" to create a custom unit in the pint module.
487
494
  custom_units_list.extend(custom_units_extracted)
488
495
 
489
496
  #now also check for the x_variable_extracted_dict
490
497
  custom_units_extracted = extract_tagged_strings(x_variable_extracted_dict["units"])
498
+ x_variable_extracted_dict["units"] = clean_brackets(x_variable_extracted_dict["units"])
491
499
  for custom_unit in custom_units_extracted: #this will be skipped if the list is empty.
492
500
  ureg.define(f"{custom_unit} = [custom]") #use "[custom]" to create a custom unit in the pint module.
493
501
  custom_units_list.extend(custom_units_extracted)
494
502
 
495
503
  #now also check for the y_variable_extracted_dict (technically not needed)
496
504
  custom_units_extracted = extract_tagged_strings(y_variable_extracted_dict["units"])
505
+ y_variable_extracted_dict["units"] = clean_brackets(y_variable_extracted_dict["units"])
497
506
  for custom_unit in custom_units_extracted: #this will be skipped if the list is empty.
498
507
  ureg.define(f"{custom_unit} = [custom]") #use "[custom]" to create a custom unit in the pint module.
499
508
  custom_units_list.extend(custom_units_extracted)
@@ -501,18 +510,24 @@ def evaluate_equation_dict(equation_dict, verbose=False):
501
510
  if graphical_dimensionality == 3:
502
511
  #now also check for the z_variable_extracted_dict (technically not needed)
503
512
  custom_units_extracted = extract_tagged_strings(z_variable_extracted_dict["units"])
513
+ z_variable_extracted_dict["units"] = clean_brackets(z_variable_extracted_dict["units"])
504
514
  for custom_unit in custom_units_extracted: #this will be skipped if the list is empty.
505
515
  ureg.define(f"{custom_unit} = [custom]") #use "[custom]" to create a custom unit in the pint module.
506
516
  custom_units_list.extend(custom_units_extracted)
507
517
 
508
518
  #now also check for the equation_string
509
519
  custom_units_extracted = extract_tagged_strings(equation_string)
520
+ equation_string = clean_brackets(equation_string)
510
521
  for custom_unit in custom_units_extracted: #this will be skipped if the list is empty.
511
522
  ureg.define(f"{custom_unit} = [custom]") #use "[custom]" to create a custom unit in the pint module.
512
523
  custom_units_list.extend(custom_units_extracted)
513
524
 
525
+ # Remove duplicates by converting to a set and back to a list.
526
+ custom_units_list = list(set(custom_units_list))
514
527
  #now sort from longest to shortest, since we will have to put them back in that way later.
528
+ # Sort the unique units by length in descending order
515
529
  custom_units_list = sorted(custom_units_list, key=len, reverse=True)
530
+
516
531
  #End of block to check for any custom units and add them to the ureg if necessary.
517
532
 
518
533
  #For graphical_dimensionality of 2, The full list of independent variables includes the x_variable and the independent_variables.
JSONGrapher/version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = '4.1'
@@ -1,3 +1,5 @@
1
+ BSD 3-Clause License
2
+
1
3
  Copyright 2025 Aditya Savara
2
4
 
3
5
  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
@@ -1,26 +1,28 @@
1
+ [![Anaconda-Server Badge](https://anaconda.org/conda-forge/jsongrapher/badges/version.svg)](https://anaconda.org/conda-forge/jsongrapher) [![Anaconda-Server Badge](https://badge.fury.io/py/jsongrapher.svg)](https://badge.fury.io/py/jsongrapher)
2
+
1
3
  # JSONGrapher (python)
2
- This is the python version of JSONGrapher with JSONRecordCreator. This package is for plotting JSON records with drag and drop and has tools for creating the JSON records.
3
4
 
4
- The software will automatically plot multiple records together, for comparison, and will automaticlaly converting units between records as needed to plot them for comparison (like kg/s and g/s).
5
+ Imagine a world where a person can simply drag a datafile into a graphing utility and a plot will be made, including scientific units, and more plots can be dragged in from data from other sources (and other units) to compare all of the data together, in an interactive graph.
6
+
7
+ Create interactive plots just by drag-and-drop of JSON records. Share the json files for easy plotting by others. JSONGrapher automatically plots multiple records together, for comparison, and will automaticlaly convert units between records to plot the data sets together. For example, if one record is in kg/s and another in g/s, JSONGrapher will do the conversion automatically to plot both records together for comparison. Tools and examples are included for how to create JSON records.
5
8
 
6
9
  To use python JSONGrapher, first install it using conda or pip:<br>
7
10
  `pip install JSONGrapher[COMPLETE]` or `conda install conda-forge::jsongrapher` <br>
8
11
  Alternatively, you can download the directory directly.<br>
9
12
 
10
13
  ## **0\. Plotting a JSON Record**
11
- It's as simple as one line! Then drag an [example](https://github.com/AdityaSavara/jsongrapher-py/tree/main/examples/example_1_drag_and_drop) JSONGrapher record into the window to plot!
12
- Below shows several plot types for 2D and 3D plots, followed by a simple example of how to use python JSONGrapher to create your own records.
14
+ To create an interactive plot, you just need one line of code! Then drag an [example](https://github.com/AdityaSavara/jsongrapher-py/tree/main/examples/example_1_drag_and_drop) JSONGrapher record into the window to plot!
15
+ Below are example 2D and 3D plots. Further below shows how easy it is to create your own json records.
13
16
  <pre>
14
- import JSONGrapher
15
- JSONGrapher.launch()
17
+ import JSONGrapher; JSONGrapher.launch()
16
18
  </pre>
17
19
 
18
- [![JSONGRapher window](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/JSONGrapher/JSONGrapherWindowShortened.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/JSONGrapher/JSONGrapherWindowShortened.png) [![JSONGRapher plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/UAN_DTA_image.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/UAN_DTA_image.png)
20
+ [![JSONGRapher window](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/JSONGrapher/JSONGrapherWindowShortened.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/JSONGrapher/JSONGrapherWindowShortened.png) [![JSONGRapher plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/UAN_DTA_image.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/UAN_DTA_image.png)
19
21
 
20
- [![rainbow plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/SrTiO3_rainbow_image.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/SrTiO3_rainbow_image.png)
21
- [![mesh3d plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/Rate_Constant_mesh3d.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/Rate_Constant_mesh3d.png)
22
- [![scatter3d plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/Rate_Constant_Scatter3d_example10.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/Rate_Constant_Scatter3d_example10.png)
23
- [![bubble plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/Rate_Constant_bubble.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/Rate_Constant_bubble.png)
22
+ [![rainbow plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/SrTiO3_rainbow_image.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/SrTiO3_rainbow_image.png)
23
+ [![mesh3d plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/Rate_Constant_mesh3d.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/Rate_Constant_mesh3d.png)
24
+ [![scatter3d plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/Rate_Constant_Scatter3d_example10.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/Rate_Constant_Scatter3d_example10.png)
25
+ [![bubble plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/Rate_Constant_bubble.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/Rate_Constant_bubble.png)
24
26
 
25
27
 
26
28
 
@@ -98,4 +100,15 @@ Record.plot_with_plotly() #Try hovering your mouse over points after this comman
98
100
  You can also see more examples: https://github.com/AdityaSavara/jsongrapher-py/tree/main/examples
99
101
 
100
102
  Additionally, json records you send to others can be plotted by them at www.jsongrapher.com
101
- This 'see the plot using a browser' capability is intended to facilitate including JSONGrapher records in supporting information of scientific publications.
103
+ This 'see the plot using a browser' capability is intended to facilitate including JSONGrapher records in supporting information of scientific publications.
104
+
105
+
106
+ ## **Contributing to JSONGrapher, Feature Suggestions, and Reporting Issues**
107
+
108
+ These interactions should be through github at https://github.com/AdityaSavara/jsongrapher-py
109
+
110
+ To contribute to JSONGrapher, make a pull request with sufficient details about what issue you are trying to solve, and adequate commenting in your code to follow the logic. After that, be prepared for further communication if needed.
111
+
112
+ To suggest features, create a new issue under the issues tab.
113
+
114
+ To report issues, create a new issue under the issues tab.
@@ -1,3 +1,5 @@
1
+ BSD 3-Clause License
2
+
1
3
  Copyright 2025 Aditya Savara
2
4
 
3
5
  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jsongrapher
3
- Version: 3.8
3
+ Version: 4.1
4
4
  Summary: The python version of JSONGrapher with tools for creating JSONGrapher Records.
5
5
  Home-page: https://github.com/AdityaSavara/jsongrapher-py
6
6
  Author: Aditya Savara
7
- Author-email: AditySavara2008@u.northwestern.edu
7
+ Author-email: AdityaSavara2008@u.northwestern.edu
8
8
  License: BSD-3-Clause
9
9
  Classifier: License :: OSI Approved :: BSD License
10
10
  Classifier: Programming Language :: Python
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
14
14
  Classifier: Programming Language :: Python :: Implementation :: PyPy
15
15
  Requires-Python: >=3.0.0
16
16
  Description-Content-Type: text/markdown
17
- License-File: LICENSE
17
+ License-File: LICENSE.txt
18
18
  Requires-Dist: numpy>=1.26
19
19
  Provides-Extra: complete
20
20
  Requires-Dist: matplotlib; extra == "complete"
@@ -25,29 +25,31 @@ Requires-Dist: urllib; extra == "complete"
25
25
  Requires-Dist: json-equationer; extra == "complete"
26
26
 
27
27
 
28
+ [![Anaconda-Server Badge](https://anaconda.org/conda-forge/jsongrapher/badges/version.svg)](https://anaconda.org/conda-forge/jsongrapher) [![Anaconda-Server Badge](https://badge.fury.io/py/jsongrapher.svg)](https://badge.fury.io/py/jsongrapher)
29
+
28
30
  # JSONGrapher (python)
29
- This is the python version of JSONGrapher with JSONRecordCreator. This package is for plotting JSON records with drag and drop and has tools for creating the JSON records.
30
31
 
31
- The software will automatically plot multiple records together, for comparison, and will automaticlaly converting units between records as needed to plot them for comparison (like kg/s and g/s).
32
+ Imagine a world where a person can simply drag a datafile into a graphing utility and a plot will be made, including scientific units, and more plots can be dragged in from data from other sources (and other units) to compare all of the data together, in an interactive graph.
33
+
34
+ Create interactive plots just by drag-and-drop of JSON records. Share the json files for easy plotting by others. JSONGrapher automatically plots multiple records together, for comparison, and will automaticlaly convert units between records to plot the data sets together. For example, if one record is in kg/s and another in g/s, JSONGrapher will do the conversion automatically to plot both records together for comparison. Tools and examples are included for how to create JSON records.
32
35
 
33
36
  To use python JSONGrapher, first install it using conda or pip:<br>
34
37
  `pip install JSONGrapher[COMPLETE]` or `conda install conda-forge::jsongrapher` <br>
35
38
  Alternatively, you can download the directory directly.<br>
36
39
 
37
40
  ## **0\. Plotting a JSON Record**
38
- It's as simple as one line! Then drag an [example](https://github.com/AdityaSavara/jsongrapher-py/tree/main/examples/example_1_drag_and_drop) JSONGrapher record into the window to plot!
39
- Below shows several plot types for 2D and 3D plots, followed by a simple example of how to use python JSONGrapher to create your own records.
41
+ To create an interactive plot, you just need one line of code! Then drag an [example](https://github.com/AdityaSavara/jsongrapher-py/tree/main/examples/example_1_drag_and_drop) JSONGrapher record into the window to plot!
42
+ Below are example 2D and 3D plots. Further below shows how easy it is to create your own json records.
40
43
  <pre>
41
- import JSONGrapher
42
- JSONGrapher.launch()
44
+ import JSONGrapher; JSONGrapher.launch()
43
45
  </pre>
44
46
 
45
- [![JSONGRapher window](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/JSONGrapher/JSONGrapherWindowShortened.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/JSONGrapher/JSONGrapherWindowShortened.png) [![JSONGRapher plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/UAN_DTA_image.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/UAN_DTA_image.png)
47
+ [![JSONGRapher window](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/JSONGrapher/JSONGrapherWindowShortened.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/JSONGrapher/JSONGrapherWindowShortened.png) [![JSONGRapher plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/UAN_DTA_image.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/UAN_DTA_image.png)
46
48
 
47
- [![rainbow plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/SrTiO3_rainbow_image.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/SrTiO3_rainbow_image.png)
48
- [![mesh3d plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/Rate_Constant_mesh3d.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/Rate_Constant_mesh3d.png)
49
- [![scatter3d plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/Rate_Constant_Scatter3d_example10.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/Rate_Constant_Scatter3d_example10.png)
50
- [![bubble plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/Rate_Constant_bubble.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/Rate_Constant_bubble.png)
49
+ [![rainbow plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/SrTiO3_rainbow_image.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/SrTiO3_rainbow_image.png)
50
+ [![mesh3d plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/Rate_Constant_mesh3d.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/Rate_Constant_mesh3d.png)
51
+ [![scatter3d plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/Rate_Constant_Scatter3d_example10.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/Rate_Constant_Scatter3d_example10.png)
52
+ [![bubble plot](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/Rate_Constant_bubble.png)](https://raw.githubusercontent.com/AdityaSavara/JSONGrapher-py/main/examples/example_1_drag_and_drop/images/Rate_Constant_bubble.png)
51
53
 
52
54
 
53
55
 
@@ -126,3 +128,14 @@ You can also see more examples: https://github.com/AdityaSavara/jsongrapher-py/t
126
128
 
127
129
  Additionally, json records you send to others can be plotted by them at www.jsongrapher.com
128
130
  This 'see the plot using a browser' capability is intended to facilitate including JSONGrapher records in supporting information of scientific publications.
131
+
132
+
133
+ ## **Contributing to JSONGrapher, Feature Suggestions, and Reporting Issues**
134
+
135
+ These interactions should be through github at https://github.com/AdityaSavara/jsongrapher-py
136
+
137
+ To contribute to JSONGrapher, make a pull request with sufficient details about what issue you are trying to solve, and adequate commenting in your code to follow the logic. After that, be prepared for further communication if needed.
138
+
139
+ To suggest features, create a new issue under the issues tab.
140
+
141
+ To report issues, create a new issue under the issues tab.
@@ -0,0 +1,19 @@
1
+ JSONGrapher/JSONRecordCreator.py,sha256=46saEYWpy3Oiai2lCAGEfayIs6ZLNBjcH7nC2Yi87G8,214735
2
+ JSONGrapher/UnitPytesting.py,sha256=xizJ-2fg9C5oNMFJyfavbBLMusayE9KWQiYIRrQQd4A,4363
3
+ JSONGrapher/UnitpyCustomUnitsTesting.py,sha256=Rwq5p8HXN0FP54lRFgLTV0kgJ9TpcFaD3_C0MEOoEzw,1297
4
+ JSONGrapher/__init__.py,sha256=fA3R6paWq3uskiLe3OglQZY_uJOejZHMFKdyX0dXWjo,310
5
+ JSONGrapher/drag_and_drop_gui.py,sha256=-7QJHLhzadHotWhONH4inerMaZ_xuwoTQSMRF_MPVe0,5632
6
+ JSONGrapher/equation_creator.py,sha256=VFu6dFo-C0wDf1N3e0CyCBI-53cCUrp3oe0umdMLofs,17968
7
+ JSONGrapher/equation_evaluator.py,sha256=Cm7UK5vWa6fT0OylD9f22_eR8zUvxY_Sy_W6n6N9bew,37012
8
+ JSONGrapher/units_list.py,sha256=ROrlAsD66oPozZmOaE65xjNUEg3CxkSmA8iPE2_ihYI,34438
9
+ JSONGrapher/version.py,sha256=plmUxJqz8GZ6qgO0GmMnNftP7opuGp9bxRvdx2Lbgco,19
10
+ JSONGrapher/styles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ JSONGrapher/styles/layout_styles_library.py,sha256=vGb1tE_ETY-Blj2frt8ddHA976ADFSCoabjzKn2ZNYU,3051
12
+ JSONGrapher/styles/trace_styles_collection_library.py,sha256=DUbbO8jTlhn2q-lOY1XYGkHV1RbJX-P5Z1SUCM0qo6M,5952
13
+ jsongrapher-4.1.data/data/LICENSE.txt,sha256=7Guw_pmj_H4sApnM7fld2L4HH6CP-3ZYdFoOXtHFx0Y,1489
14
+ jsongrapher-4.1.data/data/README.md,sha256=stbbH5GC2URwaG41OepUSmtH1_2t6CGw-nAxhYy_UJI,7151
15
+ jsongrapher-4.1.dist-info/LICENSE.txt,sha256=7Guw_pmj_H4sApnM7fld2L4HH6CP-3ZYdFoOXtHFx0Y,1489
16
+ jsongrapher-4.1.dist-info/METADATA,sha256=wYThzsgr77C_0dn1oIUN5kmb9C9KKooVlvC0ldbn2Kk,8225
17
+ jsongrapher-4.1.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
18
+ jsongrapher-4.1.dist-info/top_level.txt,sha256=5f7Ui2hCKCPTQOjD540WKghKNYOvUDNfdpnDbIlAD3Y,12
19
+ jsongrapher-4.1.dist-info/RECORD,,
@@ -1,18 +0,0 @@
1
- JSONGrapher/JSONRecordCreator.py,sha256=SFIYYNDpsUAx6vYA-I2ZGxNw7a1yA3yoIEE6oZtZWfc,205808
2
- JSONGrapher/UnitPytesting.py,sha256=xizJ-2fg9C5oNMFJyfavbBLMusayE9KWQiYIRrQQd4A,4363
3
- JSONGrapher/UnitpyCustomUnitsTesting.py,sha256=Rwq5p8HXN0FP54lRFgLTV0kgJ9TpcFaD3_C0MEOoEzw,1297
4
- JSONGrapher/__init__.py,sha256=cc5qXQidP_ABPXdnXy_DTdgPanHuR7ya45LV-BdWVh8,277
5
- JSONGrapher/drag_and_drop_gui.py,sha256=-7QJHLhzadHotWhONH4inerMaZ_xuwoTQSMRF_MPVe0,5632
6
- JSONGrapher/equation_creator.py,sha256=VFu6dFo-C0wDf1N3e0CyCBI-53cCUrp3oe0umdMLofs,17968
7
- JSONGrapher/equation_evaluator.py,sha256=coNVkZDRmEti3wYOc9NBY2Z1fRz3ei9ZxFd-ST-bOWc,36020
8
- JSONGrapher/units_list.py,sha256=ROrlAsD66oPozZmOaE65xjNUEg3CxkSmA8iPE2_ihYI,34438
9
- JSONGrapher/styles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- JSONGrapher/styles/layout_styles_library.py,sha256=vGb1tE_ETY-Blj2frt8ddHA976ADFSCoabjzKn2ZNYU,3051
11
- JSONGrapher/styles/trace_styles_collection_library.py,sha256=DUbbO8jTlhn2q-lOY1XYGkHV1RbJX-P5Z1SUCM0qo6M,5952
12
- jsongrapher-3.8.data/data/LICENSE,sha256=MroL1bQKoAHrMJv2KvABMmZX5j-DZ2EP_zaQacLUtj0,1465
13
- jsongrapher-3.8.data/data/README.md,sha256=OntfAICB_uwfjoq8tMDYc_W7eXSC2N22X0udPIGb2Nw,5888
14
- jsongrapher-3.8.dist-info/LICENSE,sha256=MroL1bQKoAHrMJv2KvABMmZX5j-DZ2EP_zaQacLUtj0,1465
15
- jsongrapher-3.8.dist-info/METADATA,sha256=IZYNGMd9jbDyWIoyqhW1oUan16p04jf228OW4h7wMrk,6959
16
- jsongrapher-3.8.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
17
- jsongrapher-3.8.dist-info/top_level.txt,sha256=5f7Ui2hCKCPTQOjD540WKghKNYOvUDNfdpnDbIlAD3Y,12
18
- jsongrapher-3.8.dist-info/RECORD,,