jsongrapher 3.7__py3-none-any.whl → 3.9__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.
@@ -8,6 +8,7 @@ import JSONGrapher.styles.trace_styles_collection_library
8
8
  global_records_list = [] #This list holds onto records as they are added. Index 0 is the merged record. Each other index corresponds to record number (like 1 is first record, 2 is second record, etc)
9
9
 
10
10
 
11
+
11
12
  #This is a JSONGrapher specific function
12
13
  #That takes filenames and adds new JSONGrapher records to a global_records_list
13
14
  #If the all_selected_file_paths and newest_file_name_and_path are [] and [], that means to clear the global_records_list.
@@ -830,6 +831,7 @@ class JSONGrapherRecord:
830
831
  def evaluate_eqution_of_data_series_by_index(self, series_index, equation_dict = None, verbose=False):
831
832
  if equation_dict != None:
832
833
  self.fig_dict["data"][series_index]["equation"] = equation_dict
834
+ data_series_dict = self.fig_dict["data"][series_index]
833
835
  self.fig_dict = evaluate_equation_for_data_series_by_index(data_series_index=data_series_dict, verbose=verbose) #implied return.
834
836
  return data_series_dict #Extra regular return
835
837
 
@@ -841,11 +843,11 @@ class JSONGrapherRecord:
841
843
  return self.fig_dict
842
844
  #The update_and_validate function will clean for plotly.
843
845
  #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.
844
- def print_to_inspect(self, update_and_validate=True, validate=True, remove_remaining_hints=False):
846
+ def print_to_inspect(self, update_and_validate=True, validate=True, clean_for_plotly = True, remove_remaining_hints=False):
845
847
  if remove_remaining_hints == True:
846
848
  self.remove_hints()
847
849
  if update_and_validate == True: #this will do some automatic 'corrections' during the validation.
848
- self.update_and_validate_JSONGrapher_record()
850
+ self.update_and_validate_JSONGrapher_record(clean_for_plotly=clean_for_plotly)
849
851
  elif validate: #this will validate without doing automatic updates.
850
852
  self.validate_JSONGrapher_record()
851
853
  print(json.dumps(self.fig_dict, indent=4))
@@ -895,9 +897,7 @@ class JSONGrapherRecord:
895
897
  #now, add the scaled data objects to the original one.
896
898
  #This is fairly easy using a list extend.
897
899
  self.fig_dict["data"].extend(scaled_fig_dict["data"])
898
-
899
-
900
-
900
+
901
901
  def import_from_dict(self, fig_dict):
902
902
  self.fig_dict = fig_dict
903
903
 
@@ -1046,7 +1046,7 @@ class JSONGrapherRecord:
1046
1046
  elif validate: #this will validate without doing automatic updates.
1047
1047
  self.validate_JSONGrapher_record()
1048
1048
 
1049
- # filepath: Optional, filename with path to save the JSON file.
1049
+ # filename with path to save the JSON file.
1050
1050
  if len(filename) > 0: #this means we will be writing to file.
1051
1051
  # Check if the filename has an extension and append `.json` if not
1052
1052
  if '.json' not in filename.lower():
@@ -1056,6 +1056,18 @@ class JSONGrapherRecord:
1056
1056
  json.dump(self.fig_dict, f, indent=4)
1057
1057
  return self.fig_dict
1058
1058
 
1059
+ 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):
1060
+ 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)
1061
+ plotly_json_string = fig.to_plotly_json()
1062
+ if len(filename) > 0: #this means we will be writing to file.
1063
+ # Check if the filename has an extension and append `.json` if not
1064
+ if '.json' not in filename.lower():
1065
+ filename += ".json"
1066
+ #Write to file using UTF-8 encoding.
1067
+ with open(filename, 'w', encoding='utf-8') as f:
1068
+ json.dump(plotly_json_string, f, indent=4)
1069
+ return plotly_json_string
1070
+
1059
1071
  #simulate all series will simulate any series as needed.
1060
1072
  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):
1061
1073
  """
@@ -1091,8 +1103,8 @@ class JSONGrapherRecord:
1091
1103
  self.apply_plot_style(plot_style=plot_style)
1092
1104
  #Now we clean out the fields and make a plotly object.
1093
1105
  if update_and_validate == True: #this will do some automatic 'corrections' during the validation.
1094
- self.update_and_validate_JSONGrapher_record() #this is the line that cleans "self.fig_dict"
1095
- self.fig_dict = clean_json_fig_dict(self.fig_dict, fields_to_update=['simulate', 'custom_units_chevrons', 'equation', 'trace_style', '3d_axes', 'bubble'])
1106
+ 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.
1107
+ self.fig_dict = clean_json_fig_dict(self.fig_dict, fields_to_update=['simulate', 'custom_units_chevrons', 'equation', 'trace_style', '3d_axes', 'bubble', 'superscripts'])
1096
1108
  fig = pio.from_json(json.dumps(self.fig_dict))
1097
1109
  #restore the original fig_dict.
1098
1110
  self.fig_dict = original_fig_dict
@@ -1389,8 +1401,8 @@ class JSONGrapherRecord:
1389
1401
  #Make some pointers to external functions, for convenience, so people can use syntax like record.function_name() if desired.
1390
1402
  def validate_JSONGrapher_record(self):
1391
1403
  validate_JSONGrapher_record(self)
1392
- def update_and_validate_JSONGrapher_record(self):
1393
- update_and_validate_JSONGrapher_record(self)
1404
+ def update_and_validate_JSONGrapher_record(self, clean_for_plotly=True):
1405
+ update_and_validate_JSONGrapher_record(self, clean_for_plotly=clean_for_plotly)
1394
1406
 
1395
1407
 
1396
1408
  # helper function to validate x axis and y axis labels.
@@ -2927,24 +2939,61 @@ def get_fig_dict_ranges(fig_dict, skip_equations=False, skip_simulations=False):
2927
2939
 
2928
2940
  ### Start section of code with functions for cleaning fig_dicts for plotly compatibility ###
2929
2941
 
2930
- def update_title_field(fig_dict, depth=1, max_depth=10):
2942
+ def update_title_field(fig_dict_or_subdict, depth=1, max_depth=10):
2931
2943
  """ This function is intended to make JSONGrapher .json files compatible with the newer plotly recommended title field formatting
2932
2944
  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.
2933
2945
  Recursively checks for 'title' fields and converts them to dictionary format. """
2934
- if depth > max_depth or not isinstance(fig_dict, dict):
2935
- return fig_dict
2946
+ if depth > max_depth or not isinstance(fig_dict_or_subdict, dict):
2947
+ return fig_dict_or_subdict
2936
2948
 
2937
- for key, value in fig_dict.items():
2938
- if key == "title" and isinstance(value, str):
2939
- fig_dict[key] = {"text": value}
2949
+ for key, value in fig_dict_or_subdict.items():
2950
+ if key == "title" and isinstance(value, str): #This is for axes labels.
2951
+ fig_dict_or_subdict[key] = {"text": value}
2940
2952
  elif isinstance(value, dict): # Nested dictionary
2941
- fig_dict[key] = update_title_field(value, depth + 1, max_depth)
2953
+ fig_dict_or_subdict[key] = update_title_field(value, depth + 1, max_depth)
2942
2954
  elif isinstance(value, list): # Lists can contain nested dictionaries
2943
- fig_dict[key] = [update_title_field(item, depth + 1, max_depth) if isinstance(item, dict) else item for item in value]
2944
- return fig_dict
2955
+ fig_dict_or_subdict[key] = [update_title_field(item, depth + 1, max_depth) if isinstance(item, dict) else item for item in value]
2956
+ return fig_dict_or_subdict
2957
+
2945
2958
 
2946
2959
 
2947
2960
 
2961
+ def update_superscripts_strings(fig_dict_or_subdict, depth=1, max_depth=10):
2962
+ """ This function is intended to make JSONGrapher .json files compatible with the newer plotly recommended title field formatting
2963
+ 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.
2964
+ Recursively checks for 'title' fields and converts them to dictionary format. """
2965
+ if depth > max_depth or not isinstance(fig_dict_or_subdict, dict):
2966
+ return fig_dict_or_subdict
2967
+
2968
+ for key, value in fig_dict_or_subdict.items():
2969
+ if key == "title": #This is for axes labels and graph title.
2970
+ if "text" in fig_dict_or_subdict[key]:
2971
+ fig_dict_or_subdict[key]["text"] = replace_superscripts(fig_dict_or_subdict[key]["text"])
2972
+ if key == "data": #This is for the legend.
2973
+ for data_dict in fig_dict_or_subdict[key]:
2974
+ if "name" in data_dict:
2975
+ data_dict["name"] = replace_superscripts(data_dict["name"])
2976
+ elif isinstance(value, dict): # Nested dictionary
2977
+ fig_dict_or_subdict[key] = update_superscripts_strings(value, depth + 1, max_depth)
2978
+ elif isinstance(value, list): # Lists can contain nested dictionaries
2979
+ fig_dict_or_subdict[key] = [update_superscripts_strings(item, depth + 1, max_depth) if isinstance(item, dict) else item for item in value]
2980
+ return fig_dict_or_subdict
2981
+
2982
+ #The below function was made with the help of copilot.
2983
+ def replace_superscripts(input_string):
2984
+ #Example usage: print(replace_superscripts("x^(2) + y**(-3) = z^(test)"))
2985
+ import re
2986
+ # Step 1: Wrap superscript expressions in <sup> tags
2987
+ output_string = re.sub(r'\^\((.*?)\)|\*\*\((.*?)\)',
2988
+ lambda m: f"<sup>{m.group(1) or m.group(2)}</sup>",
2989
+ input_string)
2990
+ # Step 2: Remove parentheses if the content is only digits
2991
+ output_string = re.sub(r'<sup>\((\d+)\)</sup>', r'<sup>\1</sup>', output_string)
2992
+ # Step 3: Remove parentheses if the content is a negative number (- followed by digits)
2993
+ # Step 4: Remove parentheses if the superscript is a single letter
2994
+ output_string = re.sub(r'<sup>\((\w)\)</sup>', r'<sup>\1</sup>', output_string)
2995
+ output_string = re.sub(r'<sup>\(-(\d+)\)</sup>', r'<sup>-\1</sup>', output_string)
2996
+ return output_string
2948
2997
 
2949
2998
 
2950
2999
  def convert_to_3d_layout(layout):
@@ -3088,6 +3137,7 @@ def clean_json_fig_dict(json_fig_dict, fields_to_update=None):
3088
3137
  because one would not want to do that by mistake before simulation is performed.
3089
3138
  This function can also remove the 'equation' field from data series. However, that is not the default behavior
3090
3139
  because one would not want to do that by mistake before the equation is evaluated.
3140
+ The "superscripts" option is not normally used until right before plotting because that will affect unit conversions.
3091
3141
  """
3092
3142
  if fields_to_update is None: # should not initialize mutable objects in arguments line, so doing here.
3093
3143
  fields_to_update = ["title_field", "extraInformation", "nested_comments"]
@@ -3111,6 +3161,8 @@ def clean_json_fig_dict(json_fig_dict, fields_to_update=None):
3111
3161
  fig_dict = remove_trace_style_field(fig_dict)
3112
3162
  if "3d_axes" in fields_to_update: #This is for 3D plots
3113
3163
  fig_dict = update_3d_axes(fig_dict)
3164
+ if "superscripts" in fields_to_update:
3165
+ fig_dict = update_superscripts_strings(fig_dict)
3114
3166
 
3115
3167
  return fig_dict
3116
3168
 
@@ -3118,6 +3170,8 @@ def clean_json_fig_dict(json_fig_dict, fields_to_update=None):
3118
3170
 
3119
3171
  ### Beginning of section of file that has functions for "simulate" and "equation" fields, to evaluate equations and call external javascript simulators, as well as support functions ###
3120
3172
 
3173
+ local_python_functions_dictionary = {} #This is a global variable that works with the "simulate" feature and lets users call python functions for data generation.
3174
+
3121
3175
  def run_js_simulation(javascript_simulator_url, simulator_input_json_dict, verbose = False):
3122
3176
  """
3123
3177
  Downloads a JavaScript file using its URL, extracts the filename, appends an export statement,
@@ -3227,7 +3281,7 @@ def convert_to_raw_github_url(url):
3227
3281
  return url # Return unchanged if not a GitHub file URL
3228
3282
 
3229
3283
  #This function takes in a data_series_dict object and then
3230
- #calls an external javascript simulation if needed
3284
+ #calls an external python or javascript simulation if needed
3231
3285
  #Then fills the data_series dict with the simulated data.
3232
3286
  #This function is not intended to be called by the regular user
3233
3287
  #because it returns extra fields that need to be parsed out.
@@ -3235,6 +3289,16 @@ def convert_to_raw_github_url(url):
3235
3289
  def simulate_data_series(data_series_dict, simulator_link='', verbose=False):
3236
3290
  if simulator_link == '':
3237
3291
  simulator_link = data_series_dict["simulate"]["model"]
3292
+ if simulator_link == "local_python": #this is the local python case.
3293
+ #Here, I haev split up the lines of code more than needed so that the logic is easy to follow.
3294
+ simulation_function_label = data_series_dict["simulate"]["simulation_function_label"]
3295
+ simulation_function = local_python_functions_dictionary[simulation_function_label]
3296
+ simulation_return = simulation_function(data_series_dict)
3297
+ if "data" in simulation_return: #the simulation return should have the data_series_dict in another dictionary.
3298
+ simulation_result = simulation_return["data"]
3299
+ else: #if there is no "data" field, we will assume that only the data_series_dict has been returned.
3300
+ simulation_result = simulation_return
3301
+ return simulation_result
3238
3302
  try:
3239
3303
  simulation_return = run_js_simulation(simulator_link, data_series_dict, verbose=verbose)
3240
3304
  if isinstance(simulation_return, dict) and "error" in simulation_return: # Check for errors in the returned data
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jsongrapher
3
- Version: 3.7
3
+ Version: 3.9
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
8
- License: Unlicense
7
+ Author-email: AdityaSavara2008@u.northwestern.edu
8
+ License: BSD-3-Clause
9
9
  Classifier: License :: OSI Approved :: BSD License
10
10
  Classifier: Programming Language :: Python
11
11
  Classifier: Programming Language :: Python :: 3
@@ -1,4 +1,4 @@
1
- JSONGrapher/JSONRecordCreator.py,sha256=bRyQPYr2TJZEtjMUF-idDO6mfRm3Rp_775iangiuRf8,204822
1
+ JSONGrapher/JSONRecordCreator.py,sha256=nENtRcr0hlysqhRIc-BhYRE8VN9Q8TaB0IC74jiKUzw,209780
2
2
  JSONGrapher/UnitPytesting.py,sha256=xizJ-2fg9C5oNMFJyfavbBLMusayE9KWQiYIRrQQd4A,4363
3
3
  JSONGrapher/UnitpyCustomUnitsTesting.py,sha256=Rwq5p8HXN0FP54lRFgLTV0kgJ9TpcFaD3_C0MEOoEzw,1297
4
4
  JSONGrapher/__init__.py,sha256=cc5qXQidP_ABPXdnXy_DTdgPanHuR7ya45LV-BdWVh8,277
@@ -9,10 +9,10 @@ JSONGrapher/units_list.py,sha256=ROrlAsD66oPozZmOaE65xjNUEg3CxkSmA8iPE2_ihYI,344
9
9
  JSONGrapher/styles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  JSONGrapher/styles/layout_styles_library.py,sha256=vGb1tE_ETY-Blj2frt8ddHA976ADFSCoabjzKn2ZNYU,3051
11
11
  JSONGrapher/styles/trace_styles_collection_library.py,sha256=DUbbO8jTlhn2q-lOY1XYGkHV1RbJX-P5Z1SUCM0qo6M,5952
12
- jsongrapher-3.7.data/data/LICENSE,sha256=MroL1bQKoAHrMJv2KvABMmZX5j-DZ2EP_zaQacLUtj0,1465
13
- jsongrapher-3.7.data/data/README.md,sha256=OntfAICB_uwfjoq8tMDYc_W7eXSC2N22X0udPIGb2Nw,5888
14
- jsongrapher-3.7.dist-info/LICENSE,sha256=MroL1bQKoAHrMJv2KvABMmZX5j-DZ2EP_zaQacLUtj0,1465
15
- jsongrapher-3.7.dist-info/METADATA,sha256=1gml4jeoRaUaS8IMQfHaftk8Q0hXTLnsUM7dGi_9qfM,6956
16
- jsongrapher-3.7.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
17
- jsongrapher-3.7.dist-info/top_level.txt,sha256=5f7Ui2hCKCPTQOjD540WKghKNYOvUDNfdpnDbIlAD3Y,12
18
- jsongrapher-3.7.dist-info/RECORD,,
12
+ jsongrapher-3.9.data/data/LICENSE,sha256=MroL1bQKoAHrMJv2KvABMmZX5j-DZ2EP_zaQacLUtj0,1465
13
+ jsongrapher-3.9.data/data/README.md,sha256=OntfAICB_uwfjoq8tMDYc_W7eXSC2N22X0udPIGb2Nw,5888
14
+ jsongrapher-3.9.dist-info/LICENSE,sha256=MroL1bQKoAHrMJv2KvABMmZX5j-DZ2EP_zaQacLUtj0,1465
15
+ jsongrapher-3.9.dist-info/METADATA,sha256=q1LGWAVZNTTawUuuWZzy8WypNwQv7tdYWNIt1xWSbsE,6960
16
+ jsongrapher-3.9.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
17
+ jsongrapher-3.9.dist-info/top_level.txt,sha256=5f7Ui2hCKCPTQOjD540WKghKNYOvUDNfdpnDbIlAD3Y,12
18
+ jsongrapher-3.9.dist-info/RECORD,,