def _bar(self, serie, parent, x0, x1, y, i, zero, secondary=False): """Internal bar drawing function""" x, y = self.view((x0, y)) x1, _ = self.view((x1, y)) width = x1 - x height = self.view.y(zero) - y series_margin = width * self._series_margin x += series_margin width -= 2 * series_margin r = serie.rounded_bars * 1 if serie.rounded_bars else 0 alter( self.svg.transposable_node( parent, 'rect', x=x, y=y, rx=r, ry=r, width=width, height=height, class_='rect reactive tooltip-trigger' ), serie.metadata.get(i) ) return x, y, width, height
def funnel(self, serie): """Draw a funnel slice""" serie_node = self.svg.serie(serie) fmt = lambda x: '%f %f' % x for i, poly in enumerate(serie.points): metadata = serie.metadata.get(i) val = self._format(serie, i) funnels = decorate( self.svg, self.svg.node(serie_node['plot'], class_="funnels"), metadata) alter(self.svg.node( funnels, 'polygon', points=' '.join(map(fmt, map(self.view, poly))), class_='funnel reactive tooltip-trigger'), metadata) # Poly center from label x, y = self.view(( self._center(self._x_pos[serie.index]), sum([point[1] for point in poly]) / len(poly))) self._tooltip_data( funnels, val, x, y, 'centered', self._get_x_label(serie.index)) self._static_value(serie_node, val, x, y, metadata)
def _draw_box(self, parent_node, quartiles, outliers, box_index, metadata): """ Return the center of a bounding box defined by a box plot. Draws a box plot on self.svg. """ width = (self.view.x(1) - self.view.x(0)) / self._order series_margin = width * self._series_margin left_edge = self.view.x(0) + width * box_index + series_margin width -= 2 * series_margin # draw lines for whiskers - bottom, median, and top for i, whisker in enumerate( (quartiles[0], quartiles[2], quartiles[4])): whisker_width = width if i == 1 else width / 2 shift = (width - whisker_width) / 2 xs = left_edge + shift xe = left_edge + width - shift alter(self.svg.line( parent_node, coords=[(xs, self.view.y(whisker)), (xe, self.view.y(whisker))], class_='reactive tooltip-trigger', attrib={'stroke-width': 3}), metadata) # draw lines connecting whiskers to box (Q1 and Q3) alter(self.svg.line( parent_node, coords=[(left_edge + width / 2, self.view.y(quartiles[0])), (left_edge + width / 2, self.view.y(quartiles[1]))], class_='reactive tooltip-trigger', attrib={'stroke-width': 2}), metadata) alter(self.svg.line( parent_node, coords=[(left_edge + width / 2, self.view.y(quartiles[4])), (left_edge + width / 2, self.view.y(quartiles[3]))], class_='reactive tooltip-trigger', attrib={'stroke-width': 2}), metadata) # box, bounded by Q1 and Q3 alter(self.svg.node( parent_node, tag='rect', x=left_edge, y=self.view.y(quartiles[1]), height=self.view.y(quartiles[3]) - self.view.y(quartiles[1]), width=width, class_='subtle-fill reactive tooltip-trigger'), metadata) # draw outliers for o in outliers: alter(self.svg.node( parent_node, tag='circle', cx=left_edge+width/2, cy=self.view.y(o), r=3, class_='subtle-fill reactive tooltip-trigger'), metadata) return (left_edge + width / 2, self.view.y( sum(quartiles) / len(quartiles)))
def _rect(self, serie, serie_node, rects, val, x, y, w, h, i): rx, ry = self.view((x, y)) rw, rh = self.view((x + w, y + h)) rw -= rx rh -= ry metadata = serie.metadata.get(i) value = self._format(val) rect = decorate(self.svg, self.svg.node(rects, class_="rect"), metadata) alter( self.svg.node(rect, 'rect', x=rx, y=ry, width=rw, height=rh, class_='rect reactive tooltip-trigger'), metadata) self._tooltip_data(rect, value, rx + rw / 2, ry + rh / 2, 'centered', self._get_x_label(i)) self._static_value(serie_node, value, rx + rw / 2, ry + rh / 2, metadata)
def _bar(self, serie, parent, x, y, i, zero, secondary=False): """Internal bar drawing function""" width = (self.view.x(1) - self.view.x(0)) / self._len x, y = self.view((x, y)) series_margin = width * self._series_margin x += series_margin width -= 2 * series_margin width /= self._order if self.horizontal: serie_index = self._order - serie.index - 1 else: serie_index = serie.index x += serie_index * width serie_margin = width * self._serie_margin x += serie_margin width -= 2 * serie_margin height = self.view.y(zero) - y r = serie.rounded_bars * 1 if serie.rounded_bars else 0 alter( self.svg.transposable_node(parent, 'rect', x=x, y=y, rx=r, ry=r, width=width, height=height, class_='rect reactive tooltip-trigger'), serie.metadata.get(i)) return x, y, width, height
def _rect(self, serie, serie_node, rects, val, x, y, w, h, i): rx, ry = self.view((x, y)) rw, rh = self.view((x + w, y + h)) rw -= rx rh -= ry metadata = serie.metadata.get(i) val = self._format(serie, i) rect = decorate( self.svg, self.svg.node(rects, class_="rect"), metadata ) alter( self.svg.node( rect, 'rect', x=rx, y=ry, width=rw, height=rh, class_='rect reactive tooltip-trigger' ), metadata ) self._tooltip_data( rect, val, rx + rw / 2, ry + rh / 2, 'centered', self._get_x_label(i) ) self._static_value(serie_node, val, rx + rw / 2, ry + rh / 2, metadata)
def needle(self, serie): serie_node = self.svg.serie(serie) for i, theta in enumerate(serie.values): if theta is None: continue fmt = lambda x: '%f %f' % x value = self._format(serie.values[i]) metadata = serie.metadata.get(i) gauges = decorate( self.svg, self.svg.node(serie_node['plot'], class_="dots"), metadata) alter(self.svg.node( gauges, 'polygon', points=' '.join([ fmt(self.view((0, 0))), fmt(self.view((.75, theta))), fmt(self.view((.8, theta))), fmt(self.view((.75, theta)))]), class_='line reactive tooltip-trigger'), metadata) x, y = self.view((.75, theta)) self._tooltip_data(gauges, value, x, y) self._static_value(serie_node, value, x, y)
def dot(self, serie, r_max): """Draw a dot line""" serie_node = self.svg.serie(serie) view_values = list(map(self.view, serie.points)) for i, value in safe_enumerate(serie.values): x, y = view_values[i] if self.logarithmic: log10min = log10(self._min) - 1 log10max = log10(self._max or 1) if value != 0: size = r_max * ( (log10(abs(value)) - log10min) / (log10max - log10min) ) else: size = 0 else: size = r_max * (abs(value) / (self._max or 1)) metadata = serie.metadata.get(i) dots = decorate( self.svg, self.svg.node(serie_node['plot'], class_="dots"), metadata) alter(self.svg.node( dots, 'circle', cx=x, cy=y, r=size, class_='dot reactive tooltip-trigger' + ( ' negative' if value < 0 else '')), metadata) value = self._format(value) self._tooltip_data(dots, value, x, y, classes='centered') self._static_value(serie_node, value, x, y)
def funnel(self, serie): """Draw a funnel slice""" serie_node = self.svg.serie(serie) fmt = lambda x: '%f %f' % x for i, poly in enumerate(serie.points): metadata = serie.metadata.get(i) val = self._format(serie, i) funnels = decorate( self.svg, self.svg.node(serie_node['plot'], class_="funnels"), metadata) alter( self.svg.node(funnels, 'polygon', points=' '.join(map(fmt, map(self.view, poly))), class_='funnel reactive tooltip-trigger'), metadata) # Poly center from label x, y = self.view((self._center(self._x_pos[serie.index]), sum([point[1] for point in poly]) / len(poly))) self._tooltip_data(funnels, val, x, y, 'centered', self._get_x_label(serie.index)) self._static_value(serie_node, val, x, y, metadata)
def gaugify(self, serie, squares, sq_dimensions, current_square): serie_node = self.svg.serie(serie) if self.half_pie: start_angle = 3 * pi / 2 center = ((current_square[1] * sq_dimensions[0]) - (sq_dimensions[0] / 2.), (current_square[0] * sq_dimensions[1]) - (sq_dimensions[1] / 4)) end_angle = pi / 2 else: start_angle = 0 center = ((current_square[1] * sq_dimensions[0]) - (sq_dimensions[0] / 2.), (current_square[0] * sq_dimensions[1]) - (sq_dimensions[1] / 2.)) end_angle = 2 * pi max_value = serie.metadata.get(0, {}).get('max_value', 100) radius = min([sq_dimensions[0] / 2, sq_dimensions[1] / 2]) * .9 small_radius = radius * serie.inner_radius self.svg.gauge_background(serie_node, start_angle, center, radius, small_radius, end_angle, self.half_pie, self._serie_format(serie, max_value)) sum_ = 0 for i, value in enumerate(serie.values): if value is None: continue ratio = min(value, max_value) / max_value if self.half_pie: angle = 2 * pi * ratio / 2 else: angle = 2 * pi * ratio val = self._format(serie, i) metadata = serie.metadata.get(i) gauge_ = decorate( self.svg, self.svg.node(serie_node['plot'], class_="gauge"), metadata) alter( self.svg.solid_gauge(serie_node, gauge_, radius, small_radius, angle, start_angle, center, val, i, metadata, self.half_pie, end_angle, self._serie_format(serie, max_value)), metadata) start_angle += angle sum_ += value x, y = center self.svg.node(serie_node['text_overlay'], 'text', class_='value gauge-sum', x=x, y=y + self.style.value_font_size / 3, attrib={ 'text-anchor': 'middle' }).text = self._serie_format(serie, sum_)
def _bar(self, serie, parent, x, y, i, zero, secondary=False): """Internal bar drawing function""" width = (self.view.x(1) - self.view.x(0)) / self._len x, y = self.view((x, y)) series_margin = width * self._series_margin x += series_margin width -= 2 * series_margin width /= self._order if self.horizontal: serie_index = self._order - serie.index - 1 else: serie_index = serie.index x += serie_index * width serie_margin = width * self._serie_margin x += serie_margin width -= 2 * serie_margin height = self.view.y(zero) - y r = serie.rounded_bars * 1 if serie.rounded_bars else 0 alter(self.svg.transposable_node( parent, 'rect', x=x, y=y, rx=r, ry=r, width=width, height=height, class_='rect reactive tooltip-trigger'), serie.metadata.get(i)) transpose = swap if self.horizontal else ident return transpose((x + width / 2, y + height / 2))
def gaugify(self, serie, squares, sq_dimensions, current_square): serie_node = self.svg.serie(serie) if self.half_pie: start_angle = 3*pi/2 center = ( (current_square[1]*sq_dimensions[0]) - (sq_dimensions[0] / 2.), (current_square[0]*sq_dimensions[1]) - (sq_dimensions[1] / 4)) end_angle = pi / 2 else: start_angle = 0 center = ( (current_square[1]*sq_dimensions[0]) - (sq_dimensions[0] / 2.), (current_square[0]*sq_dimensions[1]) - (sq_dimensions[1] / 2.)) end_angle = 2 * pi max_value = serie.metadata.get(0, {}).get('max_value', 100) radius = min([sq_dimensions[0]/2, sq_dimensions[1]/2]) * .9 small_radius = radius * serie.inner_radius self.svg.gauge_background( serie_node, start_angle, center, radius, small_radius, end_angle, self.half_pie, self._serie_format(serie, max_value)) sum_ = 0 for i, value in enumerate(serie.values): if value is None: continue ratio = min(value, max_value) / max_value if self.half_pie: angle = 2 * pi * ratio / 2 else: angle = 2 * pi * ratio val = self._format(serie, i) metadata = serie.metadata.get(i) gauge_ = decorate( self.svg, self.svg.node(serie_node['plot'], class_="gauge"), metadata) alter( self.svg.solid_gauge( serie_node, gauge_, radius, small_radius, angle, start_angle, center, val, i, metadata, self.half_pie, end_angle, self._serie_format(serie, max_value)), metadata) start_angle += angle sum_ += value x, y = center self.svg.node( serie_node['text_overlay'], 'text', class_='value gauge-sum', x=x, y=y + self.style.value_font_size / 3, attrib={'text-anchor': 'middle'} ).text = self._serie_format(serie, sum_)
def line(self, serie, rescale=False): """Draw the line serie""" serie_node = self.svg.serie(serie) if rescale and self.secondary_series: points = self._rescale(serie.points) else: points = serie.points view_values = list(map(self.view, points)) if serie.show_dots: for i, (x, y) in enumerate(view_values): if None in (x, y): continue if ( serie.show_only_major_dots and self.x_labels and i < len(self.x_labels) and self.x_labels[i] not in self._x_labels_major ): continue metadata = serie.metadata.get(i) classes = [] if x > self.view.width / 2: classes.append("left") if y > self.view.height / 2: classes.append("top") classes = " ".join(classes) self._confidence_interval(serie_node["overlay"], x, y, serie.values[i], metadata) dots = decorate(self.svg, self.svg.node(serie_node["overlay"], class_="dots"), metadata) val = self._get_value(serie.points, i) alter( self.svg.transposable_node( dots, "circle", cx=x, cy=y, r=serie.dots_size, class_="dot reactive tooltip-trigger" ), metadata, ) self._tooltip_data(dots, val, x, y, xlabel=self._get_x_label(i)) self._static_value( serie_node, val, x + self.style.value_font_size, y + self.style.value_font_size, metadata ) if serie.stroke: if self.interpolate: points = serie.interpolated if rescale and self.secondary_series: points = self._rescale(points) view_values = list(map(self.view, points)) if serie.fill: view_values = self._fill(view_values) self.svg.line( serie_node["plot"], view_values, close=self._self_close, class_="line reactive" + (" nofill" if not serie.fill else ""), )
def line(self, serie, rescale=False): """Draw the line serie""" serie_node = self.svg.serie(serie) if rescale and self.secondary_series: points = self._rescale(serie.points) else: points = serie.points view_values = list(map(self.view, points)) if serie.show_dots: for i, (x, y) in enumerate(view_values): if None in (x, y): continue if (serie.show_only_major_dots and self.x_labels and i < len(self.x_labels) and self.x_labels[i] not in self._x_labels_major): continue metadata = serie.metadata.get(i) classes = [] if x > self.view.width / 2: classes.append('left') if y > self.view.height / 2: classes.append('top') classes = ' '.join(classes) self._confidence_interval( serie_node['overlay'], x, y, serie.values[i], metadata) dots = decorate( self.svg, self.svg.node(serie_node['overlay'], class_="dots"), metadata) val = self._get_value(serie.points, i) alter(self.svg.transposable_node( dots, 'circle', cx=x, cy=y, r=serie.dots_size, class_='dot reactive tooltip-trigger'), metadata) self._tooltip_data( dots, val, x, y, xlabel=self._get_x_label(i)) self._static_value( serie_node, val, x + self.style.value_font_size, y + self.style.value_font_size, metadata) if serie.stroke: if self.interpolate: points = serie.interpolated if rescale and self.secondary_series: points = self._rescale(points) view_values = list(map(self.view, points)) if serie.fill: view_values = self._fill(view_values) self.svg.line( serie_node['plot'], view_values, close=self._self_close, class_='line reactive' + (' nofill' if not serie.fill else ''))
def needle(self, serie): """Draw a needle for each value""" serie_node = self.svg.serie(serie) for i, theta in enumerate(serie.values): if theta is None: continue def point(x, y): if self.clockwise: transform = compose(self.clockwiser, self.view) else: transform = self.view return '%f %f' % transform((x, y)) val = self._format(serie, i) metadata = serie.metadata.get(i) gauges = decorate( self.svg, self.svg.node(serie_node['plot'], class_="dots"), metadata) tolerance = 1.15 if theta < self._min: theta = self._min * tolerance if theta > self._max: theta = self._max * tolerance w = (self._box._tmax - self._box._tmin + self.view.aperture) / 4 if self.logarithmic: w = min(w, self._min - self._min * 10 ** -10) sweep_flag = '0' if self.clockwise else '1' alter( self.svg.node( gauges, 'path', d='M %s L %s A %s 1 0 %s %s Z' % ( point(.85, theta), point(self.needle_width, theta - w), '%f %f' % (self.needle_width, self.needle_width), sweep_flag, point(self.needle_width, theta + w), ), class_='line reactive tooltip-trigger'), metadata) x, y = self.view((.75, theta)) if self.clockwise: x, y = self.clockwiser((x, y)) self._tooltip_data( gauges, val, x, y, xlabel=self._get_x_label(i)) self._static_value(serie_node, val, x, y, metadata)
def slice(self, serie, start_angle, total): """Make a serie slice""" #serie_angle = totallist[2] #total=totallist[0] #start_angle=totallist[1] print(serie, total, start_angle, serie_angle) serie_node = self.svg.serie(serie) dual = self._len > 1 and not self._order == 1 slices = self.svg.node(serie_node['plot'], class_="slices") original_start_angle = start_angle if self.half_pie: center = ((self.width - self.margin_box.x) / 2., (self.height - self.margin_box.y) / 1.25) else: center = ((self.width - self.margin_box.x) / 2., (self.height - self.margin_box.y) / 2.) radius = min(center) for i, val in enumerate(serie.values): perc = val / total if self.half_pie: angle = 2 * pi * perc / 2 else: angle = 2 * pi * perc serie_angle += angle val = self._format(serie, i) metadata = serie.metadata.get(i) slice_ = decorate(self.svg, self.svg.node(slices, class_="slice"), metadata) if dual: small_radius = radius * .9 big_radius = radius else: big_radius = radius * .9 small_radius = radius * serie.inner_radius alter( self.svg.slice(serie_node, slice_, big_radius, small_radius, angle, start_angle, center, val, i, metadata), metadata) start_angle += angle if dual: val = self._serie_format(serie, sum(serie.values)) self.svg.slice(serie_node, self.svg.node(slices, class_="big_slice"), radius * .9, 0, serie_angle, original_start_angle, center, val, i, metadata) return serie_angle
def slice(self, serie, start_angle, total): """Make a serie slice""" serie_node = self.svg.serie(serie) dual = self._len > 1 and not self._order == 1 slices = self.svg.node(serie_node['plot'], class_="slices") serie_angle = 0 original_start_angle = start_angle if self.half_pie: center = ((self.width - self.margin_box.x) / 2., (self.height - self.margin_box.y) / 1.25) else: center = ((self.width - self.margin_box.x) / 2., (self.height - self.margin_box.y) / 2.) radius = min(center) for i, val in enumerate(serie.values): perc = val / total if self.half_pie: angle = 2 * pi * perc / 2 else: angle = 2 * pi * perc serie_angle += angle val = self._format(serie, i) metadata = serie.metadata.get(i) slice_ = decorate( self.svg, self.svg.node(slices, class_="slice"), metadata ) if dual: small_radius = radius * .9 big_radius = radius else: big_radius = radius * .9 small_radius = radius * serie.inner_radius alter( self.svg.slice( serie_node, slice_, big_radius, small_radius, angle, start_angle, center, val, i, metadata ), metadata ) start_angle += angle if dual: val = self._serie_format(serie, sum(serie.values)) self.svg.slice( serie_node, self.svg.node(slices, class_="big_slice"), radius * .9, 0, serie_angle, original_start_angle, center, val, i, metadata ) return serie_angle
def line(self, serie, rescale=False): """Draw the line serie""" serie_node = self.svg.serie(serie) if rescale and self.secondary_series: points = self._rescale(serie.points) else: points = serie.points view_values = list(map(self.view, points)) if serie.show_dots: for i, (x, y) in enumerate(view_values): if None in (x, y): continue if (serie.show_only_major_dots and self.x_labels and i < len(self.x_labels) and self.x_labels[i] not in self._x_major_labels): continue metadata = serie.metadata.get(i) classes = [] if x > self.view.width / 2: classes.append('left') if y > self.view.height / 2: classes.append('top') classes = ' '.join(classes) dots = decorate( self.svg, self.svg.node(serie_node['overlay'], class_="dots"), metadata) val = self._get_value(serie.points, i) alter(self.svg.transposable_node( dots, 'circle', cx=x, cy=y, r=serie.dots_size, class_='dot reactive tooltip-trigger'), metadata) self._tooltip_data( dots, val, x, y, xlabel=self._get_x_label(i)) self._static_value( serie_node, val, x + self.style.value_font_size, y + self.style.value_font_size) if serie.stroke: if self.interpolate: points = serie.interpolated if rescale and self.secondary_series: points = self._rescale(points) view_values = list(map(self.view, points)) if serie.fill: view_values = self._fill(view_values) self.svg.line( serie_node['plot'], view_values, close=self._self_close, class_='line reactive' + (' nofill' if not serie.fill else ''))
def needle(self, serie): """Draw a needle for each value""" serie_node = self.svg.serie(serie) for i, theta in enumerate(serie.values): if theta is None: continue def point(x, y): return '%f %f' % self.view((x, y)) value = self._format(serie.values[i]) metadata = serie.metadata.get(i) gauges = decorate(self.svg, self.svg.node(serie_node['plot'], class_="dots"), metadata) tolerance = 1.15 if theta < self._min: theta = self._min * tolerance if theta > self._max: theta = self._max * tolerance w = (self._box._tmax - self._box._tmin + self.view.aperture) / 4 if self.logarithmic: w = min(w, self._min - self._min * 10**-10) alter( self.svg.node( gauges, 'path', d='M %s L %s A %s 1 0 1 %s Z' % ( point(.85, theta), point(self.needle_width, theta - w), '%f %f' % (self.needle_width, self.needle_width), point(self.needle_width, theta + w), ), class_='line reactive tooltip-trigger'), metadata) x, y = self.view((.75, theta)) self._tooltip_data(gauges, value, x, y, xlabel=self._get_x_label(i)) self._static_value(serie_node, value, x, y, metadata)
def _bar(self, serie, parent, x0, x1, y, i, zero, secondary=False): x, y = self.view((x0, y)) x1, _ = self.view((x1, y)) width = x1 - x height = self.view.y(zero) - y series_margin = width * self._series_margin x += series_margin width -= 2 * series_margin r = serie.rounded_bars * 1 if serie.rounded_bars else 0 alter(self.svg.transposable_node( parent, 'rect', x=x, y=y, rx=r, ry=r, width=width, height=height, class_='rect reactive tooltip-trigger'), serie.metadata.get(i)) transpose = swap if self.horizontal else ident return transpose((x + width / 2, y + height / 2))
def needle(self, serie): serie_node = self.svg.serie(serie) for i, theta in enumerate(serie.values): if theta is None: continue def point(x, y): return '%f %f' % self.view((x, y)) value = self._format(serie.values[i]) metadata = serie.metadata.get(i) gauges = decorate( self.svg, self.svg.node(serie_node['plot'], class_="dots"), metadata) tolerance = 1.15 if theta < self._min: theta = self._min * tolerance if theta > self._max: theta = self._max * tolerance w = (self._box._tmax - self._box._tmin + self.view.aperture) / 4 if self.logarithmic: w = min(w, self._min - self._min * 10 ** -10) alter( self.svg.node( gauges, 'path', d='M %s L %s A %s 1 0 1 %s Z' % ( point(.85, theta), point(self.needle_width, theta - w), '%f %f' % (self.needle_width, self.needle_width), point(self.needle_width, theta + w), ), class_='line reactive tooltip-trigger'), metadata) x, y = self.view((.75, theta)) self._tooltip_data(gauges, value, x, y) self._static_value(serie_node, value, x, y)
def dot(self, serie, r_max): """Draw a dot line""" serie_node = self.svg.serie(serie) view_values = list(map(self.view, serie.points)) for i, value in safe_enumerate(serie.values): x, y = view_values[i] if self.logarithmic: log10min = log10(self._min) - 1 log10max = log10(self._max or 1) if value != 0: size = r_max * ((log10(abs(value)) - log10min) / (log10max - log10min)) else: size = 0 else: size = r_max * (abs(value) / (self._max or 1)) metadata = serie.metadata.get(i) dots = decorate( self.svg, self.svg.node(serie_node['plot'], class_="dots"), metadata ) alter( self.svg.node( dots, 'circle', cx=x, cy=y, r=size, class_='dot reactive tooltip-trigger' + (' negative' if value < 0 else '') ), metadata ) val = self._format(serie, i) self._tooltip_data( dots, val, x, y, 'centered', self._get_x_label(i) ) self._static_value(serie_node, val, x, y, metadata)
def _bar(self, serie, parent, x, y, i, zero, secondary=False): """Internal bar drawing function""" width = (self.view.x(1) - self.view.x(0)) / self._len original_y = y x, y = self.view((x, y or self._max)) series_margin = width * self._series_margin if len(self.series) > 1: # Bars in multi series graphs must be separated by atleast 4 series_margin = max(series_margin, 4) x += series_margin width -= 2 * series_margin width /= self._order if self.horizontal: serie_index = self._order - serie.index - 1 else: serie_index = serie.index x += serie_index * width serie_margin = width * self._serie_margin x += serie_margin width -= 2 * serie_margin height = self.view.y(zero) - y r = serie.rounded_bars * 1 if serie.rounded_bars else 0 class_name = 'rect reactive tooltip-trigger' if original_y == 0.0: class_name = 'rect reactive no-bar' alter( self.svg.transposable_node(parent, 'rect', x=x, y=y, rx=r, ry=r, width=width, height=height, class_=class_name), serie.metadata.get(i)) transpose = swap if self.horizontal else ident return x, y, width, height
def dot(self, serie, r_max): """Draw a box line""" width = (self.view.x(1) - self.view.x(0)) serie_node = self.svg.serie(serie) view_values = list(map(self.view, serie.points)) for i, value in safe_enumerate(serie.values): x, y = view_values[i] if self.logarithmic: log10min = log10(self._min) - 1 log10max = log10(self._max or 1) if value != 0: size = r_max * ((log10(abs(value)) - log10min) / (log10max - log10min)) else: size = 0 else: size = r_max * (abs(value) / (self._max or 1)) metadata = serie.metadata.get(i) dots = decorate(self.svg, self.svg.node(serie_node['plot'], class_="dots"), metadata) alter( self.svg.node(dots, 'rect', x=x - 0.5 * width, y=(y - size) if value > 0 else y, width=width, height=size, class_='dot reactive tooltip-trigger' + (' negative' if value < 0 else '')), metadata) val = self._format(serie, i) self._tooltip_data(dots, val, x, y, '', self._get_x_label(i)) self._static_value(serie_node, val, x, y, metadata)
def line(self, serie, rescale=False): """Draw the line serie""" serie_node = self.svg.serie(serie) if rescale and self.secondary_series: points = self._rescale(serie.points) else: points = serie.points view_values = list(map(self.view, points)) if serie.show_dots: for i, (x, y) in enumerate(view_values): if None in (x, y): continue if (serie.show_only_major_dots and self.x_labels and i < len(self.x_labels) and self.x_labels[i] not in self._x_labels_major): continue metadata = serie.metadata.get(i) classes = [] if x > self.view.width / 2: classes.append('left') if y > self.view.height / 2: classes.append('top') classes = ' '.join(classes) self._confidence_interval( serie_node['overlay'], x, y, serie.values[i], metadata) dots = decorate( self.svg, self.svg.node(serie_node['overlay'], class_="dots"), metadata) val = self._format(serie, i) alter(self.svg.transposable_node( dots, 'circle', cx=x, cy=y, r=serie.dots_size, class_='dot reactive tooltip-trigger'), metadata) self._tooltip_data( dots, val, x, y, xlabel=self._get_x_label(i)) self._static_value( serie_node, val, x + self.style.value_font_size, y + self.style.value_font_size, metadata) if serie.stroke: if self.interpolate: points = serie.interpolated if rescale and self.secondary_series: points = self._rescale(points) view_values = list(map(self.view, points)) if serie.fill: view_values = self._fill(view_values) if serie.allow_interruptions: # view_values are in form [(x1, y1), (x2, y2)]. We # need to split that into multiple sequences if a # None is present here sequences = [] cur_sequence = [] for x, y in view_values: if y is None and len(cur_sequence) > 0: # emit current subsequence sequences.append(cur_sequence) cur_sequence = [] elif y is None: # just discard continue else: cur_sequence.append((x, y)) # append the element if len(cur_sequence) > 0: # emit last possible sequence sequences.append(cur_sequence) else: # plain vanilla rendering sequences = [view_values] for seq in sequences: self.svg.line( serie_node['plot'], seq, close=self._self_close, class_='line reactive' + (' nofill' if not serie.fill else ''))
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 _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) value = self._get_value((area_code, value)) self._tooltip_data(area, value, 0, 0, 'auto') self.nodes['plot'].append(map)