As we are using our LoadRssFeed
as a library module now, we will want to modify the end result to return an array we can use in our chart
module that we will build shortly. We will return two counts both in an array, one for May and one for June.
Also, we will remove the print
statement as we understand that it is working properly. So, remove the print
line at the end of the LoadRssFeed
, get_all_rss_dates
function and replace it with return [month_count.count(5), month_count.count(6)]
. This will allow us to return a single object but keep two values for our chart. Here's the implementation of the file:
#!/usr/bin/env python # -*- coding: utf-8 -*- import urllib2 from xml.etree import ElementTree import time def get_all_rss_dates(): '''create a global array to our function to save our month counts.''' month_count = [] try: '''Open the file via HTTP.''' response = urllib2.urlopen('http://www.packtpub.com/rss.xml') tree = ElementTree.parse(response) root = tree.getroot() '''Array of post dates.''' news_post_date = root.findall("channel//pubDate") '''Iterate in all our searched elements and print the inner text for each.''' for date in news_post_date: '''Create a variable striping out commas, and generating a new array item for every space.''' datestr_array = date.text.replace(',', '').split(' ') '''Create a formatted string to match up with our strptime method.''' formatted_date = "{0} {1}, {2}, {3}".format(datestr_array[2], datestr_array[1], datestr_array[3], datestr_array[4]) '''Parse a time object from our string.''' blog_datetime = time.strptime(formatted_date, "%b %d, %Y, %H:%M:%S") '''Add this date's month to our count array''' month_count.append(blog_datetime.tm_mon) '''Finally, close our open network.''' response.close() except Exception as e: '''If we have an issue show a message and alert the user.''' print(e) '''Return two counts for both months.''' return [month_count.count(5), month_count.count(6)]
Next, let's create a new Python file to be our chart building library that uses pygal. We will name this file chart_build.py
and add this in our project root along with our LoadRssFeed.py
and main_chartbuild.py
files.
Next, open the chart_build.py
file and let's build a simple bar chart that shows the number of posts for the months of May and June. Like our LoadRssFeed
module we built, we will wrap our chart code in a function with a parameter called dataarr
, indicating a data array. Before we add our data to our chart, let's set up our chart configuration.
In pygal, we typically create a chart, and inside it, we specify parameters to set settings for the chart and add our parameter values to the chart object that we call. The pygal
library offers a way to modularize our configuration options.
This is helpful because in this example, we use only one chart but what if we had eight or twelve more charts to build? Having a portable configuration to set up the chart layout and theme can be very useful rather than rewriting the configuration each time.
Take a look at the following code. Here, I'm creating a Python class called ConfigChart
, which has a parameter called Config
that basically overrides the Config
object in the chart we assign this to. Inside the class, I have a list of parameters that I can cleanly update and modify. Notice that I also import pygal
, and using from pygal
, I also import Config
to work as a separate object:
import pygal from pygal import Config '''Creating our own class with values to pass in as parameters to our chart.''' class ConfigChart(Config): show_legend = True human_readable = True fill = True '''explicit_size sets a fixed size to our width height properties.''' explicit_size = True width = 860 height = 640 title= 'Posts per month on Packtpub.com' y_label = 'Posts' x_label = 'Month' y_title = 'Posts' x_title = 'Month' show_y_labels = True show_x_labels = True stroke = False tooltip_font_size = 42 title_font_size = 24 '''Always include an error message for any dynamic data.''' no_data_text = 'Unable to load data'
As you can see, I've added many of the values we discussed in Chapter 5, Tweaking pygal, and also ensured that our no_data_text
parameter had a value in case of any connection issues. Also, as we want to render this in a browser, I've set the width
and height
as well as set a parameter called explicit_size
to True
to force the SVG output to a fixed size.
Now, let's finish setting up our chart to receive data in our chart_build.py
file. Here, I've wrapped my pygal chart creation code in a function called generate_chart
and added a parameter to handle our chart's data pulled from the RSS feed.
Here's the final code for the chart, including our ConfigChart
class, applied to our chart object. Notice that for the add()
methods for the chart, I simply added dataarr
with an array index to specify the value for May
and June
, respectively:
import pygal from pygal import Config '''Creating our own class with values to pass in as parameters to our chart.''' class ConfigChart(Config): show_legend = True human_readable = True fill = True '''explicit_size sets a fixed size to our width height properties.''' explicit_size = True width = 860 height = 640 title= 'Posts per month on Packtpub.com' y_label = 'Posts' x_label = 'Month' y_title = 'Posts' x_title = 'Month' show_y_labels = True show_x_labels = True stroke = False tooltip_font_size = 42 title_font_size = 24 '''Always include an error message for any dynamic data.''' no_data_text = 'Unable to load data' '''Generate chart based on imported array, (with 2 values)''' def generate_chart(dataarr): '''Initialize the chart with our ConfigChart class.''' mychart = pygal.Bar(ConfigChart()) '''Add data to the chart for May and June.''' mychart.add('May', dataarr[0]) mychart.add('June', dataarr[1]) '''Launch our web browser with our SVG, (we can also render to file as well)''' mychart.render_in_browser()
Check our code in the LoadRssFeed.py
file before running main_chartbuild.py
. We made one change here; we changed the print
exception to throw
, an exception if we encounter a data connection issue in our try
/catch
block. If we were deploying this application publically, we would add a UI error for a consumer rather than an exception.
In this code, we've updated to return two values that we will pass into our chart:
#!/usr/bin/env python # -*- coding: utf-8 -*- import urllib2 from xml.etree import ElementTree import time def get_all_rss_dates(): '''create a global array to our function to save our month counts.''' month_count = [] try: '''Open the file via HTTP. ''' response = urllib2.urlopen('http://www.packtpub.com/rss.xml') tree = ElementTree.parse(response) root = tree.getroot() '''Array of post dates.''' news_post_date = root.findall("channel//pubDate") '''Iterate in all our searched elements and print the inner text for each. ''' for date in news_post_date: '''Create a variable striping out commas, and generating a new array item for every space.''' datestr_array = date.text.replace(',', '').split(' ') '''Create a formatted string to match up with our strptime method.''' formatted_date = "{0} {1}, {2}, {3}".format(datestr_array[2], datestr_array[1], datestr_array[3], datestr_array[4]) '''Parse a time object from our string.''' blog_datetime = time.strptime(formatted_date, "%b %d, %Y, %H:%M:%S") '''Add this date's month to our count array''' month_count.append(blog_datetime.tm_mon) '''Finally, close our open network. ''' response.close() except Exception as e: '''If we have an issue show a message and alert the user.''' throw #Return two counts for both months. return [month_count.count(5), month_count.count(6)]
One more task to do is set up our main
function, which is our main_chartbuild.py
, which is called when our script is executed. As our LoadRssFeed
module returns an array that we pass into our chart, we will call the generate_chart()
method from our chart
module that we build and pass in the result, as shown in the following code:
#!/usr/bin/env python # -*- coding: utf-8 -*- import LoadRssFeed, chart_build #Call our 'LoadRssFeed' function. chart_build.generate_chart(LoadRssFeed.get_all_rss_dates())
Let's run our main_chartbuild.py
file and take a look at the results:
3.148.102.166