0

I write a small program using tkinter and matplotlib which plots a normal distribution curve and a confidence interval. I want the plot to have a small text box in the upper left corner using plt.text()which holds some information about the user's input and some other relevant information calculated by the backend model (e.g. reliability, normvalue, etc.).

I explicitly want to avoid using fig, ax = plt.subplots() since this would open two windows (where one is empty and the other one contains the plot).

However i realized that I have to create a subplot in order to use transform=ax.transAxes and to make sure that the textbox appears in my plot window (see text caption not appearing matplotlib). Without this keyword argument I am forced to use the explicit data coordinates which I cannot do since the axis coordinates can change depending on the user input.

How can I place the textbox in the upper left corner without opening two windows?

Here is my code:

import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
import numpy as np
import math
from scipy.stats import norm
from scipy.stats import zscore


class Application:
    def __init__(self, master):
        self.master = master
        self.callPlotWindow()

    def callPlotWindow(self):

        plotdata = dict()
        plotdata['reliability'] = 0.94
        plotdata['sd'] = 10
        plotdata['mean'] = 50
        plotdata['normvalue'] = 66
        plotdata['confidence_level'] = '80%'
        plotdata['question'] = 'einseitig'
        plotdata['hypothesis'] = 'Äquivalenzhypothese'
        plotdata['plot_ci'] =  4.12298113505264
        plotdata['plot_ci_lower'] =  63.93850943247368
        plotdata['plot_ci_upper'] = 68.06149056752632

        # turn on interactive mode
        plt.ion()

        # if there is already a plot window clear old plot
        if plt:
            plt.clf()

        # create x values for normal distribution
        x_normdist = np.concatenate((
        np.linspace(plotdata["mean"] - 3 * plotdata["sd"], plotdata["mean"] - 2 * plotdata["sd"],endpoint=False),
        np.linspace(plotdata["mean"] - 2 * plotdata["sd"], plotdata["mean"] - 1 * plotdata["sd"],endpoint=False),
        np.linspace(plotdata["mean"] - 1 * plotdata["sd"], plotdata["mean"] + 1 * plotdata["sd"],endpoint=False),
        np.linspace(plotdata["mean"] + 1 * plotdata["sd"], plotdata["mean"] + 2 * plotdata["sd"],endpoint=False),
        np.linspace(plotdata["mean"] + 2 * plotdata["sd"], plotdata["mean"] + 3 * plotdata["sd"])
        ))

        # plot normal distribution curve
        y_normdist = norm.pdf(x_normdist,plotdata["mean"],plotdata["sd"])
        plt.plot(x_normdist,y_normdist)

        # create logical lists which are used for 'where'-argument in fill-between method
        average = (x_normdist >= (plotdata["mean"] - 1 * plotdata["sd"])) & (x_normdist <= (plotdata["mean"] + 1 * plotdata["sd"]))
        above_and_below_average = (x_normdist >= (plotdata["mean"] - 2 * plotdata["sd"])) & (x_normdist <= (plotdata["mean"] - 1 * plotdata["sd"])) | (x_normdist >= (plotdata["mean"] + 1 * plotdata["sd"])) & (x_normdist <= (plotdata["mean"] + 2 * plotdata["sd"]))
        far_above_and_below_average = (x_normdist >= (plotdata["mean"] - 3 * plotdata["sd"])) & (x_normdist <= (plotdata["mean"] - 2 * plotdata["sd"])) | (x_normdist >= (plotdata["mean"] + 2 * plotdata["sd"])) & (x_normdist <= (plotdata["mean"] + 3 * plotdata["sd"]))

        regions = [average,
        above_and_below_average,
        far_above_and_below_average
        ]

        alpha_values = [0.75,0.5,0.25]

        region_labels = [
        "durchschnittlich",
        "unter-/überdurchschnittlich",
        "weit unter-/überdurchschnittlich"
        ]

        # shade regions under curve, use different alpha channel values and labels
        for idx,region in enumerate(regions):
            plt.fill_between(x_normdist, y_normdist,color="C0",alpha=alpha_values[idx],label=region_labels[idx],where=regions[idx])

        # plot confidence interval
        plt.errorbar(x=plotdata["normvalue"],y=0,xerr=plotdata["plot_ci"],fmt=".k",capsize=10)

        # set x and y axis title
        plt.xlabel(xlabel="Normwert")
        plt.ylabel(ylabel=r'$\phi_{\mu\sigma}(\mathcal{X})$')

        textstr = '\n'.join((
        r'$Normwert: %.2f$' % (plotdata["normvalue"], ),
        r'$Reliabilität: %.2f$' % (plotdata["reliability"], ),
        r'$Mittelwert des Normwertes: %.2f$' % (plotdata["mean"], ),
        r'$Standardabweichung des Normwertes: %.2f$' % (plotdata["sd"],),
        r'$Untere KI-Grenze: %.2f$' % (round(plotdata["plot_ci_lower"],2),),
        r'$Obere KI-Grenze: %.2f$' % (round(plotdata["plot_ci_upper"],2),),
        ))

        # these are matplotlib.patch.Patch properties
        props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)

        # create legend
        plt.legend(loc='upper right', prop={'size': 8})

        # place a text box in upper left in axes coords
        plt.text(0.05, 0.95,textstr,fontsize=14,verticalalignment='top',bbox=props)

if __name__ == "__main__":
    root = tk.Tk()
    my_gui = Application(root)
    root.mainloop()
Priyanka
  • 169
  • 3
  • 10
Johannes Wiesner
  • 1,006
  • 12
  • 33
  • 1
    You can always use `fig = plt.gcf()` and `ax = plt.gca()` to get the handle of the current figure or axes. In general I think the design of the application can be improved, such that you wouldn't need to rely on pyplot managing your figure(s). – ImportanceOfBeingErnest Feb 07 '19 at 12:00
  • Thank you, this worked! I added `transform = plt.gca().transAxes` to the `plt.text()` method. – Johannes Wiesner Feb 07 '19 at 15:04

0 Answers0