diff --git a/resources/metawin_help.html b/resources/metawin_help.html index a10fb80..aff909e 100644 --- a/resources/metawin_help.html +++ b/resources/metawin_help.html @@ -55,6 +55,7 @@

Help Table of Contents

  • Graph Tab
  • Phylogeny Tab
  • @@ -424,6 +425,25 @@

    Markers

    the chosen marker is a filled marker. Unfilled markers will turn invisible if the no fill option is checked.

    +

    A Note on Captions

    +

    + MetaWin 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 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. +

    +

    + 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. +

    Phylogeny Tab

    diff --git a/src/MetaWinAnalysisFunctions.py b/src/MetaWinAnalysisFunctions.py index 5982f53..fe0579d 100644 --- a/src/MetaWinAnalysisFunctions.py +++ b/src/MetaWinAnalysisFunctions.py @@ -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 = [] @@ -640,20 +632,13 @@ def simple_meta_analysis(data, options, decimal_places: int = 4, alpha: float = if options.create_graph: figure, chart_data = MetaWinCharts.chart_forest_plot("basic analysis", 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.common_forest_plot_caption(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) + 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 @@ -676,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): @@ -717,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) @@ -865,12 +848,8 @@ def grouped_meta_analysis(data, options, decimal_places: int = 4, alpha: float = if options.create_graph: figure, chart_data = MetaWinCharts.chart_forest_plot("grouped analysis", effect_sizes.label, forest_data, - alpha, options.bootstrap_mean, groups.label) - # fig_caption = get_text("group_forest_plot").format(groups.label) + \ - # MetaWinCharts.common_forest_plot_caption(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) + 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: @@ -882,8 +861,6 @@ def grouped_meta_analysis(data, options, decimal_places: int = 4, alpha: float = return (output_blocks, figure, chart_data, group_ma_values(global_values, group_mean_values, group_het_values, model_het, error_het), citations) - # return (output_blocks, figure, fig_caption, chart_data, - # group_ma_values(global_values, group_mean_values, group_het_values, model_het, error_het), citations) # ---------- cumulative meta-analysis ---------- @@ -910,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: @@ -975,17 +951,12 @@ def cumulative_meta_analysis(data, options, decimal_places: int = 4, alpha: floa if options.create_graph: figure, chart_data = MetaWinCharts.chart_forest_plot("cumulative analysis", effect_sizes.label, cumulative_means, alpha, options.bootstrap_mean, - order.label) - # fig_caption = get_text("cumulative_forest_plot").format(order.label) + \ - # MetaWinCharts.common_forest_plot_caption(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) + order.label, normal_ci=norm_ci) + else: output_blocks.append([get_text("Fewer than two studies were valid for analysis")]) return output_blocks, figure, chart_data - # return output_blocks, figure, fig_caption, chart_data # ---------- simple regression meta-analysis ---------- @@ -1038,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 @@ -1152,16 +1122,11 @@ def regression_meta_analysis(data, options, decimal_places: int = 4, alpha: floa 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) - # fig_caption = get_text("regression_caption").format(effect_sizes.label, options.independent_variable.label, - # model, ref_list) + \ - # create_reference_list(fig_citations, True) else: output_blocks.append([get_text("Fewer than two studies were valid for analysis")]) return output_blocks, figure, chart_data, reg_ma_values(global_values, model_het, error_het, predictors), citations - # return (output_blocks, figure, fig_caption, chart_data, - # reg_ma_values(global_values, model_het, error_het, predictors), citations) # ---------- complex (glm) meta-analysis ---------- @@ -1586,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 @@ -1709,17 +1673,10 @@ def nested_meta_analysis(data, options, decimal_places: int = 4, alpha: float = if options.create_graph: figure, chart_data = MetaWinCharts.chart_forest_plot("nested analysis", effect_sizes.label, forest_data, - alpha, options.bootstrap_mean) - # fig_caption = get_text("nest_caption") + MetaWinCharts.common_forest_plot_caption(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) + alpha, options.bootstrap_mean, normal_ci=norm_ci) return (output_blocks, figure, chart_data, group_ma_values(global_values, group_mean_values, group_het_values, model_het_values, error_het_values), citations) - # 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) # ---------- trim-and-fill analysis ---------- @@ -1753,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 = [] @@ -1877,9 +1833,6 @@ def trim_and_fill_analysis(data, options, decimal_places: int = 4, alpha: float if options.create_graph: 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")]) @@ -2199,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 = [] @@ -2295,12 +2247,7 @@ def jackknife_meta_analysis(data, options, decimal_places: int = 4, alpha: float if options.create_graph: figure, chart_data = MetaWinCharts.chart_forest_plot("jackknife analysis", effect_sizes.label, forest_data, - alpha, options.bootstrap_mean) - # fig_caption = get_text("jackknife_forest_plot") + \ - # MetaWinCharts.common_forest_plot_caption(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) + alpha, options.bootstrap_mean, normal_ci=norm_ci) else: output_blocks.append([get_text("Fewer than two studies were valid for analysis")]) diff --git a/src/MetaWinCharts.py b/src/MetaWinCharts.py index d95c02a..77aa5b3 100644 --- a/src/MetaWinCharts.py +++ b/src/MetaWinCharts.py @@ -8,6 +8,7 @@ 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 @@ -39,6 +40,10 @@ "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: @@ -133,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): """ @@ -312,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): """ @@ -400,12 +429,14 @@ def __init__(self): self.regression_scatter = None def __str__(self): - "Normal Quantile plot following {}. The " - "standardized effect size is the effect size divided by the " - "square-root of its variance. The solid line represents the " - "regression and the dashed lines the 95% prediction envelope." - - return get_text("normal_quantile_caption").format(get_citation("Wang_and_Bushman_1998")) + \ + 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) @@ -440,19 +471,6 @@ def __str__(self): return get_text("Radial_chart_caption").format(self.e_label) -class ForestPlotBaseCaption: - def __init__(self): - self.e_label = "" - self.alpha = 0.05 - self.bootstrap_n = None - - -class ForestPlotCaption(ForestPlotBaseCaption): - def __str__(self): - return get_text("Forest plot of individual effect sizes for each study.") + \ - common_forest_plot_caption(self.e_label, self.alpha, inc_median=False) - - class RegressionCaption: def __init__(self): self.e_label = "" @@ -475,20 +493,76 @@ def __init__(self): self.inferred_mean = None def __str__(self): - "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 " - "the mean effect size including the inferred data." + 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") + \ - create_reference_list(new_cites, True) + 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.") + \ - common_forest_plot_caption(self.e_label, self.alpha, self.bootstrap_n) + 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): @@ -498,12 +572,13 @@ def __init__(self): def __str__(self): return get_text("group_forest_plot").format(self.group_label) + \ - common_forest_plot_caption(self.e_label, self.alpha, self.bootstrap_n) + 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") + common_forest_plot_caption(self.e_label, self.alpha, self.bootstrap_n) + return get_text("nest_caption") + self.base_forest_plot_caption() + self.mid_forest_plot_caption() + \ + self.extra_forest_plot_caption() class CumulativeAnalysisCaption(ForestPlotBaseCaption): @@ -513,30 +588,13 @@ def __init__(self): def __str__(self): return get_text("cumulative_forest_plot").format(self.order_label) + \ - common_forest_plot_caption(self.e_label, self.alpha, self.bootstrap_n) + 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") + common_forest_plot_caption(self.e_label, self.alpha, - self.bootstrap_n) - - -def common_forest_plot_caption(effect_name: str, alpha: float = 0.05, bootstrap_n: Optional[int] = None, - inc_median: bool = True) -> str: - # " 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." - # - text = get_text("forest_plot_common_caption").format(effect_name, 1 - alpha) - if inc_median: - text += get_text("forest_plot_median_caption") - if bootstrap_n is not None: - citation = "Adams_et_1997" - text += get_text("bootstrap_caption").format(bootstrap_n, get_citation(citation)) + \ - create_reference_list([citation], True) - return text + 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 ---------- # @@ -753,12 +811,13 @@ def create_figure(chart_data): 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) -> Tuple[FigureCanvasQTAgg, ChartData]: + 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": @@ -802,22 +861,24 @@ def chart_forest_plot(analysis_type: str, effect_name, forest_data, alpha: float 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 @@ -997,12 +1058,6 @@ def chart_histogram(e_data, w_data, n_bins, e_label, else: cnts, bins = numpy.histogram(e_data, n_bins, weights=w_data) y_label = get_text("Weighted Count") - # if weighted: - # cnts, bins = numpy.histogram(e_data, n_bins, weights=w_data) - # y_label = get_text("Weighted Count") - # else: - # cnts, bins = numpy.histogram(e_data, n_bins) - # y_label = get_text("Count") chart_data = ChartData("histogram") chart_data.caption.e_label = e_label @@ -1150,3 +1205,23 @@ def chart_trim_fill_plot(effect_label, data, n, original_mean, new_mean) -> Tupl 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/MetaWinLanguage.py b/src/MetaWinLanguage.py index 26c2ba5..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", @@ -226,10 +241,14 @@ "Normal Quantile Plot": "Normal Quantile Plot", "normal_quantile_caption": "Normal Quantile plot following {}. The " "standardized effect size is the effect size divided by the " - "square-root of its variance. The solid line represents the " - "regression and the dashed lines the 95% prediction envelope.", + "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/tests/test_metawin.py b/tests/test_metawin.py index 237f6e4..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 @@ -1485,3 +1486,41 @@ def test_phylogenetic_glm_simple(): options.structure = MetaWinAnalysis.PHYLOGENETIC_MA 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)) +