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