With the growing demand for Data Science and Analytics skill sets, drawing graphics programmatically is a very popular task these days. You can easily solve it by combining the Matplotlib library with Python4Delphi (P4D). P4D is a free set of powerful tools that allows you to work with Python scripts, modules, and types in Delphi and easily create Windows GUI with it.
Matplotlib is a comprehensive Python library for creating static, animated, and interactive visualizations. Matplotlib produces publication-quality figures in a variety of hardcopy formats and interactive environments across platforms. Matplotlib can be used in Python scripts, the Python and IPython shell, web application servers, and various graphical user interface toolkits (in this post, Python GUI by Delphi’s VCL using P4D!).
This post will guide you on how to run the Matplotlib library using Python for Delphi to display it in the Delphi Windows GUI app.
First, open and run our Python GUI using project Demo01
from Python4Delphi with RAD Studio. Then insert the script into the lower Memo
, click the Execute script
button, and get the result in the upper Memo
. You can find the Demo01
source on GitHub. The behind the scene details of how Delphi manages to run your Python code in this amazing Python GUI can be found at this link.
Python Matplotlib library provides various tools for working with graphics. With this library, you can create graphics, customize legends, style sheets, color schemes, and manipulate images.
Table of Contents
Matplotlib enables us to:
Create:
- Develop publication-quality plots with just a few lines of code
- Use interactive figures that can zoom, pan, update, etc.
Customize:
- Take full control of line styles, font properties, axes properties, etc.
- Export and embed to various file formats and interactive environments
Extend:
- Explore tailored functionality provided by third-party packages
- Learn more about Matplotlib through the many external learning resources (very active learning ecosystem)
The following are some real-world use cases in plotting with Matplotlib:
1. Show Percentiles as Horizontal Bar Chart
Bar charts are useful for visualizing counts, or summary statistics with error bars.
This example comes from an application in which grade school gym teachers wanted to be able to show parents how their child did across a handful of fitness tests, and importantly, relative to how other children did. For demo purposes, we’ll just make up some data:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
import numpy as np import matplotlib import matplotlib.pyplot as plt from matplotlib.ticker import MaxNLocator from collections import namedtuple np.random.seed(42) Student = namedtuple('Student', ['name', 'grade', 'gender']) Score = namedtuple('Score', ['score', 'percentile']) # GLOBAL CONSTANTS test_names = ['Pacer Test', 'Flexed Armn Hang', 'Mile Run', 'Agility', 'Push Ups'] test_units = dict(zip(test_names, ['laps', 'sec', 'min:sec', 'sec', ''])) def attach_ordinal(num): """Convert an integer to an ordinal string, e.g. 2 -> '2nd'.""" suffixes = {str(i): v for i, v in enumerate(['th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th'])} v = str(num) # special case early teens if v in {'11', '12', '13'}: return v + 'th' return v + suffixes[v[-1]] def format_score(score, test): """ Create score labels for the right y-axis as the test name followed by the measurement unit (if any), split over two lines. """ unit = test_units[test] if unit: return f'{score}n{unit}' else: # If no unit, don't include a newline, so that label stays centered. return score def format_ycursor(y): y = int(y) if y < 0 or y >= len(test_names): return '' else: return test_names[y] def plot_student_results(student, scores, cohort_size): fig, ax1 = plt.subplots(figsize=(9, 7)) # Create the figure fig.subplots_adjust(left=0.115, right=0.88) fig.canvas.set_window_title('Eldorado K-8 Fitness Chart') pos = np.arange(len(test_names)) rects = ax1.barh(pos, [scores[k].percentile for k in test_names], align='center', height=0.5, tick_label=test_names) ax1.set_title(student.name) ax1.set_xlim([0, 100]) ax1.xaxis.set_major_locator(MaxNLocator(11)) ax1.xaxis.grid(True, linestyle='--', which='major', color='grey', alpha=.25) # Plot a solid vertical gridline to highlight the median position ax1.axvline(50, color='grey', alpha=0.25) # Set the right-hand Y-axis ticks and labels ax2 = ax1.twinx() # Set the tick locations ax2.set_yticks(pos) # Set equal limits on both yaxis so that the ticks line up ax2.set_ylim(ax1.get_ylim()) # Set the tick labels ax2.set_yticklabels([format_score(scores[k].score, k) for k in test_names]) ax2.set_ylabel('Test Scores') xlabel = ('Percentile Ranking Across {grade} Grade {gender}sn' 'Cohort Size: {cohort_size}') ax1.set_xlabel(xlabel.format(grade=attach_ordinal(student.grade), gender=student.gender.title(), cohort_size=cohort_size)) rect_labels = [] # Lastly, write in the ranking inside each bar to aid in interpretation for rect in rects: # Rectangle widths are already integer-valued but are floating # type, so it helps to remove the trailing decimal point and 0 by # converting width to int type width = int(rect.get_width()) rank_str = attach_ordinal(width) # The bars aren't wide enough to print the ranking inside if width < 40: # Shift the text to the right side of the right edge xloc = 5 # Black against white background clr = 'black' align = 'left' else: # Shift the text to the left side of the right edge xloc = -5 # White on magenta clr = 'white' align = 'right' # Center the text vertically in the bar yloc = rect.get_y() + rect.get_height() / 2 label = ax1.annotate( rank_str, xy=(width, yloc), xytext=(xloc, 0), textcoords="offset points", horizontalalignment=align, verticalalignment='center', color=clr, weight='bold', clip_on=True) rect_labels.append(label) # Make the interactive mouse over give the bar title ax2.fmt_ydata = format_ycursor # Return all of the artists created return {'fig': fig, 'ax': ax1, 'ax_right': ax2, 'bars': rects, 'perc_labels': rect_labels} student = Student('Johnny Doe', 2, 'boy') scores = dict(zip( test_names, (Score(v, p) for v, p in zip(['7', '48', '12:52', '17', '14'], np.round(np.random.uniform(0, 100, len(test_names)), 0))))) cohort_size = 62 # The number of other 2nd grade boys arts = plot_student_results(student, scores, cohort_size) plt.show() |
Run the code above in our Python GUI, we will get the following result:
2. Stacked Horizontal Bar Chart to Visualize Discrete Distribution
We can visualize discrete distributions as stacked bar charts.
In this Section, we will visualize the result of a survey in which people could rate their agreement to questions on a five-element scale. Let’s run the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
import numpy as np import matplotlib.pyplot as plt category_names = ['Strongly disagree', 'Disagree', 'Neither agree nor disagree', 'Agree', 'Strongly agree'] results = { 'Question 1': [10, 15, 17, 32, 26], 'Question 2': [26, 22, 29, 10, 13], 'Question 3': [35, 37, 7, 2, 19], 'Question 4': [32, 11, 9, 15, 33], 'Question 5': [21, 29, 5, 5, 40], 'Question 6': [8, 19, 5, 30, 38] } def survey(results, category_names): """ Parameters ---------- results : dict A mapping from question labels to a list of answers per category. It is assumed all lists contain the same number of entries and that it matches the length of *category_names*. category_names : list of str The category labels. """ labels = list(results.keys()) data = np.array(list(results.values())) data_cum = data.cumsum(axis=1) category_colors = plt.get_cmap('RdYlGn')( np.linspace(0.15, 0.85, data.shape[1])) fig, ax = plt.subplots(figsize=(9.2, 5)) ax.invert_yaxis() ax.xaxis.set_visible(False) ax.set_xlim(0, np.sum(data, axis=1).max()) for i, (colname, color) in enumerate(zip(category_names, category_colors)): widths = data[:, i] starts = data_cum[:, i] - widths ax.barh(labels, widths, left=starts, height=0.5, label=colname, color=color) xcenters = starts + widths / 2 r, g, b, _ = color text_color = 'white' if r * g * b < 0.5 else 'darkgrey' for y, (x, c) in enumerate(zip(xcenters, widths)): ax.text(x, y, str(int(c)), ha='center', va='center', color=text_color) ax.legend(ncol=len(category_names), bbox_to_anchor=(0, 1), loc='lower left', fontsize='small') return fig, ax survey(results, category_names) plt.show() |
The result:
3. Creating Annotated Heatmaps
It is often desirable to show data that depends on two independent variables as a color-coded image plot. This is often referred to as a heatmap.
If the data is categorical, this would be called a categorical heatmap.
The following examples show how to create a heatmap with annotations:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import numpy as np import matplotlib import matplotlib.pyplot as plt vegetables = ["cucumber", "tomato", "lettuce", "asparagus", "potato", "wheat", "barley"] farmers = ["Farmer Joe", "Upland Bros.", "Smith Gardening", "Agrifun", "Organiculture", "BioGoods Ltd.", "Cornylee Corp."] harvest = np.array([[0.8, 2.4, 2.5, 3.9, 0.0, 4.0, 0.0], [2.4, 0.0, 4.0, 1.0, 2.7, 0.0, 0.0], [1.1, 2.4, 0.8, 4.3, 1.9, 4.4, 0.0], [0.6, 0.0, 0.3, 0.0, 3.1, 0.0, 0.0], [0.7, 1.7, 0.6, 2.6, 2.2, 6.2, 0.0], [1.3, 1.2, 0.0, 0.0, 0.0, 3.2, 5.1], [0.1, 2.0, 0.0, 1.4, 0.0, 1.9, 6.3]]) fig, ax = plt.subplots() im = ax.imshow(harvest) # We want to show all ticks... ax.set_xticks(np.arange(len(farmers))) ax.set_yticks(np.arange(len(vegetables))) # ... and label them with the respective list entries ax.set_xticklabels(farmers) ax.set_yticklabels(vegetables) # Rotate the tick labels and set their alignment. plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor") # Loop over data dimensions and create text annotations. for i in range(len(vegetables)): for j in range(len(farmers)): text = ax.text(j, i, harvest[i, j], ha="center", va="center", color="w") ax.set_title("Harvest of local farmers (in tons/year)") fig.tight_layout() plt.show() |
Run the code above in our Python GUI, we will get the following result:
Congratulations, now you have learned how to run the Matplotlib library using Python for Delphi to display it in the Delphi Windows GUI app! Now you can solve various real-world problems using plots created by the Matplotlib library and Python4Delphi. The only limitation is your imagination.
Check out the matplotlib
data visualization library for Python and use it in your projects: https://pypi.org/project/matplotlib/ and
Check out Python4Delphi
which easily allows you to build Python GUIs for Windows using Delphi: https://github.com/pyscripter/python4delphi
References & further readings
[1] Hakim, M. A. (2023).
Article02 – Matplotlib. pythongui.orgRepo_Python4Delphi-Python-Libraries GitHub. github.com/MuhammadAzizulHakim/pythongui.org Repo_Python4Delphi-Python-Libraries/tree/main/Article02%20-%20Matplotlib
[2] Hunter, J., Dale, D., Firing, E., Droettboom, M., and the Matplotlib development team. (2002-2023).
Creating annotated heatmaps: A simple categorical heatmap. The Matplotlib development team. matplotlib.org/stable/gallery/images_contours_and_ fields/image_annotated_heatmap.html#sphx-glr-gallery-images-contours-and-fields-image-annotated-heatmap-py
[3] Hunter, J., Dale, D., Firing, E., Droettboom, M., and the Matplotlib development team. (2002-2023).
Discrete distribution as horizontal bar chart. The Matplotlib development team. matplotlib.org/stable/gallery/lines_bars_ and_markers/horizontal_barchart_distribution.html# sphx-glr-gallery-lines-bars-and-markers-horizontal-barchart-distribution-py
[4] Hunter, J., Dale, D., Firing, E., Droettboom, M., and the Matplotlib development team. (2002-2023).
Percentiles as horizontal bar chart. The Matplotlib development team. matplotlib.org/stable/gallery/statistics/ barchart_demo.html#sphx-glr-gallery-statistics-barchart-demo-py