Ejemplo n.º 1
0
    def _load_graph(self, info_cache):
        rpc = self.rpc
        visits = info_cache['visits']
        creds = info_cache['credentials']
        messages_count = rpc(
            'db/table/count',
            'messages',
            query_filter={'campaign_id': self.config['campaign_id']})
        messages_not_opened = rpc('db/table/count',
                                  'messages',
                                  query_filter={
                                      'campaign_id':
                                      self.config['campaign_id'],
                                      'opened': None
                                  })

        bars = []
        bars.append(messages_count)
        bars.append(messages_count - messages_not_opened)
        bars.append(len(visits))
        bars.append(len(unique(visits, key=lambda visit: visit.message_id)))
        if len(creds):
            bars.append(len(creds))
            bars.append(len(unique(creds, key=lambda cred: cred.message_id)))
        yticklabels = ('Messages', 'Opened', 'Visits', 'Unique\nVisits',
                       'Credentials', 'Unique\nCredentials')
        self.graph_bar(bars, len(yticklabels), yticklabels[:len(bars)])
        return
Ejemplo n.º 2
0
    def _load_graph(self, info_cache):
        rpc = self.rpc
        messages_count = rpc(
            'db/table/count',
            'messages',
            query_filter={'campaign_id': self.config['campaign_id']})
        if not messages_count:
            self._graph_null_pie('No Messages Sent')
            return
        visits_count = len(
            unique(info_cache['visits'], key=lambda visit: visit.message_id))
        credentials_count = len(
            unique(info_cache['credentials'],
                   key=lambda cred: cred.message_id))

        if not credentials_count <= visits_count <= messages_count:
            raise ValueError(
                'credential visit and message counts are inconsistent')
        labels = ['Without Visit', 'With Visit', 'With Credentials']
        sizes = []
        sizes.append(
            (float(messages_count - visits_count) / float(messages_count)) *
            100)
        sizes.append(
            (float(visits_count - credentials_count) / float(messages_count)) *
            100)
        sizes.append((float(credentials_count) / float(messages_count)) * 100)
        if not credentials_count:
            labels.pop()
            sizes.pop()
        if not visits_count:
            labels.pop()
            sizes.pop()
        self.graph_pie(sizes, legend_labels=labels)
        return
Ejemplo n.º 3
0
	def _load_graph(self, info_cache):
		rpc = self.parent.rpc
		messages_count = rpc('campaign/messages/count', self.config['campaign_id'])
		if not messages_count:
			self._graph_null_pie('No Messages Sent')
			return
		visits_count = len(unique(info_cache['visits'], key=lambda visit: visit.message_id))
		credentials_count = len(unique(info_cache['credentials'], key=lambda cred: cred.message_id))

		assert credentials_count <= visits_count <= messages_count
		labels = ['Without Visit', 'With Visit', 'With Credentials']
		sizes = []
		sizes.append((float(messages_count - visits_count) / float(messages_count)) * 100)
		sizes.append((float(visits_count - credentials_count) / float(messages_count)) * 100)
		sizes.append((float(credentials_count) / float(messages_count)) * 100)
		colors = ['yellowgreen', 'gold', 'indianred']
		explode = [0.1, 0, 0]
		if not credentials_count:
			labels.pop()
			sizes.pop()
			colors.pop()
			explode.pop()
		if not visits_count:
			labels.pop()
			sizes.pop()
			colors.pop()
			explode.pop()
		ax = self.axes[0]
		ax.pie(sizes, explode=explode, labels=labels, labeldistance=1.05, colors=colors, autopct='%1.1f%%', shadow=True, startangle=45)
		ax.axis('equal')
		return
Ejemplo n.º 4
0
	def _load_graph(self, info_cache):
		rpc = self.rpc
		messages_count = rpc('db/table/count', 'messages', query_filter={'campaign_id': self.config['campaign_id']})
		if not messages_count:
			self._graph_null_pie('No Messages Sent')
			return
		visits_count = len(unique(info_cache['visits'], key=lambda visit: visit.message_id))
		credentials_count = len(unique(info_cache['credentials'], key=lambda cred: cred.message_id))

		assert credentials_count <= visits_count <= messages_count
		labels = ['Without Visit', 'With Visit', 'With Credentials']
		sizes = []
		sizes.append((float(messages_count - visits_count) / float(messages_count)) * 100)
		sizes.append((float(visits_count - credentials_count) / float(messages_count)) * 100)
		sizes.append((float(credentials_count) / float(messages_count)) * 100)
		colors = ['yellowgreen', 'gold', 'indianred']
		explode = [0.1, 0, 0]
		if not credentials_count:
			labels.pop()
			sizes.pop()
			colors.pop()
			explode.pop()
		if not visits_count:
			labels.pop()
			sizes.pop()
			colors.pop()
			explode.pop()
		ax = self.axes[0]
		ax.pie(sizes, explode=explode, labels=tuple("{0:.1f}%".format(s) for s in sizes), labeldistance=1.15, colors=colors, shadow=True, startangle=45)
		ax.axis('equal')
		self.add_legend_patch(zip(colors, labels), fontsize='x-small')
		return
Ejemplo n.º 5
0
	def _campaign_stats(self, campaign):
		rpc = self.rpc
		messages = list(rpc.remote_table('messages', query_filter={'campaign_id': campaign.id}))
		visits = list(rpc.remote_table('visits', query_filter={'campaign_id': campaign.id}))
		credentials = list(rpc.remote_table('credentials', query_filter={'campaign_id': campaign.id}))
		stats = {
			'created': campaign.created,
			'expiration': campaign.expiration,
			'reject_after_credentials': campaign.reject_after_credentials,
			'id': campaign.id,
			'name': generate_hash(campaign.name),
			'messages': {
				'total': len(messages),
				'unique': {
					'by_target': len(unique(messages, key=lambda message: message.target_email)),
				}
			},
			'visits': {
				'total': len(visits),
				'unique': {
					'by_message': len(unique(visits, key=lambda visit: visit.message_id)),
				}
			},
			'credentials': {
				'total': len(credentials),
				'unique': {
					'by_message': len(unique(credentials, key=lambda credential: credential.message_id)),
					'by_visit': len(unique(credentials, key=lambda credential: credential.visit_id))
				}
			}
		}
		return stats
Ejemplo n.º 6
0
    def _load_graph(self, info_cache):
        messages = info_cache['messages']
        messages_count = messages['total']
        if not messages_count:
            self._graph_null_pie('No Messages Sent')
            return
        visits_count = len(
            unique(info_cache['visits']['edges'],
                   key=lambda visit: visit['node']['messageId']))
        credentials_count = len(
            unique(info_cache['credentials']['edges'],
                   key=lambda cred: cred['node']['messageId']))

        if not credentials_count <= visits_count <= messages_count:
            raise ValueError(
                'credential visit and message counts are inconsistent')
        labels = ['Without Visit', 'With Visit', 'With Credentials']
        sizes = []
        sizes.append(
            (float(messages_count - visits_count) / float(messages_count)) *
            100)
        sizes.append(
            (float(visits_count - credentials_count) / float(messages_count)) *
            100)
        sizes.append((float(credentials_count) / float(messages_count)) * 100)
        if not credentials_count:
            labels.pop()
            sizes.pop()
        if not visits_count:
            labels.pop()
            sizes.pop()
        self.graph_pie(sizes, legend_labels=labels)
        return
Ejemplo n.º 7
0
    def _load_graph(self, info_cache):
        messages = info_cache['messages']['edges']
        messages = [
            message['node'] for message in messages
            if message['node']['companyDepartment'] is not None
        ]
        if not messages:
            self._graph_null_bar('')
            return
        messages = dict((message['id'], message) for message in messages)

        visits = info_cache['visits']['edges']
        visits = [
            visit['node'] for visit in visits
            if visit['node']['messageId'] in messages
        ]
        visits = unique(visits, key=lambda visit: visit['messageId'])
        visits = dict((visit['id'], visit) for visit in visits)

        creds = info_cache['credentials']['edges']
        creds = [
            cred['node'] for cred in creds
            if cred['node']['messageId'] in messages
        ]
        creds = unique(creds, key=lambda cred: cred['messageId'])
        creds = dict((cred['id'], cred) for cred in creds)

        department_messages = collections.Counter()
        department_messages.update(message['companyDepartment']['name']
                                   for message in messages.values())

        department_visits = collections.Counter()
        department_visits.update(
            messages[visit['messageId']]['companyDepartment']['name']
            for visit in visits.values())

        department_credentials = collections.Counter()
        department_credentials.update(
            messages[cred['messageId']]['companyDepartment']['name']
            for cred in creds.values())

        bars = []
        department_names = tuple(department_messages.keys())
        for department_name in department_names:
            dep_messages = float(department_messages[department_name])
            dep_creds = float(department_credentials.get(
                department_name, 0)) / dep_messages * 100
            dep_visits = (float(department_visits.get(department_name, 0)) /
                          dep_messages * 100) - dep_creds
            bars.append(
                (dep_creds, dep_visits, (100.0 - (dep_creds + dep_visits))))
        bar_colors = (self.get_color('map_marker1', ColorHexCode.RED),
                      self.get_color('map_marker2', ColorHexCode.YELLOW),
                      self.get_color('bar_bg', ColorHexCode.GRAY))
        self.graph_bar_stacked(bars, bar_colors, department_names)
        self.add_legend_patch(tuple(
            zip(bar_colors[:2], ('With Credentials', 'Without Credentials'))),
                              fontsize=10)
        return
Ejemplo n.º 8
0
	def _load_graph(self, info_cache):
		messages = info_cache['messages']['edges']
		messages = [message['node'] for message in messages if message['node']['companyDepartment'] is not None]
		if not messages:
			self._graph_null_bar('')
			return
		messages = dict((message['id'], message) for message in messages)

		visits = info_cache['visits']['edges']
		visits = [visit['node'] for visit in visits if visit['node']['messageId'] in messages]
		visits = unique(visits, key=lambda visit: visit['messageId'])
		visits = dict((visit['id'], visit) for visit in visits)

		creds = info_cache['credentials']['edges']
		creds = [cred['node'] for cred in creds if cred['node']['messageId'] in messages]
		creds = unique(creds, key=lambda cred: cred['messageId'])
		creds = dict((cred['id'], cred) for cred in creds)

		department_messages = collections.Counter()
		department_messages.update(message['companyDepartment']['name'] for message in messages.values())

		department_visits = collections.Counter()
		department_visits.update(messages[visit['messageId']]['companyDepartment']['name'] for visit in visits.values())

		department_credentials = collections.Counter()
		department_credentials.update(messages[cred['messageId']]['companyDepartment']['name'] for cred in creds.values())

		bars = []
		department_names = tuple(department_messages.keys())
		for department_name in department_names:
			dep_messages = float(department_messages[department_name])
			dep_creds = float(department_credentials.get(department_name, 0)) / dep_messages * 100
			dep_visits = (float(department_visits.get(department_name, 0)) / dep_messages * 100) - dep_creds
			bars.append((
				dep_creds,
				dep_visits,
				(100.0 - (dep_creds + dep_visits))
			))
		bar_colors = (
			self.get_color('map_marker1', ColorHexCode.RED),
			self.get_color('map_marker2', ColorHexCode.YELLOW),
			self.get_color('bar_bg', ColorHexCode.GRAY)
		)
		self.graph_bar_stacked(
			bars,
			bar_colors,
			department_names
		)
		self.add_legend_patch(tuple(zip(bar_colors[:2], ('With Credentials', 'Without Credentials'))), fontsize=10)
		return
Ejemplo n.º 9
0
	def _load_graph(self, info_cache):
		rpc = self.rpc
		visits = info_cache['visits']
		creds = info_cache['credentials']

		bars = []
		bars.append(rpc('db/table/count', 'messages', query_filter={'campaign_id': self.config['campaign_id']}))
		bars.append(len(visits))
		bars.append(len(unique(visits, key=lambda visit: visit.message_id)))
		if len(creds):
			bars.append(len(creds))
			bars.append(len(unique(creds, key=lambda cred: cred.message_id)))
		yticklabels = ('Messages', 'Visits', 'Unique\nVisits', 'Credentials', 'Unique\nCredentials')
		self.graph_bar(bars, len(yticklabels), yticklabels[:len(bars)])
		return
Ejemplo n.º 10
0
	def _load_graph(self, info_cache):
		rpc = self.parent.rpc
		visits = info_cache['visits']
		creds = info_cache['credentials']

		bars = []
		bars.append(rpc('campaign/messages/count', self.config['campaign_id']))
		bars.append(len(visits))
		bars.append(len(unique(visits, key=lambda visit: visit.message_id)))
		if len(creds):
			bars.append(len(creds))
			bars.append(len(unique(creds, key=lambda cred: cred.message_id)))
		xticklabels = ('Messages', 'Visits', 'Unique\nVisits', 'Credentials', 'Unique\nCredentials')[:len(bars)]
		bars = self.graph_bar(bars, xticklabels=xticklabels, ylabel='Grand Total')
		return
Ejemplo n.º 11
0
 def _campaign_stats(self, campaign):
     rpc = self.rpc
     messages = list(
         rpc.remote_table('messages',
                          query_filter={'campaign_id': campaign.id}))
     visits = list(
         rpc.remote_table('visits',
                          query_filter={'campaign_id': campaign.id}))
     credentials = list(
         rpc.remote_table('credentials',
                          query_filter={'campaign_id': campaign.id}))
     stats = {
         'created': campaign.created,
         'expiration': campaign.expiration,
         'reject_after_credentials': campaign.reject_after_credentials,
         'id': campaign.id,
         'name': generate_hash(campaign.name),
         'messages': {
             'total': len(messages),
             'unique': {
                 'by_target':
                 len(
                     unique(messages,
                            key=lambda message: message.target_email)),
             }
         },
         'visits': {
             'total': len(visits),
             'unique': {
                 'by_message':
                 len(unique(visits, key=lambda visit: visit.message_id)),
             }
         },
         'credentials': {
             'total': len(credentials),
             'unique': {
                 'by_message':
                 len(
                     unique(credentials,
                            key=lambda credential: credential.message_id)),
                 'by_visit':
                 len(
                     unique(credentials,
                            key=lambda credential: credential.visit_id))
             }
         }
     }
     return stats
Ejemplo n.º 12
0
	def _load_graph(self, info_cache):
		visits = unique(info_cache['visits'], key=lambda visit: visit.message_id)
		cred_ips = set(cred.message_id for cred in info_cache['credentials'])
		cred_ips = set([visit.visitor_ip for visit in visits if visit.message_id in cred_ips])

		ax = self.axes[0]
		bm = mpl_toolkits.basemap.Basemap(resolution='c', ax=ax, **self.basemap_args)
		if self.draw_states:
			bm.drawstates()
		bm.drawcoastlines()
		bm.drawcountries()
		bm.fillcontinents(color=MPL_COLOR_LAND, lake_color=MPL_COLOR_WATER)
		bm.drawparallels((-60, -30, 0, 30, 60), labels=(1, 1, 0, 0))
		bm.drawmeridians((0, 90, 180, 270), labels=(0, 0, 0, 1))
		bm.drawmapboundary(fill_color=MPL_COLOR_WATER)

		if not visits:
			return

		ctr = collections.Counter()
		ctr.update([visit.visitor_ip for visit in visits])

		base_markersize = self.markersize_scale
		base_markersize = max(base_markersize, 3.05)
		base_markersize = min(base_markersize, 9)
		self._plot_visitor_map_points(bm, ctr, base_markersize, cred_ips)

		self.add_legend_patch(((self.mpl_color_with_creds, 'With Credentials'), (self.mpl_color_without_creds, 'Without Credentials')))
		return
Ejemplo n.º 13
0
	def _load_graph(self, info_cache):
		departments = info_cache['company_departments']
		departments = dict((department.id, department.name) for department in departments)

		messages = info_cache['messages']
		message_departments = dict((message.id, departments[message.company_department_id]) for message in messages if message.company_department_id is not None)
		if not len(message_departments):
			self._graph_null_bar('')
			return
		messages = [message for message in messages if message.id in message_departments]

		visits = info_cache['visits']
		visits = [visit for visit in visits if visit.message_id in message_departments]
		visits = unique(visits, key=lambda visit: visit.message_id)

		department_visits = collections.Counter()
		department_visits.update(message_departments[visit.message_id] for visit in visits)

		department_totals = collections.Counter()
		department_totals.update(message_departments[message.id] for message in messages)

		department_scores = dict((department, (department_visits[department] / total) * 100) for department, total in department_totals.items())
		department_scores = sorted(department_scores.items(), key=lambda x: (x[1], x[0]), reverse=True)
		department_scores = collections.OrderedDict(department_scores)

		yticklabels, bars = zip(*department_scores.items())
		self.graph_bar(bars, len(yticklabels), yticklabels)
		return
Ejemplo n.º 14
0
	def _load_graph(self, info_cache):
		visits = info_cache['visits']['edges']
		creds = info_cache['credentials']['edges']
		messages = info_cache['messages']
		messages_count = messages['total']
		messages_opened = [message['node'] for message in messages['edges'] if message['node']['opened'] is not None]

		bars = []
		bars.append(messages_count)
		bars.append(len(messages_opened))
		bars.append(len(visits))
		bars.append(len(unique(visits, key=lambda visit: visit['node']['messageId'])))
		if len(creds):
			bars.append(len(creds))
			bars.append(len(unique(creds, key=lambda cred: cred['node']['messageId'])))
		yticklabels = ('Messages', 'Opened', 'Visits', 'Unique\nVisits', 'Credentials', 'Unique\nCredentials')
		self.graph_bar(bars, yticklabels[:len(bars)])
		return
Ejemplo n.º 15
0
	def _load_graph(self, info_cache):
		rpc = self.rpc
		messages_count = rpc('db/table/count', 'messages', query_filter={'campaign_id': self.config['campaign_id']})
		if not messages_count:
			self._graph_null_pie('No Messages Sent')
			return
		visits_count = len(unique(info_cache['visits'], key=lambda visit: visit.message_id))
		credentials_count = len(unique(info_cache['credentials'], key=lambda cred: cred.message_id))

		assert credentials_count <= visits_count <= messages_count
		labels = ['Without Visit', 'With Visit', 'With Credentials']
		sizes = []
		sizes.append((float(messages_count - visits_count) / float(messages_count)) * 100)
		sizes.append((float(visits_count - credentials_count) / float(messages_count)) * 100)
		sizes.append((float(credentials_count) / float(messages_count)) * 100)
		if not credentials_count:
			labels.pop()
			sizes.pop()
		if not visits_count:
			labels.pop()
			sizes.pop()
		self.graph_pie(sizes, legend_labels=labels)
		return
Ejemplo n.º 16
0
	def _load_graph(self, info_cache):
		messages = info_cache['messages']
		messages_count = messages['total']
		if not messages_count:
			self._graph_null_pie('No Messages Sent')
			return
		visits_count = len(unique(info_cache['visits']['edges'], key=lambda visit: visit['node']['messageId']))
		credentials_count = len(unique(info_cache['credentials']['edges'], key=lambda cred: cred['node']['messageId']))

		if not credentials_count <= visits_count <= messages_count:
			raise ValueError('credential visit and message counts are inconsistent')
		labels = ['Without Visit', 'With Visit', 'With Credentials']
		sizes = []
		sizes.append((float(messages_count - visits_count) / float(messages_count)) * 100)
		sizes.append((float(visits_count - credentials_count) / float(messages_count)) * 100)
		sizes.append((float(credentials_count) / float(messages_count)) * 100)
		if not credentials_count:
			labels.pop()
			sizes.pop()
		if not visits_count:
			labels.pop()
			sizes.pop()
		self.graph_pie(sizes, legend_labels=labels)
		return
Ejemplo n.º 17
0
    def _load_graph(self, info_cache):
        visits = info_cache['visits']['edges']
        creds = info_cache['credentials']['edges']
        messages = info_cache['messages']
        messages_count = messages['total']
        messages_opened = [
            message['node'] for message in messages['edges']
            if message['node']['opened'] is not None
        ]

        bars = []
        bars.append(messages_count)
        bars.append(len(messages_opened))
        bars.append(len(visits))
        bars.append(
            len(unique(visits, key=lambda visit: visit['node']['messageId'])))
        if len(creds):
            bars.append(len(creds))
            bars.append(
                len(unique(creds, key=lambda cred: cred['node']['messageId'])))
        yticklabels = ('Messages', 'Opened', 'Visits', 'Unique\nVisits',
                       'Credentials', 'Unique\nCredentials')
        self.graph_bar(bars, yticklabels[:len(bars)])
        return
Ejemplo n.º 18
0
    def _load_graph(self, info_cache):
        campaign_id = self.config['campaign_id']
        departments = info_cache['companyDepartments']
        departments = dict(
            (department['node']['id'], department['node']['name'])
            for department in departments['edges'])

        messages = info_cache['messages']
        message_departments = dict(
            (message['node']['id'],
             departments.get(str(message['node'].get('companyDepartmentId'))))
            for message in messages['edges']
            if message['node']['companyDepartmentId'] is not None)
        if not len(message_departments):
            self._graph_null_bar('')
            return
        messages = [
            message['node'] for message in messages['edges']
            if message['node']['id'] in message_departments
        ]

        visits = info_cache['visits']
        visits = [
            visit['node'] for visit in visits['edges']
            if visit['node']['messageId'] in message_departments
        ]
        visits = unique(visits, key=lambda visit: visit['messageId'])

        department_visits = collections.Counter()
        department_visits.update(message_departments[visit['messageId']]
                                 for visit in visits)

        department_totals = collections.Counter()
        department_totals.update(message_departments[message['id']]
                                 for message in messages)

        department_scores = dict(
            (department,
             (float(department_visits[department]) / float(total)) * 100)
            for department, total in department_totals.items())
        department_scores = sorted(department_scores.items(),
                                   key=lambda x: (x[1], x[0]),
                                   reverse=True)
        department_scores = collections.OrderedDict(department_scores)

        yticklabels, bars = zip(*department_scores.items())
        self.graph_bar(bars, len(yticklabels), yticklabels)
        return
Ejemplo n.º 19
0
    def _load_graph(self, info_cache):
        visits = unique(info_cache['visits']['edges'],
                        key=lambda visit: visit['node']['messageId'])
        cred_ips = set(cred['node']['messageId']
                       for cred in info_cache['credentials']['edges'])
        cred_ips = set([
            visit['node']['visitorIp'] for visit in visits
            if visit['node']['messageId'] in cred_ips
        ])

        color_fg = self.get_color('fg', ColorHexCode.BLACK)
        color_land = self.get_color('map_land', ColorHexCode.GRAY)
        color_water = self.get_color('map_water', ColorHexCode.WHITE)

        ax = self.axes[0]
        bm = mpl_toolkits.basemap.Basemap(resolution='c',
                                          ax=ax,
                                          **self.basemap_args)
        if self.draw_states:
            bm.drawstates()
        bm.drawcoastlines()
        bm.drawcountries()
        bm.fillcontinents(color=color_land, lake_color=color_water)
        parallels = bm.drawparallels((-60, -30, 0, 30, 60),
                                     labels=(1, 1, 0, 0))
        self._map_set_line_color(parallels, color_fg)

        meridians = bm.drawmeridians((0, 90, 180, 270), labels=(0, 0, 0, 1))
        self._map_set_line_color(meridians, color_fg)
        bm.drawmapboundary(fill_color=color_water, linewidth=0)

        if not visits:
            return

        ctr = collections.Counter()
        ctr.update([visit['node']['visitorIp'] for visit in visits])

        base_markersize = self.markersize_scale
        base_markersize = max(base_markersize, 3.05)
        base_markersize = min(base_markersize, 9)
        self._plot_visitor_map_points(bm, ctr, base_markersize, cred_ips)

        self.add_legend_patch(
            ((self.color_with_creds, 'With Credentials'),
             (self.color_without_creds, 'Without Credentials')))
        return
Ejemplo n.º 20
0
	def _load_graph(self, info_cache):
		visits = unique(info_cache['visits'], key=lambda visit: visit.message_id)
		cred_ips = set(cred.message_id for cred in info_cache['credentials'])
		cred_ips = set([visit.visitor_ip for visit in visits if visit.message_id in cred_ips])

		color_fg = self.get_color('fg', ColorHexCode.BLACK)
		color_land = self.get_color('map_land', ColorHexCode.GRAY)
		color_water = self.get_color('map_water', ColorHexCode.WHITE)

		ax = self.axes[0]
		bm = mpl_toolkits.basemap.Basemap(resolution='c', ax=ax, **self.basemap_args)
		if self.draw_states:
			bm.drawstates()
		bm.drawcoastlines()
		bm.drawcountries()
		bm.fillcontinents(color=color_land, lake_color=color_water)
		parallels = bm.drawparallels(
			(-60, -30, 0, 30, 60),
			labels=(1, 1, 0, 0)
		)
		self._map_set_line_color(parallels, color_fg)

		meridians = bm.drawmeridians(
			(0, 90, 180, 270),
			labels=(0, 0, 0, 1)
		)
		self._map_set_line_color(meridians, color_fg)
		bm.drawmapboundary(
			fill_color=color_water,
			linewidth=0
		)

		if not visits:
			return

		ctr = collections.Counter()
		ctr.update([visit.visitor_ip for visit in visits])

		base_markersize = self.markersize_scale
		base_markersize = max(base_markersize, 3.05)
		base_markersize = min(base_markersize, 9)
		self._plot_visitor_map_points(bm, ctr, base_markersize, cred_ips)

		self.add_legend_patch(((self.color_with_creds, 'With Credentials'), (self.color_without_creds, 'Without Credentials')))
		return