Open rouilj opened 3 years ago
I just came across this, but my use case was a stackedbar + line system.
After some trial and error with it telling me that positive_cumulation doesn't exist, I found that for python's multi-inheritance to work for LineStackedBar the inheritance must be flipped so pygal.StackedBar is the primary.
CSS required is still the inline translate, but the serie number and px can be changed depending on use case.
I just came across this, but my use case was a stackedbar + line system.
Nice use case.
After some trial and error with it telling me that positive_cumulation doesn't exist, I found that for python's multi-inheritance to work for LineStackedBar the inheritance must be flipped so pygal.StackedBar is the primary.
Interesting. If only LineStackedBar has that method, method resolution order should should pick it up regardless of the order of the inherited classes.
CSS required is still the inline translate, but the serie number and px can be changed depending on use case.
Can you post your class and an example of using it for anybody else trying to build on this idea.
I'll just drop my version of a LineBar
chart here, to avoid opening more issues. My version assumes that all primary data is a line and all secondary data is a bar, to make things easier.
I ran into a whole bunch of CSS/offset issues, especially with the secondary series, that I tried not to hardcode too much, but my code is still quite a bit more hacky/invasive than yours. In the hopes that it helps at least somebody out there:
class LineBar(pygal.Line, pygal.Bar):
"""Class that renders primary data as line, and secondary data as bar."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.secondary_range = kwargs.get("secondary_range")
def add(self, label, data, **kwargs):
# We add an empty data point, because otherwise the secondary series (the bar chart)
# would overlay the axis.
super().add(label, data + [None], **kwargs)
def _fix_style(self):
# We render the plot twice, this time to find the width of a single bar
# Would that you could just offset things in SVG by percentages without nested SVGs or similar dark magic.
bar_width = int(
float(
self.render_tree().findall(".//*[@class='bar']/rect")[0].attrib["width"]
)
)
line_offset = str(bar_width / 2 + 6)
bar_offset = str(bar_width + 3)
added_css = """
{{ id }} g.series .line {
transform: translate({line_offset}px, 0);
}
{{ id }} g.series .dots {
transform: translate({line_offset}px, 0);
}
{{ id }} g.series .bar rect {
transform: translate(-{bar_offset}px, 0);
}
""".replace(
"{line_offset}", line_offset
).replace(
"{bar_offset}", bar_offset
)
# We have to create a tempfile here because pygal only does templating
# when loading CSS from files. Sadness. Cleanup takes place in render()
timestamp = int(dt.datetime.now().timestamp())
custom_css_file = f"/tmp/pygal_custom_style_{timestamp}.css"
with open(custom_css_file, "w") as f:
f.write(added_css)
self.config.css.append("file://" + custom_css_file)
def _plot(self):
primary_range = (self.view.box.ymin, self.view.box.ymax)
real_order = self._order
if self.secondary_range:
self.view.box.ymin = self.secondary_range[0]
self.view.box.ymax = self.secondary_range[1]
self._order = len(self.secondary_series)
for i, serie in enumerate(self.secondary_series, 1):
self.bar(serie, False)
self._order = real_order
self.view.box.ymin = primary_range[0]
self.view.box.ymax = primary_range[1]
for i, serie in enumerate(self.series, 1):
self.line(serie)
def render(self, *args, **kwargs):
self._fix_style()
result = super().render(*args, **kwargs)
# remove all the custom css files
for css_file in self.config.css:
if css_file.startswith("file:///tmp"):
os.remove(css_file[7:])
return result
Usage is normal:
chart = LineBar(**config)
chart.x_labels = [x[0] for x in data] + [""] # remember to add an extra label
chart.add("", [x[1] for x in data]) # line data
chart.add("", [x[2] for x in data], secondary=True) # bar data
A lot of this is optional, but eg. the hacky _order
change makes it so that the bars don't grow thinner with added lines, which they otherwise would, etc. The result looks something like this:
Since there seems to be a bit of interest in line/bar plotting, I thought I would put my attempt up here. I just started on this last night, so it might not be the best. I am recreating a previous series of plots using pyChart (python 2.x only and abandoned). Here is the original:
and my pygal reproduction:
This method builds on #399 and allows passing the graph type by passing a
plotas
parameter to the add() method. Then I grovel insideself.svg.graph.raw_serie
to get theplotas
value to determine which method to use.There are a couple of issues that I had to work around. If I didn't set the range for the plot, the bars started below the 0 axis line. If I didn't add an extra empty label at the end, the bars overlapped the right hand border.
I also made it look a little more like the pyChart display by using inline css: