def _compute_y_labels(self): y_pos = compute_scale( self.min_, self.max_, self.logarithmic, self.order_min, self.min_scale, self.max_scale ) if self.y_labels: self._y_labels = [] for i, y_label in enumerate(self.y_labels): if isinstance(y_label, dict): pos = self._adapt(y_label.get('value')) title = y_label.get('label', self._y_format(pos)) elif is_str(y_label): pos = self._adapt(y_pos[i]) title = y_label else: pos = self._adapt(y_label) title = self._y_format(pos) self._y_labels.append((title, pos)) self.min_ = min(self.min_, min(cut(self._y_labels, 1))) self.max_ = max(self.max_, max(cut(self._y_labels, 1))) self._box.set_polar_box( 0, 1, self.min_, self.max_) else: self._y_labels = list(zip(map(self._y_format, y_pos), y_pos))
def _compute_margin(self): """Compute graph margins from set texts""" if self.show_legend and self.series: h, w = get_texts_box( map(lambda x: truncate(x, self.truncate_legend or 15), cut(self.series, 'title')), self.legend_font_size) if self.legend_at_bottom: h_max = max(h, self.legend_box_size) self.margin.bottom += 10 + h_max * round( sqrt(self._order) - 1) * 1.5 + h_max else: self.margin.right += 10 + w + self.legend_box_size if self.title: h, _ = get_text_box(self.title[0], self.title_font_size) self.margin.top += len(self.title) * (10 + h) if self._x_labels: h, w = get_texts_box(cut(self._x_labels), self.label_font_size) self._x_labels_height = 10 + max( w * sin(rad(self.x_label_rotation)), h) self.margin.bottom += self._x_labels_height if self.x_label_rotation: self.margin.right = max(w * cos(rad(self.x_label_rotation)), self.margin.right) else: self._x_labels_height = 0 if self._y_labels: h, w = get_texts_box(cut(self._y_labels), self.label_font_size) self.margin.left += 10 + max(w * cos(rad(self.y_label_rotation)), h)
def _compute(self): x_pos = [(x + 1) / self._order for x in range(self._order) ] if self._order != 1 else [.5] # Center if only one value previous = [[self.zero, self.zero] for i in range(self._len)] for i, serie in enumerate(self.series): y_height = -sum(serie.safe_values) / 2 all_x_pos = [0] + x_pos serie.points = [] for j, value in enumerate(serie.values): poly = [] poly.append((all_x_pos[i], previous[j][0])) poly.append((all_x_pos[i], previous[j][1])) previous[j][0] = y_height y_height = previous[j][1] = y_height + value poly.append((all_x_pos[i + 1], previous[j][1])) poly.append((all_x_pos[i + 1], previous[j][0])) serie.points.append(poly) val_max = max(list(map(sum, cut(self.series, 'values'))) + [self.zero]) self._box.ymin = -val_max self._box.ymax = val_max y_pos = compute_scale(self._box.ymin, self._box.ymax, self.logarithmic, self.order_min) if not self.y_labels else map( float, self.y_labels) self._x_labels = list( zip(cut(self.series, 'title'), map(lambda x: x - 1 / (2 * self._order), x_pos))) self._y_labels = list(zip(map(self._format, y_pos), y_pos))
def _compute_margin(self): """Compute graph margins from set texts""" if self.show_legend and self.series: h, w = get_texts_box( map(lambda x: truncate(x, self.truncate_legend or 15), cut(self.series, 'title')), self.legend_font_size) if self.legend_at_bottom: h_max = max(h, self.legend_box_size) self.margin.bottom += 10 + h_max * round( sqrt(self._order) - 1) * 1.5 + h_max else: self.margin.right += 10 + w + self.legend_box_size if self.title: h, _ = get_text_box(self.title[0], self.title_font_size) self.margin.top += len(self.title) * (10 + h) if self._x_labels: h, w = get_texts_box( cut(self._x_labels), self.label_font_size) self._x_labels_height = 10 + max( w * sin(rad(self.x_label_rotation)), h) self.margin.bottom += self._x_labels_height if self.x_label_rotation: self.margin.right = max( w * cos(rad(self.x_label_rotation)), self.margin.right) else: self._x_labels_height = 0 if self._y_labels: h, w = get_texts_box( cut(self._y_labels), self.label_font_size) self.margin.left += 10 + max( w * cos(rad(self.y_label_rotation)), h)
def _compute(self): xlen = len(self.series) x_pos = [(x + 1) / xlen for x in range(xlen) ] if xlen != 1 else [.5] # Center if only one value previous = [[0, 0] for i in range(self._len)] for i, serie in enumerate(self.series): y_height = - sum(serie.values) / 2 all_x_pos = [0] + x_pos serie.points = [] for j, value in enumerate(serie.values): poly = [] poly.append((all_x_pos[i], previous[j][0])) poly.append((all_x_pos[i], previous[j][1])) previous[j][0] = y_height y_height = previous[j][1] = y_height + value poly.append((all_x_pos[i + 1], previous[j][1])) poly.append((all_x_pos[i + 1], previous[j][0])) serie.points.append(poly) val_max = max(map(sum, cut(self.series, 'values'))) self._box.ymin = -val_max self._box.ymax = val_max y_pos = compute_scale( self._box.ymin, self._box.ymax, self.logarithmic, self.order_min ) if not self.y_labels else map(float, self.y_labels) self._x_labels = zip(cut(self.series, 'title'), map(lambda x: x - 1 / (2 * xlen), x_pos)) self._y_labels = zip(map(self._format, y_pos), y_pos)
def _compute(self): x_pos = [ (x + 1) / self._order for x in range(self._order) ] if self._order != 1 else [.5] # Center if only one value previous = [[self.zero, self.zero] for i in range(self._len)] for i, serie in enumerate(self.series): y_height = - sum(serie.safe_values) / 2 all_x_pos = [0] + x_pos serie.points = [] for j, value in enumerate(serie.values): poly = [] poly.append((all_x_pos[i], previous[j][0])) poly.append((all_x_pos[i], previous[j][1])) previous[j][0] = y_height y_height = previous[j][1] = y_height + value poly.append((all_x_pos[i + 1], previous[j][1])) poly.append((all_x_pos[i + 1], previous[j][0])) serie.points.append(poly) val_max = max(list(map(sum, cut(self.series, 'values'))) + [self.zero]) self._box.ymin = -val_max self._box.ymax = val_max y_pos = compute_scale( self._box.ymin, self._box.ymax, self.logarithmic, self.order_min, self.min_scale, self.max_scale ) if not self.y_labels else list(map(float, self.y_labels)) self._x_labels = list( zip(cut(self.series, 'title'), map(lambda x: x - 1 / (2 * self._order), x_pos))) self._y_labels = list(zip(map(self._format, y_pos), y_pos))
def populate(self): all = self.get_query().order_by(self.criteria).all() if self.criteria_name == 'spent_time': self.chart.x_labels = [ "<1s", "1s", "2s", "5s", "10s", "20s", "30s", "1min", "2min", "5min", ">10min"] else: self.chart.x_labels = list(map(str, map(int, cut(all, 0)))) self.chart.add(labelize(self.criteria_name, self.lang), list(map(float, cut(all, 1))))
def _binary_tree(self, data, total, x, y, w, h, parent=None): if total == 0: return if len(data) == 1: if parent: i, datum = data[0] serie, serie_node, rects = parent self._rect(serie, serie_node, rects, datum, x, y, w, h, i) else: datum = data[0] serie_node = self.svg.serie(datum) self._binary_tree( list(enumerate(datum.values)), total, x, y, w, h, ( datum, serie_node, self.svg.node(serie_node['plot'], class_="rects") ) ) return midpoint = total / 2 pivot_index = 1 running_sum = 0 for i, elt in enumerate(data): if running_sum >= midpoint: pivot_index = i break running_sum += elt[1] if parent else sum(elt.values) half1 = data[:pivot_index] half2 = data[pivot_index:] if parent: half1_sum = sum(cut(half1, 1)) half2_sum = sum(cut(half2, 1)) else: half1_sum = sum(map(sum, map(lambda x: x.values, half1))) half2_sum = sum(map(sum, map(lambda x: x.values, half2))) pivot_pct = half1_sum / total if h > w: y_pivot = pivot_pct * h self._binary_tree(half1, half1_sum, x, y, w, y_pivot, parent) self._binary_tree( half2, half2_sum, x, y + y_pivot, w, h - y_pivot, parent ) else: x_pivot = pivot_pct * w self._binary_tree(half1, half1_sum, x, y, x_pivot, h, parent) self._binary_tree( half2, half2_sum, x + x_pivot, y, w - x_pivot, h, parent )
def _binary_tree(self, data, total, x, y, w, h, parent=None): if total == 0: return if len(data) == 1: if parent: i, datum = data[0] serie, serie_node, rects = parent self._rect(serie, serie_node, rects, datum, x, y, w, h, i) else: datum = data[0] serie_node = self.svg.serie(datum) self._binary_tree( list(enumerate(datum.values)), total, x, y, w, h, (datum, serie_node, self.svg.node(serie_node['plot'], class_="rects"))) return midpoint = total / 2 pivot_index = 1 running_sum = 0 for i, elt in enumerate(data): if running_sum >= midpoint: pivot_index = i break running_sum += elt[1] if parent else sum(elt.values) half1 = data[:pivot_index] half2 = data[pivot_index:] if parent: half1_sum = sum(cut(half1, 1)) half2_sum = sum(cut(half2, 1)) else: half1_sum = sum(map(sum, map(lambda x: x.values, half1))) half2_sum = sum(map(sum, map(lambda x: x.values, half2))) pivot_pct = half1_sum / total if h > w: y_pivot = pivot_pct * h self._binary_tree( half1, half1_sum, x, y, w, y_pivot, parent) self._binary_tree( half2, half2_sum, x, y + y_pivot, w, h - y_pivot, parent) else: x_pivot = pivot_pct * w self._binary_tree( half1, half1_sum, x, y, x_pivot, h, parent) self._binary_tree( half2, half2_sum, x + x_pivot, y, w - x_pivot, h, parent)
def adapt(chart, data): if isinstance(chart, pygal.DateY): # Convert to a credible datetime return list(map( lambda t: (datetime.fromtimestamp(1360000000 + t[0] * 987654) if t[0] is not None else None, t[1]), data)) if isinstance(chart, pygal.XY): return data data = cut(data) if isinstance(chart, pygal.Worldmap): return list( map(lambda x: list( COUNTRIES.keys())[ int(x) % len(COUNTRIES)] if x is not None else None, data)) elif isinstance(chart, pygal.FrenchMap_Regions): return list( map(lambda x: list( REGIONS.keys())[ int(x) % len(REGIONS)] if x is not None else None, data)) elif isinstance(chart, pygal.FrenchMap_Departments): return list( map(lambda x: list( DEPARTMENTS.keys())[ int(x) % len(DEPARTMENTS)] if x is not None else None, data)) return data
def adapt(chart, data): if isinstance(chart, pygal.DateY): # Convert to a credible datetime return list( map( lambda t: (datetime.fromtimestamp(1360000000 + t[0] * 987654) if t[0] is not None else None, t[1]), data)) if isinstance(chart, pygal.XY): return data data = cut(data) if isinstance(chart, pygal.Worldmap): return list( map( lambda x: list(COUNTRIES.keys())[x % len(COUNTRIES)] if x is not None else None, data)) elif isinstance(chart, pygal.FrenchMap_Regions): return list( map( lambda x: list(REGIONS.keys())[x % len(REGIONS)] if x is not None else None, data)) elif isinstance(chart, pygal.FrenchMap_Departments): return list( map( lambda x: list(DEPARTMENTS.keys())[x % len(DEPARTMENTS)] if x is not None else None, data)) return data
def _plot(self): map = etree.fromstring(MAP) map.set('width', str(self.view.width)) map.set('height', str(self.view.height)) for i, serie in enumerate(self.series): safe_vals = list(filter( lambda x: x is not None, cut(serie.values, 1))) if not safe_vals: continue min_ = min(safe_vals) max_ = max(safe_vals) for j, (country_code, value) in enumerate(serie.values): if value is None: continue if max_ == min_: ratio = 1 else: ratio = .3 + .7 * (value - min_) / (max_ - min_) try: country = map.find('.//*[@id="%s"]' % country_code) except SyntaxError: # Python 2.6 (you'd better install lxml) country = None for e in map: if e.attrib.get('id', '') == country_code: country = e if country is None: continue cls = country.get('class', '').split(' ') cls.append('color-%d' % i) country.set('class', ' '.join(cls)) country.set( 'style', 'fill-opacity: %f' % ( ratio)) metadata = serie.metadata.get(j) if metadata: node = decorate(self.svg, country, metadata) if node != country: country.remove(node) index = list(map).index(country) map.remove(country) node.append(country) map.insert(index, node) last_node = len(country) > 0 and country[-1] if last_node is not None and last_node.tag == 'title': title_node = last_node text = title_node.text + '\n' else: title_node = self.svg.node(country, 'title') text = '' title_node.text = text + '[%s] %s: %s' % ( serie.title, self.country_names[country_code], self._format(value)) self.nodes['plot'].append(map)
def test_metadata(Chart): chart = Chart() v = range(7) if Chart == pygal.XY: v = map(lambda x: (x, x + 1), v) chart.add('Serie with metadata', [ v[0], {'value': v[1]}, {'value': v[2], 'label': 'Three'}, {'value': v[3], 'xlink': 'http://4.example.com/'}, {'value': v[4], 'xlink': 'http://5.example.com/', 'label': 'Five'}, {'value': v[5], 'xlink': { 'href': 'http://6.example.com/'}, 'label': 'Six'}, {'value': v[6], 'xlink': { 'href': 'http://7.example.com/', 'target': '_blank'}, 'label': 'Seven'} ]) q = chart.render_pyquery() for md in ( 'Three', 'http://4.example.com/', 'Five', 'http://7.example.com/', 'Seven'): assert md in cut(q('desc'), 'text') assert len(v) == len(q('.tooltip-trigger').siblings('.value'))
def _compute(self): """Compute y min and max and y scale and set labels""" self._x_pos = [ (x + 1) / self._order for x in range(self._order) ] if self._order != 1 else [.5] # Center if only one value previous = [[self.zero, self.zero] for i in range(self._len)] for i, serie in enumerate(self.series): y_height = -sum(serie.safe_values) / 2 all_x_pos = [0] + self._x_pos serie.points = [] for j, value in enumerate(serie.values): poly = [] poly.append((all_x_pos[i], previous[j][0])) poly.append((all_x_pos[i], previous[j][1])) previous[j][0] = y_height y_height = previous[j][1] = y_height + value poly.append((all_x_pos[i + 1], previous[j][1])) poly.append((all_x_pos[i + 1], previous[j][0])) serie.points.append(poly) val_max = max(list(map(sum, cut(self.series, 'values'))) + [self.zero]) self._box.ymin = -val_max self._box.ymax = val_max if self.range and self.range[0] is not None: self._box.ymin = self.range[0] if self.range and self.range[1] is not None: self._box.ymax = self.range[1]
def test_metadata(Chart): chart = Chart() v = range(7) if Chart in (pygal.Box,): return # summary charts cannot display per-value metadata elif Chart == pygal.XY: v = list(map(lambda x: (x, x + 1), v)) elif issubclass(Chart, BaseMap): v = [(i, k) for k, i in enumerate(Chart.x_labels)] chart.add('Serie with metadata', [ v[0], {'value': v[1]}, {'value': v[2], 'label': 'Three'}, {'value': v[3], 'xlink': 'http://4.example.com/'}, {'value': v[4], 'xlink': 'http://5.example.com/', 'label': 'Five'}, {'value': v[5], 'xlink': { 'href': 'http://6.example.com/'}, 'label': 'Six'}, {'value': v[6], 'xlink': { 'href': 'http://7.example.com/', 'target': '_blank'}, 'label': 'Seven'} ]) q = chart.render_pyquery() for md in ( 'Three', 'http://4.example.com/', 'Five', 'http://7.example.com/', 'Seven'): assert md in cut(q('desc'), 'text') if Chart in (pygal.Pie, pygal.Treemap): # Slices with value 0 are not rendered assert len(v) - 1 == len(q('.tooltip-trigger').siblings('.value')) elif not issubclass(Chart, BaseMap): # Tooltip are not working on maps assert len(v) == len(q('.tooltip-trigger').siblings('.value'))
def all(style='default', interpolate=None): width, height = 600, 400 data = random.randrange(1, 10) order = random.randrange(1, 10) xy_series = _random(data, order) other_series = [] for title, values in xy_series: other_series.append((title, cut(values, 1))) xy_series = b64encode(pickle.dumps(xy_series)) other_series = b64encode(pickle.dumps(other_series)) config = Config() config.width = width config.height = height config.fill = bool(random.randrange(0, 2)) config.human_readable = True config.interpolate = interpolate config.style = styles[style] config.x_labels = [random_label() for i in range(data)] svgs = [] for chart in pygal.CHARTS: type = chart.__name__ svgs.append({ 'type': type, 'series': xy_series if type == 'XY' else other_series, 'config': b64encode(pickle.dumps(config)) }) return render_template('svgs.jinja2', svgs=svgs, width=width, height=height)
def get(data): if isinstance(chart, pygal.XY): if isinstance(chart, pygal.DateY): # Convert to a credible datetime return datetime.fromtimestamp(1360000000 + data * 987654) return data return cut(data)
def _compute_y_labels_major(self): if self.y_labels_major_every: self._y_labels_major = [ self._y_labels[i][1] for i in range(0, len(self._y_labels), self.y_labels_major_every) ] elif self.y_labels_major_count: label_count = len(self._y_labels) major_count = self.y_labels_major_count if (major_count >= label_count): self._y_labels_major = [label[1] for label in self._y_labels] else: self._y_labels_major = [ self._y_labels[int( i * (label_count - 1) / (major_count - 1) )][1] for i in range(major_count) ] elif self.y_labels_major: self._y_labels_major = list(map(self._adapt, self.y_labels_major)) elif self._y_labels: self._y_labels_major = majorize(cut(self._y_labels, 1)) else: self._y_labels_major = []
def all(style='default', color=None, interpolate=None, base_style=None): width, height = 600, 400 data = random.randrange(1, 10) order = random.randrange(1, 10) if color is None: style = styles[style] else: style = parametric_styles[style]( color, base_style=styles[base_style or 'default']) xy_series = _random(data, order) other_series = [] for title, values in xy_series: other_series.append( (title, cut(values, 1))) xy_series = b64encode(pickle.dumps(xy_series)) other_series = b64encode(pickle.dumps(other_series)) config = Config() config.width = width config.height = height config.fill = bool(random.randrange(0, 2)) config.human_readable = True config.interpolate = interpolate config.style = style config.x_labels = [random_label() for i in range(data)] svgs = [] for chart in pygal.CHARTS: type = chart.__name__ svgs.append({'type': type, 'series': xy_series if type == 'XY' else other_series, 'config': b64encode(pickle.dumps(config))}) return render_template('svgs.jinja2', svgs=svgs, width=width, height=height)
def _compute(self): """Compute y min and max and y scale and set labels""" self._x_pos = [ (x + 1) / self._order for x in range(self._order) ] if self._order != 1 else [.5] # Center if only one value previous = [[self.zero, self.zero] for i in range(self._len)] for i, serie in enumerate(self.series): y_height = - sum(serie.safe_values) / 2 all_x_pos = [0] + self._x_pos serie.points = [] for j, value in enumerate(serie.values): poly = [] poly.append((all_x_pos[i], previous[j][0])) poly.append((all_x_pos[i], previous[j][1])) previous[j][0] = y_height y_height = previous[j][1] = y_height + value poly.append((all_x_pos[i + 1], previous[j][1])) poly.append((all_x_pos[i + 1], previous[j][0])) serie.points.append(poly) val_max = max(list(map(sum, cut(self.series, 'values'))) + [self.zero]) self._box.ymin = -val_max self._box.ymax = val_max if self.range and self.range[0] is not None: self._box.ymin = self.range[0] if self.range and self.range[1] is not None: self._box.ymax = self.range[1]
def test_metadata(Chart): chart = Chart() v = range(7) if Chart == pygal.XY: v = list(map(lambda x: (x, x + 1), v)) elif Chart == pygal.Worldmap or Chart == pygal.SupranationalWorldmap: v = list(map(lambda x: x, i18n.COUNTRIES)) chart.add('Serie with metadata', [ v[0], {'value': v[1]}, {'value': v[2], 'label': 'Three'}, {'value': v[3], 'xlink': 'http://4.example.com/'}, {'value': v[4], 'xlink': 'http://5.example.com/', 'label': 'Five'}, {'value': v[5], 'xlink': { 'href': 'http://6.example.com/'}, 'label': 'Six'}, {'value': v[6], 'xlink': { 'href': 'http://7.example.com/', 'target': '_blank'}, 'label': 'Seven'} ]) q = chart.render_pyquery() for md in ( 'Three', 'http://4.example.com/', 'Five', 'http://7.example.com/', 'Seven'): assert md in cut(q('desc'), 'text') if Chart == pygal.Pie: # Slices with value 0 are not rendered assert len(v) - 1 == len(q('.tooltip-trigger').siblings('.value')) elif Chart != pygal.Worldmap and Chart != pygal.SupranationalWorldmap: # Tooltip are not working on worldmap assert len(v) == len(q('.tooltip-trigger').siblings('.value'))
def test_metadata(Chart): chart = Chart() v = range(7) if Chart in (pygal.Box,): return # summary charts cannot display per-value metadata elif Chart == pygal.XY: v = list(map(lambda x: (x, x + 1), v)) elif Chart == pygal.Worldmap or Chart == pygal.SupranationalWorldmap: v = list(map(lambda x: x, i18n.COUNTRIES)) chart.add( "Serie with metadata", [ v[0], {"value": v[1]}, {"value": v[2], "label": "Three"}, {"value": v[3], "xlink": "http://4.example.com/"}, {"value": v[4], "xlink": "http://5.example.com/", "label": "Five"}, {"value": v[5], "xlink": {"href": "http://6.example.com/"}, "label": "Six"}, {"value": v[6], "xlink": {"href": "http://7.example.com/", "target": "_blank"}, "label": "Seven"}, ], ) q = chart.render_pyquery() for md in ("Three", "http://4.example.com/", "Five", "http://7.example.com/", "Seven"): assert md in cut(q("desc"), "text") if Chart == pygal.Pie: # Slices with value 0 are not rendered assert len(v) - 1 == len(q(".tooltip-trigger").siblings(".value")) elif Chart != pygal.Worldmap and Chart != pygal.SupranationalWorldmap: # Tooltip are not working on worldmap assert len(v) == len(q(".tooltip-trigger").siblings(".value"))
def _plot(self): map = etree.fromstring(self.svg_map) map.set('width', str(self.view.width)) map.set('height', str(self.view.height)) for i, serie in enumerate(self.series): safe_vals = list(filter( lambda x: x is not None, cut(serie.values, 1))) if not safe_vals: continue min_ = min(safe_vals) max_ = max(safe_vals) for j, (area_code, value) in enumerate(serie.values): if isinstance(area_code, Number): area_code = '%2d' % area_code if value is None: continue if max_ == min_: ratio = 1 else: ratio = .3 + .7 * (value - min_) / (max_ - min_) areae = map.findall( ".//*[@class='%s%s %s map-element']" % ( self.area_prefix, area_code, self.kind)) if not areae: continue for area in areae: cls = area.get('class', '').split(' ') cls.append('color-%d' % i) area.set('class', ' '.join(cls)) area.set('style', 'fill-opacity: %f' % (ratio)) metadata = serie.metadata.get(j) if metadata: node = decorate(self.svg, area, metadata) if node != area: area.remove(node) for g in map: if area not in g: continue index = list(g).index(area) g.remove(area) node.append(area) g.insert(index, node) last_node = len(area) > 0 and area[-1] if last_node is not None and last_node.tag == 'title': title_node = last_node text = title_node.text + '\n' else: title_node = self.svg.node(area, 'title') text = '' title_node.text = text + '[%s] %s: %s' % ( serie.title, self.area_names[area_code], self._format(value)) self.nodes['plot'].append(map)
def _plot(self): map = etree.fromstring(self.svg_map) map.set("width", str(self.view.width)) map.set("height", str(self.view.height)) for i, serie in enumerate(self.series): safe_vals = list(filter(lambda x: x is not None, cut(serie.values, 1))) if not safe_vals: continue min_ = min(safe_vals) max_ = max(safe_vals) for j, (area_code, value) in enumerate(serie.values): if isinstance(area_code, Number): area_code = "%2d" % area_code if value is None: continue if max_ == min_: ratio = 1 else: ratio = 0.3 + 0.7 * (value - min_) / (max_ - min_) areae = map.xpath( "//*[contains(concat(' ', normalize-space(@class), ' ')," " ' %s%s ')]" % (self.area_prefix, area_code) ) if not areae: continue for area in areae: cls = area.get("class", "").split(" ") cls.append("color-%d" % i) area.set("class", " ".join(cls)) area.set("style", "fill-opacity: %f" % (ratio)) metadata = serie.metadata.get(j) if metadata: parent = area.getparent() node = decorate(self.svg, area, metadata) if node != area: area.remove(node) index = parent.index(area) parent.remove(area) node.append(area) parent.insert(index, node) last_node = len(area) > 0 and area[-1] if last_node is not None and last_node.tag == "title": title_node = last_node text = title_node.text + "\n" else: title_node = self.svg.node(area, "title") text = "" title_node.text = text + "[%s] %s: %s" % ( serie.title, self.area_names[area_code], self._format(value), ) self.nodes["plot"].append(map)
def _plot(self): map = etree.fromstring(MAP) map.set('width', str(self.view.width)) map.set('height', str(self.view.height)) for i, serie in enumerate(self.series): safe_vals = list( filter(lambda x: x is not None, cut(serie.values, 1))) if not safe_vals: continue min_ = min(safe_vals) max_ = max(safe_vals) for j, (country_code, value) in enumerate(serie.values): if value is None: continue if max_ == min_: ratio = 1 else: ratio = .3 + .7 * (value - min_) / (max_ - min_) try: country = map.find('.//*[@id="%s"]' % country_code) except SyntaxError: # Python 2.6 (you'd better install lxml) country = None for e in map: if e.attrib.get('id', '') == country_code: country = e if country is None: continue cls = country.get('class', '').split(' ') cls.append('color-%d' % i) country.set('class', ' '.join(cls)) country.set('style', 'fill-opacity: %f' % (ratio)) metadata = serie.metadata.get(j) if metadata: node = decorate(self.svg, country, metadata) if node != country: country.remove(node) index = list(map).index(country) map.remove(country) node.append(country) map.insert(index, node) last_node = len(country) > 0 and country[-1] if last_node is not None and last_node.tag == 'title': title_node = last_node text = title_node.text + '\n' else: title_node = self.svg.node(country, 'title') text = '' title_node.text = text + '[%s] %s: %s' % ( serie.title, self.country_names[country_code], self._format(value)) self.nodes['plot'].append(map)
def _plot(self): map = etree.fromstring(self.svg_map) map.set('width', str(self.view.width)) map.set('height', str(self.view.height)) for i, serie in enumerate(self.series): safe_vals = list( filter(lambda x: x is not None, cut(serie.values, 1))) if not safe_vals: continue min_ = min(safe_vals) max_ = max(safe_vals) for j, (area_code, value) in enumerate(serie.values): if isinstance(area_code, Number): area_code = '%2d' % area_code if value is None: continue if max_ == min_: ratio = 1 else: ratio = .3 + .7 * (value - min_) / (max_ - min_) areae = map.findall(".//*[@class='%s%s %s map-element']" % (self.area_prefix, area_code, self.kind)) if not areae: continue for area in areae: cls = area.get('class', '').split(' ') cls.append('color-%d' % i) area.set('class', ' '.join(cls)) area.set('style', 'fill-opacity: %f' % (ratio)) metadata = serie.metadata.get(j) if metadata: node = decorate(self.svg, area, metadata) if node != area: area.remove(node) for g in map: if area not in g: continue index = list(g).index(area) g.remove(area) node.append(area) g.insert(index, node) last_node = len(area) > 0 and area[-1] if last_node is not None and last_node.tag == 'title': title_node = last_node text = title_node.text + '\n' else: title_node = self.svg.node(area, 'title') text = '' title_node.text = text + '[%s] %s: %s' % ( serie.title, self.area_names[area_code], self._format(value)) self.nodes['plot'].append(map)
def test_metadata(Chart): """Test metadata values""" chart = Chart() v = range(7) if Chart in (pygal.Box, ): return # summary charts cannot display per-value metadata elif Chart == pygal.XY: v = list(map(lambda x: (x, x + 1), v)) elif issubclass(Chart, BaseMap): v = [(k, i) for i, k in enumerate(Chart.x_labels) if k not in ['oecd', 'nafta', 'eur']] chart.add( 'Serie with metadata', [ v[0], { 'value': v[1] }, { 'value': v[2], 'label': 'Three' }, { 'value': v[3], 'xlink': 'http://4.example.com/' }, { 'value': v[4], 'xlink': 'http://5.example.com/', 'label': 'Five' }, { 'value': v[5], 'xlink': { 'href': 'http://6.example.com/' }, 'label': 'Six' }, { 'value': v[6], 'xlink': { 'href': 'http://7.example.com/', 'target': '_blank' }, 'label': 'Seven' } ] ) q = chart.render_pyquery() for md in ('Three', 'Five', 'Seven'): assert md in cut(q('desc'), 'text') for md in ('http://7.example.com/', 'http://4.example.com/'): assert md in [e.attrib.get('xlink:href') for e in q('a')] if Chart in (pygal.Pie, pygal.Treemap, pygal.SolidGauge): # Slices with value 0 are not rendered assert len(v) - 1 == len(q('.tooltip-trigger').siblings('.value')) elif not issubclass(Chart, BaseMap): # Tooltip are not working on maps assert len(v) == len(q('.tooltip-trigger').siblings('.value'))
def test_metadata(Chart): chart = Chart() v = range(7) if Chart in (pygal.Box, ): return # summary charts cannot display per-value metadata elif Chart == pygal.XY: v = list(map(lambda x: (x, x + 1), v)) elif Chart == pygal.Worldmap or Chart == pygal.SupranationalWorldmap: v = [(i, k) for k, i in enumerate(i18n.COUNTRIES.keys())] elif Chart == pygal.FrenchMap_Regions: v = [(i, k) for k, i in enumerate(REGIONS.keys())] elif Chart == pygal.FrenchMap_Departments: v = [(i, k) for k, i in enumerate(DEPARTMENTS.keys())] chart.add('Serie with metadata', [ v[0], { 'value': v[1] }, { 'value': v[2], 'label': 'Three' }, { 'value': v[3], 'xlink': 'http://4.example.com/' }, { 'value': v[4], 'xlink': 'http://5.example.com/', 'label': 'Five' }, { 'value': v[5], 'xlink': { 'href': 'http://6.example.com/' }, 'label': 'Six' }, { 'value': v[6], 'xlink': { 'href': 'http://7.example.com/', 'target': '_blank' }, 'label': 'Seven' } ]) q = chart.render_pyquery() for md in ('Three', 'http://4.example.com/', 'Five', 'http://7.example.com/', 'Seven'): assert md in cut(q('desc'), 'text') if Chart == pygal.Pie: # Slices with value 0 are not rendered assert len(v) - 1 == len(q('.tooltip-trigger').siblings('.value')) elif Chart not in (pygal.Worldmap, pygal.SupranationalWorldmap, pygal.FrenchMap_Regions, pygal.FrenchMap_Departments): # Tooltip are not working on maps assert len(v) == len(q('.tooltip-trigger').siblings('.value'))
def populate(self): all = (self.filter(self.db .query(Visit.day, count(1), count(distinct(Visit.uuid)))) .group_by(Visit.day) .order_by(Visit.day) .all()) self.chart.x_labels = list(map( lambda x: x.strftime('%Y-%m-%d'), cut(all, 0))) self.chart.add(labelize('all', self.lang), cut(all, 1)) self.chart.add(labelize('unique', self.lang), cut(all, 2)) new = (self.filter( self.db .query(count(distinct(Visit.uuid)))) .filter(Visit.last_visit == None) .group_by(Visit.day) .order_by(Visit.day) .all()) self.chart.add(labelize('new', self.lang), cut(new, 0)) self.chart.x_label_rotation = 45
def _plot(self): map = etree.fromstring(MAP) map.set('width', str(self.view.width)) map.set('height', str(self.view.height)) for i, serie in enumerate(self.series): safe_vals = list(filter( lambda x: x is not None, cut(serie.values, 1))) if not safe_vals: continue min_ = min(safe_vals) max_ = max(safe_vals) serie.values = self.replace_supranationals(serie.values) for j, (country_code, value) in enumerate(serie.values): if value is None: continue if max_ == min_: ratio = 1 else: ratio = .3 + .7 * (value - min_) / (max_ - min_) country = map.find('.//*[@id="%s"]' % country_code) if country is None: continue cls = country.get('class', '').split(' ') cls.append('color-%d' % i) country.set('class', ' '.join(cls)) country.set( 'style', 'fill-opacity: %f' % ( ratio)) metadata = serie.metadata.get(j) if metadata: parent = country.getparent() node = decorate(self.svg, country, metadata) if node != country: country.remove(node) index = parent.index(country) parent.remove(country) node.append(country) parent.insert(index, node) last_node = len(country) > 0 and country[-1] if last_node is not None and last_node.tag == 'title': title_node = last_node text = title_node.text + '\n' else: title_node = self.svg.node(country, 'title') text = '' title_node.text = text + '[%s] %s: %d' % ( serie.title, self.country_names[country_code], value) self.nodes['plot'].append(map)
def adapt(chart, data): """Adapt data to chart type""" if isinstance(chart, pygal.XY): return data data = cut(data) if isinstance(chart, BaseMap): return list( map(lambda x: chart.__class__.x_labels[ int(x) % len(chart.__class__.x_labels)] if x is not None else None, data)) return data
def _compute_x_labels(self): x_pos = compute_scale(self._box.xmin, self._box.xmax, self.logarithmic, self.order_min, self.min_scale, self.max_scale) if self.x_labels: self._x_labels = [] for i, x_label in enumerate(self.x_labels): if isinstance(x_label, dict): pos = self._x_adapt(x_label.get('value')) title = x_label.get('label', self._x_format(pos)) elif is_str(x_label): pos = self._x_adapt(x_pos[i % len(x_pos)]) title = x_label else: pos = self._x_adapt(x_label) title = self._x_format(pos) self._x_labels.append((title, pos)) self._box.xmin = min(self._box.xmin, min(cut(self._x_labels, 1))) self._box.xmax = max(self._box.xmax, max(cut(self._x_labels, 1))) else: self._x_labels = list(zip(map(self._x_format, x_pos), x_pos))
def _compute(self): x_len = self._len y_len = self._order self._box.xmax = x_len self._box.ymax = y_len x_pos = [n / 2 for n in range(1, 2 * x_len, 2)] y_pos = [n / 2 for n in reversed(range(1, 2 * y_len, 2))] for j, serie in enumerate(self.series): serie.points = [(x_pos[i], y_pos[j]) for i in range(x_len)] self._x_labels = self.x_labels and list(zip(self.x_labels, x_pos)) self._y_labels = list(zip(self.y_labels or cut(self.series, "title"), y_pos))
def _plot(self): map = etree.fromstring(MAP) map.set('width', str(self.view.width)) map.set('height', str(self.view.height)) for i, serie in enumerate(self.series): safe_vals = list( filter(lambda x: x is not None, cut(serie.values, 1))) if not safe_vals: continue min_ = min(safe_vals) max_ = max(safe_vals) serie.values = self.replace_supranationals(serie.values) for j, (country_code, value) in enumerate(serie.values): if value is None: continue if max_ == min_: ratio = 1 else: ratio = .3 + .7 * (value - min_) / (max_ - min_) country = map.find('.//*[@id="%s"]' % country_code) if country is None: continue cls = country.get('class', '').split(' ') cls.append('color-%d' % i) country.set('class', ' '.join(cls)) country.set('style', 'fill-opacity: %f' % (ratio)) metadata = serie.metadata.get(j) if metadata: parent = country.getparent() node = decorate(self.svg, country, metadata) if node != country: country.remove(node) index = parent.index(country) parent.remove(country) node.append(country) parent.insert(index, node) last_node = len(country) > 0 and country[-1] if last_node is not None and last_node.tag == 'title': title_node = last_node text = title_node.text + '\n' else: title_node = self.svg.node(country, 'title') text = '' title_node.text = text + '[%s] %s: %d' % ( serie.title, self.country_names[country_code], value) self.nodes['plot'].append(map)
def _compute(self): x_len = self._len y_len = self._order self._box.xmax = x_len self._box.ymax = y_len x_pos = [n / 2 for n in range(1, 2 * x_len, 2)] y_pos = [n / 2 for n in reversed(range(1, 2 * y_len, 2))] for j, serie in enumerate(self.series): serie.points = [(x_pos[i], y_pos[j]) for i in range(x_len)] self._x_labels = self.x_labels and list(zip(self.x_labels, x_pos)) self._y_labels = list( zip(self.y_labels or cut(self.series, 'title'), y_pos))
def _compute_x_labels(self): x_pos = compute_scale( self._box.xmin, self._box.xmax, self.logarithmic, self.order_min, self.min_scale, self.max_scale ) if self.x_labels: self._x_labels = [] for i, x_label in enumerate(self.x_labels): if isinstance(x_label, dict): pos = self._x_adapt(x_label.get('value')) title = x_label.get('label', self._x_format(pos)) elif is_str(x_label): pos = self._x_adapt(x_pos[i % len(x_pos)]) title = x_label else: pos = self._x_adapt(x_label) title = self._x_format(pos) self._x_labels.append((title, pos)) self._box.xmin = min(self._box.xmin, min(cut(self._x_labels, 1))) self._box.xmax = max(self._box.xmax, max(cut(self._x_labels, 1))) else: self._x_labels = list(zip(map(self._x_format, x_pos), x_pos))
def adapt(chart, data): if isinstance(chart, pygal.DateY): # Convert to a credible datetime return list(map( lambda t: (datetime.fromtimestamp(1360000000 + t[0] * 987654) if t[0] is not None else None, t[1]), data)) if isinstance(chart, pygal.XY): return data data = cut(data) if isinstance(chart, pygal.Worldmap): return list(map(lambda x: COUNTRY_KEYS[x % len(COUNTRIES)] if x is not None else None, data)) return data
def _plot(self): map = etree.fromstring(MAP) map.set("width", str(self.view.width)) map.set("height", str(self.view.height)) for i, serie in enumerate(self.series): safe_vals = list(filter(lambda x: x is not None, cut(serie.values, 1))) if not safe_vals: continue min_ = min(safe_vals) max_ = max(safe_vals) for j, (country_code, value) in enumerate(serie.values): if value is None: continue if max_ == min_: ratio = 1 else: ratio = 0.3 + 0.7 * (value - min_) / (max_ - min_) country = map.find('.//*[@id="%s"]' % country_code) if country is None: continue cls = country.get("class", "").split(" ") cls.append("color-%d" % i) country.set("class", " ".join(cls)) country.set("style", "fill-opacity: %f" % (ratio)) metadata = serie.metadata.get(j) if metadata: parent = country.getparent() node = decorate(self.svg, country, metadata) if node != country: country.remove(node) index = parent.index(country) parent.remove(country) node.append(country) parent.insert(index, node) last_node = len(country) > 0 and country[-1] if last_node is not None and last_node.tag == "title": title_node = last_node text = title_node.text + "\n" else: title_node = self.svg.node(country, "title") text = "" title_node.text = text + "[%s] %s: %d" % (serie.title, self.country_names[country_code], value) self.nodes["plot"].append(map)
def test_metadata(Chart): chart = Chart() v = range(7) if Chart == pygal.XY: v = list(map(lambda x: (x, x + 1), v)) chart.add('Serie with metadata', [ v[0], { 'value': v[1] }, { 'value': v[2], 'label': 'Three' }, { 'value': v[3], 'xlink': 'http://4.example.com/' }, { 'value': v[4], 'xlink': 'http://5.example.com/', 'label': 'Five' }, { 'value': v[5], 'xlink': { 'href': 'http://6.example.com/' }, 'label': 'Six' }, { 'value': v[6], 'xlink': { 'href': 'http://7.example.com/', 'target': '_blank' }, 'label': 'Seven' } ]) q = chart.render_pyquery() for md in ('Three', 'http://4.example.com/', 'Five', 'http://7.example.com/', 'Seven'): assert md in cut(q('desc'), 'text') if Chart == pygal.Pie: # Slices with value 0 are not rendered assert len(v) - 1 == len(q('.tooltip-trigger').siblings('.value')) elif Chart != pygal.Worldmap: # Tooltip are not working on worldmap assert len(v) == len(q('.tooltip-trigger').siblings('.value'))
def adapt(chart, data): # if isinstance(chart, pygal.DateY): # # Convert to a credible datetime # return list(map( # lambda t: # (datetime.fromtimestamp(1360000000 + t[0] * 987654) # if t[0] is not None else None, t[1]), data)) if isinstance(chart, pygal.XY): return data data = cut(data) if isinstance(chart, BaseMap): return list( map(lambda x: chart.__class__.x_labels[ int(x) % len(chart.__class__.x_labels)] if x is not None else None, data)) return data
def _y_axis(self, draw_axes=True): """Override y axis to make it polar""" if not self._y_labels: return axis = self.svg.node(self.nodes['plot'], class_="axis y web") if self.y_labels_major: y_labels_major = self.y_labels_major elif self.y_labels_major_every: y_labels_major = [self._y_labels[i][1] for i in range( 0, len(self._y_labels), self.y_labels_major_every)] elif self.y_labels_major_count: label_count = len(self._y_labels) major_count = self.y_labels_major_count if (major_count >= label_count): y_labels_major = [label[1] for label in self._y_labels] else: y_labels_major = [self._y_labels[ int(i * (label_count - 1) / (major_count - 1))][1] for i in range(major_count)] else: y_labels_major = majorize( cut(self._y_labels, 1) ) for label, r in reversed(self._y_labels): major = r in y_labels_major if not (self.show_minor_y_labels or major): continue guides = self.svg.node(axis, class_='guides') self.svg.line( guides, [self.view((r, theta)) for theta in self._x_pos], close=True, class_='%sguide line' % ( 'major ' if major else '')) x, y = self.view((r, self._x_pos[0])) self.svg.node( guides, 'text', x=x - 5, y=y, class_='major' if major else '' ).text = label
def _y_major_labels(self): """Getter for the y major label""" if self.y_labels_major: return self.y_labels_major if self.y_labels_major_every: return [self._y_labels[i][1] for i in range( 0, len(self._y_labels), self.y_labels_major_every)] if self.y_labels_major_count: label_count = len(self._y_labels) major_count = self.y_labels_major_count if (major_count >= label_count): return [label[1] for label in self._y_labels] return [self._y_labels[ int(i * (label_count - 1) / (major_count - 1))][1] for i in range(major_count)] return majorize( cut(self._y_labels, 1) )
def _y_axis(self, draw_axes=True): if not self._y_labels: return axis = self.svg.node(self.nodes['plot'], class_="axis y web") if self.y_labels_major: y_labels_major = self.y_labels_major elif self.y_labels_major_every: y_labels_major = [ self._y_labels[i][1] for i in range(0, len(self._y_labels), self.y_labels_major_every) ] elif self.y_labels_major_count: label_count = len(self._y_labels) major_count = self.y_labels_major_count if (major_count >= label_count): y_labels_major = [label[1] for label in self._y_labels] else: y_labels_major = [ self._y_labels[int(i * (label_count - 1) / (major_count - 1))][1] for i in range(major_count) ] else: y_labels_major = majorize(cut(self._y_labels, 1)) for label, r in reversed(self._y_labels): major = r in y_labels_major if not (self.show_minor_y_labels or major): continue guides = self.svg.node(axis, class_='guides') self.svg.line(guides, [self.view((r, theta)) for theta in self.x_pos], close=True, class_='%sguide line' % ('major ' if major else '')) x, y = self.view((r, self.x_pos[0])) self.svg.node(guides, 'text', x=x - 5, y=y, class_='major' if major else '').text = label
def test_metadata(Chart): chart = Chart() v = range(1, 8) if Chart == pygal.XY: v = map(lambda x: (x, x + 1), v) chart.add('Serie with metadata', [ v[0], { 'value': v[1] }, { 'value': v[2], 'label': 'Three' }, { 'value': v[3], 'xlink': 'http://4.example.com/' }, { 'value': v[4], 'xlink': 'http://5.example.com/', 'label': 'Five' }, { 'value': v[5], 'xlink': { 'href': 'http://6.example.com/' }, 'label': 'Six' }, { 'value': v[6], 'xlink': { 'href': 'http://7.example.com/', 'target': '_blank' }, 'label': 'Seven' } ]) q = chart.render_pyquery() for md in ('Three', 'http://4.example.com/', 'Five', 'http://7.example.com/', 'Seven'): assert md in cut(q('desc'), 'text') assert len(v) == len(q('.tooltip-trigger').siblings('.value'))
def test_metadata(Chart): chart = Chart() v = range(7) if Chart in (pygal.Box,): return # summary charts cannot display per-value metadata elif Chart == pygal.XY: v = list(map(lambda x: (x, x + 1), v)) elif Chart == pygal.Worldmap or Chart == pygal.SupranationalWorldmap: v = [(i, k) for k, i in enumerate(i18n.COUNTRIES.keys())] elif Chart == pygal.FrenchMap_Regions: v = [(i, k) for k, i in enumerate(REGIONS.keys())] elif Chart == pygal.FrenchMap_Departments: v = [(i, k) for k, i in enumerate(DEPARTMENTS.keys())] chart.add('Serie with metadata', [ v[0], {'value': v[1]}, {'value': v[2], 'label': 'Three'}, {'value': v[3], 'xlink': 'http://4.example.com/'}, {'value': v[4], 'xlink': 'http://5.example.com/', 'label': 'Five'}, {'value': v[5], 'xlink': { 'href': 'http://6.example.com/'}, 'label': 'Six'}, {'value': v[6], 'xlink': { 'href': 'http://7.example.com/', 'target': '_blank'}, 'label': 'Seven'} ]) q = chart.render_pyquery() for md in ( 'Three', 'http://4.example.com/', 'Five', 'http://7.example.com/', 'Seven'): assert md in cut(q('desc'), 'text') if Chart == pygal.Pie: # Slices with value 0 are not rendered assert len(v) - 1 == len(q('.tooltip-trigger').siblings('.value')) elif Chart not in ( pygal.Worldmap, pygal.SupranationalWorldmap, pygal.FrenchMap_Regions, pygal.FrenchMap_Departments): # Tooltip are not working on maps assert len(v) == len(q('.tooltip-trigger').siblings('.value'))
def all(style='default', color=None, interpolate=None, base_style=None): width, height = 600, 400 data = random.randrange(1, 10) order = random.randrange(1, 10) if color is None: style = styles[style] else: style = parametric_styles[style](color, base_style=styles[base_style or 'default']) xy_series = _random(data, order) other_series = [] for title, values, config in xy_series: other_series.append((title, cut(values, 1), config)) xy_series = b64encode(pickle.dumps(xy_series)) other_series = b64encode(pickle.dumps(other_series)) config = Config() config.width = width config.height = height config.fill = bool(random.randrange(0, 2)) config.interpolate = interpolate config.style = style svgs = [] for chart in pygal.CHARTS: type = '.'.join((chart.__module__, chart.__name__)) if chart._dual: config.x_labels = None else: config.x_labels = [random_label() for i in range(data)] svgs.append({ 'type': type, 'series': xy_series if chart._dual else other_series, 'config': b64encode(pickle.dumps(config)) }) return render_template('svgs.jinja2', svgs=svgs, width=width, height=height)
def all(style="default", color=None, interpolate=None, base_style=None): width, height = 600, 400 data = random.randrange(1, 10) order = random.randrange(1, 10) if color is None: style = styles[style] else: style = parametric_styles[style](color, base_style=styles[base_style or "default"]) xy_series = _random(data, order) other_series = [] for title, values, config in xy_series: other_series.append((title, cut(values, 1), config)) xy_series = b64encode(pickle.dumps(xy_series)) other_series = b64encode(pickle.dumps(other_series)) config = Config() config.width = width config.height = height config.fill = bool(random.randrange(0, 2)) config.interpolate = interpolate config.style = style svgs = [] for chart in pygal.CHARTS: type = ".".join((chart.__module__, chart.__name__)) if chart._dual: config.x_labels = None else: config.x_labels = [random_label() for i in range(data)] svgs.append( { "type": type, "series": xy_series if chart._dual else other_series, "config": b64encode(pickle.dumps(config)), } ) return render_template("svgs.jinja2", svgs=svgs, width=width, height=height)
def _y_axis(self): """Make the y axis: labels and guides""" if not self._y_labels or not self.show_y_labels: return axis = self.svg.node(self.nodes['plot'], class_="axis y") if (0 not in [label[1] for label in self._y_labels] and self.show_y_guides): self.svg.node(axis, 'path', d='M%f %f h%f' % (0, self.view.height, self.view.width), class_='line') if self.y_labels_major: y_labels_major = self.y_labels_major elif self.y_labels_major_every: y_labels_major = [ self._y_labels[i][1] for i in range(0, len(self._y_labels), self.y_labels_major_every) ] elif self.y_labels_major_count: label_count = len(self._y_labels) major_count = self.y_labels_major_count if (major_count >= label_count): y_labels_major = [label[1] for label in self._y_labels] else: y_labels_major = [ self._y_labels[int(i * (label_count - 1) / (major_count - 1))][1] for i in range(major_count) ] else: y_labels_major = majorize(cut(self._y_labels, 1)) for label, position in self._y_labels: major = position in y_labels_major if not (self.show_minor_y_labels or major): continue guides = self.svg.node( axis, class_='%sguides' % ('logarithmic ' if self.logarithmic else '')) x = -5 y = self.view.y(position) if not y: continue if self.show_y_guides: self.svg.node( guides, 'path', d='M%f %f h%f' % (0, y, self.view.width), class_='%s%sline' % ('major ' if major else '', 'guide ' if position != 0 or not self.show_y_guides else '')) text = self.svg.node(guides, 'text', x=x, y=y + .35 * self.label_font_size, class_='major' if major else '') if isinstance(label, dict): label = label['title'] text.text = label if self.y_label_rotation: text.attrib['transform'] = "rotate(%d %f %f)" % ( self.y_label_rotation, x, y) if self._y_2nd_labels: secondary_ax = self.svg.node(self.nodes['plot'], class_="axis y2") for label, position in self._y_2nd_labels: major = position in y_labels_major if not (self.show_minor_x_labels or major): continue # it is needed, to have the same structure as primary axis guides = self.svg.node(secondary_ax, class_='guides') x = self.view.width + 5 y = self.view.y(position) text = self.svg.node(guides, 'text', x=x, y=y + .35 * self.label_font_size, class_='major' if major else '') text.text = label if self.y_label_rotation: text.attrib['transform'] = "rotate(%d %f %f)" % ( self.y_label_rotation, x, y)
def _legend(self): """Make the legend box""" if not self.show_legend: return truncation = self.truncate_legend if self.legend_at_bottom: x = self.margin_box.left + self.spacing y = ( self.margin_box.top + self.view.height + self._x_title_height + self._x_labels_height + self.spacing ) cols = self.legend_at_bottom_columns or ceil(sqrt(self._order) ) or 1 if not truncation: available_space = self.view.width / cols - ( self.legend_box_size + 5 ) truncation = reverse_text_len( available_space, self.style.legend_font_size ) else: x = self.spacing y = self.margin_box.top + self.spacing cols = 1 if not truncation: truncation = 15 legends = self.svg.node( self.nodes['graph'], class_='legends', transform='translate(%d, %d)' % (x, y) ) h = max(self.legend_box_size, self.style.legend_font_size) x_step = self.view.width / cols if self.legend_at_bottom: secondary_legends = legends # svg node is the same else: # draw secondary axis on right x = self.margin_box.left + self.view.width + self.spacing if self._y_2nd_labels: h, w = get_texts_box( cut(self._y_2nd_labels), self.style.label_font_size ) x += self.spacing + max( w * abs(cos(rad(self.y_label_rotation))), h ) y = self.margin_box.top + self.spacing secondary_legends = self.svg.node( self.nodes['graph'], class_='legends', transform='translate(%d, %d)' % (x, y) ) serie_number = -1 i = 0 for titles, is_secondary in ((self._legends, False), (self._secondary_legends, True)): if not self.legend_at_bottom and is_secondary: i = 0 for title in titles: serie_number += 1 if title is None: continue col = i % cols row = i // cols legend = self.svg.node( secondary_legends if is_secondary else legends, class_='legend reactive activate-serie', id="activate-serie-%d" % serie_number ) self.svg.node( legend, 'rect', x=col * x_step, y=1.5 * row * h + ( self.style.legend_font_size - self.legend_box_size if self.style.legend_font_size > self.legend_box_size else 0 ) / 2, width=self.legend_box_size, height=self.legend_box_size, class_="color-%d reactive" % serie_number ) if isinstance(title, dict): node = decorate(self.svg, legend, title) title = title['title'] else: node = legend truncated = truncate(title, truncation) self.svg.node( node, 'text', x=col * x_step + self.legend_box_size + 5, y=1.5 * row * h + .5 * h + .3 * self.style.legend_font_size ).text = truncated if truncated != title: self.svg.node(legend, 'title').text = title i += 1
def _compute_margin(self): """Compute graph margins from set texts""" self._legend_at_left_width = 0 for series_group in (self.series, self.secondary_series): if self.show_legend and series_group: h, w = get_texts_box( map(lambda x: truncate(x, self.truncate_legend or 15), cut(series_group, 'title')), self.legend_font_size) if self.legend_at_bottom: h_max = max(h, self.legend_box_size) cols = (self._order // self.legend_at_bottom_columns if self.legend_at_bottom_columns else ceil(sqrt(self._order)) or 1) self.margin.bottom += self.spacing + h_max * round( cols - 1) * 1.5 + h_max else: if series_group is self.series: legend_width = self.spacing + w + self.legend_box_size self.margin.left += legend_width self._legend_at_left_width += legend_width else: self.margin.right += ( self.spacing + w + self.legend_box_size) self._x_labels_height = 0 if (self._x_labels or self._x_2nd_labels) and self.show_x_labels: for xlabels in (self._x_labels, self._x_2nd_labels): if xlabels: h, w = get_texts_box( map(lambda x: truncate(x, self.truncate_label or 25), cut(xlabels)), self.label_font_size) self._x_labels_height = self.spacing + max( w * sin(rad(self.x_label_rotation)), h) if xlabels is self._x_labels: self.margin.bottom += self._x_labels_height else: self.margin.top += self._x_labels_height if self.x_label_rotation: self.margin.right = max( w * cos(rad(self.x_label_rotation)), self.margin.right) if self.show_y_labels: for ylabels in (self._y_labels, self._y_2nd_labels): if ylabels: h, w = get_texts_box( cut(ylabels), self.label_font_size) if ylabels is self._y_labels: self.margin.left += self.spacing + max( w * cos(rad(self.y_label_rotation)), h) else: self.margin.right += self.spacing + max( w * cos(rad(self.y_label_rotation)), h) self.title = split_title( self.title, self.width, self.title_font_size) if self.title: h, _ = get_text_box(self.title[0], self.title_font_size) self.margin.top += len(self.title) * (self.spacing + h) self.x_title = split_title( self.x_title, self.width - self.margin.x, self.title_font_size) self._x_title_height = 0 if self.x_title: h, _ = get_text_box(self.x_title[0], self.title_font_size) height = len(self.x_title) * (self.spacing + h) self.margin.bottom += height self._x_title_height = height + self.spacing self.y_title = split_title( self.y_title, self.height - self.margin.y, self.title_font_size) self._y_title_height = 0 if self.y_title: h, _ = get_text_box(self.y_title[0], self.title_font_size) height = len(self.y_title) * (self.spacing + h) self.margin.left += height self._y_title_height = height + self.spacing
def make_data(chart, datas, secondary=False): for data in datas: chart.add(data[0], data[1] if chart.__class__ == pygal.XY else cut(data[1]), secondary=secondary) return chart
def _compute_margin(self): """Compute graph margins from set texts""" self._legend_at_left_width = 0 for series_group in (self.series, self.secondary_series): if self.show_legend and series_group: h, w = get_texts_box( map( lambda x: truncate(x, self.truncate_legend or 15), [ serie.title['title'] if isinstance(serie.title, dict) else serie.title or '' for serie in series_group ] ), self.style.legend_font_size ) if self.legend_at_bottom: h_max = max(h, self.legend_box_size) cols = ( self._order // self.legend_at_bottom_columns if self.legend_at_bottom_columns else ceil(sqrt(self._order)) or 1 ) self.margin_box.bottom += self.spacing + h_max * round( cols - 1 ) * 1.5 + h_max else: if series_group is self.series: legend_width = self.spacing + w + self.legend_box_size self.margin_box.left += legend_width self._legend_at_left_width += legend_width else: self.margin_box.right += ( self.spacing + w + self.legend_box_size ) self._x_labels_height = 0 if (self._x_labels or self._x_2nd_labels) and self.show_x_labels: for xlabels in (self._x_labels, self._x_2nd_labels): if xlabels: h, w = get_texts_box( map( lambda x: truncate(x, self.truncate_label or 25), cut(xlabels) ), self.style.label_font_size ) self._x_labels_height = self.spacing + max( w * abs(sin(rad(self.x_label_rotation))), h ) if xlabels is self._x_labels: self.margin_box.bottom += self._x_labels_height else: self.margin_box.top += self._x_labels_height if self.x_label_rotation: if self.x_label_rotation % 180 < 90: self.margin_box.right = max( w * abs(cos(rad(self.x_label_rotation))), self.margin_box.right ) else: self.margin_box.left = max( w * abs(cos(rad(self.x_label_rotation))), self.margin_box.left ) if self.show_y_labels: for ylabels in (self._y_labels, self._y_2nd_labels): if ylabels: h, w = get_texts_box( cut(ylabels), self.style.label_font_size ) if ylabels is self._y_labels: self.margin_box.left += self.spacing + max( w * abs(cos(rad(self.y_label_rotation))), h ) else: self.margin_box.right += self.spacing + max( w * abs(cos(rad(self.y_label_rotation))), h ) self._title = split_title( self.title, self.width, self.style.title_font_size ) if self.title: h, _ = get_text_box(self._title[0], self.style.title_font_size) self.margin_box.top += len(self._title) * (self.spacing + h) self._x_title = split_title( self.x_title, self.width - self.margin_box.x, self.style.title_font_size ) self._x_title_height = 0 if self._x_title: h, _ = get_text_box(self._x_title[0], self.style.title_font_size) height = len(self._x_title) * (self.spacing + h) self.margin_box.bottom += height self._x_title_height = height + self.spacing self._y_title = split_title( self.y_title, self.height - self.margin_box.y, self.style.title_font_size ) self._y_title_height = 0 if self._y_title: h, _ = get_text_box(self._y_title[0], self.style.title_font_size) height = len(self._y_title) * (self.spacing + h) self.margin_box.left += height self._y_title_height = height + self.spacing # Inner margin if self.print_values_position == 'top': gh = self.height - self.margin_box.y alpha = 1.1 * (self.style.value_font_size / gh) * self._box.height if self._max and self._max > 0: self._box.ymax += alpha if self._min and self._min < 0: self._box.ymin -= alpha
def _plot(self): """Insert a map in the chart and apply data on it""" map = etree.fromstring(self.svg_map) map.set('width', str(self.view.width)) map.set('height', str(self.view.height)) for i, serie in enumerate(self.series): safe_vals = list( filter(lambda x: x is not None, cut(serie.values, 1))) if not safe_vals: continue min_ = min(safe_vals) max_ = max(safe_vals) for j, (area_code, value) in self.enumerate_values(serie): area_code = self.adapt_code(area_code) if value is None: continue if max_ == min_: ratio = 1 else: ratio = .3 + .7 * (value - min_) / (max_ - min_) try: areae = map.findall( ".//*[@class='%s%s %s map-element']" % (self.area_prefix, area_code, self.kind)) except SyntaxError: # Python 2.6 (you'd better install lxml) raise ImportError('lxml is required under python 2.6') if not areae: continue for area in areae: cls = area.get('class', '').split(' ') cls.append('color-%d' % i) cls.append('serie-%d' % i) cls.append('series') area.set('class', ' '.join(cls)) area.set('style', 'fill-opacity: %f' % ratio) metadata = serie.metadata.get(j) if metadata: node = decorate(self.svg, area, metadata) if node != area: area.remove(node) for g in map: if area not in g: continue index = list(g).index(area) g.remove(area) node.append(area) g.insert(index, node) for node in area: cls = node.get('class', '').split(' ') cls.append('reactive') cls.append('tooltip-trigger') cls.append('map-area') node.set('class', ' '.join(cls)) alter(node, metadata) val = self._format(serie, j) self._tooltip_data(area, val, 0, 0, 'auto') self.nodes['plot'].append(map)
def _legend(self): """Make the legend box""" if not self.show_legend: return truncation = self.truncate_legend if self.legend_at_bottom: x = self.margin.left + self.spacing y = (self.margin.top + self.view.height + self._x_title_height + self._x_labels_height + self.spacing) cols = self.legend_at_bottom_columns or ceil(sqrt( self._order)) or 1 if not truncation: available_space = self.view.width / cols - ( self.legend_box_size + 5) truncation = reverse_text_len(available_space, self.legend_font_size) else: x = self.spacing y = self.margin.top + self.spacing cols = 1 if not truncation: truncation = 15 legends = self.svg.node(self.nodes['graph'], class_='legends', transform='translate(%d, %d)' % (x, y)) h = max(self.legend_box_size, self.legend_font_size) x_step = self.view.width / cols if self.legend_at_bottom: # if legends at the bottom, we dont split the windows # gen structure - (i, (j, (l, tf))) # i - global serie number - used for coloring and identification # j - position within current legend box # l - label # tf - whether it is secondary label gen = enumerate( enumerate( chain(zip(self._legends, repeat(False)), zip(self._secondary_legends, repeat(True))))) secondary_legends = legends # svg node is the same else: gen = enumerate( chain(enumerate(zip(self._legends, repeat(False))), enumerate(zip(self._secondary_legends, repeat(True))))) # draw secondary axis on right x = self.margin.left + self.view.width + self.spacing if self._y_2nd_labels: h, w = get_texts_box(cut(self._y_2nd_labels), self.label_font_size) x += self.spacing + max(w * cos(rad(self.y_label_rotation)), h) y = self.margin.top + self.spacing secondary_legends = self.svg.node(self.nodes['graph'], class_='legends', transform='translate(%d, %d)' % (x, y)) for (global_serie_number, (i, (title, is_secondary))) in gen: col = i % cols row = i // cols legend = self.svg.node( secondary_legends if is_secondary else legends, class_='legend reactive activate-serie', id="activate-serie-%d" % global_serie_number) self.svg.node( legend, 'rect', x=col * x_step, y=1.5 * row * h + (self.legend_font_size - self.legend_box_size if self.legend_font_size > self.legend_box_size else 0) / 2, width=self.legend_box_size, height=self.legend_box_size, class_="color-%d reactive" % (global_serie_number % len(self.style['colors']))) if isinstance(title, dict): node = decorate(self.svg, legend, title) title = title['title'] else: node = legend truncated = truncate(title, truncation) self.svg.node(node, 'text', x=col * x_step + self.legend_box_size + 5, y=1.5 * row * h + .5 * h + .3 * self.legend_font_size).text = truncated if truncated != title: self.svg.node(legend, 'title').text = title