PK ccIhg chorogrid/Chorogrid.py#!/usr/bin/python
# Filename: Chorogrid.py
import xml.etree.ElementTree as ET
import pandas as pd
import re
import sys
from math import sqrt
from IPython.display import SVG, display
class Chorogrid(object):
""" An object which makes choropleth grids, instantiated with:
csv_path: the path to a csv data file with the following columns:
* ids: e.g., states or countries, corresponding to
the Colorbin.colorlist
* coordinates or path
ids: a listlike object of ids corresponding to colors
colors: a listlike object of colors in hex (#123456) format
corresponding to ids
id_column: the name of the column in csv_path containing ids
if there is not a 1:1 map between the ids object
and the contents of id_column, you will be warned
Methods (introspect to see arguments)
set_colors: pass a new list of colors to replace the one
used when the class was instantiated
set_title: set a title for the map
set_legend: set a legend
add_svg: add some custom svg code. This must be called
after the draw_... method, because it needs to know
the margins.
draw_squares: draw a square grid choropleth
draw_hex: draw a hex-based choropleth
draw_multihex: draw a multiple-hex-based choropleth
draw_multisquare: draw a multiple-square-based choropleth
draw_map: draw a regular, geographic choropleth
done: save and/or display the result in IPython notebook
done_with_overlay: overlay two Chorogrid objects
"""
def __init__(self, csv_path, ids, colors, id_column='abbrev'):
self.df = pd.read_csv(csv_path)
comparison_set = set(self.df[id_column])
invalid = set(ids).difference(comparison_set)
missing = comparison_set.difference(set(ids))
if len(invalid) > 0:
print('WARNING: The following are not recognized'
' ids: {}'.format(invalid), file=sys.stderr)
if len(missing) > 0:
print('WARNING: The following ids in the csv are not '
'included: {}'.format(missing), file=sys.stderr)
self.colors = list(colors)
self.ids = list(ids)
self.svglist = []
assert id_column in self.df.columns, ("{} is not a column in"
" {}".format(id_column, csv_path))
self.id_column = id_column
self.title = ''
self.additional_svg = []
self.additional_offset = [0, 0]
self.legend_params = None
#methods called from within methods, beginning with underscore
def _update_default_dict(self, default_dict, dict_name, kwargs):
"""Updates a dict based on kwargs"""
if dict_name in kwargs.keys():
kwarg_dict = kwargs[dict_name]
for k, v in kwarg_dict.items():
assert k in default_dict.keys(), ("kwarg {} specified invalid"
" key".format(dict_name))
if k == 'font-size' and type(k) is int:
default_dict[k] = str(v) + 'px'
else:
default_dict[k] = v
return default_dict
def _dict2style(self, dict_):
"""Returns a concatenated string from the dict"""
to_return = []
for k,v in dict_.items():
to_return.append(k + ':' + str(v) + ';')
to_return[-1] = to_return[-1][:-1]
return ''.join(to_return)
def _make_svg_top(self, width, height):
"""Writes first part of svg"""
self.svg = ET.Element('svg', xmlns="http://www.w3.org/2000/svg",
version="1.1", height=str(height), width=str(width))
def _draw_title(self, x, y):
if len(self.title) > 0:
font_style = self._dict2style(self.title_font_dict)
_ = ET.SubElement(self.svg, "text", id="title", x=str(x),
y=str(y), style=font_style)
_.text = self.title
def _determine_font_colors(self, kwargs):
if 'font_colors' in kwargs.keys():
fc = kwargs['font_colors']
if type(fc) is str:
font_colors = [fc] * len(self.ids)
elif type(fc) is list:
font_colors = fc
elif type(fc) is dict:
font_colors = [fc[x] for x in self.colors]
else:
font_colors = ['#000000'] * len(self.ids)
return font_colors
def _calc_hexagon(self, x, y, w, true_rows):
if true_rows:
h = w/sqrt(3)
return "{},{} {},{} {},{} {},{} {},{} {},{}".format(x, y,
x+w/2, y-h/2,
x+w, y,
x+w, y+h,
x+w/2, y+1.5*h,
x, y+h)
else:
ww = w/2
hh = w * sqrt(3) / 2
return "{},{} {},{} {},{} {},{} {},{} {},{}".format(x, y,
x+ww, y,
x+ww*3/2, y-hh/2,
x+ww, y-hh,
x, y-hh,
x-ww/2, y-hh/2)
def _increment_multihex(self, x, y, w, direction):
h = w/sqrt(3)
if direction == 'a':
return 'L', x+w/2, y-h/2
elif direction == 'b':
return 'L', x+w/2, y+h/2
elif direction == 'c':
return 'L', x, y+h
elif direction == 'd':
return 'L', x-w/2, y+h/2
elif direction == 'e':
return 'L', x-w/2, y-h/2
elif direction == 'f':
return 'L', x, y-h
elif direction == 'A':
return 'M', x+w/2, y-h/2
elif direction == 'B':
return 'M', x+w/2, y+h/2
elif direction == 'C':
return 'M', x, y+h
elif direction == 'D':
return 'M', x-w/2, y+h/2
elif direction == 'E':
return 'M', x-w/2, y-h/2
elif direction == 'F':
return 'M', x, y-h
def _calc_multihex(self, x, y, w, contour):
result = []
result.append("M{}, {}".format(x, y))
for letter in contour:
LM, x, y = self._increment_multihex(x, y, w, letter)
result.append("{}{}, {}".format(LM, x, y))
result.append('Z')
return " ".join(result)
def _increment_multisquare(self, x, y, w, direction):
if direction == 'a':
return 'L', x+w, y
elif direction == 'b':
return 'L', x, y+w
elif direction == 'c':
return 'L', x-w, y
elif direction == 'd':
return 'L', x, y-w
elif direction == 'A':
return 'M', x+w, y
elif direction == 'B':
return 'M', x, y-w
elif direction == 'C':
return 'M', x-w, y
elif direction == 'D':
return 'M', x, y+w
def _calc_multisquare(self, x, y, w, contour):
result = []
result.append("M{}, {}".format(x, y))
for letter in contour:
LM, x, y = self._increment_multisquare(x, y, w, letter)
result.append("{}{} {}".format(LM, x, y))
result.append('Z')
return " ".join(result)
# functions to set properties that will be retained across different
# types of grid
def set_colors(self, colors):
"""change colors list specified when Chorogrid is instantiated"""
self.colors = colors
assert len(ids) == len(colors), ("ids and colors must be "
"the same length")
def set_title(self, title, **kwargs):
"""Set a title for the grid
kwargs:
font_dict
default = {'font-style': 'normal', 'font-weight': 'normal',
'font-size': '21px', 'line-height': '125%',
'text-anchor': 'middle', 'font-family': 'sans-serif',
'letter-spacing': '0px', 'word-spacing': '0px',
'fill-opacity': 1, 'stroke': 'none',
'stroke-width': '1px', 'stroke-linecap': 'butt',
'stroke-linejoin': 'miter', 'stroke-opacity': 1,
'fill': '#000000'}"""
self.title_font_dict = {'font-style': 'normal',
'font-weight': 'normal',
'font-size': '21px',
'line-height': '125%',
'text-anchor': 'middle',
'font-family': 'sans-serif',
'letter-spacing': '0px',
'word-spacing': '0px',
'fill-opacity': 1,
'stroke': 'none',
'stroke-width': '1px',
'stroke-linecap': 'butt',
'stroke-linejoin': 'miter',
'stroke-opacity': 1,
'fill': '#000000'}
self.title_font_dict = self._update_default_dict(
self.title_font_dict, 'font_dict', kwargs)
self.title = title
def set_legend(self, colors, labels, title=None, width="square",
height=100, gutter=2, stroke_width=0.5, label_x_offset=2,
label_y_offset = 3, stroke_color="#303030", **kwargs):
"""Creates a legend that will be included in any draw method.
* width can be the text "square" or a number of pixels.
* a gradient can be made with a large number of colors, and ''
for each label that is not specified, and non-square width
* height does not include title
* if len(labels) can be len(colors) or len(colors)+1; the labels
will be aside the boxes, or at the interstices/fenceposts,
respectively; alternately, if len(labels) == 2, two fenceposts
will be assigned
kwarg: font_dict
default: {'font-style': 'normal', 'font-weight': 'normal',
'font-size': '12px', 'line-height': '125%',
'text-anchor': 'left', 'font-family': 'sans-serif',
'letter-spacing': '0px', 'word-spacing': '0px',
'fill-opacity': 1, 'stroke': 'none',
'stroke-width': '1px', 'stroke-linecap': 'butt',
'stroke-linejoin': 'miter', 'stroke-opacity': 1,
'fill': '#000000'}
"""
font_dict = {'font-style': 'normal',
'font-weight': 'normal',
'font-size': '12px',
'line-height': '125%',
'text-anchor': 'left',
'font-family': 'sans-serif',
'letter-spacing': '0px',
'word-spacing': '0px',
'fill-opacity': 1,
'stroke': 'none',
'stroke-width': '1px',
'stroke-linecap': 'butt',
'stroke-linejoin': 'miter',
'stroke-opacity': 1}
self.legend_height = height
colors = colors[::-1]
labels = labels[::-1]
num_boxes = len(colors)
if len(labels) == 2 and len(colors) > 2:
_ = []
_.append(labels[0])
for i in range(num_boxes-1):
_.append('')
_.append(labels[1])
labels = _
height_n = len(labels)
if title is not None and len(title)>0:
height_n += 1
box_height = ((height - gutter) / height_n ) - gutter
if width == "square":
width = box_height
assert len(labels) - len(colors) <= 1, ("Length of labels must be"
"two, or equal to colors or one more than colors")
box_offset = (len(labels) - len(colors)) * (box_height + gutter) / 2
font_style = self._dict2style(font_dict)
if title is not None and len(title) > 0:
y_offset = (int(font_dict['font-size'].replace('px', '')) +
gutter * 0.75) # ugly tweak
else:
y_offset = 0
# create a dict of legend parameters because these need to be defined BEFORE
# the draw_ method creates the lxml SubElements.
self.legend_params = {
'colors': colors,
'stroke_width': stroke_width,
'stroke_color': stroke_color,
'y_offset': y_offset,
'box_height': box_height,
'gutter': gutter,
'width': width,
'font_style': font_style,
'label_x_offset': label_x_offset,
'label_y_offset': label_y_offset,
'labels': labels,
'title': title}
# another function-from-within, I'm placing it here to be right below the set_legend method
def _apply_legend(self):
d = self.legend_params # convenient one-letter-long dict name
for i, color in enumerate(d['colors']):
style_text = ("fill:{0};stroke-width:{1}px;stroke:{2};fill-rule:"
"evenodd;stroke-linecap:butt;stroke-linejoin:miter;"
"stroke-opacity:1".format(color,
d['stroke_width'],
d['stroke_color']))
ET.SubElement(self.legendsvg,
"rect",
id="legendbox{}".format(i),
x="0",
y=str(d['y_offset'] + i * (d['box_height'] +
d['gutter'])),
height=str(d['box_height']),
width=str(d['width']),
style=style_text)
for i, label in enumerate(d['labels']):
style_text = d['font_style'] + ";alignment-baseline:middle"
_ = ET.SubElement(self.legendsvg, "text", id="legendlabel{}".format(
i), x=str(d['label_x_offset'] + d['width'] + d['gutter']),
y=str(d['label_y_offset'] + d['y_offset'] + i * (
d['box_height'] + d['gutter']) +
(d['box_height']) / 2), style=style_text)
_.text = label
if d['title'] is not None and len(d['title']) > 0:
_ = ET.SubElement(self.legendsvg, "text", id="legendtitle", x="0",
y="0", style=d['font_style'])
_.text = d['title']
def add_svg(self, text, offset=[0, 0]):
"""Adds svg text to the final output. Can be called more than once."""
offset[0] += self.additional_offset[0]
offset[1] += self.additional_offset[1]
translate_text = "translate({} {})".format(offset[0], offset[1])
text = ("".format(translate_text) +
text + "")
self.additional_svg.append(text)
def done_and_overlay(self, other_chorogrid, show=True, save_filename=None):
"""Overlays a second chorogrid object on top of the root object."""
svgstring = ET.tostring(self.svg).decode('utf-8')
svgstring = svgstring.replace('', ''.join(self.additional_svg) + '')
svgstring = svgstring.replace(">", ">\n")
svgstring = svgstring.replace("", "")
svgstring_overlaid = ET.tostring(other_chorogrid.svg).decode('utf-8')
svgstring_overlaid = svgstring_overlaid.replace('',
''.join(other_chorogrid.additional_svg) + '')
svgstring_overlaid = svgstring_overlaid.replace(">", ">\n")
svgstring_overlaid = re.sub('', '', svgstring_overlaid)
svgstring += svgstring_overlaid
if save_filename is not None:
if save_filename[-4:] != '.svg':
save_filename += '.svg'
with open(save_filename, 'w+', encoding='utf-8') as f:
f.write(svgstring)
if show:
display(SVG(svgstring))
# the .done() method
def done(self, show=True, save_filename=None):
"""if show == True, displays the svg in IPython notebook. If save_filename
is specified, saves svg file"""
svgstring = ET.tostring(self.svg).decode('utf-8')
svgstring = svgstring.replace('', ''.join(self.additional_svg) + '')
svgstring = svgstring.replace(">", ">\n")
if save_filename is not None:
if save_filename[-4:] != '.svg':
save_filename += '.svg'
with open(save_filename, 'w+', encoding='utf-8') as f:
f.write(svgstring)
if show:
display(SVG(svgstring))
# the methods to draw square grids, map (traditional choropleth),
# hex grid, four-hex grid, multi-square grid
def draw_squares(self, x_column='square_x',
y_column='square_y', **kwargs):
""" Creates an SVG file based on a square grid, with coordinates from
the specified columns in csv_path (specified when Chorogrid class
initialized).
Note on kwarg dicts: defaults will be used for all keys unless
overridden, i.e. you don't need to state all the key-value pairs.
kwarg: font_dict
default: {'font-style': 'normal', 'font-weight': 'normal',
'font-size': '12px', 'line-height': '125%',
'text-anchor': 'middle', 'font-family': 'sans-serif',
'letter-spacing': '0px', 'word-spacing': '0px',
'fill-opacity': 1, 'stroke': 'none',
'stroke-width': '1px', 'stroke-linecap': 'butt',
'stroke-linejoin': 'miter', 'stroke-opacity': 1,
'fill': '#000000'}
kwarg: spacing_dict
default: {'margin_left': 30, 'margin_top': 60,
'margin_right': 40, 'margin_bottom': 20,
'cell_width': 40, 'title_y_offset': 30,
'name_y_offset': 15, 'roundedness': 3,
'gutter': 1, 'stroke_color': '#ffffff',
'stroke_width': 0, 'missing_color': '#a0a0a0',
'legend_offset': [0, -10]}
kwarg: font_colors
default = "#000000"
if specified, must be either listlike object of colors
corresponding to ids, a dict of hex colors to font color, or a
string of a single color.
"""
font_dict = {'font-style': 'normal', 'font-weight': 'normal',
'font-size': '12px', 'line-height': '125%',
'text-anchor': 'middle', 'font-family': 'sans-serif',
'letter-spacing': '0px', 'word-spacing': '0px',
'fill-opacity': 1, 'stroke': 'none',
'stroke-width': '1px', 'stroke-linecap': 'butt',
'stroke-linejoin': 'miter', 'stroke-opacity': 1}
spacing_dict = {'margin_left': 30, 'margin_top': 60,
'margin_right': 80, 'margin_bottom': 20,
'cell_width': 40, 'title_y_offset': 30,
'name_y_offset': 15, 'roundedness': 3,
'gutter': 1, 'stroke_color': '#ffffff',
'missing_color': '#a0a0a0', 'stroke_width': 0,
'missing_font_color': '#000000',
'legend_offset': [0, -10]}
font_dict = self._update_default_dict(font_dict, 'font_dict', kwargs)
spacing_dict = self._update_default_dict(spacing_dict,
'spacing_dict', kwargs)
font_colors = self._determine_font_colors(kwargs)
font_style = self._dict2style(font_dict)
total_width = (spacing_dict['margin_left'] +
(self.df[x_column].max() + 1) *
spacing_dict['cell_width'] +
self.df[x_column].max() *
spacing_dict['gutter'] +
spacing_dict['margin_right'])
total_height = (spacing_dict['margin_top'] +
(self.df[y_column].max() + 1) *
spacing_dict['cell_width'] +
self.df[x_column].max() *
spacing_dict['gutter'] +
spacing_dict['margin_bottom'])
self._make_svg_top(total_width, total_height)
if spacing_dict['roundedness'] > 0:
roundxy = spacing_dict['roundedness']
else:
roundxy = 0
for i, id_ in enumerate(self.df[self.id_column]):
if id_ in self.ids:
this_color = self.colors[self.ids.index(id_)]
this_font_color = font_colors[self.ids.index(id_)]
else:
this_color = spacing_dict['missing_color']
this_font_color = spacing_dict['missing_font_color']
across = self.df[x_column].iloc[i]
down = self.df[y_column].iloc[i]
x = (spacing_dict['margin_left'] +
across * (spacing_dict['cell_width'] +
spacing_dict['gutter']))
y = (spacing_dict['margin_top'] +
down * (spacing_dict['cell_width'] +
spacing_dict['gutter']))
style_text = ("stroke:{0};stroke-width:{1};stroke-miterlimit:4;"
"stroke-opacity:1;stroke-dasharray:none;fill:"
"{2}".format(spacing_dict['stroke_color'],
spacing_dict['stroke_width'],
this_color))
this_font_style = font_style + ';fill:{}'.format(this_font_color)
ET.SubElement(self.svg,
"rect",
id="rect{}".format(id_),
x=str(x),
y=str(y),
ry = str(roundxy),
width=str(spacing_dict['cell_width']),
height=str(spacing_dict['cell_width']),
style=style_text)
_ = ET.SubElement(self.svg,
"text",
id="text{}".format(id_),
x=str(x + spacing_dict['cell_width']/2),
y=str(y + spacing_dict['name_y_offset']),
style=this_font_style)
_.text =str(id_)
if self.legend_params is not None and len(self.legend_params) > 0:
self.legendsvg = ET.SubElement(self.svg, "g", transform=
"translate({} {})".format(total_width -
spacing_dict['margin_right'] +
spacing_dict['legend_offset'][0],
total_height - self.legend_height +
spacing_dict['legend_offset'][1]))
self._apply_legend()
self._draw_title((total_width - spacing_dict['margin_left'] -
spacing_dict['margin_right']) / 2 +
spacing_dict['margin_left'],
spacing_dict['title_y_offset'])
def draw_map(self, path_column='map_path', **kwargs):
""" Creates an SVG file based on SVG paths delineating a map,
with paths from the specified columns in csv_path
(specified when Chorogrid class initialized).
Note on kwarg dict: defaults will be used for all keys unless
overridden, i.e. you don't need to state all the key-value pairs.
Note that the map does not have an option for font_dict, as
it will not print labels.
kwarg: spacing_dict
# Note that total_width and total_height will depend on where
# the paths came from.
# For the USA map included with this python module,
# they are 959 and 593.
default: {'map_width': 959, 'map_height': 593,
'margin_left': 10, 'margin_top': 20,
'margin_right': 80, 'margin_bottom': 20,
'title_y_offset': 45,
'stroke_color': '#ffffff', 'stroke_width': 0.5,
'missing_color': '#a0a0a0',
'legend_offset': [0, 0]}
"""
spacing_dict = {'map_width': 959,
'map_height': 593,
'margin_left': 10,
'margin_top': 20,
'margin_right': 80,
'margin_bottom': 20,
'title_y_offset': 45,
'stroke_color': '#ffffff',
'stroke_width': 0.5,
'missing_color': '#a0a0a0',
'legend_offset': [0, 0]}
spacing_dict = self._update_default_dict(spacing_dict,
'spacing_dict', kwargs)
total_width = (spacing_dict['map_width'] +
spacing_dict['margin_left'] +
spacing_dict['margin_right'])
total_height = (spacing_dict['map_height'] +
spacing_dict['margin_top'] +
spacing_dict['margin_bottom'])
self._make_svg_top(total_width, total_height)
translate_text = "translate({} {})".format(spacing_dict['margin_left'],
spacing_dict['margin_top'])
self.additional_offset = [spacing_dict['margin_left'],
spacing_dict['margin_top']]
mapsvg = ET.SubElement(self.svg,
"g",
transform=translate_text)
for i, id_ in enumerate(self.df[self.id_column]):
path = self.df[self.df[self.id_column] == id_][path_column].iloc[0]
if id_ in self.ids:
this_color = self.colors[self.ids.index(id_)]
else:
this_color = spacing_dict['missing_color']
style_text = ("stroke:{0};stroke-width:{1};stroke-miterlimit:4;"
"stroke-opacity:1;stroke-dasharray:none;fill:"
"{2}".format(spacing_dict['stroke_color'],
spacing_dict['stroke_width'],
this_color))
ET.SubElement(mapsvg,
"path",
id=str(id_),
d=path,
style=style_text)
if self.legend_params is not None and len(self.legend_params) > 0:
self.legendsvg = ET.SubElement(self.svg, "g", transform=
"translate({} {})".format(total_width -
spacing_dict['margin_right'] +
spacing_dict['legend_offset'][0],
total_height - self.legend_height +
spacing_dict['legend_offset'][1]))
self._apply_legend()
self._draw_title((total_width - spacing_dict['margin_left'] -
spacing_dict['margin_right']) / 2 +
spacing_dict['margin_left'],
spacing_dict['title_y_offset'])
def draw_hex(self, x_column='hex_x', y_column='hex_y', true_rows=True, **kwargs):
""" Creates an SVG file based on a hexagonal grid, with coordinates
from the specified columns in csv_path (specified when Chorogrid class
initialized).
Note that hexagonal grids can have two possible layouts:
1. 'true rows' (the default), in which:
* hexagons lie in straight rows joined by vertical sides to east and west
* hexagon points lie to north and south
* the home point (x=0, y=0 from upper left/northwest) has (1,0) to its immediate east
* the home point (0,0) shares its southeast side with (0,1)'s northwest side
* then (0,1) shares its southwest side with (0,2)'s northeast side
* thus odd rows are offset to the east of even rows
2. 'true columns', in which:
* hexagons lie in straight columns joined by horizontal sides to north and south
* hexagon points lie to east and west
* the home point (x=0, y=0 from upper left/northwest) has (0,1) to its immediate south
* the home point (0,0) shares its southeast side with (1,0)'s northwest side.
* then (1,0) shares its northeast side with (2,0)'s southwest side.
* thus odd columns are offset to the south of even columns
Note on kwarg dicts: defaults will be used for all keys unless
overridden, i.e. you don't need to state all the key-value pairs.
kwarg: font_dict
default: {'font-style': 'normal', 'font-weight': 'normal',
'font-size': '12px', 'line-height': '125%',
'text-anchor': 'middle', 'font-family': 'sans-serif',
'letter-spacing': '0px', 'word-spacing': '0px',
'fill-opacity': 1, 'stroke': 'none',
'stroke-width': '1px', 'stroke-linecap': 'butt',
'stroke-linejoin': 'miter', 'stroke-opacity': 1,
'fill': '#000000'}
kwarg: spacing_dict
default: {'margin_left': 30, 'margin_top': 60,
'margin_right': 40, 'margin_bottom': 20,
'cell_width': 40, 'title_y_offset': 30,
'name_y_offset': 15, 'stroke_width': 0
'gutter': 1, 'stroke_color': '#ffffff',
'missing_color': '#a0a0a0',
'legend_offset': [0, -10]}
kwarg: font_colors
default: "#000000"
if specified, must be either listlike object of colors
corresponding to ids, a dict of hex colors to font color, or a
string of a single color.
"""
font_dict = {'font-style': 'normal',
'font-weight': 'normal',
'font-size': '12px',
'line-height': '125%',
'text-anchor': 'middle',
'font-family': 'sans-serif',
'letter-spacing': '0px',
'word-spacing': '0px',
'fill-opacity': 1,
'stroke': 'none',
'stroke-width': '1px',
'stroke-linecap': 'butt',
'stroke-linejoin': 'miter',
'stroke-opacity': 1}
spacing_dict = {'margin_left': 30,
'margin_top': 60,
'margin_right': 80,
'margin_bottom': 20,
'cell_width': 40,
'title_y_offset': 30,
'name_y_offset': 15,
'roundedness': 3,
'stroke_width': 0,
'stroke_color': '#ffffff',
'missing_color': '#a0a0a0',
'gutter': 1,
'missing_font_color': '#000000',
'legend_offset': [0, -10]}
font_dict = self._update_default_dict(font_dict, 'font_dict', kwargs)
spacing_dict = self._update_default_dict(spacing_dict,
'spacing_dict', kwargs)
font_colors = self._determine_font_colors(kwargs)
font_style = self._dict2style(font_dict)
if true_rows:
total_width = (spacing_dict['margin_left'] +
(self.df[x_column].max()+1.5) *
spacing_dict['cell_width'] +
(self.df[x_column].max()-1) *
spacing_dict['gutter'] +
spacing_dict['margin_right'])
total_height = (spacing_dict['margin_top'] +
(self.df[y_column].max()*0.866 + 0.289) *
spacing_dict['cell_width'] +
(self.df[y_column].max()-1) *
spacing_dict['gutter'] +
spacing_dict['margin_bottom'])
else:
total_width = (spacing_dict['margin_left'] +
(self.df[x_column].max()*0.75 + 0.25) *
spacing_dict['cell_width'] +
(self.df[x_column].max()-1) *
spacing_dict['gutter'] +
spacing_dict['margin_right'])
total_height = (spacing_dict['margin_top'] +
(self.df[y_column].max() + 1.5) *
spacing_dict['cell_width'] +
(self.df[y_column].max()-1) *
spacing_dict['gutter'] +
spacing_dict['margin_bottom'])
self._make_svg_top(total_width, total_height)
w = spacing_dict['cell_width']
for i, id_ in enumerate(self.df[self.id_column]):
if id_ in self.ids:
this_color = self.colors[self.ids.index(id_)]
this_font_color = font_colors[self.ids.index(id_)]
else:
this_color = spacing_dict['missing_color']
this_font_color = spacing_dict['missing_font_color']
across = self.df[x_column].iloc[i]
down = self.df[y_column].iloc[i]
# offset odd rows to the right or down
x_offset = 0
y_offset = 0
if true_rows:
if down % 2 == 1:
x_offset = w/2
x = (spacing_dict['margin_left'] +
x_offset + across * (w + spacing_dict['gutter']))
y = (spacing_dict['margin_top'] +
down * (1.5 * w / sqrt(3) + spacing_dict['gutter']))
else:
x_offset = 0.25 * w # because northwest corner is to the east of westmost point
if across % 2 == 1:
y_offset = w*0.866/2
x = (spacing_dict['margin_left'] +
x_offset + across * 0.75 * (w + spacing_dict['gutter']))
y = (spacing_dict['margin_top'] +
y_offset + down * (sqrt(3) / 2 * w + spacing_dict['gutter']))
polystyle = ("stroke:{0};stroke-miterlimit:4;stroke-opacity:1;"
"stroke-dasharray:none;fill:{1};stroke-width:"
"{2}".format(spacing_dict['stroke_color'],
this_color,
spacing_dict['stroke_width']))
this_font_style = font_style + ';fill:{}'.format(this_font_color)
ET.SubElement(self.svg,
"polygon",
id="hex{}".format(id_),
points=self._calc_hexagon(x, y, w, true_rows),
style=polystyle)
_ = ET.SubElement(self.svg,
"text",
id="text{}".format(id_),
x=str(x+w/2),
y=str(y + spacing_dict['name_y_offset']),
style=this_font_style)
_.text =str(id_)
if self.legend_params is not None and len(self.legend_params) > 0:
self.legendsvg = ET.SubElement(self.svg, "g", transform=
"translate({} {})".format(total_width -
spacing_dict['margin_right'] +
spacing_dict['legend_offset'][0],
total_height - self.legend_height +
spacing_dict['legend_offset'][1]))
self._apply_legend()
self._draw_title((total_width - spacing_dict['margin_left'] -
spacing_dict['margin_right']) / 2 +
spacing_dict['margin_left'],
spacing_dict['title_y_offset'])
def draw_multihex(self, x_column='fourhex_x', y_column='fourhex_y',
contour_column = 'fourhex_contour',
x_label_offset_column = 'fourhex_label_offset_x',
y_label_offset_column = 'fourhex_label_offset_y',
**kwargs):
""" Creates an SVG file based on a hexagonal grid, with contours
described by the following pattern:
a: up and to the right
b: down and to the right
c: down
d: down and to the left
e: up and to the left
f: up
Capital letters signify a move without drawing.
Note on kwarg dicts: defaults will be used for all keys unless
overridden, i.e. you don't need to state all the key-value pairs.
kwarg: font_dict
default: {'font-style': 'normal', 'font-weight': 'normal',
'font-size': '12px', 'line-height': '125%',
'text-anchor': 'middle', 'font-family': 'sans-serif',
'letter-spacing': '0px', 'word-spacing': '0px',
'fill-opacity': 1, 'stroke': 'none',
'stroke-width': '1px', 'stroke-linecap': 'butt',
'stroke-linejoin': 'miter', 'stroke-opacity': 1,
'fill': '#000000'}
kwarg: spacing_dict
default: {'margin_left': 30, 'margin_top': 60,
'margin_right': 40, 'margin_bottom': 20,
'cell_width': 30, 'title_y_offset': 30,
'name_y_offset': 15, 'stroke_width': 1
'stroke_color': '#ffffff', 'missing_color': '#a0a0a0',
'legend_offset': [0, -10]}
(note that there is no gutter)
kwarg: font_colors
default = "#000000"
if specified, must be either listlike object of colors
corresponding to ids, a dict of hex colors to font color, or a
string of a single color.
"""
font_dict = {'font-style': 'normal',
'font-weight': 'normal',
'font-size': '12px',
'line-height': '125%',
'text-anchor': 'middle',
'font-family': 'sans-serif',
'letter-spacing': '0px',
'word-spacing': '0px',
'fill-opacity': 1,
'stroke': 'none',
'stroke-width': '1px',
'stroke-linecap': 'butt',
'stroke-linejoin': 'miter',
'stroke-opacity': 1}
spacing_dict = {'margin_left': 30,
'margin_top': 60,
'margin_right': 80,
'margin_bottom': 20,
'cell_width': 30,
'title_y_offset': 30,
'name_y_offset': 15,
'roundedness': 3,
'stroke_width': 1,
'stroke_color': '#ffffff',
'missing_color': '#a0a0a0',
'missing_font_color': '#000000',
'legend_offset': [0, -10]}
font_dict = self._update_default_dict(font_dict, 'font_dict', kwargs)
spacing_dict = self._update_default_dict(spacing_dict,
'spacing_dict', kwargs)
font_colors = self._determine_font_colors(kwargs)
font_style = self._dict2style(font_dict)
total_width = (spacing_dict['margin_left'] +
(self.df[x_column].max()+1.5) *
spacing_dict['cell_width'] +
spacing_dict['margin_right'])
total_height = (spacing_dict['margin_top'] +
(self.df[y_column].max() + 1.711) *
spacing_dict['cell_width'] +
spacing_dict['margin_bottom'])
self._make_svg_top(total_width, total_height)
w = spacing_dict['cell_width']
h = w/sqrt(3)
for i, id_ in enumerate(self.df[self.id_column]):
if id_ in self.ids:
this_color = self.colors[self.ids.index(id_)]
this_font_color = font_colors[self.ids.index(id_)]
else:
this_color = spacing_dict['missing_color']
this_font_color = spacing_dict['missing_font_color']
across = self.df[x_column].iloc[i]
down = self.df[y_column].iloc[i]
contour = self.df[contour_column].iloc[i]
label_off_x = self.df[x_label_offset_column].iloc[i]
label_off_y = self.df[y_label_offset_column].iloc[i]
# offset odd rows to the right
if down % 2 == 1:
x_offset = w/2
else:
x_offset = 0
x = (spacing_dict['margin_left'] +
x_offset + across * w)
y = (spacing_dict['margin_top'] +
down * (1.5 * w / sqrt(3)))
polystyle = ("stroke:{0};stroke-miterlimit:4;stroke-opacity:1;"
"stroke-dasharray:none;fill:{1};stroke-width:"
"{2}".format(spacing_dict['stroke_color'],
this_color,
spacing_dict['stroke_width']))
this_font_style = font_style + ';fill:{}'.format(this_font_color)
ET.SubElement(self.svg,
"path",
id="hex{}".format(id_),
d=self._calc_multihex(x, y, w, contour),
style=polystyle)
_ = ET.SubElement(self.svg,
"text",
id="text{}".format(id_),
x=str(x + w/2 + w * label_off_x),
y=str(y + spacing_dict['name_y_offset'] +
h * label_off_y),
style=this_font_style)
_.text =str(id_)
if self.legend_params is not None and len(self.legend_params) > 0:
self.legendsvg = ET.SubElement(self.svg, "g", transform=
"translate({} {})".format(total_width -
spacing_dict['margin_right'] +
spacing_dict['legend_offset'][0],
total_height - self.legend_height +
spacing_dict['legend_offset'][1]))
self._apply_legend()
self._draw_title((total_width - spacing_dict['margin_left'] -
spacing_dict['margin_right']) / 2 +
spacing_dict['margin_left'],
spacing_dict['title_y_offset'])
def draw_multisquare(self, x_column='multisquare_x', y_column='multisquare_y',
contour_column = 'multisquare_contour',
x_label_offset_column = 'multisquare_label_offset_x',
y_label_offset_column = 'multisquare_label_offset_y',
**kwargs):
""" Creates an SVG file based on a square grid, with contours
described by the following pattern:
a: right
b: down
c: left
d: up
A: right (without drawing)
B: down (without drawing)
C: left (without drawing)
D: up (without drawing)
Note on kwarg dicts: defaults will be used for all keys unless
overridden, i.e. you don't need to state all the key-value pairs.
kwarg: font_dict
default: {'font-style': 'normal', 'font-weight': 'normal',
'font-size': '12px', 'line-height': '125%',
'text-anchor': 'middle', 'font-family': 'sans-serif',
'letter-spacing': '0px', 'word-spacing': '0px',
'fill-opacity': 1, 'stroke': 'none',
'stroke-width': '1px', 'stroke-linecap': 'butt',
'stroke-linejoin': 'miter', 'stroke-opacity': 1,
'fill': '#000000'}
kwarg: spacing_dict
default: {'margin_left': 30, 'margin_top': 60,
'margin_right': 40, 'margin_bottom': 20,
'cell_width': 30, 'title_y_offset': 30,
'name_y_offset': 15, 'stroke_width': 1
'stroke_color': '#ffffff', 'missing_color': '#a0a0a0',
'legend_offset': [0, -10]}
(note that there is no gutter)
kwarg: font_colors
default = "#000000"
if specified, must be either listlike object of colors
corresponding to ids, a dict of hex colors to font color, or a
string of a single color.
"""
font_dict = {'font-style': 'normal',
'font-weight': 'normal',
'font-size': '12px',
'line-height': '125%',
'text-anchor': 'middle',
'font-family': 'sans-serif',
'letter-spacing': '0px',
'word-spacing': '0px',
'fill-opacity': 1,
'stroke': 'none',
'stroke-width': '1px',
'stroke-linecap': 'butt',
'stroke-linejoin': 'miter',
'stroke-opacity': 1}
spacing_dict = {'margin_left': 30,
'margin_top': 60,
'margin_right': 80,
'margin_bottom': 20,
'cell_width': 30,
'title_y_offset': 30,
'name_y_offset': 15,
'roundedness': 3,
'stroke_width': 1,
'stroke_color': '#ffffff',
'missing_color': '#a0a0a0',
'missing_font_color': '#000000',
'legend_offset': [0, -10]}
font_dict = self._update_default_dict(font_dict, 'font_dict', kwargs)
spacing_dict = self._update_default_dict(spacing_dict,
'spacing_dict', kwargs)
font_colors = self._determine_font_colors(kwargs)
font_style = self._dict2style(font_dict)
total_width = (spacing_dict['margin_left'] +
(self.df[x_column].max()+1) *
spacing_dict['cell_width'] +
spacing_dict['margin_right'])
total_height = (spacing_dict['margin_top'] +
(self.df[y_column].max()+1) *
spacing_dict['cell_width'] +
spacing_dict['margin_bottom'])
self._make_svg_top(total_width, total_height)
w = spacing_dict['cell_width']
for i, id_ in enumerate(self.df[self.id_column]):
if id_ in self.ids:
this_color = self.colors[self.ids.index(id_)]
this_font_color = font_colors[self.ids.index(id_)]
else:
this_color = spacing_dict['missing_color']
this_font_color = spacing_dict['missing_font_color']
across = self.df[x_column].iloc[i]
down = self.df[y_column].iloc[i]
contour = self.df[contour_column].iloc[i]
label_off_x = self.df[x_label_offset_column].iloc[i]
label_off_y = self.df[y_label_offset_column].iloc[i]
x = (spacing_dict['margin_left'] + across * w)
y = (spacing_dict['margin_top'] +
down * w)
polystyle = ("stroke:{0};stroke-miterlimit:4;stroke-opacity:1;"
"stroke-dasharray:none;fill:{1};stroke-width:"
"{2}".format(spacing_dict['stroke_color'],
this_color,
spacing_dict['stroke_width']))
this_font_style = font_style + ';fill:{}'.format(this_font_color)
ET.SubElement(self.svg,
"path",
id="square{}".format(id_),
d=self._calc_multisquare(x, y, w, contour),
style=polystyle)
_ = ET.SubElement(self.svg,
"text",
id="text{}".format(id_),
x=str(x + w/2 + w * label_off_x),
y=str(y + spacing_dict['name_y_offset'] +
w * label_off_y),
style=this_font_style)
_.text = str(id_)
if self.legend_params is not None and len(self.legend_params) > 0:
self.legendsvg = ET.SubElement(self.svg, "g", transform=
"translate({} {})".format(total_width -
spacing_dict['margin_right'] +
spacing_dict['legend_offset'][0],
total_height - self.legend_height +
spacing_dict['legend_offset'][1]))
self._apply_legend()
self._draw_title((total_width - spacing_dict['margin_left'] -
spacing_dict['margin_right']) / 2 +
spacing_dict['margin_left'],
spacing_dict['title_y_offset'])
PK ccI9Y chorogrid/Colorbin.py#!/usr/bin/python
# Filename: Colorbin.py
class Colorbin(object):
""" Instantiate with a list of quantities and colors, then retrieve
the following attributes:
.colors_out : output list of colors, same length as quantities
.fenceposts : divisions between bins
.labels: one per color
.fencepostlabels: one per fencepost
.complements: list of colors, see set_complements, below
attributes that can be changed:
.proportional : if True, all bins have fenceposts same distance
apart (with default bin_min, bin_mid and bin_max)
: if False, all bins have (insofar as possible) the same
number of members
: note that this can break if not every quantity is
unique
.bin_min, .bin_max, .bin_mid
.decimals : if None, no rounding; otherwise round to this number
methods:
.set_decimals(n): just what it sounds like
.recalc(fenceposts=True): recalculate colors (and fenceposts, if True)
based on attributes
.calc_complements(cutoff [between 0 and 1], color_below, color_above):
if the greyscale color is below the cutoff (i.e. darker),
complement is assigned color_below, otherwise color_above.
"""
def __init__(self, quantities, colors_in, proportional=True, decimals=None):
self.quantities = quantities
self.colors_in = colors_in
self.proportional = proportional
self.bin_min = min(self.quantities)
self.bin_max = max(self.quantities)
self.bin_mid = (self.bin_min + self.bin_max) / 2
self.decimals = None
self.recalc()
self.complements = None
def _calc_fenceposts(self):
if self.proportional:
self.fenceposts = []
step_1 = (self.bin_mid - self.bin_min) / len(self.colors_in) * 2
step_2 = (self.bin_max - self.bin_mid) / len(self.colors_in) * 2
for i in range(len(self.colors_in)+1):
if i < len(self.colors_in)/2:
self.fenceposts.append(self.bin_min + i * step_1)
elif i == len(self.colors_in)/2:
self.fenceposts.append(self.bin_mid)
else:
self.fenceposts.append(self.bin_max -
(len(self.colors_in) - i) * step_2)
else:
quant_sorted = list(self.quantities[:])
quant_sorted.sort()
step = len(quant_sorted) / len(self.colors_in)
self.fenceposts = []
for i in range(len(self.colors_in)):
self.fenceposts.append(quant_sorted[int(i*step)])
self.fenceposts.append(quant_sorted[-1])
if self.decimals is not None:
self.fenceposts = [round(x, self.decimals) for x in self.fenceposts]
def _calc_labels(self):
self.labels = []
self.fencepostlabels = []
for n1, n2 in zip(self.fenceposts[:-1], self.fenceposts[1:]):
self.labels.append('{}-{}'.format(n1, n2))
self.fencepostlabels.append(str(n1))
self.fencepostlabels.append(str(n2))
def _calc_colors(self):
self.colors_out = []
self.bin_counts = [0] * len(self.colors_in)
for qty in self.quantities:
bin_ = 0
for i in range(1, len(self.colors_in)):
if qty >= self.fenceposts[i]:
bin_ = i
self.colors_out.append(self.colors_in[bin_])
self.bin_counts[bin_] += 1
def set_decimals(self, decimals):
self.decimals = decimals
def recalc(self, fenceposts = True):
if fenceposts:
self._calc_fenceposts()
self._calc_labels()
self._calc_colors()
def count_bins(self):
print('count label')
print('===== =====')
for label, cnt in zip(self.labels, self.bin_counts):
print('{:5d} {}'.format(cnt, label))
def calc_complements(self, cutoff, color_below, color_above):
self.complements = []
for color in self.colors_out:
r, g, b = tuple(int(color[1:][i:i + 6 // 3], 16)
for i in range(0, 6, 2))
grey = (0.299 * r + 0.587 * g + 0.114* b) / 256
if grey < cutoff:
self.complements.append(color_below)
else:
self.complements.append(color_above)PK ecI= chorogrid/__init__.py#!/usr/bin/python
# Filename: __init__.py
# Author: David Taylor (@Prooffreader)
"""A python script to produce choropleths and colored square- and hex-grid maps"""
__version__ = '0.0.1'
from chorogrid.Colorbin import Colorbin
from chorogrid.Chorogrid import Chorogrid
PK ccI$K K . chorogrid/databases/canada_federal_ridings.csvdistrict_code,province,federal_electoral_district,population,electors,area_km2,square_x,square_y,truecolhex_x,truecolhex_y
48001,AB,Banff—Airdrie,105442,87666,12282,8,3,11,6
48007,AB,Calgary Heritage,108320,79349,70,9,6,11,8
48012,AB,Calgary Signal Hill,109647,83227,66,8,5,11,7
48022,AB,Foothills,105515,80188,20877,10,7,11,9
48034,AB,Yellowhead,98855,72816,76127,7,3,11,5
48004,AB,Calgary Centre,108931,83497,49,9,5,12,8
48008,AB,Calgary Midnapore,111227,85280,87,10,6,12,9
48010,AB,Calgary Rocky Ridge,108901,84093,90,8,4,12,7
48020,AB,Edmonton West,104422,78293,105,7,1,12,5
48021,AB,Edmonton—Wetaskiwin,110644,95162,4947,7,2,12,6
48005,AB,Calgary Confederation,111785,85997,54,10,5,13,7
48011,AB,Calgary Shepard,110296,93794,186,11,6,13,8
48018,AB,Edmonton Riverbend,106302,79932,60,8,2,13,5
48024,AB,Grande Prairie—Mackenzie,106738,79309,109194,7,0,13,2
48026,AB,Lethbridge,105999,80020,3028,11,7,13,9
48029,AB,Red Deer—Mountain View,110793,84758,7659,9,3,13,6
48031,AB,St. Albert—Edmonton,105162,82120,104,10,0,13,3
48033,AB,Sturgeon River—Parkland,105733,82718,4000,9,0,13,4
48003,AB,Bow river,103871,73598,24427,11,4,14,9
48006,AB,Calgary Forest Lawn,108251,73857,53,11,5,14,8
48009,AB,Calgary Nose Hill,109286,80629,57,9,4,14,7
48014,AB,Edmonton Centre,106121,76739,46,8,1,14,5
48015,AB,Edmonton Griesbach,107809,78785,46,9,1,14,4
48023,AB,Fort McMurray—Cold Lake,101538,75312,147412,11,0,14,2
48028,AB,Peace River—Westlock,108095,73627,105924,8,0,14,3
48030,AB,Red Deer—Lacombe,113693,83963,6316,10,3,14,6
48002,AB,Battle River—Crowfoot,107140,79873,53072,11,3,15,7
48013,AB,Calgary Skyview,110189,71741,123,10,4,15,8
48016,AB,Edmonton Manning,106262,78003,158,10,1,15,3
48017,AB,Edmonton Mill Woods,106103,72151,50,10,2,15,6
48019,AB,Edmonton Strathcona,103183,74710,80,9,2,15,5
48025,AB,Lakeland,104616,77809,31877,11,1,15,2
48027,AB,Medicine Hat—Cardston—Warner,102847,75944,29982,11,8,15,9
48032,AB,Sherwood Park—Fort Saskatchewan,111541,87943,1271,11,2,15,4
59009,BC,Courtenay—Alberni,110391,88673,8571,0,7,1,7
59010,BC,Cowichan—Malahat—Langford,99160,78148,4749,0,9,1,9
59018,BC,Nanaimo—Ladysmith,114998,91240,1753,0,8,1,8
59026,BC,Esquimalt—Saanich—Sooke,113004,87281,404,1,9,1,10
59027,BC,Saanich—Gulf Islands,104285,83970,518,1,8,2,9
59041,BC,Victoria,110942,90217,43,2,9,2,10
59025,BC,Richmond Centre,93863,67734,49,3,7,3,6
59031,BC,Steveston—Richmond East,96610,70676,10,4,7,3,7
59037,BC,North Island—Powell River,103458,57911,79517,0,6,3,4
59039,BC,Vancouver Quadra,102416,72409,42,2,6,3,5
59011,BC,Delta,100588,72865,207,4,8,4,8
59028,BC,Skeena—Bulkley Valley,90586,62234,327275,6,3,4,4
59034,BC,Vancouver Centre,102480,84273,14,3,4,4,5
59036,BC,Vancouver Granville,99886,76973,23,3,5,4,6
59040,BC,Vancouver South,100966,68733,21,3,6,4,7
59019,BC,New Westminster—Burnaby,108652,77639,29,4,6,5,7
59030,BC,South Surrey—White Rock,94678,74649,154,5,8,5,9
59033,BC,Surrey—Newton,105183,62855,30,5,7,5,8
59038,BC,Vancouver Kingsway,102003,69812,15,4,5,5,6
59042,BC,West Vancouver—Sunshine Coast—Sea to Sky Country,112875,86370,13237,3,3,5,5
59003,BC,Burnaby South,105037,73925,47,5,5,6,7
59007,BC,Cloverdale—Langley City,100318,75076,60,6,7,6,8
59035,BC,Vancouver East,110097,85900,21,4,4,6,6
59002,BC,Burnaby—Seymour,100632,72393,115,5,3,7,5
59008,BC,Coquitlam-Port Coquitlam,110277,82358,650,6,4,7,6
59012,BC,Fleetwood—Port Kells,109742,72487,74,6,6,7,7
59016,BC,Langley—Aldergrove,103084,80360,382,6,8,7,8
59001,BC,Abbotsford,96819,67509,176,7,8,8,9
59021,BC,North Vancouver,109639,82085,342,4,3,8,5
59022,BC,Pitt Meadows—Maple Ridge,94111,70289,2141,6,5,8,6
59023,BC,Port Moody—Coquitlam,108326,77368,101,5,4,8,7
59032,BC,Surrey Centre,111486,68719,44,5,6,8,8
59004,BC,Cariboo—Prince George,108252,77042,83193,7,5,9,4
59005,BC,Central Okanagan—Similkameen—Nicola,104398,84348,16208,8,7,9,6
59006,BC,Chilliwack—Hope,92734,70240,3355,8,8,9,8
59014,BC,Kelowna—Lake Country,110051,86934,1670,9,7,9,7
59017,BC,Mission—Matsqui—Fraser Canyon,90871,61105,21769,7,7,9,5
59013,BC,Kamloops—Thompson—Cariboo,118618,92130,38320,7,6,10,6
59015,BC,Kootenay—Columbia,107589,83190,64336,10,8,10,8
59020,BC,North Okanagan—Shuswap,121474,94179,16734,8,6,10,7
59024,BC,Prince George—Peace River—Northern Rockies,107382,75063,243276,7,4,10,5
59029,BC,South Okanagan—West Kootenay,112096,88519,18139,9,8,10,9
46001,MB,Brandon—Souris,83814,59459,18290,14,8,20,9
46002,MB,Charleswood—St. James—Assiniboia—Headingley,81864,62583,207,14,5,20,10
46007,MB,Portage—Lisgar,91019,61350,12665,14,7,20,11
46004,MB,Dauphin—Swan River—Neepawa,87374,61579,56820,14,2,21,8
46011,MB,Winnipeg Centre,82026,54719,29,15,5,21,9
46013,MB,Winnipeg South,85540,62156,105,15,7,21,11
46014,MB,Winnipeg South Centre,90711,67988,46,14,6,21,10
46003,MB,Churchill—Keewatinook Aski,85148,47940,494701,15,2,22,8
46005,MB,Elmwood—Transcona,85906,64395,50,15,4,22,10
46009,MB,Saint Boniface—Saint Vital,84353,64202,65,15,6,22,11
46012,MB,Winnipeg North,88616,56380,38,14,4,22,9
46006,MB,Kildonan—St. Paul,81794,61252,172,15,3,23,8
46008,MB,Provencher,88640,63356,18773,15,8,23,10
46010,MB,Selkirk—Interlake—Eastman,91463,69587,25824,14,3,23,9
13003,NB,Fredericton,81759,59284,1678,44,8,44,7
13005,NB,Madawaska—Restigouche,62540,50442,11962,44,6,44,5
13010,NB,Tobique—Mactaquac,70632,53129,15130,44,7,44,6
13001,NB,Acadie—Bathurst,79340,66374,5183,45,6,45,4
13006,NB,Miramichi—Grand Lake,59343,47813,17420,45,7,45,5
13007,NB,Moncton—Riverview—Dieppe,89484,70357,168,46,8,45,6
13008,NB,New Brunswick Southwest,66197,51004,10770,44,9,45,7
13002,NB,Beauséjour,80416,65927,4214,46,7,46,6
13004,NB,Fundy Royal,79331,62270,7686,45,8,46,7
13009,NB,Saint John—Rothesay,82129,61223,457,45,9,46,8
10004,NL,Labrador,26728,20084,294330,44,2,47,0
10005,NL,Long Range Mountains,87592,70272,41606,46,2,48,1
10003,NL,Coast of Bays—Central—Notre Dame,78092,63621,43596,46,3,49,1
10002,NL,Bonavista—Burin—Trinity,76704,61088,18961,47,3,50,2
10001,NL,Avalon,81540,66653,7303,48,3,51,2
10006,NL,St. John's East,81936,64137,363,48,2,51,1
10007,NL,St. John's South—Mount Pearl,81944,66020,503,49,3,52,2
12011,NS,West Nova,83654,65963,9965,47,10,48,9
12006,NS,Halifax West,87275,68266,260,48,9,49,8
12009,NS,South Shore—St. Margarets,92561,75086,9855,48,10,49,9
12003,NS,Cumberland—Colchester,82321,64065,8269,47,7,50,7
12005,NS,Halifax,92643,68609,244,49,10,50,9
12007,NS,Kings—Hants,83306,65347,4440,48,8,50,8
12002,NS,Central Nova,74597,58513,10347,48,7,51,6
12004,NS,Dartmouth—Cole Harbour,91212,71949,102,49,9,51,8
12008,NS,Sackville—Preston—Chezzetcook,85583,66578,777,49,8,51,7
12001,NS,Cape Breton—Canso,75247,60238,10039,49,7,52,6
12010,NS,Sydney—Victoria,73328,58932,5085,50,7,52,5
61001,NT,Northwest Territories,41462,28795,1346106,13,0,14,0
62001,NU,Nunavut,31906,18124,2093190,15,0,22,0
35042,ON,Kenora,55977,42138,321741,16,8,24,9
35046,ON,Kitchener—Conestoga,93827,67890,949,16,16,24,16
35091,ON,Sarnia—Lambton,106293,80029,1568,12,20,24,17
35105,ON,Thunder Bay—Rainy River,82984,62207,39545,17,8,24,10
35117,ON,Windsor West,118973,84700,83,11,21,24,18
35052,ON,London North Centre,118079,87668,63,13,18,25,17
35083,ON,Perth—Wellington,104912,75217,3782,16,15,25,14
35092,ON,Sault Ste. Marie,82052,63417,5921,19,9,25,11
35106,ON,Thunder Bay—Superior North,82827,63192,87965,18,8,25,10
35107,ON,Timmins—James Bay,83104,60202,251599,20,8,25,11
35112,ON,Waterloo,103192,77312,78,16,17,25,16
35113,ON,Wellington—Halton Hills,115880,88674,1584,16,12,25,15
35116,ON,Windsor—Tecumseh,115528,86351,174,12,21,25,18
35002,ON,Algoma—Manitoulin—Kapuskasing,79801,62230,100103,19,8,26,11
35008,ON,Brampton Centre,103122,64148,46,18,14,26,14
35014,ON,Bruce—Grey—Owen Sound,106475,81389,6447,17,11,26,12
35026,ON,Essex,120477,90591,1177,12,22,26,19
35032,ON,Guelph,121688,94632,92,16,13,26,15
35040,ON,Huron—Bruce,104842,79533,5896,16,14,26,13
35045,ON,Kitchener Centre,102433,76163,44,15,17,26,16
35048,ON,Lambton—Kent—Middlesex,105919,80027,5278,14,18,26,17
35053,ON,London West,119090,91601,82,13,19,26,18
35103,ON,Sudbury,92048,71844,977,20,9,26,10
35010,ON,Brampton North,111951,72312,36,18,13,27,14
35011,ON,Brampton South,107364,70449,49,17,14,27,15
35012,ON,Brampton West,101762,68796,61,17,13,27,13
35017,ON,Chatham-Kent—Leamington,111866,78803,2183,13,22,27,19
35022,ON,Dufferin—Caledon,116341,91269,2293,17,12,27,12
35051,ON,London—Fanshawe,119334,85124,124,13,20,27,17
35057,ON,Milton,88065,70430,472,16,18,27,16
35080,ON,Oxford,108656,83003,2384,14,19,27,18
35099,ON,Simcoe—Grey,116307,95511,1950,18,11,27,11
35100,ON,Simcoe North,108672,85156,1894,22,11,27,10
35005,ON,Barrie—Springwater—Oro-Medonte,97876,74783,1027,21,11,28,11
35009,ON,Brampton East,99712,65818,89,18,15,28,13
35016,ON,Cambridge,111693,82103,373,15,19,28,18
35025,ON,Elgin—Middlesex—London,110109,82062,2640,13,21,28,19
35043,ON,King—Vaughan,109235,83550,449,19,12,28,12
35047,ON,Kitchener South—Hespeler,97763,71873,111,15,18,28,17
35058,ON,Mississauga Centre,118756,81920,24,17,17,28,16
35062,ON,Mississauga—Malton,118046,73591,102,18,16,28,15
35069,ON,Nickel Belt,90962,72134,30490,21,9,28,10
35111,ON,Vaughan—Woodbridge,105450,73190,84,18,12,28,14
35004,ON,Barrie—Innisfil,101584,76389,3862011,20,11,29,11
35013,ON,Brantford—Brant,132443,95616,886,14,20,29,19
35015,ON,Burlington,120569,94679,84,16,19,29,18
35029,ON,Etobicoke North,117601,67544,57,19,13,29,14
35063,ON,Mississauga—Streetsville,118757,82618,49,17,15,29,16
35065,ON,Newmarket—Aurora,109457,82454,62,23,12,29,12
35073,ON,Oakville North—Burlington,114378,84100,92,17,18,29,17
35082,ON,Parry Sound—Muskoka,91263,74977,14402,21,10,29,10
35104,ON,Thornhill,110427,80288,66,20,12,29,13
35121,ON,Humber River—Black Creek,108198,60343,32,20,13,29,15
35003,ON,Aurora—Oak Ridges—Richmond Hill,106064,77411,100,22,12,30,13
35030,ON,Flamborough—Glanbrook,97081,77774,941,15,20,30,20
35035,ON,Hamilton Centre,101932,68247,32,16,20,30,19
35054,ON,Markham—Stouffville,109780,86464,299,26,12,30,12
35060,ON,Mississauga—Erin Mills,117199,80912,36,17,16,30,17
35072,ON,Oakville,119649,87670,83,17,19,30,18
35087,ON,Richmond Hill,108658,79513,12,21,12,30,14
35118,ON,York Centre,100277,63682,37,21,13,30,15
35119,ON,York—Simcoe,94616,74911,844,19,11,30,11
35120,ON,York South—Weston,116606,69754,26,19,15,30,16
35020,ON,Don Valley North,103073,71081,26,23,13,31,16
35027,ON,Etobicoke Centre,114910,86412,37,19,16,31,17
35033,ON,Haldimand—Norfolk,108051,81773,3061,14,21,31,20
35034,ON,Haliburton—Kawartha Lakes—Brock,110182,90594,8941,23,11,31,12
35056,ON,Markham—Unionville,104693,81583,89,25,12,31,14
35059,ON,Mississauga East—Cooksville,121792,80906,34,18,17,31,18
35061,ON,Mississauga—Lakeshore,118893,85379,92,18,18,31,19
35070,ON,Nipissing—Timiskaming,90996,70342,15313,22,10,31,11
35084,ON,Peterborough—Kawartha,115269,90352,3473,24,11,31,13
35115,ON,Willowdale,109680,74205,21,22,13,31,15
35018,ON,Davenport,102360,70794,13,20,15,32,16
35023,ON,Durham,115395,92317,953,27,12,32,13
35028,ON,Etobicoke—Lakeshore,115437,90167,53,19,17,32,17
35037,ON,Hamilton Mountain,103615,76549,35,16,21,32,19
35038,ON,Hamilton West—Ancaster—Dundas,109535,82929,110,15,21,32,18
35049,ON,Lanark—Frontenac—Kingston,98409,77808,7322,26,11,32,12
35055,ON,Markham—Thornhill,102221,70211,44,24,12,32,14
35086,ON,Renfrew—Nipissing—Pembroke,102537,77520,12583,25,10,32,11
35090,ON,Toronto—St. Paul's,103983,75852,14,21,15,32,15
35021,ON,Don Valley West,99820,69333,32,20,14,33,15
35024,ON,Eglinton—Lawrence,113150,76739,24,19,14,33,14
35036,ON,Hamilton East—Stoney Creek,107786,79750,72,17,21,33,18
35039,ON,Hastings—Lennox and Addington,92528,71818,9217,25,11,33,12
35041,ON,Kanata—Carleton,100846,78431,795,26,10,33,11
35078,ON,Ottawa—Vanier,110999,82040,41,29,10,33,10
35081,ON,Parkdale—High Park,105103,76952,16,20,17,33,17
35085,ON,Pickering—Uxbridge,109344,84997,687,25,14,33,13
35093,ON,Scarborough—Agincourt,104499,68748,22,24,13,33,16
35001,ON,Ajax,109600,83542,71,26,14,34,14
35031,ON,Glengarry—Prescott—Russell,106240,84340,3018,31,10,34,10
35064,ON,Nepean,104775,81062,179,28,11,34,12
35068,ON,Niagara West,86533,68333,1057,18,21,34,19
35071,ON,Northumberland—Peterborough South,107840,88276,3001,28,12,34,13
35079,ON,Ottawa West—Nepean,111881,81646,71,27,10,34,11
35096,ON,Scarborough North,101080,64427,32,25,13,34,16
35101,ON,Spadina—Fort York,82480,73179,21,21,17,34,18
35110,ON,University—Rosedale,98605,71945,14,20,16,34,17
35114,ON,Whitby,122022,90964,155,26,13,34,15
35019,ON,Don Valley East,93007,62362,24,21,14,35,16
35044,ON,Kingston and the Islands,116996,87460,434,30,12,35,13
35067,ON,Niagara Falls,128357,101505,579,19,22,35,18
35075,ON,Ottawa Centre,113619,89360,35,28,10,35,10
35076,ON,Orléans,119247,94830,211,30,10,35,11
35077,ON,Ottawa South,121894,85946,16,29,11,35,12
35094,ON,Scarborough Centre,108826,70145,30,22,14,35,15
35097,ON,Scarborough—Rouge Park,102646,71291,57,24,14,35,14
35108,ON,Toronto Centre,93971,66351,6,21,16,35,17
35109,ON,Toronto—Danforth,104017,76567,26,22,16,35,19
35006,ON,Bay of Quinte,109488,83427,2000,29,12,36,16
35007,ON,Beaches—East York,107084,75169,17,22,15,36,17
35066,ON,Niagara Centre,105860,81364,334,18,22,36,19
35074,ON,Oshawa,125771,94928,65,27,13,36,14
35089,ON,St. Catharines,110596,83821,61,19,21,36,18
35095,ON,Scarborough—Guildwood,101914,63296,27,23,14,36,15
35102,ON,Stormont—Dundas—South Glengarry,100913,78167,2765,31,11,36,13
35050,ON,Leeds—Grenville—Thousand Islands and Rideau Lakes,99306,78225,3751,30,11,37,14
35088,ON,Carleton,89522,71947,1229,27,11,37,13
35098,ON,Scarborough Southwest,106733,71577,29,23,15,37,16
11003,PE,Egmont,34598,26908,1527,47,5,47,3
11004,PE,Malpeque,35039,27677,1663,48,5,48,4
11002,PE,Charlottetown,34562,26400,46,49,5,49,4
11001,PE,Cardigan,36005,27958,2658,50,5,50,4
24002,QC,Abitibi—Témiscamingue,102794,82695,37429,26,9,33,5
24027,QC,Gatineau,106424,83651,125,29,9,33,9
24030,QC,Hull—Aylmer,103447,78679,65,28,9,33,8
24057,QC,Pontiac,106499,86585,30586,27,9,33,6
24005,QC,Argenteuil—La Petite-Nation,94208,78234,5412,30,9,34,9
24013,QC,Thérèse-De Blainville,98499,78804,77,33,6,34,8
24031,QC,Joliette,100683,85545,9102,32,6,34,6
24035,QC,Lac-Saint-Jean,105783,85092,60405,34,4,34,5
24038,QC,Laurentides—Labelle,111357,95907,19694,31,7,34,7
24029,QC,Honoré-Mercier,102587,78428,39,37,5,35,8
24048,QC,Mirabel,103536,86304,868,31,8,35,9
24050,QC,Montcalm,99518,82538,906,36,4,35,6
24063,QC,Rivière-du-Nord,102085,88586,381,34,5,35,7
24033,QC,La Pointe-de-l'Île,103512,84335,43,37,6,36,8
24040,QC,Laval—Les Îles,103053,81562,47,33,7,36,11
24056,QC,Pierrefonds—Dollard,108740,84978,53,32,8,36,12
24062,QC,Rivière-des-Mille-Îles,102816,80957,117,32,7,36,10
24065,QC,Marc-Aurèle-Fortin,96082,75579,54,35,5,36,9
24075,QC,Terrebonne,106322,83775,159,35,4,36,7
24003,QC,Ahuntsic-Cartierville,110473,82863,22,34,7,37,11
24004,QC,Alfred-Pellan,98045,77898,116,36,5,37,8
24012,QC,Berthier—Maskinongé,98590,82109,4420,33,5,37,7
24055,QC,Papineau,108977,78515,10,35,7,37,9
24068,QC,Saint-Laurent,93842,68685,44,33,8,37,12
24076,QC,Trois-Rivières,108774,90709,133,38,4,37,6
24078,QC,Vimy,104373,85511,35,34,6,37,10
24009,QC,Bécancour—Nicolet—Saurel,93779,77971,2870,39,5,38,8
24015,QC,Bourassa,100286,70444,14,35,6,38,9
24036,QC,Lac-Saint-Louis,105622,85663,81,32,9,38,14
24052,QC,Mount Royal,101258,74055,23,34,8,38,12
24064,QC,Rosemont—La Petite-Patrie,106293,83936,11,36,7,38,11
24069,QC,Saint-Léonard—Saint-Michel,110649,76351,21,36,6,38,10
24070,QC,Saint-Maurice—Champlain,110273,91588,38904,39,4,38,7
24074,QC,Vaudreuil—Soulanges,111905,89766,408,31,9,38,13
24001,QC,Abitibi—Baie-James—Nunavik—Eeyou,85475,62881,854754,30,8,39,2
24021,QC,Châteauguay—Lacolle,92169,75157,940,33,10,39,13
24028,QC,Hochelaga,103436,82245,20,37,7,39,9
24032,QC,Jonquière,87596,72605,42453,42,2,39,3
24037,QC,LaSalle—Émard—Verdun,105317,83876,19,34,9,39,12
24053,QC,Notre-Dame-de-Grâce—Westmount,104410,79071,17,35,8,39,11
24054,QC,Outremont,100916,71300,12,36,8,39,10
24058,QC,Portneuf—Jacques-Cartier,104394,86884,7617,40,4,39,5
24060,QC,Repentigny,111191,91542,198,37,4,39,8
24071,QC,Salaberry—Suroît,107036,91444,2271,32,10,39,14
24014,QC,Pierre-Boucher—Les Patriotes—Verchères,95326,78251,688,38,5,40,9
24019,QC,Charlesbourg—Haute-Saint-Charles,103331,83648,118,42,3,40,6
24022,QC,Chicoutimi—Le Fjord,81501,66674,2819,43,2,40,4
24025,QC,Drummond,98681,80750,1670,39,6,40,8
24034,QC,La Prairie,99811,81637,295,35,10,40,14
24039,QC,Laurier—Sainte-Marie,107034,83730,11,37,8,40,11
24042,QC,Lévis—Lotbinière,107593,86700,2123,40,5,40,7
24045,QC,Louis-Saint-Laurent,106888,91332,141,41,3,40,5
24046,QC,Manicouagan,94766,75124,264226,44,3,40,3
24049,QC,Montarville,95095,75181,158,38,7,40,10
24067,QC,Saint-Jean,108244,88081,734,34,10,40,13
24077,QC,Ville-Marie—Le Sud-Ouest—Île-des-Sœurs,103070,83351,19,35,9,40,12
24008,QC,Beauport—Limoilou,92944,78601,35,43,4,41,6
24011,QC,Beloeil—Chambly,109955,90271,402,38,6,41,11
24017,QC,Brossard—Saint-Lambert,100828,83194,58,36,9,41,12
24020,QC,Beauport—Côte-de-Beaupré—Île d’Orléans—Charlevoix,92496,75750,11634,43,3,41,5
24024,QC,Dorval—Lachine—LaSalle,106886,85471,51,33,9,41,13
24043,QC,Longueuil—Saint-Hubert,104366,85657,56,38,8,41,10
24044,QC,Louis-Hébert,104038,81285,97,41,4,41,8
24059,QC,Québec,96525,79277,36,42,4,41,7
24066,QC,Saint-Hyacinthe—Bagot,99629,80577,1948,39,7,41,9
24010,QC,Bellechasse—Les Etchemins—Lévis,112385,91899,3293,41,5,42,8
24016,QC,Brome—Missisquoi,98616,84274,3035,36,10,42,13
24018,QC,Rimouski-Neigette—Témiscouata—Les Basques,84809,69631,8061,43,5,42,5
24041,QC,Longueuil—Charles-LeMoyne,104895,83360,39,37,9,42,12
24051,QC,Montmagny—L'Islet—Kamouraska—Rivière-du-Loup,97261,78291,7495,42,5,42,6
24061,QC,Richmond—Arthabaska,103897,85118,3571,40,7,42,9
24072,QC,Shefford,107538,87902,1434,38,9,42,11
24073,QC,Sherbrooke,107988,86809,105,39,8,42,10
24006,QC,Avignon—La Mitis—Matane—Matapédia,74547,60721,14723,44,5,43,4
24007,QC,Beauce,106337,84518,4242,41,6,43,9
24023,QC,Compton—Stanstead,101946,81405,4815,40,8,43,11
24047,QC,Mégantic—L'Érable,88745,71133,6278,40,6,43,10
24026,QC,Gaspésie—Les Îles-de-la-Madeleine,78833,65717,17145,45,5,44,4
47001,SK,Battlefords—Lloydminster,70034,48638,30125,12,2,16,7
47002,SK,Cypress Hills—Grasslands,67834,49713,77822,12,8,16,10
47004,SK,Carlton Trail—Eagle Creek,72607,53836,29040,12,3,16,8
47010,SK,Saskatoon—Grasswood,72010,57268,342,12,5,16,9
47007,SK,Regina—Lewvan,79587,61879,58,12,7,17,9
47011,SK,Saskatoon—University,76257,55219,71,13,4,17,8
47012,SK,Saskatoon West,76704,54086,93,12,4,17,7
47003,SK,Desnethé—Missinippi—Churchill River,69471,43128,342903,13,2,18,7
47005,SK,Moose Jaw—Lake Centre—Lanigan,76106,56621,32882,12,6,18,9
47006,SK,Prince Albert,79344,55873,18927,13,3,18,8
47009,SK,Regina—Wascana,77208,55497,63,13,7,18,10
47008,SK,Regina—Qu'Appelle,72891,52220,13430,13,6,19,9
47013,SK,Souris—Moose Mountain,72058,51580,43184,13,8,19,10
47014,SK,Yorkton—Melville,71270,53446,43272,13,5,19,8
60001,YT,Yukon,33897,25264,482443,5,0,6,0
PK ccI;:e e B chorogrid/databases/canada_federal_ridings_column_descriptions.txtdistrict_code Federal electoral district code
province Two-letter province postal abbreviation
federal_electoral_district Name of federal riding
population Population in riding
electors Number of eligible voters in riding
area_km2 Area of riding in square kilometers
square_x Horizontal position of square
square_y Vertical position of square
truecolhex_x Horizontal position of hex with true columns
truecolhex_y Vertical position of hex with true columnsPK ccIft1{ { ( chorogrid/databases/canada_provinces.csvprovince,multisquare_x,multisquare_y,multisquare_contour,multisquare_label_offset_x,multisquare_label_offset_y
NL,44,2,abcdAAabadababccccdd,3.5,1.1
PE,47,5,aaaabccccd,1.5,0.1
NS,47,7,aaaabcbbbcccdaddcd,1.5,1
NB,44,6,aababbcbccdddd,0.5,1
QC,42,2,aababcbaabccccbcbbccbccbcccccdccccccdaaaadadadadadaaaaaaadad,-5.5,4
ON,16,8,aaaaabababaadaaaaaaabbcbcccbcbcccbcbcbcccbcbcbaaabbccdccccbccdcdadaddaadadddddadaaaadccdcccd,6,5
MB,14,2,aabbbbbbbccddddddd,0.5,3
SK,12,2,aabbbbbbbccddddddd,0.5,3
AB,7,0,aaaaabbbbbbbbbcdcdcdcddcdddd,2,2
BC,0,6,abbababcccddddAAadddaaaababbabababcccccccdcdcd,5,-1.5
YT,5,0,abcd,0,0
NT,13,0,abcd,0,0
NU,15,0,abcd,0,0
PK ccI㎢ <