Некоторые люди лучше усваивают информацию посредством практических занятий, а кому-то проще иметь визуальную симуляцию в качестве наглядного материала. По крайней мере, нам это постоянно говорят исследователи. Так что, в духе моих прошлых статей, я решил проверить, как хорошо вы усвоили мои уроки, и перейти к визуальной части уравнения, рассказав вам о том, как делать графики с помощью wxPython.
Вы могли этого не знать, но в wxPython для этого даже предусмотрен специальный виджет. Он называется PyPlot. Испольузя PyPlot вы сможете создавать графики быстро и просто! Если вам нужны какие странные и сложные диаграммы, я рекомендовал бы использовать вместо него matplotlib. К счастью, wxPython и matplotlib отлично взаимодействуют друг с другом, но мы не будем уделять внимания matplotlib в данной статье.
Учтите: примеры в данной статье не работают в wxPython 3.0.2.0 Classic, так как в нём есть известный баг (http://trac.wxwidgets.org/ticket/16767).
Приступая к работе (со столбиковой диаграммой!)
Если вы взглянете на файл plot.py в дистрибутиве wxPython, вы заметите, что PyPlot требует Numeric, numarray или numpy (в порядке убывания), так что установите хотя бы одного из них, чтобы использовать этот виджет. Чтобы установить numpy вы можете использовать pip, что значительно упростит вам задачу:
1 |
pip install numpy |
В любом случае, в самом низу файла plot.py есть простое демо, показывающее как можно делать графики, используя виджет PyPlot. Давайте взглянем на фрагмент его кода и выясним, каков спектр наших возможностей:
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 |
import wx from wx.lib.plot import PolyLine, PlotCanvas, PlotGraphics def drawBarGraph(): # Стобликовый график points1=[(1,0), (1,10)] line1 = PolyLine(points1, colour='green', legend='Feb.', width=10) points1g=[(2,0), (2,4)] line1g = PolyLine(points1g, colour='red', legend='Mar.', width=10) points1b=[(3,0), (3,6)] line1b = PolyLine(points1b, colour='blue', legend='Apr.', width=10) points2=[(4,0), (4,12)] line2 = PolyLine(points2, colour='Yellow', legend='May', width=10) points2g=[(5,0), (5,8)] line2g = PolyLine(points2g, colour='orange', legend='June', width=10) points2b=[(6,0), (6,4)] line2b = PolyLine(points2b, colour='brown', legend='July', width=10) return PlotGraphics([line1, line1g, line1b, line2, line2g, line2b], "Bar Graph - (Turn on Grid, Legend)", "Months", "Number of Students") class MyGraph(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, wx.ID_ANY, 'My First Plot (to take over the world!)') # Добавляем панель таким образом, чтобы она корректно отображалась на всех платформах. panel = wx.Panel(self, wx.ID_ANY) # Создаём некоторые сайзеры mainSizer = wx.BoxSizer(wx.VERTICAL) checkSizer = wx.BoxSizer(wx.HORIZONTAL) # Создаём виджеты self.canvas = PlotCanvas(panel) self.canvas.Draw(drawBarGraph()) toggleGrid = wx.CheckBox(panel, label="Show Grid") toggleGrid.Bind(wx.EVT_CHECKBOX, self.onToggleGrid) toggleLegend = wx.CheckBox(panel, label="Show Legend") toggleLegend.Bind(wx.EVT_CHECKBOX, self.onToggleLegend) # Размещаем виджеты mainSizer.Add(self.canvas, 1, wx.EXPAND) checkSizer.Add(toggleGrid, 0, wx.ALL, 5) checkSizer.Add(toggleLegend, 0, wx.ALL, 5) mainSizer.Add(checkSizer) panel.SetSizer(mainSizer) def onToggleGrid(self, event): """""" self.canvas.SetEnableGrid(event.IsChecked()) def onToggleLegend(self, event): """""" self.canvas.SetEnableLegend(event.IsChecked()) if __name__ == '__main__': app = wx.App(False) frame = MyGraph() frame.Show() app.MainLoop() |
Функция drawBarGraph были извлечена прямо из файла plot.py, о котором я упоминал ранее. В данном примере, имя этой функции было изменено с «_draw6Objects» на «drawBarGraph», чтобы было проще следовать этому коду. Давайте взглянем на неё. Точками являются точки нашего графика [(x1, y1), (x2, y2)].
Они сообщают PyPlot где чертить, используя метод PolyLine. Как вы видите, PolyLine берёт список кортежей точек графика и, опционально, цвет, легенду, ширину и стиль (не отображено). Мы создаём серию PolyLines и затем добавляем её в инстанцию PlotGraphics. Первым методом PlotGraphics является список PolyLines (или других PolyXXX объектов), заголовок xLabel и yLabel. Затем вы возвращаем объект PlotGraphics назад к вызывающему, который располагается в нашем классе wxPython.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Теперь мы возвращаем наше внимание к тому классу, который ласково назвали MyGraph. Первые пару строк кода будут знакомы любому, кто уже использовал wxPython ранее, так что давайте их пропустим и сразу перейдём к той части, где создаётся виджет. Здесь мы видим, как создать PlotCanvas используя лишь пустую wx.Panel в качестве его перента.
Чтобы нарисовать столбиковую диаграмму, мы вызываем метод нашего объекта холста, который называется Draw, проводим его через объект, который был возвращён из функции drawBarGraph. Не стесняйтесь прочитать это несколько раз, чтобы понять, что произошло, так понимание этого процесса крайне важно для того, что я собираюсь рассказать дальше.
Вы готовы? Теперь давайте продолжим! После того, как мы нарисовали столбиковую диаграмму, мы создаём несколько чекбоксов, позволяющих нам переключатся между сеткой диаграммы и её легендой. Затем мы размещаем виджеты в рамке. Названия методов check box говорят сами за себя, так что вам стоит разобраться с тем, что они делает самостоятельно. Подсказка IsChecked() возвращает Boolean.
Рисуем диаграммы, используя сохранённые данные
Нормально, то что вы предпочтёт использовать сохранённые данные, базу данных или веб-сервис, а не вписывать их каждый раз в код вручную. В данной части мы рассмотрим, как нарисовать диаграмму, используя сохранённые данные. Вот данные, которые мы будем использовать (вы скорее всего захотите загрузить архивы в виде исходного кода с Github):
http://www.wunderground.com/history/airport/KMIW/2010/9/22/WeeklyHistory.html?format=1
КИПА КОПИПАСТА, не думаю, что вы захотите платить за неё)
1 2 3 4 5 6 7 |
# http://www.wunderground.com/history/airport/KMIW/2010/9/22/WeeklyHistory.html?format=1 CDT,Max TemperatureF,Mean TemperatureF,Min TemperatureF,Max Dew PointF,MeanDew PointF,Min DewpointF,Max Humidity, Mean Humidity, Min Humidity, Max Sea Level PressureIn, Mean Sea Level PressureIn, Min Sea Level PressureIn, Max VisibilityMiles, Mean VisibilityMiles, Min VisibilityMiles, Max Wind SpeedMPH, Mean Wind SpeedMPH, Max Gust SpeedMPH,PrecipitationIn, CloudCover, Events<br /> 2010-9-19,56,52,47,55,49,44,100,97,93,30.21,30.17,30.11,10,5,2,14,9,20,0.34,8,Rain-Thunderstorm<br /> 2010-9-20,88,72,56,71,62,55,100,73,46,30.10,29.94,29.77,10,6,0,25,12,32,T,4,Fog-Rain<br /> 2010-9-21,75,70,64,66,64,63,93,83,73,29.89,29.83,29.75,10,7,0,22,7,30,1.79,5,Fog-Rain-Thunderstorm<br /> 2010-9-22,75,70,64,68,64,63,100,93,69,30.00,29.96,29.86,10,5,1,15,4,,0.26,8,Rain<br /> <!-- 0.481:1 --> |
Первая строчка – это вебсайт, вторая говорит нам о том, какие запятые означают переход на следующую строку. Остальные строки – это чистые данные, содержащие немного HTML-мусора в конце каждой строки. Последняя строка также содержит информацию, которую мы проигнорируем. А теперь давайте напишем код, превращающий эти данные в диаграмму!
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 |
import wx from wx.lib.plot import PolyLine, PlotCanvas, PlotGraphics class MyGraph(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, wx.ID_ANY, 'Plotting File Data') # Add a panel so it looks the correct on all platforms panel = wx.Panel(self, wx.ID_ANY) self.canvas = PlotCanvas(panel) self.canvas.Draw(self.createPlotGraphics()) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.canvas, 1, wx.EXPAND) panel.SetSizer(sizer) def readFile(self): """ Reads the hard-coded file """ # normally you would want to pass a file path in, NOT hard code it! with open("data.txt") as fobj: # skip the first two lines of text in the file data = fobj.readlines()[2:-1] temps = [] for line in data: parts = line.split(",") date = parts[0].split("-") day = date[2] points = [(day, parts[3]), (day, parts[1])] temps.append(points) return temps def createPlotGraphics(self): """ Create the plot's graphics """ temps = self.readFile() lines = [] for temp in temps: tempInt = int(temp[1][1]) if tempInt < 60: color = "blue" elif tempInt >=60 and tempInt <= 75: color = "orange" else: color = "red" lines.append(PolyLine(temp, colour=color, width=10)) return PlotGraphics(lines, "Bar Graph of Temperatures", "Days", "Temperatures") if __name__ == '__main__': app = wx.App(False) frame = MyGraph() frame.Show() app.MainLoop() |
Вы поняли, что делает этот код? Ладно, если вы не можете (или не хотите) понимать, то вы можете прочесть об этом прямо сейчас. Точно так же, как и в предыдущем примере, мы импортируем кое-что и создаём wx.Frame, содержащий панель и PlotCanvas. Мы используем простой метод readFile и более сложный, под названием createPlotGraphics. Именно на этих двух методах мы и сфокусируемся.
Метод readFile вызывается методом createPlotGraphics. Всё, что он делает – считывает файл. В данном примере, путь к этому файлу вписан прямо в код. Скорее всего вы захотите использовать что-то вроде бразуера для загрузки файла, но мы пойдём по супер-простому пути. Когда мы считываем линии из файла, мы пропускаем первые две строки, используя следующий синтаксис:
1 |
data = f.readlines()[2:-1] |
Всё, что он делает это считывает весь файл, исключая первые две строки и последнюю. Таким образом мы избавляемся от мусора в начала и конце файла. Ну разве Python не крут? Затем мы создаём простой «for loop», мы извлекаем только данные, которые нам нужны, а именно день, высокую и низкую температуры. Остальное мы просто отбрасываем.
В методе createPlotGraphics мы берём список температур, возвращённый нам из метода readFile и запускаем с ним цикл, создавая новый список PolyLines. Мы используем несколько условных выражений, чтобы решить, какого цвета сделать каждый столбик в диаграмме. Наконец, мы перемещаем все PolyLines в инстанцию PlotGraphics, и возвращаем её к вызванному в методе __init__. Вот и всё!
Точечный график с тысячами точек
Теперь, мы собираемся создать точечную диаграмму, состоящую из 25 тысяч точек! Она также взята из демо-пакета. Вот код:
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 |
import numpy as _Numeric import wx from wx.lib.plot import PlotCanvas, PlotGraphics, PolyLine, PolyMarker def drawLinePlot(): # Линия с 25 тысячами точек data1 = _Numeric.arange(5e5,1e6,10) data1.shape = (25000, 2) line1 = PolyLine(data1, legend='Wide Line', colour='green', width=5) # Ещё немного точек markers2 = PolyMarker(data1, legend='Square', colour='blue', marker='square') return PlotGraphics([line1, markers2], "25,000 Points", "Value X", "") class MyGraph(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, wx.ID_ANY, 'It Looks Like a Line Graph!') # Добавляет панель так, чтобы она корректно отображалась на всех платформах. panel = wx.Panel(self, wx.ID_ANY) # Создаёт несколько сайзеров mainSizer = wx.BoxSizer(wx.VERTICAL) checkSizer = wx.BoxSizer(wx.HORIZONTAL) # Создаёт виджеты self.canvas = PlotCanvas(panel) self.canvas.Draw(drawLinePlot()) toggleGrid = wx.CheckBox(panel, label="Show Grid") toggleGrid.Bind(wx.EVT_CHECKBOX, self.onToggleGrid) toggleLegend = wx.CheckBox(panel, label="Show Legend") toggleLegend.Bind(wx.EVT_CHECKBOX, self.onToggleLegend) # Размещает виджеты mainSizer.Add(self.canvas, 1, wx.EXPAND) checkSizer.Add(toggleGrid, 0, wx.ALL, 5) checkSizer.Add(toggleLegend, 0, wx.ALL, 5) mainSizer.Add(checkSizer) panel.SetSizer(mainSizer) def onToggleGrid(self, event): """""" self.canvas.SetEnableGrid(event.IsChecked()) def onToggleLegend(self, event): """""" self.canvas.SetEnableLegend(event.IsChecked()) if __name__ == '__main__': app = wx.App(False) frame = MyGraph() frame.Show() app.MainLoop() |
Мы повторно использовали большую часть wxPython кода из первоначального примера и просто вызвали другую функцию. Функция drawLinePlot довольно проста. Например, мы используем numpy, чтобы создать график, содержащий 25 тысяч точек, а затем создаём PolyLine с ними. Если вы увеличите изображение, то увидите, что некоторые точки квадратные, а не круглые. Для этого нам и нужен был класс PolyMarker. Он задаёт класс «маркера». Теперь вы готовы взглянуть на следующий пример!
Создаём синусный/косинусный график
Данный пример показывает, как взять синус и косину, а затем превратить их в диаграмму. Вон выглядит как двойная спираль, расположенная горизонтально. В любом случае: вот код.
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 |
import numpy as _Numeric import wx from wx.lib.plot import PlotCanvas, PlotGraphics, PolyLine, PolyMarker def drawSinCosWaves(): # Синусная функция, содержащая 100 точек, которая будет отображаться зелёной линией data1 = 2.*_Numeric.pi*_Numeric.arange(200)/200. data1.shape = (100, 2) data1[:,1] = _Numeric.sin(data1[:,0]) markers1 = PolyMarker(data1, legend='Green Markers', colour='green', marker='circle',size=1) # Косинусная функция, содержащая 50 точек, которая будет отображаться красной линией data1 = 2.*_Numeric.pi*_Numeric.arange(100)/100. data1.shape = (50,2) data1[:,1] = _Numeric.cos(data1[:,0]) lines = PolyLine(data1, legend= 'Red Line', colour='red') # Ещё несколько точек... pi = _Numeric.pi markers2 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.), (3.*pi/4., -1)], legend='Cross Legend', colour='blue', marker='cross') return PlotGraphics([markers1, lines, markers2],"Graph Title", "X Axis", "Y Axis") class MyGraph(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, wx.ID_ANY, 'Sin / Cos Plot') # Добавляем панель, чтобы она корректно отображалась на всех платформах. panel = wx.Panel(self, wx.ID_ANY) # Создаём некоторые сайзеры mainSizer = wx.BoxSizer(wx.VERTICAL) checkSizer = wx.BoxSizer(wx.HORIZONTAL) # Создаём виджеты self.canvas = PlotCanvas(panel) self.canvas.Draw(drawSinCosWaves()) toggleGrid = wx.CheckBox(panel, label="Show Grid") toggleGrid.Bind(wx.EVT_CHECKBOX, self.onToggleGrid) toggleLegend = wx.CheckBox(panel, label="Show Legend") toggleLegend.Bind(wx.EVT_CHECKBOX, self.onToggleLegend) # Размещаем виджеты mainSizer.Add(self.canvas, 1, wx.EXPAND) checkSizer.Add(toggleGrid, 0, wx.ALL, 5) checkSizer.Add(toggleLegend, 0, wx.ALL, 5) mainSizer.Add(checkSizer) panel.SetSizer(mainSizer) def onToggleGrid(self, event): """""" self.canvas.SetEnableGrid(event.IsChecked()) def onToggleLegend(self, event): """""" self.canvas.SetEnableLegend(event.IsChecked()) if __name__ == '__main__': app = wx.App(False) frame = MyGraph() frame.Show() app.MainLoop() |
Данный пример для тех, кто хорошо разбирается в математике. Я не занимался тригонометрией или геометрией уже довольно давно, так что я не смогу объяснить вам, что эта диаграмма выражает. Вы можете выяснить это, используя вашу любимую поисковую систему. Данный пример использует одну PolyLine и два PolyMarkers, чтобы создать график. Здесь всё так же, как и в прошлых примерах, так что добавить мне особо нечего.
Итоги
Теперь вы должны быть более чем готовы к самостоятельному созданию графиков с использованием wxPython. Если у вас возникнут трудности, то есть несколько примеров в файле plot.py, а также люди, находящиеся в записной книжке wxPython довольно дружелюбны, так что вы наверняка получите помощь, если вежливо о ней попросите. Дайте мне знать, если создадите что-то крутое!
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»