From cd69a041a4ee48f9614119edb11a3183bca5f8af Mon Sep 17 00:00:00 2001 From: vedina Date: Tue, 2 Apr 2024 23:00:02 +0300 Subject: [PATCH] rewritten with xlsxwriter --- .../datamodel/templates/blueprint.py | 135 ++++++++++++------ .../resource/nmparser/template_pchem.xlsx | Bin 11293 -> 9496 bytes 2 files changed, 91 insertions(+), 44 deletions(-) diff --git a/src/pynanomapper/datamodel/templates/blueprint.py b/src/pynanomapper/datamodel/templates/blueprint.py index 516f52a..47eca5a 100644 --- a/src/pynanomapper/datamodel/templates/blueprint.py +++ b/src/pynanomapper/datamodel/templates/blueprint.py @@ -37,7 +37,7 @@ def json2frame(json_data,sortby=None): def get_method_metadata(json_blueprint): _header = { - "Project Work Package" : json_blueprint.get("provenance_workpackage",""), + "Project Work Package" : json_blueprint.get("provenance_project",""), "Partner conducting test/assay" : json_blueprint.get("provenance_workpackage",""), "Test facility - Laboratory name" : json_blueprint.get("provenance_provider",""), "Lead Scientist & contact for test" : json_blueprint.get("provenance_contact",""), @@ -175,20 +175,39 @@ def autofit_columns(sheet,cols=None): sheet.cell(row=1, column=col_num+1).fill = pf #break - +def autofit_multilevel(df,worksheet): + for idx, col in enumerate(df.columns): + # Find the maximum length of the column header (using the last level of multi-index) + max_length = max(len(str(level)) for level in col) + 1 + # Set the column width based on the length of the column header + worksheet.set_column(idx, idx, max_length) + def pchem_format_2excel(file_path_xlsx,json_blueprint): + _SHEET_INFO = "Provider_informations" + _SHEET_RAW = "Raw_data_TABLE" + _SHEET_RESULT = "Results_TABLE" + _SHEET_MATERIAL = "Materials" current_script_directory = os.path.dirname(os.path.abspath(__file__)) - resource_file = os.path.join(current_script_directory, "../../resource/nmparser","template_pchem.xlsx") - shutil.copy2(resource_file, file_path_xlsx) - with pd.ExcelWriter(file_path_xlsx, engine='openpyxl', mode='a') as writer: - sheet = writer.book["Provider_informations"] - sheet["E7"] = json_blueprint.get("METHOD","") - sheet["C2"] = json_blueprint.get("EXPERIMENT","") - sheet["B10"] = json_blueprint.get("template_name","") - sheet["B11"] = json_blueprint.get("template_status","") - sheet["B12"] = json_blueprint.get("template_author","") - sheet["B13"] = json_blueprint.get("template_acknowledgment","") - sheet["B15"] = datetime.now().strftime("%Y-%m-%d") + #resource_file = os.path.join(current_script_directory, "../../resource/nmparser","template_pchem.xlsx") + #shutil.copy2(resource_file, file_path_xlsx) + with pd.ExcelWriter(file_path_xlsx, engine='xlsxwriter', mode='w') as writer: + #sheet = writer.book["Provider_informations"] + worksheet = writer.book.add_worksheet(_SHEET_INFO) + bold_format = writer.book.add_format({'bold': True}) + orange_bg_format = writer.book.add_format({'bg_color': '#FFF2CC'}) + material_format = writer.book.add_format({'bg_color': '#00B0F0'}) + position_format = writer.book.add_format({'bg_color': '#FFC000'}) + #_colors = { "top" : '#002060' , "orange" : '#FFC000', 'skyblue' : '#00B0F0' , 'grey' : '#C0C0C0', 'input' : '#FFF2CC'} + for t in [("A2","General information"), + ("A10","Template name"),("A11","Template version "), ("A12","Template authors"),("A13","Template acknowledgment"), + ("A6","Project"),("A7","Workpackage"),("A8","Partner"),("D7","Study"),("A15","Template downloaded"),("B15",datetime.now().strftime("%Y-%m-%d"))]: + worksheet.write(t[0],t[1], bold_format) + for t in [("E7","METHOD"),("C2","EXPERIMENT"), ("B10","template_name"),("B11","template_status"), + ("B12","template_author"),("B13","template_acknowledgment"), + ("B6","provenance_project"),("B7","provenance_workpackage"),("B8","provenance_provider")]: + worksheet.write(t[0],json_blueprint.get(t[1],""),orange_bg_format) + + worksheet = writer.book.add_worksheet(_SHEET_RESULT) df = create_nested_headers_dataframe(json_blueprint,keys= {"raw_data_report" : {'name' : 'raw_endpoint','type' : 'raw_aggregate', 'unit' : 'raw_unit'}, "question3" : {'name' : 'result_name','type' : 'result_aggregate','unit' : 'result_unit'}}, @@ -197,32 +216,59 @@ def pchem_format_2excel(file_path_xlsx,json_blueprint): ) #df.insert(0, 'Material ID',None) df.insert(0, 'Position_ID',None) - df.to_excel(writer,sheet_name="Results_TABLE") - sheet = writer.book["Results_TABLE"] - sheet["A1"] = "Material ID" - sheet["A2"] = "" - sheet["A3"] = "" - sheet["A4"] = "" - autofit_columns(writer.book["Results_TABLE"],df.columns) + df.to_excel(writer,sheet_name=_SHEET_RESULT) + worksheet = writer.book.get_worksheet_by_name(_SHEET_RESULT) + worksheet.write('A1', 'Material ID',material_format) + worksheet.write('A2', ' ',material_format) + worksheet.write('A3', ' ',material_format) + worksheet.write('A4', ' ',material_format) + worksheet.write('B1', 'Position_ID',position_format) + worksheet.write('B2', ' ',position_format) + worksheet.write('B3', ' ',position_format) + worksheet.write('B4', ' ',position_format) + autofit_multilevel(df,worksheet) df = create_nested_headers_dataframe(json_blueprint,keys={"METADATA_PARAMETERS" : {'group' : 'param_group', 'name' : 'param_name', 'unit' : 'param_unit'}}) - #df.insert(0, 'Position_ID',None) - df.to_excel(writer,sheet_name="Measuring_conditions") - sheet = writer.book["Measuring_conditions"] - sheet["A1"] = "Position_ID" - sheet["A2"] = "" - sheet["A3"] = "" - sheet["A4"] = "" - autofit_columns(writer.book["Measuring_conditions"],df.columns) - - df = create_nested_headers_dataframe(json_blueprint,keys= - {"METADATA_SAMPLE_INFO" : {'group' : 'param_sample_group', 'name' : 'param_sample_name'}, + _SHEET_MEASUREMENT = "Measuring_conditions" + df.to_excel(writer,sheet_name=_SHEET_MEASUREMENT) + worksheet = writer.book.get_worksheet_by_name(_SHEET_MEASUREMENT) + worksheet.write('A1', 'Position_ID',position_format) + worksheet.write('A2', ' ',position_format) + worksheet.write('A3', ' ',position_format) + worksheet.write('A4', ' ',position_format) + worksheet.write('A5', '1',position_format) + worksheet.write('A6', '2',position_format) + position_identifiers_range = "{}!$A5:$A1048576".format(_SHEET_MEASUREMENT) # Entire column A + writer.book.define_name('Position_Identifiers', position_identifiers_range) + autofit_multilevel(df,worksheet) + validation = { + 'validate': 'list', + 'source': '=Position_Identifiers' + } + writer.book.get_worksheet_by_name(_SHEET_RESULT).data_validation("B5:B1048576", validation) + + df = create_nested_headers_dataframe(json_blueprint,keys={ + #"METADATA_SAMPLE_INFO" : {'group' : 'param_sample_group', 'name' : 'param_sample_name'}, "METADATA_SAMPLE_PREP" : {'group' : 'param_sampleprep_group', 'name' : 'param_sampleprep_name'}}, levels = ['group','name'], lookup = {'METADATA_SAMPLE_INFO' : "Sample", "METADATA_SAMPLE_PREP" : "Sample preparation", "group" : ""}) - df.insert(0, 'Material ID',None) df.to_excel(writer,sheet_name="SAMPLES") - autofit_columns(writer.book["SAMPLES"],df.columns) + worksheet = writer.book.get_worksheet_by_name("SAMPLES") + worksheet.write('A1', 'Material ID',material_format) + worksheet.write('A2', ' ',material_format) + worksheet.write('A3', ' ',material_format) + + validation = { + 'validate': 'list', + 'source': '=ERM_Identifiers' + } + writer.book.get_worksheet_by_name("SAMPLES").data_validation("A4:A1048576", validation) + autofit_multilevel(df,worksheet) + + + materials_sheet = create_materials_sheet(writer.book,writer, + materials=_SHEET_MATERIAL, + info=None,results=[_SHEET_RESULT],material_column="A5:A1048576") add_hidden_jsondef(file_path_xlsx,json_blueprint) def add_hidden_jsondef(file_path_xlsx,json_blueprint): @@ -240,7 +286,6 @@ def add_hidden_jsondef(file_path_xlsx,json_blueprint): print(err) def add_plate_layout(file_path_xlsx,json_blueprint): - print(json_blueprint.get("data_sheets")) if "data_platelayout" in json_blueprint.get("data_sheets",[]): platexlsx = "platelayout_{}well.xlsx".format(json_blueprint.get("data_platelayout",96) ) current_script_directory = os.path.dirname(os.path.abspath(__file__)) @@ -473,8 +518,8 @@ def iom_format_2excel(file_path, df_info,df_result,df_raw=None,df_conditions=Non info=_SHEET_INFO,results=linksheets) -def create_materials_sheet(workbook,writer,materials,info,results): - info_sheet = writer.sheets[info] +def create_materials_sheet(workbook,writer,materials,info=None,results=[],material_column="A3:A1048576"): + info_sheet = None if info is None else writer.sheets[info] materials_sheet = workbook.add_worksheet(materials) column_headers = get_materials_columns() table = pd.DataFrame(columns=column_headers) @@ -486,19 +531,21 @@ def create_materials_sheet(workbook,writer,materials,info,results): 'validate': 'list', 'source': '=ERM_Identifiers' } - info_sheet.data_validation(validation_cell, validation) - vlookup = [('B26',3),('B27',9),('B28',4),('B29',6),('B31',8)] - for v in vlookup: - formula = '=VLOOKUP($B$25,Materials!B:J,"{}",FALSE)'.format(v[1]) - info_sheet.write_formula(v[0], formula) - readonly_format = workbook.add_format({'locked': True}) + if not info_sheet is None: + info_sheet.data_validation(validation_cell, validation) + vlookup = [('B26',3),('B27',9),('B28',4),('B29',6),('B31',8)] + for v in vlookup: + formula = '=VLOOKUP($B$25,Materials!B:J,"{}",FALSE)'.format(v[1]) + info_sheet.write_formula(v[0], formula) + readonly_format = workbook.add_format({'locked': True}) for result in results: try: result_sheet = writer.sheets[result] - result_sheet.data_validation("A3:A1048576", validation) + result_sheet.data_validation(material_column, validation) #protect_headers(result_sheet,readonly_format) - except: + except Exception as err: + print(err) pass return materials_sheet diff --git a/src/pynanomapper/resource/nmparser/template_pchem.xlsx b/src/pynanomapper/resource/nmparser/template_pchem.xlsx index 61d51215a9a000fa89eec074c143eafc09fb4e19..899c7bf884b00976e585b2cca613b5d6616d8474 100644 GIT binary patch delta 3652 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 delta 5281 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