diff --git a/MetaWin_mac.spec b/MetaWin_mac.spec index b19c3d0..35f775e 100644 --- a/MetaWin_mac.spec +++ b/MetaWin_mac.spec @@ -43,6 +43,7 @@ added_files = [("resources/images/exit@256px.png", "resources/images"), ("resources/images/flag-united-states@256px.png", "resources/images"), ("resources/images/flag-spain@256px.png", "resources/images"), ("resources/images/letter-t@256px.png", "resources/images"), + ("resources/images/sum-gear-filled@256px.png", "resources/images"), ("resources/images/metawin3icon.png", "resources/images"), ("resources/images/draw_forest.png", "resources/images"), diff --git a/MetaWin_windows.spec b/MetaWin_windows.spec index e212679..cc15f5b 100644 --- a/MetaWin_windows.spec +++ b/MetaWin_windows.spec @@ -43,6 +43,7 @@ added_files = [("resources/images/exit@256px.png", "resources/images"), ("resources/images/flag-united-states@256px.png", "resources/images"), ("resources/images/flag-spain@256px.png", "resources/images"), ("resources/images/letter-t@256px.png", "resources/images"), + ("resources/images/sum-gear-filled@256px.png", "resources/images"), ("resources/images/metawin3icon.png", "resources/images"), ("resources/images/draw_forest.png", "resources/images"), diff --git a/resources/images/sum-gear-filled@256px.png b/resources/images/sum-gear-filled@256px.png new file mode 100644 index 0000000..53b8cda Binary files /dev/null and b/resources/images/sum-gear-filled@256px.png differ diff --git a/resources/metawin_help.html b/resources/metawin_help.html index 70e4eb7..aff909e 100644 --- a/resources/metawin_help.html +++ b/resources/metawin_help.html @@ -55,6 +55,7 @@
+ survey done at + XKCD. This set was chosen primarily because of it's size; other standard color names, + e.g., CSS4, only include names for fewer than 150 colors. With that many crowd-coursed color + names, many me be a bit odd, so simply be warned if strange color labels appear. +
+ will autogenerate captions for each figure it creates, including + references in some cases. As style elements of figures can be user edited, this creates a bit of a + challenge for captioning, particularly as it relates to specifying color. Computers can + generate over 16.7 million unique colors, most of which obviously do not have unique names. + Although exact, specifying colors by RGB or hex number in captions would not be particularly + useful. Therefore, whenever a caption includes a reference to a color, a color name is chosen by + matching the specific color to the closest named color from a set of approximately 1,000 + named colors based on a+ Currently only line and markers description appear in captions. Line descriptions include only color + and style (solid, dahsed, etc.), while markers include color and shape (two colors if the primary + and border colors are different). Properties such as size and thickness are not included in captions. +
@@ -2487,7 +2507,7 @@
Normal Quantile plot following Wang and Bushman (1998). The standardized effect size is the - efffect size divided by the square-root of its variance. The solid line represents the regression + effect size divided by the square-root of its variance. The solid line represents the regression and the dashed lines the 95% prediction envelope.
diff --git a/src/MetaWinAnalysis.py b/src/MetaWinAnalysis.py index cbb4295..f6ab1d7 100644 --- a/src/MetaWinAnalysis.py +++ b/src/MetaWinAnalysis.py @@ -1798,62 +1798,56 @@ def do_meta_analysis(data, options, decimal_places: int = 4, alpha: float = 0.05 output, all_citations = options.report_choices() output_blocks.extend(output) if options.structure == SIMPLE_MA: - (output, figure, fig_caption, chart_data, analysis_values, + (output, figure, chart_data, analysis_values, citations) = MetaWinAnalysisFunctions.simple_meta_analysis(data, options, decimal_places, alpha, norm_ci) elif options.structure == GROUPED_MA: - (output, figure, fig_caption, chart_data, analysis_values, + (output, figure, chart_data, analysis_values, citations) = MetaWinAnalysisFunctions.grouped_meta_analysis(data, options, decimal_places, alpha, norm_ci) elif options.structure == CUMULATIVE_MA: - output, figure, fig_caption, chart_data = MetaWinAnalysisFunctions.cumulative_meta_analysis(data, options, - decimal_places, - alpha, norm_ci) + output, figure, chart_data = MetaWinAnalysisFunctions.cumulative_meta_analysis(data, options, + decimal_places, alpha, norm_ci) analysis_values = None citations = [] elif options.structure == REGRESSION_MA: - (output, figure, fig_caption, chart_data, analysis_values, + (output, figure, chart_data, analysis_values, citations) = MetaWinAnalysisFunctions.regression_meta_analysis(data, options, decimal_places, alpha, norm_ci) elif options.structure == COMPLEX_MA: output, analysis_values, citations = MetaWinAnalysisFunctions.complex_meta_analysis(data, options, decimal_places, alpha, norm_ci) figure = None - fig_caption = None chart_data = None elif options.structure == NESTED_MA: - (output, figure, fig_caption, chart_data, analysis_values, + (output, figure, chart_data, analysis_values, citations) = MetaWinAnalysisFunctions.nested_meta_analysis(data, options, decimal_places, alpha, norm_ci) elif options.structure == TRIM_FILL: - (output, figure, fig_caption, chart_data, analysis_values, + (output, figure, chart_data, analysis_values, citations) = MetaWinAnalysisFunctions.trim_and_fill_analysis(data, options, decimal_places, alpha, norm_ci) elif options.structure == JACKKNIFE: - (output, figure, fig_caption, - chart_data, citations) = MetaWinAnalysisFunctions.jackknife_meta_analysis(data, options, decimal_places, - alpha, norm_ci) + (output, figure, chart_data, + citations) = MetaWinAnalysisFunctions.jackknife_meta_analysis(data, options, decimal_places, alpha, norm_ci) analysis_values = None elif options.structure == PHYLOGENETIC_MA: output, citations = MetaWinAnalysisFunctions.phylogenetic_meta_analysis(data, options, tree, decimal_places, alpha, norm_ci) analysis_values = None figure = None - fig_caption = None chart_data = None elif options.structure == RANKCOR: output, citations = MetaWinAnalysisFunctions.rank_correlation_analysis(data, options, decimal_places) figure = None - fig_caption = None chart_data = None analysis_values = None else: output = [] analysis_values = None figure = None - fig_caption = None chart_data = None citations = [] all_citations.extend(citations) output_blocks.extend(output) output_blocks.extend(create_reference_list(all_citations)) - return output_blocks, figure, fig_caption, chart_data, analysis_values + return output_blocks, figure, chart_data, analysis_values def meta_analysis(sender, data, last_effect, last_var, decimal_places: int = 4, alpha: float = 0.05, @@ -1909,12 +1903,9 @@ def meta_analysis(sender, data, last_effect, last_var, decimal_places: int = 4, meta_analysis_options.structure = None if meta_analysis_options.structure is not None: - output, figure, fig_caption, chart_data, _ = do_meta_analysis(data, meta_analysis_options, decimal_places, - alpha, tree, norm_ci) + output, figure, chart_data, _ = do_meta_analysis(data, meta_analysis_options, decimal_places, alpha, tree, + norm_ci) sender.last_effect = meta_analysis_options.effect_data sender.last_var = meta_analysis_options.effect_vars - return output, figure, fig_caption, chart_data - else: - return None, None, None, None - else: - return None, None, None, None + return output, figure, chart_data + return None, None, None diff --git a/src/MetaWinAnalysisFunctions.py b/src/MetaWinAnalysisFunctions.py index 741c4d7..fe0579d 100644 --- a/src/MetaWinAnalysisFunctions.py +++ b/src/MetaWinAnalysisFunctions.py @@ -13,8 +13,8 @@ import MetaWinConstants from MetaWinConstants import mean_data_tuple -from MetaWinUtils import create_output_table, inline_float, interval_to_str, create_reference_list, get_citation, \ - exponential_label, prob_z_score +from MetaWinUtils import create_output_table, inline_float, interval_to_str, get_citation, exponential_label, \ + prob_z_score import MetaWinCharts from MetaWinLanguage import get_text @@ -525,13 +525,6 @@ def output_filtered_bad(filtered: list, bad_data: list) -> list: return output_blocks -def caption_bootstrap_text(bs_n: int): - """ - extra figure caption info for forest plots with bootstrap confidence intervals - """ - citation = "Adams_et_1997" - return get_text("bootstrap_caption").format(bs_n, get_citation(citation)), [citation] - # ---- I'm not convinced this is correct, which is why I'm removing it for now --- # def calc_aic(q: float, n: int, p: int) -> float: # """ @@ -577,7 +570,6 @@ def simple_meta_analysis(data, options, decimal_places: int = 4, alpha: float = output_blocks = output_filtered_bad(filtered, bad_data) figure = None - fig_caption = None chart_data = None n = len(e_data) citations = [] @@ -639,21 +631,15 @@ def simple_meta_analysis(data, options, decimal_places: int = 4, alpha: float = citations.extend(new_cites) if options.create_graph: - figure, chart_data = MetaWinCharts.chart_forest_plot(effect_sizes.label, forest_data, alpha, - options.bootstrap_mean) - fig_caption = get_text("Forest plot of individual effect sizes for each study, as well as the overall " - "mean.") + MetaWinCharts.caption_forest_plot_text(effect_sizes.label, alpha) - if options.bootstrap_mean is not None: - new_text, new_cites = caption_bootstrap_text(options.bootstrap_mean) - fig_caption += new_text + create_reference_list(new_cites, True) + figure, chart_data = MetaWinCharts.chart_forest_plot("basic analysis", effect_sizes.label, forest_data, + alpha, options.bootstrap_mean, normal_ci=norm_ci) else: output_blocks.append([get_text("Fewer than two studies were valid for analysis")]) qt, df, p, pooled_var, i2 = None, None, None, None, None mean_data = None - return (output_blocks, figure, fig_caption, chart_data, simple_ma_values(mean_data, pooled_var, qt, df, p, i2), - citations) + return output_blocks, figure, chart_data, simple_ma_values(mean_data, pooled_var, qt, df, p, i2), citations # ---------- grouped meta-analysis ---------- @@ -675,7 +661,6 @@ def check_data_for_group(output_blocks, n, group_cnts, group_label) -> bool: output.append(get_text("Please filter problematic data to continue")) output_blocks.append(output) return all_good - # return True def grouped_meta_analysis(data, options, decimal_places: int = 4, alpha: float = 0.05, norm_ci: bool = True): @@ -716,7 +701,6 @@ def grouped_meta_analysis(data, options, decimal_places: int = 4, alpha: float = output_blocks = output_filtered_bad(filtered, bad_data) figure = None - fig_caption = None chart_data = None n = len(e_data) g_cnt = len(group_names) @@ -863,13 +847,9 @@ def grouped_meta_analysis(data, options, decimal_places: int = 4, alpha: float = citations.extend(new_cites) if options.create_graph: - figure, chart_data = MetaWinCharts.chart_forest_plot(effect_sizes.label, forest_data, alpha, - options.bootstrap_mean) - fig_caption = get_text("group_forest_plot").format(groups.label) + \ - MetaWinCharts.caption_forest_plot_text(effect_sizes.label, alpha) - if options.bootstrap_mean is not None: - new_text, new_cites = caption_bootstrap_text(options.bootstrap_mean) - fig_caption += new_text + create_reference_list(new_cites, True) + figure, chart_data = MetaWinCharts.chart_forest_plot("grouped analysis", effect_sizes.label, forest_data, + alpha, options.bootstrap_mean, groups.label, + normal_ci=norm_ci) global_values = simple_ma_values(global_mean_data, pooled_var, qt, df, pqt, i2) else: @@ -879,7 +859,7 @@ def grouped_meta_analysis(data, options, decimal_places: int = 4, alpha: float = model_het = None error_het = None - return (output_blocks, figure, fig_caption, chart_data, + return (output_blocks, figure, chart_data, group_ma_values(global_values, group_mean_values, group_het_values, model_het, error_het), citations) @@ -907,7 +887,6 @@ def cumulative_meta_analysis(data, options, decimal_places: int = 4, alpha: floa output_blocks = output_filtered_bad(filtered, bad_data) figure = None - fig_caption = None chart_data = None n = len(tmp_data) if n > 1: @@ -970,17 +949,14 @@ def cumulative_meta_analysis(data, options, decimal_places: int = 4, alpha: floa decimal_places, alpha, options.log_transformed)) if options.create_graph: - figure, chart_data = MetaWinCharts.chart_forest_plot(effect_sizes.label, cumulative_means, alpha, - options.bootstrap_mean) - fig_caption = get_text("cumulative_forest_plot").format(order.label) + \ - MetaWinCharts.caption_forest_plot_text(effect_sizes.label, alpha) - if options.bootstrap_mean is not None: - new_text, new_cites = caption_bootstrap_text(options.bootstrap_mean) - fig_caption += new_text + create_reference_list(new_cites, True) + figure, chart_data = MetaWinCharts.chart_forest_plot("cumulative analysis", effect_sizes.label, + cumulative_means, alpha, options.bootstrap_mean, + order.label, normal_ci=norm_ci) + else: output_blocks.append([get_text("Fewer than two studies were valid for analysis")]) - return output_blocks, figure, fig_caption, chart_data + return output_blocks, figure, chart_data # ---------- simple regression meta-analysis ---------- @@ -1033,7 +1009,6 @@ def regression_meta_analysis(data, options, decimal_places: int = 4, alpha: floa output_blocks = output_filtered_bad(filtered, bad_data) figure = None - fig_caption = None chart_data = None model_het = None error_het = None @@ -1132,8 +1107,6 @@ def regression_meta_analysis(data, options, decimal_places: int = 4, alpha: floa citations.extend(new_cites) if options.create_graph: - figure, chart_data = MetaWinCharts.chart_regression(options.independent_variable.label, effect_sizes.label, - x_data, e_data, b1_slope, b0_intercept) if options.random_effects: ref_list = "{}, {}, and {}".format(get_citation("Hedges_Olkin_1985"), get_citation("Greenland_1987"), @@ -1146,16 +1119,14 @@ def regression_meta_analysis(data, options, decimal_places: int = 4, alpha: floa get_citation("Greenland_1987")) model = get_text("fixed effects") fig_citations = ["Hedges_Olkin_1985", "Greenland_1987"] - - fig_caption = get_text("regression_caption").format(effect_sizes.label, options.independent_variable.label, - model, ref_list) + \ - create_reference_list(fig_citations, True) + figure, chart_data = MetaWinCharts.chart_regression(options.independent_variable.label, effect_sizes.label, + x_data, e_data, b1_slope, b0_intercept, model, + ref_list, fig_citations) else: output_blocks.append([get_text("Fewer than two studies were valid for analysis")]) - return (output_blocks, figure, fig_caption, chart_data, - reg_ma_values(global_values, model_het, error_het, predictors), citations) + return output_blocks, figure, chart_data, reg_ma_values(global_values, model_het, error_het, predictors), citations # ---------- complex (glm) meta-analysis ---------- @@ -1580,7 +1551,6 @@ def nested_meta_analysis(data, options, decimal_places: int = 4, alpha: float = n = len(e_data) figure = None - fig_caption = None chart_data = None group_het_values = None group_mean_values = None @@ -1702,16 +1672,11 @@ def nested_meta_analysis(data, options, decimal_places: int = 4, alpha: float = citations.extend(new_cites) if options.create_graph: - figure, chart_data = MetaWinCharts.chart_forest_plot(effect_sizes.label, forest_data, alpha, - options.bootstrap_mean) - fig_caption = get_text("nest_caption") + MetaWinCharts.caption_forest_plot_text(effect_sizes.label, alpha) - if options.bootstrap_mean is not None: - new_text, new_cites = caption_bootstrap_text(options.bootstrap_mean) - fig_caption += new_text + create_reference_list(new_cites, True) + figure, chart_data = MetaWinCharts.chart_forest_plot("nested analysis", effect_sizes.label, forest_data, + alpha, options.bootstrap_mean, normal_ci=norm_ci) - return (output_blocks, figure, fig_caption, chart_data, - group_ma_values(global_values, group_mean_values, group_het_values, model_het_values, error_het_values), - citations) + return (output_blocks, figure, chart_data, group_ma_values(global_values, group_mean_values, group_het_values, + model_het_values, error_het_values), citations) # ---------- trim-and-fill analysis ---------- @@ -1745,7 +1710,6 @@ def trim_and_fill_analysis(data, options, decimal_places: int = 4, alpha: float output_blocks = output_filtered_bad(filtered, bad_data) figure = None - fig_caption = None chart_data = None n = len(e_data) citations = [] @@ -1867,16 +1831,13 @@ def trim_and_fill_analysis(data, options, decimal_places: int = 4, alpha: float decimal_places, alpha, options.log_transformed)) if options.create_graph: - figure, chart_data = MetaWinCharts.chart_trim_fill_plot(effect_sizes.label, tmp_data, n, original_mean, + figure, chart_data = MetaWinCharts.chart_trim_fill_plot(effect_sizes.label, tmp_data, n, original_mean, mean_e) - fig_caption = get_text("trim_fill_caption").format(effect_sizes.label, "Duval and Tweedie 2000a, b") - new_cites = ["Duval_Tweedie_2000a", "Duval_Tweedie_2000b"] - fig_caption += create_reference_list(new_cites, True) else: output_blocks.append([get_text("Fewer than two studies were valid for analysis")]) - return output_blocks, figure, fig_caption, chart_data, None, citations + return output_blocks, figure, chart_data, None, citations # ---------- phylogenetic meta-analysis ---------- @@ -2191,7 +2152,6 @@ def jackknife_meta_analysis(data, options, decimal_places: int = 4, alpha: float output_blocks = output_filtered_bad(filtered, bad_data) figure = None - fig_caption = None chart_data = None n = len(e_data) citations = [] @@ -2286,17 +2246,12 @@ def jackknife_meta_analysis(data, options, decimal_places: int = 4, alpha: float citations.extend(new_cites) if options.create_graph: - figure, chart_data = MetaWinCharts.chart_forest_plot(effect_sizes.label, forest_data, alpha, - options.bootstrap_mean) - fig_caption = get_text("jackknife_forest_plot") + \ - MetaWinCharts.caption_forest_plot_text(effect_sizes.label, alpha) - if options.bootstrap_mean is not None: - new_text, new_cites = caption_bootstrap_text(options.bootstrap_mean) - fig_caption += new_text + create_reference_list(new_cites, True) + figure, chart_data = MetaWinCharts.chart_forest_plot("jackknife analysis", effect_sizes.label, forest_data, + alpha, options.bootstrap_mean, normal_ci=norm_ci) else: output_blocks.append([get_text("Fewer than two studies were valid for analysis")]) - return output_blocks, figure, fig_caption, chart_data, citations + return output_blocks, figure, chart_data, citations # ---------- rank correlation analysis ---------- diff --git a/src/MetaWinCharts.py b/src/MetaWinCharts.py index 10c51ad..77aa5b3 100644 --- a/src/MetaWinCharts.py +++ b/src/MetaWinCharts.py @@ -8,14 +8,20 @@ from matplotlib import patches from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg from matplotlib.figure import Figure +from matplotlib.colors import XKCD_COLORS, hex2color import numpy import scipy.stats -from MetaWinUtils import exponential_label +from MetaWinUtils import exponential_label, get_citation, create_reference_list from MetaWinLanguage import get_text import MetaWinWidgets +# weighting options for the histograms +WEIGHT_NONE = 0 +WEIGHT_INVVAR = 1 +WEIGHT_N = 2 + LINE_STYLES = ("solid", "dashed", "dotted", "dashdot") # MARKER_STYLES = {"point": ".", "circle": "o", "downward triangle": "v", "upward triangle": "^", # "left triangle": "<", "right triangle": ">", "downard tri": "1", "upward tri": "2", "left tri": "3", @@ -34,7 +40,12 @@ "centered upward caret": 10, "centered downward caret": 11, "centered left caret": 8, "centered right caret": 9} +UNFILLED_MARKERS = {"point", "plus", "X", "vertical line", "horizontal line", "tick left", "tick right", "tick up", + "tick down", "upward caret", "downward caret", "left caret", "right caret", + "centered upward caret", "centered downward caret", "centered left caret", "centered right caret"} + +# ---------- Chart Data Classes ---------- # class BaseChartData: def __init__(self): self.name = "" @@ -127,6 +138,27 @@ def update_style(self): self.size = float(self.size_box.text()) self.marker = MARKER_STYLES[self.marker_box.currentText()] + def style_text(self) -> str: + marker_name = list(MARKER_STYLES.keys())[list(MARKER_STYLES.values()).index(self.marker)] + if marker_name.endswith("s"): + s = "ses" + else: + s = "s" + if marker_name in UNFILLED_MARKERS: + if self.color == "none": + return get_text("nothing (marker is invisible)") + else: + return find_color_name(self.color) + " " + marker_name + s + else: + if self.color == self.edgecolors: + return find_color_name(self.color) + " " + marker_name + s + else: + if self.color == "none": + return get_text("marker_style_open_text").format(marker_name, s, find_color_name(self.edgecolors)) + else: + return get_text("marker_style_text").format(find_color_name(self.color), marker_name, s, + find_color_name(self.edgecolors)) + class HistogramData(BaseChartData): """ @@ -306,6 +338,9 @@ def update_style(self): self.linewidth = float(self.linewidth_box.text()) self.color = self.color_button.color + def style_text(self) -> str: + return self.linestyle + " " + find_color_name(self.color) + " line" + class ArcData(BaseChartData): """ @@ -383,19 +418,229 @@ def create_edit_panel(self): return self.edit_panel +# ---------- Chart Caption Classes ---------- # +class NormalQuantileCaption: + def __init__(self): + self.upper_limit = None + self.lower_limit = None + self.horizontal_mean = None + self.vertical_mean = None + self.regression = None + self.regression_scatter = None + + def __str__(self): + regression_text = self.regression.style_text() + upper_text = self.upper_limit.style_text() + lower_text = self.lower_limit.style_text() + if upper_text == lower_text: + style_text = get_text("normal_quantile_style1").format(regression_text, upper_text) + else: + style_text = get_text("normal_quantile_style2").format(regression_text, upper_text, lower_text) + return get_text("normal_quantile_caption").format(get_citation("Wang_and_Bushman_1998")) + style_text + \ + create_reference_list(["Wang_and_Bushman_1998"], True) + + +class ScatterCaption: + def __init__(self): + self.x_label = "" + self.y_label = "" + + def __str__(self): + return get_text("Scatter plot of {} vs. {}.").format(self.y_label, self.x_label) + + +class HistogramCaption: + def __init__(self): + self.e_label = "" + self.weight_type = WEIGHT_NONE + + def __str__(self): + caption = get_text("Histogram of {} from individual studies.").format(self.e_label) + if self.weight_type == WEIGHT_INVVAR: + caption += get_text(" Counts were weighted by the inverse of the variance of each effect size.") + elif self.weight_type == WEIGHT_N: + caption += get_text(" Counts were weighted by a sample size associated with each effect size.") + return caption + + +class RadialCaption: + def __init__(self): + self.e_label = "" + + def __str__(self): + return get_text("Radial_chart_caption").format(self.e_label) + + +class RegressionCaption: + def __init__(self): + self.e_label = "" + self.i_label = "" + self.model = "" + self.ref_list = "" + self.citations = [] + + def __str__(self): + return get_text("regression_caption").format(self.e_label, self.i_label, self.model, self.ref_list) + \ + create_reference_list(self.citations, True) + + +class TrimAndFillCaption: + def __init__(self): + self.e_label = "" + self.original_scatter = None + self.inferred_scatter = None + self.original_mean = None + self.inferred_mean = None + + def __str__(self): + original_mean_text = self.original_mean.style_text() + inferred_mean_text = self.inferred_mean.style_text() + original_marker_text = self.original_scatter.style_text() + inferred_marker_text = self.inferred_scatter.style_text() + new_cites = ["Duval_Tweedie_2000a", "Duval_Tweedie_2000b"] + return get_text("trim_fill_caption").format(self.e_label, "Duval and Tweedie 2000a, b", original_marker_text, + inferred_marker_text, original_mean_text, + inferred_mean_text) + create_reference_list(new_cites, True) + + +class ForestPlotBaseCaption: + def __init__(self): + self.e_label = "" + self.alpha = 0.05 + self.bootstrap_n = None + self.normal_ci = True + self.no_effect = None + self.means = None + self.medians = None + self.boot = None + self.bias = None + + def base_forest_plot_caption(self) -> str: + """ + Basic forest plot description + """ + return get_text("forest_plot_common_caption1").format(self.e_label, self.no_effect.style_text()) + + def mid_forest_plot_caption(self) -> str: + """ + Middle part of forest plot captions, when no study specific intervals are included + """ + if self.normal_ci: + dist_text = get_text("normal_ci_dist") + else: + dist_text = get_text("t_ci_dist") + return get_text("mid_forest_plot_caption").format(self.means.style_text(), 1-self.alpha, dist_text) + + def extra_forest_plot_caption(self, inc_median: bool = True) -> str: + """ + Final part of forest plot captions, to indicate median and bootstrap style markers + """ + text = "" + if inc_median: + text += get_text("forest_plot_median_caption").format(self.medians.style_text()) + if self.bootstrap_n is not None: + citation = "Adams_et_1997" + text += get_text("bootstrap_caption").format(self.bootstrap_n, get_citation(citation), + self.boot.style_text(), self.bias.style_text()) + \ + create_reference_list([citation], True) + return text + + +class ForestPlotCaption(ForestPlotBaseCaption): + def __str__(self): + return get_text("Forest plot of individual effect sizes for each study.") + \ + self.base_forest_plot_caption() + \ + get_text("study_forest_plot_extra").format(self.means.style_text(), 1-self.alpha) + + +class BasicAnalysisCaption(ForestPlotBaseCaption): + def __str__(self): + if self.normal_ci: + dist_text = get_text("normal_ci_dist") + else: + dist_text = get_text("mixed_ci_dist") + return get_text("Forest plot of individual effect sizes for each study, as well as the overall mean.") + \ + self.base_forest_plot_caption() + \ + get_text("basic_analysis_forest_plot_extra").format(self.means.style_text(), 1-self.alpha, dist_text) + \ + self.extra_forest_plot_caption() + + +class GroupedAnalysisCaption(ForestPlotBaseCaption): + def __init__(self): + super().__init__() + self.group_label = "" + + def __str__(self): + return get_text("group_forest_plot").format(self.group_label) + \ + self.base_forest_plot_caption() + self.mid_forest_plot_caption() + self.extra_forest_plot_caption() + + +class NestedAnalysisCaption(ForestPlotBaseCaption): + def __str__(self): + return get_text("nest_caption") + self.base_forest_plot_caption() + self.mid_forest_plot_caption() + \ + self.extra_forest_plot_caption() + + +class CumulativeAnalysisCaption(ForestPlotBaseCaption): + def __init__(self): + super().__init__() + self.order_label = "" + + def __str__(self): + return get_text("cumulative_forest_plot").format(self.order_label) + \ + self.base_forest_plot_caption() + self.mid_forest_plot_caption() + self.extra_forest_plot_caption() + + +class JackknifeAnalysisCaption(ForestPlotBaseCaption): + def __str__(self): + return get_text("jackknife_forest_plot") + self.base_forest_plot_caption() + self.mid_forest_plot_caption() + \ + self.extra_forest_plot_caption() + + +# ---------- Main Chart Data Class ---------- # class ChartData: """ an object to contain all data that appears on charts this will allow chart data exporting, as well as open up the possibility of figure editing """ - def __init__(self): + def __init__(self, caption_type): self.x_label = "" self.y_label = "" self.data = [] # special adjustments self.suppress_y = False self.rescale_x = None + # caption + if caption_type == "normal quantile": + self.caption = NormalQuantileCaption() + elif caption_type == "scatter plot": + self.caption = ScatterCaption() + elif caption_type == "histogram": + self.caption = HistogramCaption() + elif caption_type == "radial": + self.caption = RadialCaption() + elif caption_type == "regression": + self.caption = RegressionCaption() + elif caption_type == "trim and fill": + self.caption = TrimAndFillCaption() + elif caption_type == "basic analysis": + self.caption = BasicAnalysisCaption() + elif caption_type == "grouped analysis": + self.caption = GroupedAnalysisCaption() + elif caption_type == "nested analysis": + self.caption = NestedAnalysisCaption() + elif caption_type == "cumulative analysis": + self.caption = CumulativeAnalysisCaption() + elif caption_type == "jackknife analysis": + self.caption = JackknifeAnalysisCaption() + elif caption_type == "forest plot": + self.caption = ForestPlotCaption() + else: + self.caption = "" + + def caption_text(self): + return str(self.caption) def add_scatter(self, name: str, x_data, y_data, marker: Union[str, int] = "o", label="", zorder=0, color="#1f77b4", edgecolors="#1f77b4", size=36, linewidths=1.5, linestyle="solid"): @@ -412,6 +657,7 @@ def add_scatter(self, name: str, x_data, y_data, marker: Union[str, int] = "o", new_scatter.linewidths = linewidths new_scatter.linestyle = linestyle self.data.append(new_scatter) + return new_scatter def add_line(self, name: str, x_min, y_min, x_max, y_max, linestyle="solid", color="silver", zorder=0, linewidth=1.5): @@ -424,6 +670,7 @@ def add_line(self, name: str, x_min, y_min, x_max, y_max, linestyle="solid", col new_line.zorder = zorder new_line.linewidth = linewidth self.data.append(new_line) + return new_line def add_histogram(self, name, cnts, bins, linewidth=1, edgecolor="black", color="#1f77b4", linestyle="solid"): new_hist = HistogramData() @@ -435,6 +682,7 @@ def add_histogram(self, name, cnts, bins, linewidth=1, edgecolor="black", color= new_hist.edgecolor = edgecolor new_hist.color = color self.data.append(new_hist) + return new_hist def add_arc(self, name, xc, yc, height, width, start_angle, end_angle, zorder=0, edgecolor="silver", linestyle="solid", linewidth=1.5): @@ -451,6 +699,7 @@ def add_arc(self, name, xc, yc, height, width, start_angle, end_angle, zorder=0, new_arc.linestyle = linestyle new_arc.linewidth = linewidth self.data.append(new_arc) + return new_arc def add_multi_line(self, name, x_values, y_values, linestyle="solid", color="silver", zorder=0, linewidth=1.5): new_ml = LineData() @@ -462,6 +711,7 @@ def add_multi_line(self, name, x_values, y_values, linestyle="solid", color="sil new_ml.zorder = zorder new_ml.linewidth = linewidth self.data.append(new_ml) + return new_ml def add_ci(self, name, min_x, max_x, y, zorder=0, color="#1f77b4", linestyle="solid", linewidth=1.5): new_ci = ForestCIData() @@ -474,6 +724,7 @@ def add_ci(self, name, min_x, max_x, y, zorder=0, color="#1f77b4", linestyle="so new_ci.linestyle = linestyle new_ci.linewidth = linewidth self.data.append(new_ci) + return new_ci def add_labels(self, name, labels, y_data): new_labels = LabelData() @@ -481,6 +732,7 @@ def add_labels(self, name, labels, y_data): new_labels.labels = labels new_labels.y_pos = y_data self.data.append(new_labels) + return new_labels def add_annotations(self, name, annotations, x_data, y_data): new_annotation = AnnotationData() @@ -489,6 +741,7 @@ def add_annotations(self, name, annotations, x_data, y_data): new_annotation.y = y_data new_annotation.annotations = annotations self.data.append(new_annotation) + return new_annotation def export_to_list(self): outlist = ["X-axis label\t{}\n".format(self.x_label), @@ -557,9 +810,19 @@ def create_figure(chart_data): return figure_canvas -def chart_forest_plot(effect_name, forest_data, alpha: float = 0.05, - bootstrap_n: Optional[int] = None) -> Tuple[FigureCanvasQTAgg, ChartData]: - chart_data = ChartData() +def chart_forest_plot(analysis_type: str, effect_name, forest_data, alpha: float = 0.05, + bootstrap_n: Optional[int] = None, extra_name: Optional[str] = None, + normal_ci: bool = True) -> Tuple[FigureCanvasQTAgg, ChartData]: + chart_data = ChartData(analysis_type) + chart_data.caption.e_label = effect_name + chart_data.caption.alpha = alpha + chart_data.caption.bootstrap_n = bootstrap_n + chart_data.caption.normal_ci = normal_ci + if analysis_type == "grouped analysis": + chart_data.caption.group_label = extra_name + elif analysis_type == "cumulative analysis": + chart_data.caption.order_label = extra_name + chart_data.x_label = effect_name chart_data.suppress_y = True @@ -598,34 +861,29 @@ def chart_forest_plot(effect_name, forest_data, alpha: float = 0.05, bs_cis.extend([d.lower_bs_ci, d.upper_bs_ci]) bias_cis.extend([d.lower_bias_ci, d.upper_bias_ci]) - chart_data.add_line(get_text("Line of No Effect"), 0, 0, 0, -(n_effects+1), color="silver", linestyle="dotted", - zorder=1) + chart_data.caption.no_effect = chart_data.add_line(get_text("Line of No Effect"), 0, 0, 0, -(n_effects+1), + color="silver", linestyle="dotted", zorder=1) chart_data.add_ci(get_text("Confidence Intervals"), min_cis, max_cis, y_data, zorder=3) - chart_data.add_scatter(get_text("Means"), mean_data, y_data, marker="o", zorder=5, - label="mean and {:0.0%} CI (t-dist)".format(1-alpha)) + chart_data.caption.means = chart_data.add_scatter(get_text("Means"), mean_data, y_data, marker="o", zorder=5, + label="mean and {:0.0%} CI (t-dist)".format(1-alpha)) if median_data is not None: - chart_data.add_scatter(get_text("Medians"), median_data, y_data, marker="x", label="median", zorder=5, - color="#ff7f0e") + chart_data.caption.medians = chart_data.add_scatter(get_text("Medians"), median_data, y_data, marker="x", + label="median", zorder=5, color="#ff7f0e") chart_data.add_labels(get_text("Vertical Axis Tick Labels"), labels, y_data) if bootstrap: - chart_data.add_scatter(get_text("Bootstrap Confidence Limits"), bs_cis, ci_y_data, marker=6, zorder=4, - color="#2ca02c", label="{:0.0%} CI (bootstrap)".format(1-alpha)) + chart_data.caption.boot = chart_data.add_scatter(get_text("Bootstrap Confidence Limits"), bs_cis, ci_y_data, + marker=6, zorder=4, color="#2ca02c", + label="{:0.0%} CI (bootstrap)".format(1-alpha)) - chart_data.add_scatter(get_text("Bias-corrected Bootstrap Confidence Limits"), bias_cis, ci_y_data, marker=7, - zorder=4, color="#d62728", label="{:0.0%} CI (bias-corrected bootstrap)".format(1-alpha)) + chart_data.caption.bias = chart_data.add_scatter(get_text("Bias-corrected Bootstrap Confidence Limits"), + bias_cis, ci_y_data, marker=7, zorder=4, color="#d62728", + label="{:0.0%} CI (bias-corrected bootstrap)".format(1-alpha)) figure_canvas = create_figure(chart_data) return figure_canvas, chart_data -def caption_forest_plot_text(effect_name: str, alpha: float = 0.05, inc_median: bool = True) -> str: - text = get_text("forest_plot_common_caption").format(effect_name, 1 - alpha) - if inc_median: - text += get_text("forest_plot_median_caption") - return text - - def add_regression_to_chart(x_name: str, y_name: str, x_data, y_data, slope: float, intercept: float, x_min: float, x_max: float, chart_data) -> None: y_at_min = slope*x_min + intercept @@ -633,14 +891,22 @@ def add_regression_to_chart(x_name: str, y_name: str, x_data, y_data, slope: flo chart_data.x_label = x_name chart_data.y_label = y_name - chart_data.add_scatter(get_text("Point Data"), x_data, y_data, zorder=10) - chart_data.add_line(get_text("Regression Line"), x_min, y_at_min, x_max, y_at_max, zorder=8, color="silver") + chart_data.caption.regression_scatter = chart_data.add_scatter(get_text("Point Data"), x_data, y_data, zorder=10) + chart_data.caption.regression = chart_data.add_line(get_text("Regression Line"), x_min, y_at_min, x_max, y_at_max, + zorder=8, color="silver") -def chart_regression(x_name, y_name, x_data, y_data, slope, intercept) -> Tuple[FigureCanvasQTAgg, ChartData]: +def chart_regression(x_name, y_name, x_data, y_data, slope, intercept, model, ref_list, + citations) -> Tuple[FigureCanvasQTAgg, ChartData]: x_min = numpy.min(x_data) x_max = numpy.max(x_data) - chart_data = ChartData() + chart_data = ChartData("regression") + chart_data.caption.e_label = y_name + chart_data.caption.i_label = x_name + chart_data.caption.model = model + chart_data.caption.ref_list = ref_list + chart_data.caption.citations = citations + add_regression_to_chart(x_name, y_name, x_data, y_data, slope, intercept, x_min, x_max, chart_data) figure_canvas = create_figure(chart_data) @@ -668,21 +934,22 @@ def add_quantile_axes_to_chart(x_data, y_data, slope: float, intercept: float, c y_lower = [y_pos[i] - t_score*math.sqrt(mse*(1 + 1/n + ((x_pos[i] - x_mean)**2)/ss_x)) for i in range(nsteps)] y_upper = [y_pos[i] + t_score*math.sqrt(mse*(1 + 1/n + ((x_pos[i] - x_mean)**2)/ss_x)) for i in range(nsteps)] - chart_data.add_multi_line(get_text("Lower Prediction Limit"), x_pos, y_lower, linestyle="dashed", color="silver", - zorder=3) - chart_data.add_multi_line(get_text("Upper Prediction Limit"), x_pos, y_upper, linestyle="dashed", color="silver", - zorder=3) + chart_data.caption.lower_limit = chart_data.add_multi_line(get_text("Lower Prediction Limit"), x_pos, y_lower, + linestyle="dashed", color="silver", zorder=3) + chart_data.caption.upper_limit = chart_data.add_multi_line(get_text("Upper Prediction Limit"), x_pos, y_upper, + linestyle="dashed", color="silver", zorder=3) # draw center lines - chart_data.add_line(get_text("Horizontal Axis Mean Line"), 0, min(y_min, min(y_lower)), 0, max(y_max, max(y_upper)), - linestyle="dotted", color="silver") - chart_data.add_line(get_text("Vertical Axis Mean Line"), x_min, y_mean, x_max, y_mean, linestyle="dotted", - color="silver") + chart_data.caption.horizontal_mean = chart_data.add_line(get_text("Horizontal Axis Mean Line"), 0, + min(y_min, min(y_lower)), 0, max(y_max, max(y_upper)), + linestyle="dotted", color="silver") + chart_data.caption.vertical_mean = chart_data.add_line(get_text("Vertical Axis Mean Line"), x_min, y_mean, x_max, + y_mean, linestyle="dotted", color="silver") def chart_normal_quantile(x_name, y_name, x_data, y_data, slope, intercept, alpha: float = 0.05) -> Tuple[FigureCanvasQTAgg, ChartData]: - chart_data = ChartData() + chart_data = ChartData("normal quantile") add_quantile_axes_to_chart(x_data, y_data, slope, intercept, chart_data, alpha) x_min = numpy.min(x_data) x_max = numpy.max(x_data) @@ -769,7 +1036,8 @@ def add_radial_curve_to_chart(effect_label: str, r: float, min_e: float, max_e: def chart_radial(e_name, x_data, y_data, slope, min_e, max_e, is_log: bool = False) -> Tuple[FigureCanvasQTAgg, ChartData]: - chart_data = ChartData() + chart_data = ChartData("radial") + chart_data.caption.e_label = e_name max_d = numpy.max(numpy.sqrt(numpy.square(x_data) + numpy.square(y_data)))+1 x_min = 0 x_max = (max_d + 1)*math.cos(math.atan(slope)) @@ -782,15 +1050,19 @@ def chart_radial(e_name, x_data, y_data, slope, min_e, max_e, is_log: bool = Fal return figure_canvas, chart_data -def chart_histogram(e_data, w_data, n_bins, e_label, weighted: bool = False) -> Tuple[FigureCanvasQTAgg, ChartData]: - if weighted: - cnts, bins = numpy.histogram(e_data, n_bins, weights=w_data) - y_label = get_text("Weighted Count") - else: +def chart_histogram(e_data, w_data, n_bins, e_label, + weighted: int = WEIGHT_NONE) -> Tuple[FigureCanvasQTAgg, ChartData]: + if weighted == WEIGHT_NONE: cnts, bins = numpy.histogram(e_data, n_bins) y_label = get_text("Count") + else: + cnts, bins = numpy.histogram(e_data, n_bins, weights=w_data) + y_label = get_text("Weighted Count") + + chart_data = ChartData("histogram") + chart_data.caption.e_label = e_label + chart_data.caption.weight_type = weighted - chart_data = ChartData() chart_data.x_label = e_label chart_data.y_label = y_label chart_data.add_histogram("Bin Counts", cnts, bins, edgecolor="black", linewidth=1) @@ -893,7 +1165,9 @@ def chart_phylogeny(root) -> FigureCanvasQTAgg: def chart_scatter(x_data, y_data, x_label: str = "x", y_label: str = "y") -> Tuple[FigureCanvasQTAgg, ChartData]: - chart_data = ChartData() + chart_data = ChartData("scatter plot") + chart_data.caption.x_label = x_label + chart_data.caption.y_label = y_label chart_data.x_label = x_label chart_data.y_label = y_label chart_data.add_scatter(get_text("Point Data"), x_data, y_data) @@ -903,27 +1177,51 @@ def chart_scatter(x_data, y_data, x_label: str = "x", y_label: str = "y") -> Tup def chart_trim_fill_plot(effect_label, data, n, original_mean, new_mean) -> Tuple[FigureCanvasQTAgg, ChartData]: - chart_data = ChartData() + chart_data = ChartData("trim and fill") + chart_data.caption.e_label = effect_label chart_data.x_label = effect_label chart_data.y_label = "{} (1/SE)".format(get_text("Precision")) # plot original points x_data = data[:n, 0] y_data = numpy.reciprocal(numpy.sqrt(data[:n, 2])) - chart_data.add_scatter(get_text("Original Data"), x_data, y_data, color="black", edgecolors="black") + chart_data.caption.original_scatter = chart_data.add_scatter(get_text("Original Data"), x_data, y_data, + color="black", edgecolors="black") y_min = numpy.min(y_data) y_max = numpy.max(y_data) # plot inferred points x_data = data[n:, 0] y_data = numpy.reciprocal(numpy.sqrt(data[n:, 2])) - chart_data.add_scatter(get_text("Inferred Data"), x_data, y_data, edgecolors="red", color="none") + chart_data.caption.inferred_scatter = chart_data.add_scatter(get_text("Inferred Data"), x_data, y_data, + edgecolors="red", color="none") # draw original and new mean - chart_data.add_line(get_text("Original Mean"), original_mean, y_min, original_mean, y_max, color="silver", - linestyle="dashed", zorder=1) - chart_data.add_line(get_text("Inferred Mean"), new_mean, y_min, new_mean, y_max, color="red", linestyle="dashed", - zorder=1) + chart_data.caption.original_mean = chart_data.add_line(get_text("Original Mean"), original_mean, y_min, + original_mean, y_max, color="silver", linestyle="dashed", + zorder=1) + chart_data.caption.inferred_mean = chart_data.add_line(get_text("Inferred Mean"), new_mean, y_min, new_mean, y_max, + color="red", linestyle="dashed", zorder=1) figure_canvas = create_figure(chart_data) return figure_canvas, chart_data + + +def find_color_name(color: str) -> str: + """ + Given a color as a hex string, e.g., #0123A5, find the closest named color from the CSS 4 color name list + and return that name + """ + names = list(XKCD_COLORS) + dist = 10000 + match = "None" + r, g, b = hex2color(color) + for n in names: + rx, gx, bx = hex2color(XKCD_COLORS[n]) + # Squared Euclidean distance in RGB space should be good enough + # Squared is more computationally efficient than non-squared, as we skip calculating the square-root + d = (rx-r)**2 + (gx-g)**2 + (bx-b)**2 + if d < dist: + match = n + dist = d + return match[5:] diff --git a/src/MetaWinConstants.py b/src/MetaWinConstants.py index bf4efea..5eeaba1 100644 --- a/src/MetaWinConstants.py +++ b/src/MetaWinConstants.py @@ -13,7 +13,7 @@ MAJOR_VERSION = 3 MINOR_VERSION = 0 -PATCH_VERSION = 6 +PATCH_VERSION = 7 # validity check when fetching value from data matrix VALUE_NUMBER = 0 @@ -79,6 +79,7 @@ def resource_path(relative_path: str, inc_file: bool = False) -> str: save_graph_icon = resource_path(icon_path + "save-filled-picture-filled@256px.png") edit_graph_icon = resource_path(icon_path + "picture-edit-filled@256px.png") export_graph_data_icon = resource_path(icon_path + "data-export@256px.png") +analysis_options_icon = resource_path(icon_path + "sum-gear-filled@256px.png") language_icon = resource_path(icon_path + "translation@256px.png") alpha_icon = resource_path(icon_path + "letter-alpha@256px.png") show_toolbar_icon = resource_path(icon_path + "toolbar-position-left-add-filled@256px.png") diff --git a/src/MetaWinDraw.py b/src/MetaWinDraw.py index 8e45445..bc53f10 100644 --- a/src/MetaWinDraw.py +++ b/src/MetaWinDraw.py @@ -22,17 +22,10 @@ import MetaWinConstants from MetaWinConstants import mean_data_tuple from MetaWinWidgets import add_ok_cancel_help_button_layout, add_effect_choice_to_dialog -from MetaWinUtils import create_reference_list, get_citation import MetaWinCharts from MetaWinLanguage import get_text -# weighting options for the histograms -WEIGHT_NONE = 0 -WEIGHT_INVVAR = 1 -WEIGHT_N = 2 - - class MetaAnalysisDrawScatterDialog(QDialog): def __init__(self, data: MetaWinData): super().__init__() @@ -378,9 +371,9 @@ def draw_scatter_plot(data, x_data_col, y_data_col): x_data = numpy.array(x_data) y_data = numpy.array(y_data) figure, chart_data = MetaWinCharts.chart_scatter(x_data, y_data, x_data_col.label, y_data_col.label) - fig_caption = get_text("Scatter plot of {} vs. {}.").format(y_data_col.label, x_data_col.label) - return figure, fig_caption, chart_data - return None, None, None + # fig_caption = get_text("Scatter plot of {} vs. {}.").format(y_data_col.label, x_data_col.label) + return figure, chart_data + return None, None def draw_scatter_dialog(sender, data): @@ -388,12 +381,12 @@ def draw_scatter_dialog(sender, data): if sender.draw_dialog.exec(): x_data_col = sender.draw_dialog.columns[sender.draw_dialog.x_box.currentIndex()] y_data_col = sender.draw_dialog.columns[sender.draw_dialog.y_box.currentIndex()] - figure, fig_caption, chart_data = draw_scatter_plot(data, x_data_col, y_data_col) + figure, chart_data = draw_scatter_plot(data, x_data_col, y_data_col) if figure is not None: - return figure, fig_caption, chart_data + return figure, chart_data else: MetaWinMessages.report_critical(sender, "Error", "No valid data found for given options.") - return None, None, None + return None, None def calculate_regression(x: numpy.array, y: numpy.array) -> Tuple[float, float]: @@ -439,11 +432,11 @@ def draw_normal_quantile_plot(data, e_data_col, v_data_col, alpha: float = 0.05) figure, chart_data = MetaWinCharts.chart_normal_quantile(get_text("Normal Quantile"), get_text("Standardized Effect Size"), x_data, y_data, slope, intercept, alpha) - fig_caption = get_text("normal_quantile_caption").format(get_citation("Wang_and_Bushman_1998")) + \ - create_reference_list(["Wang_and_Bushman_1998"], True) + # fig_caption = get_text("normal_quantile_caption").format(get_citation("Wang_and_Bushman_1998")) + \ + # create_reference_list(["Wang_and_Bushman_1998"], True) - return figure, fig_caption, chart_data - return None, None, None + return figure, chart_data + return None, None def draw_normal_quantile_dialog(sender, data, last_effect, last_var, alpha: float = 0.05): @@ -451,12 +444,12 @@ def draw_normal_quantile_dialog(sender, data, last_effect, last_var, alpha: floa if sender.draw_dialog.exec(): e_data_col = sender.draw_dialog.columns[sender.draw_dialog.effect_size_box.currentIndex()] v_data_col = sender.draw_dialog.columns[sender.draw_dialog.variance_box.currentIndex()] - figure, fig_caption, chart_data = draw_normal_quantile_plot(data, e_data_col, v_data_col, alpha) + figure, chart_data = draw_normal_quantile_plot(data, e_data_col, v_data_col, alpha) if figure is not None: - return figure, fig_caption, chart_data + return figure, chart_data else: MetaWinMessages.report_critical(sender, "Error", "No valid data found for given options.") - return None, None, None + return None, None def draw_radial_plot(data, e_data_col, v_data_col, is_log: bool = False): @@ -489,10 +482,10 @@ def draw_radial_plot(data, e_data_col, v_data_col, is_log: bool = False): figure, chart_data = MetaWinCharts.chart_radial(e_data_col.label, x_data, y_data, slope_through_origin, min_e, max_e, is_log) - fig_caption = get_text("Radial_chart_caption").format(e_data_col.label) - fig_caption += create_reference_list(["Galbraith_1988", "Galbraith_1994"], True) - return figure, fig_caption, chart_data - return None, None, None + # fig_caption = get_text("Radial_chart_caption").format(e_data_col.label) + # fig_caption += create_reference_list(["Galbraith_1988", "Galbraith_1994"], True) + return figure, chart_data + return None, None def draw_radial_dialog(sender, data, last_effect, last_var): @@ -501,12 +494,12 @@ def draw_radial_dialog(sender, data, last_effect, last_var): e_data_col = sender.draw_dialog.columns[sender.draw_dialog.effect_size_box.currentIndex()] v_data_col = sender.draw_dialog.columns[sender.draw_dialog.variance_box.currentIndex()] is_log = sender.draw_dialog.log_transform_box.isChecked() - figure, fig_caption, chart_data = draw_radial_plot(data, e_data_col, v_data_col, is_log) + figure, chart_data = draw_radial_plot(data, e_data_col, v_data_col, is_log) if figure is not None: - return figure, fig_caption, chart_data + return figure, chart_data else: MetaWinMessages.report_critical(sender, "Error", "No valid data found for given options.") - return None, None, None + return None, None def draw_histogram_plot(data, e_data_col, w_data_col, weight_type, n_bins: int): @@ -517,10 +510,10 @@ def draw_histogram_plot(data, e_data_col, w_data_col, weight_type, n_bins: int): for r, row in enumerate(data.rows): if row.not_filtered(): e = data.check_value(r, e_data_col.position(), value_type=MetaWinConstants.VALUE_NUMBER) - if weight_type != WEIGHT_NONE: + if weight_type != MetaWinCharts.WEIGHT_NONE: w = data.check_value(r, w_data_col.position(), value_type=MetaWinConstants.VALUE_NUMBER) if (e is not None) and (w is not None): - if weight_type == WEIGHT_INVVAR: + if weight_type == MetaWinCharts.WEIGHT_INVVAR: if w > 0: w_data.append(1/w) e_data.append(e) @@ -540,20 +533,9 @@ def draw_histogram_plot(data, e_data_col, w_data_col, weight_type, n_bins: int): if len(e_data) > 0: e_data = numpy.array(e_data) w_data = numpy.array(w_data) - if weight_type == WEIGHT_NONE: - weighted = False - else: - weighted = True - - figure, chart_data = MetaWinCharts.chart_histogram(e_data, w_data, n_bins, e_data_col.label, weighted) - fig_caption = get_text("Histogram of {} from individual studies.").format(e_data_col.label) - if weight_type == WEIGHT_INVVAR: - fig_caption += get_text(" Counts were weighted by the inverse of the variance of each effect size.") - elif weight_type == WEIGHT_N: - fig_caption += get_text(" Counts were weighted by a sample size associated with each effect size.") - return figure, fig_caption, chart_data - else: - return None, None, None + figure, chart_data = MetaWinCharts.chart_histogram(e_data, w_data, n_bins, e_data_col.label, weight_type) + return figure, chart_data + return None, None def draw_histogram_dialog(sender, data, last_effect, last_var): @@ -562,18 +544,18 @@ def draw_histogram_dialog(sender, data, last_effect, last_var): e_data_col = sender.draw_dialog.columns[sender.draw_dialog.effect_size_box.currentIndex()] w_data_col = sender.draw_dialog.columns[sender.draw_dialog.weight_box.currentIndex()] if sender.draw_dialog.weight_var.isChecked(): - weight_type = WEIGHT_INVVAR + weight_type = MetaWinCharts.WEIGHT_INVVAR elif sender.draw_dialog.weight_n.isChecked(): - weight_type = WEIGHT_N + weight_type = MetaWinCharts.WEIGHT_N else: - weight_type = WEIGHT_NONE + weight_type = MetaWinCharts.WEIGHT_NONE n_bins = int(sender.draw_dialog.bin_edit.text()) - figure, fig_caption, chart_data = draw_histogram_plot(data, e_data_col, w_data_col, weight_type, n_bins) + figure, chart_data = draw_histogram_plot(data, e_data_col, w_data_col, weight_type, n_bins) if figure is not None: - return figure, fig_caption, chart_data + return figure, chart_data else: MetaWinMessages.report_critical(sender, "Error", "No valid data found for given options.") - return None, None, None + return None, None def draw_forest_plot(data, e_data_col, v_data_col, alpha: float = 0.05): @@ -594,11 +576,11 @@ def draw_forest_plot(data, e_data_col, v_data_col, alpha: float = 0.05): else: filtered.append(row.label) - figure, chart_data = MetaWinCharts.chart_forest_plot(e_data_col.label, data_list, alpha, None) + figure, chart_data = MetaWinCharts.chart_forest_plot("forest plot", e_data_col.label, data_list, alpha, None) - fig_caption = get_text("Forest plot of individual effect sizes for each study.") + \ - MetaWinCharts.caption_forest_plot_text(e_data_col.label, alpha, inc_median=False) - return figure, fig_caption, chart_data + # fig_caption = get_text("Forest plot of individual effect sizes for each study.") + \ + # MetaWinCharts.common_forest_plot_caption(e_data_col.label, alpha, inc_median=False) + return figure, chart_data def draw_forest_dialog(sender, data, last_effect, last_var, alpha: float = 0.05): @@ -606,12 +588,12 @@ def draw_forest_dialog(sender, data, last_effect, last_var, alpha: float = 0.05) if sender.draw_dialog.exec(): e_data_col = sender.draw_dialog.columns[sender.draw_dialog.effect_size_box.currentIndex()] v_data_col = sender.draw_dialog.columns[sender.draw_dialog.variance_box.currentIndex()] - figure, fig_caption, chart_data = draw_forest_plot(data, e_data_col, v_data_col, alpha) + figure, chart_data = draw_forest_plot(data, e_data_col, v_data_col, alpha) if figure is not None: - return figure, fig_caption, chart_data + return figure, chart_data else: MetaWinMessages.report_critical(sender, "Error", "No valid data found for given options.") - return None, None, None + return None, None def edit_figure(sender, chart_data): diff --git a/src/MetaWinLanguage.py b/src/MetaWinLanguage.py index 2f5f335..064470f 100644 --- a/src/MetaWinLanguage.py +++ b/src/MetaWinLanguage.py @@ -22,9 +22,9 @@ "Basic Meta-Analysis": "Basic Meta-Analysis", "Between": "Between", "Bootstrap Mean Effect Size(s)": "Bootstrap Mean Effect Size(s)", - "bootstrap_caption": " Upward-pointing triangles mark the confidence interval from a " - "bootstrap ({:,} iterations) procedure, following {}; downward-pointing " - "triangles mark the bias-corrected bootstrap interval.", + "bootstrap_caption": " Confidence intervals from a boostrap ({:,} iterations) procedure, " + "following {}, are indicated by {}; the bias-corrected bootstrap interval " + "is indicated by {}.", "Bootstrap Confidence Limits": "Bootstrap Confidence Limits", "Bias-corrected Bootstrap Confidence Limits": "Bias-corrected Bootstrap Confidence Limits", "Calculate Effect Sizes": "Calculate Effect Sizes", @@ -132,11 +132,24 @@ "Forest plot of individual effect sizes for each study.", "Forest plot of individual effect sizes for each study, as well as the overall mean.": "Forest plot of individual effect sizes for each study, as well as the overall mean.", - "forest_plot_common_caption": " Effect size measured as {}. The dotted vertical line " - "represents no effect, or a mean of zero. Circles represent " - "mean effect size, with the corresponding line " - "the {:0.0%} confidence interval.", - "forest_plot_median_caption": " X's represent the median.", + "study_forest_plot_extra": " Study effect sizes are indicated by {}, with the corresponding line " + "the {:0.0%} confidence interval based on a Normal distribution.", + "basic_analysis_forest_plot_extra": " Study and mean effect sizes are indicated by {}, with the " + "corresponding line the {:0.0%} confidence interval based on " + "{}.", + "mid_forest_plot_caption": " Mean effect sizes are indicated by {}, with the corresponding " + "line the {:0.0%} confidence interval based on {}.", + "normal_ci_dist": "the Normal distribution", + "t_ci_dist": "Student's t distribution", + "mixed_ci_dist": "Student's t distribution for the mean and the Normal distribution for the " + "individual studies", + "forest_plot_common_caption1": " Effect size measured as {}. The vertical {} represents no " + "effect.", + "forest_plot_common_caption2": " Effect size measured as {}. The vertical {} " + "represents no effect. The mean effect size is " + "indicated by {}, with the corresponding line the {:0.0%} " + "confidence interval.", + "forest_plot_median_caption": " Medians are represented by {}.", "group_forest_plot": "Forest plot of effect sizes for the mean of all studies, as well as subgroups of studies " "designated by {}.", @@ -196,6 +209,8 @@ "Log Transformed Measure": "Log Transformed Measure", "Lower Prediction Limit": "Lower Prediction Limit", "Markdown": "Markdown", + "marker_style_text": "{} {}{} with a {} border", + "marker_style_open_text": "open {}{} with a {} border", "Mean": "Mean", "Means": "Means", "Means and Standard Deviations": "Means and Standard Deviations", @@ -225,11 +240,15 @@ "Normal Quantile": "Normal Quantile", "Normal Quantile Plot": "Normal Quantile Plot", "normal_quantile_caption": "Normal Quantile plot following {}. The " - "standardized effect size is the efffect size divided by the " - "square-root of its variance. The solid line represents the " - "regression and the dashed lines the 95% prediction envelope.", + "standardized effect size is the effect size divided by the " + "square-root of its variance. ", + "normal_quantile_style1": "The {} represents the regression and the {}s the 95% prediction " + "envelope.", + "normal_quantile_style2": "The {} represents the regression and the {} and {} the upper and " + "lower limits of the 95% prediction envelope, respectively.", "note_funnel_plot": "Note: A funnel plot is just a scatter plot of a metric (such as a
" "mean or effect size) vs. it\'s variance or sample size.", + "nothing (marker is invisible)": "nothing (marker is invisible)", "Number of Bins": "Number of Bins", "Number of decimal places": "Number of decimal places", "Number of Decimal Places to Display": "Number of Decimal Places to Display", @@ -338,9 +357,9 @@ "Trim and Fill Analysis estimated {} missing studies.": "Trim and Fill Analysis estimated {} missing studies.", "trim_fill_caption": "Funnel plot of {} vs. precision, showing the results of a Trim and " - "Fill Analysis ({}). Solid black circles represent the original data; " - "open red circles represent inferred \"missing\" data. The dashed line " - "represents the mean effect size of the original data, the dotted line " + "Fill Analysis ({}). Original data are represented by {}, inferred " + "\"missing\" data by {}. The {} " + "represents the mean effect size of the original data, the {} " "the mean effect size including the inferred data.", "Two x Two Contingency Table": "Two x Two Contingency Table", "Unable to write to ": "Unable to write to ", diff --git a/src/MetaWinMain.py b/src/MetaWinMain.py index 2270a55..2fde591 100644 --- a/src/MetaWinMain.py +++ b/src/MetaWinMain.py @@ -77,7 +77,6 @@ def __init__(self, config: dict): self.empty_col_num = 10 self.empty_row_num = 15 self.chart_data = None - self.chart_caption = "" self.init_ui() def init_ui(self): @@ -171,7 +170,7 @@ def init_ui(self): options_menu.addMenu(data_options_menu) # analysis options submenu analysis_options_menu = QMenu(get_text("Analysis Options"), self) - # analysis_options_menu.setIcon(QIcon(MetaWinConstants.output_icon)) + analysis_options_menu.setIcon(QIcon(MetaWinConstants.analysis_options_icon)) output_alpha_action = QAction(QIcon(MetaWinConstants.alpha_icon), get_text("Significance Level"), self) output_alpha_action.triggered.connect(self.set_alpha_significance) analysis_options_menu.addAction(output_alpha_action) @@ -696,18 +695,18 @@ def meta_analysis(self) -> None: norm_ci = False else: norm_ci = True - output, figure, fig_caption, chart_data = MetaWinAnalysis.meta_analysis(self, self.data, self.last_effect, - self.last_var, self.output_decimals, - self.alpha, self.phylogeny, norm_ci) + output, figure, chart_data = MetaWinAnalysis.meta_analysis(self, self.data, self.last_effect, + self.last_var, self.output_decimals, + self.alpha, self.phylogeny, norm_ci) if output is not None: self.write_multi_output_blocks(output) self.main_area.setCurrentIndex(1) if figure is not None: - self.show_figure(figure, fig_caption, chart_data) + self.show_figure(figure, chart_data) else: MetaWinMessages.report_warning(self, get_text("Warning"), get_text("No data has been loaded.")) - def show_figure(self, figure, fig_caption: str, chart_data) -> None: + def show_figure(self, figure, chart_data) -> None: """ Replace the current figure in the graphics tab with a new figure, toolbar, and caption """ @@ -720,8 +719,7 @@ def show_figure(self, figure, fig_caption: str, chart_data) -> None: toolbar = NavigationToolbar2QT(figure, None) self.save_graph_action.triggered.connect(toolbar.save_figure) caption_box = QTextEdit() - caption_box.setText(fig_caption) - self.chart_caption = fig_caption + caption_box.setText(chart_data.caption_text()) self.graph_layout.addWidget(figure, stretch=8) self.graph_layout.addWidget(caption_box, stretch=1) self.chart_data = chart_data @@ -744,41 +742,39 @@ def refresh_tree_panel(self) -> None: def draw_scatter_plot(self) -> None: if self.data is not None: - figure, fig_caption, chart_data = MetaWinDraw.draw_scatter_dialog(self, self.data) + figure, chart_data = MetaWinDraw.draw_scatter_dialog(self, self.data) if figure is not None: - self.show_figure(figure, fig_caption, chart_data) + self.show_figure(figure, chart_data) self.main_area.setCurrentIndex(2) def draw_histogram(self) -> None: if self.data is not None: - figure, fig_caption, chart_data = MetaWinDraw.draw_histogram_dialog(self, self.data, self.last_effect, - self.last_var) + figure, chart_data = MetaWinDraw.draw_histogram_dialog(self, self.data, self.last_effect, self.last_var) if figure is not None: - self.show_figure(figure, fig_caption, chart_data) + self.show_figure(figure, chart_data) self.main_area.setCurrentIndex(2) def draw_normal_quantile_plot(self) -> None: if self.data is not None: - figure, fig_caption, chart_data = MetaWinDraw.draw_normal_quantile_dialog(self, self.data, self.last_effect, - self.last_var) + figure, chart_data = MetaWinDraw.draw_normal_quantile_dialog(self, self.data, self.last_effect, + self.last_var) if figure is not None: - self.show_figure(figure, fig_caption, chart_data) + self.show_figure(figure, chart_data) self.main_area.setCurrentIndex(2) def draw_radial_plot(self) -> None: if self.data is not None: - figure, fig_caption, chart_data = MetaWinDraw.draw_radial_dialog(self, self.data, self.last_effect, - self.last_var) + figure, chart_data = MetaWinDraw.draw_radial_dialog(self, self.data, self.last_effect, self.last_var) if figure is not None: - self.show_figure(figure, fig_caption, chart_data) + self.show_figure(figure, chart_data) self.main_area.setCurrentIndex(2) def draw_forest_plot(self) -> None: if self.data is not None: - figure, fig_caption, chart_data = MetaWinDraw.draw_forest_dialog(self, self.data, self.last_effect, - self.last_var, self.alpha) + figure, chart_data = MetaWinDraw.draw_forest_dialog(self, self.data, self.last_effect, self.last_var, + self.alpha) if figure is not None: - self.show_figure(figure, fig_caption, chart_data) + self.show_figure(figure, chart_data) self.main_area.setCurrentIndex(2) def clear_filters(self) -> None: @@ -851,8 +847,7 @@ def edit_graph(self): if self.chart_data is not None: figure = MetaWinDraw.edit_figure(self, self.chart_data) if figure is not None: - caption = self.chart_caption - self.show_figure(figure, caption, self.chart_data) + self.show_figure(figure, self.chart_data) def change_conf_int_distribution(self): if self.confidence_interval_dist == "Normal": diff --git a/tests/test_metawin.py b/tests/test_metawin.py index b47364a..404521d 100644 --- a/tests/test_metawin.py +++ b/tests/test_metawin.py @@ -15,6 +15,7 @@ from typing import Tuple from PyQt6.QtWidgets import QDialog, QVBoxLayout, QFrame, QPushButton, QTextEdit +import matplotlib.colors as mcolors # note these may be marked by the IDE as unknown modules, but pytest.ini will resolve the errors when tests # are actually executed @@ -28,7 +29,7 @@ import MetaWinDraw -TEST_FIGURES = False +TEST_FIGURES = True class TestFigureDialog(QDialog): @@ -291,11 +292,11 @@ def test_simple_meta_analysis(): options.rosenberg_failsafe = 0.05 options.create_graph = True - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() mean_values = analysis_values.mean_data @@ -328,11 +329,11 @@ def test_simple_meta_analysis_lep(): options.effect_vars = data.cols[5] options.create_graph = True - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() mean_values = analysis_values.mean_data @@ -365,11 +366,11 @@ def test_simple_meta_analysis_lep_randeff(): options.effect_vars = data.cols[5] options.create_graph = True - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() mean_values = analysis_values.mean_data @@ -405,7 +406,7 @@ def test_simple_meta_analysis_random_effects(): options.rosenthal_failsafe = 0.05 options.rosenberg_failsafe = 0.05 - output, _, _, _, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, _, _, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) mean_values = analysis_values.mean_data @@ -443,10 +444,10 @@ def test_simple_meta_analysis_bootstrap(): options.bootstrap_mean = 9999 options.create_graph = True - output, figure, fig_caption, chart_data, _ = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, _ = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() @@ -483,11 +484,11 @@ def test_group_meta_analysis_lep_suborders(): options.groups = data.cols[1] options.create_graph = True - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() global_values = analysis_values.global_values @@ -537,11 +538,11 @@ def test_group_meta_analysis_lep_suborders_rand_eff(): options.random_effects = True options.create_graph = True - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() global_values = analysis_values.global_values @@ -618,11 +619,11 @@ def test_group_meta_analysis_lep_families(): options.create_graph = True data.cols[2].group_filter = ["Yponomeutidae", "Lycaenidae", "Hesperiidae"] - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() global_values = analysis_values.global_values @@ -661,10 +662,10 @@ def test_group_meta_analysis_bootstrap(): options.bootstrap_mean = 9999 options.create_graph = True - output, figure, fig_caption, chart_data, _ = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, _ = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() @@ -681,10 +682,10 @@ def test_group_meta_analysis_lrr(): options.create_graph = True - output, figure, fig_caption, chart_data, _ = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, _ = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() @@ -713,10 +714,10 @@ def test_cumulative_meta_analysis(): options.create_graph = True - output, figure, fig_caption, chart_data, _ = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, _ = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() @@ -732,10 +733,10 @@ def test_cumulative_meta_analysis_bootstrap(): options.create_graph = True - output, figure, fig_caption, chart_data, _ = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, _ = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() @@ -761,11 +762,11 @@ def test_regression_meta_analysis_lep(): options.independent_variable = data.cols[3] options.create_graph = True - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() global_values = analysis_values.global_values @@ -807,11 +808,11 @@ def test_regression_meta_analysis_lep_randeff(): options.random_effects = True options.create_graph = True - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() global_values = analysis_values.global_values @@ -872,7 +873,7 @@ def test_complex_meta_analysis_lep_simple_regression(): options.continuous_vars = [data.cols[3]] options.create_graph = True - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, _, _, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) global_values = analysis_values.global_values @@ -918,7 +919,7 @@ def test_complex_meta_analysis_lep_group(): options.continuous_vars = [] options.create_graph = True - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, _, _, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) global_values = analysis_values.global_values @@ -966,7 +967,7 @@ def test_complex_meta_analysis_lep(): options.continuous_vars = [data.cols[3]] options.create_graph = True - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, _, _, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) global_values = analysis_values.global_values @@ -1025,7 +1026,7 @@ def test_complex_meta_analysis_lep_randomeff(): options.random_effects = True options.create_graph = True - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, _, _, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) global_values = analysis_values.global_values @@ -1088,11 +1089,11 @@ def test_nested_meta_analysis_lep(): options.create_graph = True data.cols[2].group_filter = ["Yponomeutidae", "Lycaenidae", "Hesperiidae"] - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() global_values = analysis_values.global_values @@ -1200,8 +1201,8 @@ def test_scatter_plot(): x_col = data.cols[11] y_col = data.cols[10] if TEST_FIGURES: - figure, fig_caption, _ = MetaWinDraw.draw_scatter_plot(data, x_col, y_col) - test_win = TestFigureDialog(figure, fig_caption) + figure, chart_data = MetaWinDraw.draw_scatter_plot(data, x_col, y_col) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() @@ -1210,8 +1211,8 @@ def test_normal_quantile_plot(): e_col = data.cols[10] v_col = data.cols[11] if TEST_FIGURES: - figure, fig_caption, _ = MetaWinDraw.draw_normal_quantile_plot(data, e_col, v_col) - test_win = TestFigureDialog(figure, fig_caption) + figure, chart_data = MetaWinDraw.draw_normal_quantile_plot(data, e_col, v_col) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() @@ -1220,8 +1221,8 @@ def test_radial_plot_d(): e_col = data.cols[10] v_col = data.cols[11] if TEST_FIGURES: - figure, fig_caption, _ = MetaWinDraw.draw_radial_plot(data, e_col, v_col, False) - test_win = TestFigureDialog(figure, fig_caption) + figure, chart_data = MetaWinDraw.draw_radial_plot(data, e_col, v_col, False) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() @@ -1230,8 +1231,8 @@ def test_radial_plot_lnrr(): e_col = data.cols[10] v_col = data.cols[11] if TEST_FIGURES: - figure, fig_caption, _ = MetaWinDraw.draw_radial_plot(data, e_col, v_col, True) - test_win = TestFigureDialog(figure, fig_caption) + figure, chart_data = MetaWinDraw.draw_radial_plot(data, e_col, v_col, True) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() @@ -1240,8 +1241,8 @@ def test_histogram_d_unweighted(): e_col = data.cols[10] v_col = data.cols[11] if TEST_FIGURES: - figure, fig_caption, _ = MetaWinDraw.draw_histogram_plot(data, e_col, v_col, 0, 10) - test_win = TestFigureDialog(figure, fig_caption) + figure, chart_data = MetaWinDraw.draw_histogram_plot(data, e_col, v_col, 0, 10) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() @@ -1250,8 +1251,8 @@ def test_histogram_d_weighted_invvar(): e_col = data.cols[10] v_col = data.cols[11] if TEST_FIGURES: - figure, fig_caption, _ = MetaWinDraw.draw_histogram_plot(data, e_col, v_col, 1, 10) - test_win = TestFigureDialog(figure, fig_caption) + figure, chart_data = MetaWinDraw.draw_histogram_plot(data, e_col, v_col, 1, 10) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() @@ -1260,8 +1261,8 @@ def test_histogram_d_weighted_sample_size(): e_col = data.cols[10] v_col = data.cols[2] if TEST_FIGURES: - figure, fig_caption, _ = MetaWinDraw.draw_histogram_plot(data, e_col, v_col, 2, 15) - test_win = TestFigureDialog(figure, fig_caption) + figure, chart_data = MetaWinDraw.draw_histogram_plot(data, e_col, v_col, 2, 15) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() @@ -1270,8 +1271,8 @@ def test_forest_plot(): e_col = data.cols[10] v_col = data.cols[11] if TEST_FIGURES: - figure, fig_caption, _ = MetaWinDraw.draw_forest_plot(data, e_col, v_col) - test_win = TestFigureDialog(figure, fig_caption) + figure, chart_data = MetaWinDraw.draw_forest_plot(data, e_col, v_col) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() @@ -1296,11 +1297,11 @@ def test_trim_and_fill_analysis(): options.create_graph = True options.k_estimator = "L" - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() @@ -1325,11 +1326,11 @@ def test_trim_and_fill_analysis_negative_mean(): options.create_graph = True options.k_estimator = "R" - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() @@ -1354,11 +1355,11 @@ def test_jackknife(): options.effect_vars = data.cols[5] options.create_graph = True - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) if TEST_FIGURES: - test_win = TestFigureDialog(figure, fig_caption) + test_win = TestFigureDialog(figure, chart_data.caption_text()) test_win.exec() @@ -1377,22 +1378,21 @@ def test_phylogenetic_simple_test(): options.tip_names = data.cols[0] print("UNWEIGHTED") options.structure = MetaWinAnalysis.SIMPLE_MA - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) print() print() print("WEIGHTED") options.effect_vars = data.cols[2] - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) print() print() print("PHYLOGENETIC") options.structure = MetaWinAnalysis.PHYLOGENETIC_MA - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4, - tree=tree) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4, tree=tree) print_test_output(output) @@ -1409,12 +1409,11 @@ def test_phylogenetic_glm_simple(): options.random_effects = True options.structure = MetaWinAnalysis.SIMPLE_MA - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) options.structure = MetaWinAnalysis.PHYLOGENETIC_MA print(data.column_labels()[1]) - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4, - tree=tree) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4, tree=tree) print_test_output(output) print() @@ -1426,11 +1425,10 @@ def test_phylogenetic_glm_simple(): options.effect_vars = data.cols[4] options.structure = MetaWinAnalysis.SIMPLE_MA - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) options.structure = MetaWinAnalysis.PHYLOGENETIC_MA - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4, - tree=tree) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4, tree=tree) print_test_output(output) print() @@ -1441,11 +1439,10 @@ def test_phylogenetic_glm_simple(): options.effect_data = data.cols[5] options.effect_vars = data.cols[6] options.structure = MetaWinAnalysis.SIMPLE_MA - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) options.structure = MetaWinAnalysis.PHYLOGENETIC_MA - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4, - tree=tree) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4, tree=tree) print_test_output(output) print() @@ -1456,11 +1453,10 @@ def test_phylogenetic_glm_simple(): options.effect_data = data.cols[7] options.effect_vars = data.cols[8] options.structure = MetaWinAnalysis.SIMPLE_MA - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) options.structure = MetaWinAnalysis.PHYLOGENETIC_MA - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4, - tree=tree) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4, tree=tree) print_test_output(output) print() @@ -1471,11 +1467,10 @@ def test_phylogenetic_glm_simple(): options.effect_data = data.cols[9] options.effect_vars = data.cols[10] options.structure = MetaWinAnalysis.SIMPLE_MA - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) options.structure = MetaWinAnalysis.PHYLOGENETIC_MA - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4, - tree=tree) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4, tree=tree) print_test_output(output) print() @@ -1486,9 +1481,46 @@ def test_phylogenetic_glm_simple(): options.effect_data = data.cols[11] options.effect_vars = data.cols[12] options.structure = MetaWinAnalysis.SIMPLE_MA - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4) print_test_output(output) options.structure = MetaWinAnalysis.PHYLOGENETIC_MA - output, figure, fig_caption, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4, - tree=tree) + output, figure, chart_data, analysis_values = MetaWinAnalysis.do_meta_analysis(data, options, 4, tree=tree) print_test_output(output) + + +def test_colors(): + colors = mcolors.CSS4_COLORS + names = list(colors) + for n in names: + print(n, colors[n]) + + +def test_match_color_to_name(): + colors = mcolors.CSS4_COLORS + xcolors = mcolors.XKCD_COLORS + c = "#ff7f0e" + r, g, b = mcolors.hex2color(c) + print(r, g, b) + cnames = list(colors) + cdist = 10000 + cmatch = "None" + for n in cnames: + rc, gc, bc = mcolors.hex2color(colors[n]) + d = (rc-r)**2 + (gc-g)**2 + (bc-b)**2 + if d < cdist: + cmatch = n + cdist = d + xnames = list(xcolors) + xdist = 10000 + xmatch = "None" + for n in xnames: + print(n) + rx, gx, bx = mcolors.hex2color(xcolors[n]) + d = (rx-r)**2 + (gx-g)**2 + (bx-b)**2 + if d < xdist: + xmatch = n + xdist = d + print(cmatch, math.sqrt(cdist)) + print(xmatch, math.sqrt(xdist)) + print(len(cnames), len(xnames)) +