-
Notifications
You must be signed in to change notification settings - Fork 546
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature: multiple axes in charts #141
Comments
I've started implementing this in my multiple_axes branch. So far I have the |
Have you given any thought to what the API would look like? I've found that folks who start with the implementation usually end up with something that needs to be rewritten from scratch before commit. Not intending to spoil the fun though :) There's nothing wrong with working your own fork for your own purposes. Just wanted to provide an early heads up that if you wanted to contribute your work back to the main branch you might do better with an outside-in approach :) |
Read your second post first, just now getting to the first one, thought it was one I read from earlier, sorry about that :) I'm thinking the right api for the axis access would be: Chart.value_axis
# and
Chart.secondary_value_axis where each would return either the appropriate axis object or The category axis access API would look the same. Chart.category_axis
# and
Chart.secondary_category_axis The only question in my mind is whether there could possibly be more than four, in particular in the case of a 3D chart. I haven't looked into those seriously yet, but it probably bears an initial investigation at least. @AlexMooney it probably makes sense to get a feature analysis page started in your branch we can collaborate on to noodle out and document these decisions. The key success factor for getting a pull committed turns out to be whether it started with an analysis page. There are just so many small detail to be decided up front that become big factors in the design and implementation that it's statistically very improbable to get it right unless some up-front "pre-factoring" gets done :) Let me know if you want more on how to get one started. Here's an example: |
Hi, here is my solution at https://github.com/huandzh/python-pptx/compare/chart_cat_multiLvl. It worked for my working scripts for sometime.
I hope it is helpful. Sample code: from pptx import Presentation
from pptx.enum.chart import XL_CHART_TYPE
from pptx.util import Inches
from pptx.chart.data import ChartDataMoreDetails
# create presentation with 1 slide ------
prs = Presentation('chart-01test.pptx')
chart = prs.slides[0].shapes[0].chart
# define chart data ---------------------
chart_data = ChartDataMoreDetails()
#one line of categories
# chart_data.categories = [#[(0,'top3'),(4,'others')],
# [(0,1),(1,2),(2,3),(4,4),(5,5)]]
# categories and values in (idx, value) pair
chart_data.categories = [[(0,'top3'),(4,'others')],
[(0,1),(1,2),(2,3),(4,4),(5,5)]]
#chart_data.values_len = 6
chart_data.add_series('Score>90',
[(0,98.32),(1,97.12),(2,95.54),(4,92.12)],
format_code='0.0')
chart.replace_data(chart_data)
prs.save('chartmore-02.pptx') |
@huandzh, those are some great changes but as far as I can tell, there's no implementation of secondary axes. Can you create charts like the one I pasted below? @scanny, this scatter plot has 4 This surface plot has a I'll start writing a feature analysis... |
I've uploaded a draft of an analysis page. It's here. |
Hi Alex, this looks great :) I'm traveling this week, but will take a closer look over the weekend and provide some comments. |
Hi @AlexMooney, I read through your draft feature analysis. My main first reaction is that I'm rethinking my idea of the axis access API being Chart.value_axis, .secondary_value_axis, .category_axis, .secondary_category_axis. That would be fine if that were the most there could be, but your example showing four value axes demonstrates that design is not going to be general enough. Taking a closer look at the Microsoft API it looks like the possible axis configurations for a chart are a partial cover of a three-dimensional space where axis-type(value, category, series), axis-group(primary, secondary), and orientation(horizontal, vertical) are the three dimensions. I'm thinking the best next step is a reasonably thorough case analysis about what configurations are possible. Those would inform the API design and then go on to be test cases. I'm starting to like the idea of having an Axis collection on Chart that would allow iteration over the various axes, whatever they happen to be. Then having .type, .axis_group, and .orientation properties on each axis so they could each be well characterized. The .value_axis and .category_axis properties on Chart could stay since they're so handy for the most common cases. I suppose the next step after that would be to have a way to add an axis. The MS API uses the Chart.HasAxis(type, group) method/property for this, but I think we'd want something a bit more Pythonic in this case; it would be odd to see something like this in Python code although it might be possible to make it work with descriptors or something: chart.has_axis(XL_AXIS_TYPE.VALUE, XL_AXIS_GROUP.SECONDARY) = True Off the top of my head I'd be thinking something more like: chart.add_axis(XL_AXIS_TYPE.VALUE, XL_AXIS_GROUP.SECONDARY) I don't see how they manage to cover all the possibilities with just the two parameters though. I suspect they overload XlValue and XlCategory to mean VALUE+VERTICAL and VALUE+HORIZONTAL respectively in the case of an XY Scatter plot. It would take some experimentation with the MS API to discover that I expect. Anyway, it looks like it's shaping up to be a fairly big job to handle the general case. I think the following list of features would be a good place to start because 1) they don't break anything, 2) they would be required infrastructure for the rest of it, and 3) they would provide an opportunity to uncover more of the details required for the other bits like adding a new axis:
On the issue of validation, I think we'd need to have the case analysis worked out pretty firmly so we could keep the user from doing something that PowerPoint would barf on. It creates a support problem when newbie users can generate a presentation that won't load. Much better to just raise an exception when they try something we know won't work. Of course that means we'd need to do the analysis to know what will work and what won't. |
In the XML, orientation and group are not directly represented. Instead of orientation there is position (top, bottom, right, left; presumably others for 3d charts), although we could make a lookup to do something like
|
I added the Not sure how to implement |
I don't have my head freshly wrapped around this, so take that into account, but off-hand I'd say that This would require that all axes be constructed by an _Axes object. That might not be a bad idea anyway. So the operation might look vaguely like this: class _BaseAxis(...):
@property
def group(self):
return self._parent.axis_group(self)
class _Axes(...):
def axis_group(axis):
# work out which group this axis belongs to here
return _AxisGroup(...) The protocol would be reminiscent of how What do you think? |
Worked like a charm, thanks! That's committed but breaks most of the axis tests since making an axis has a different signature now. Should the tests be rewritten or should there be a default To summarize, we now have |
Hi Alex, sorry for the delay in responding, been a crazy week or so :) In general, I'd say just update the tests using If there are any that actually require the I haven't taken a look at them, but if you fix the ones where a I've encountered this situation before as an object gets to the point where it needs to delegate something "upward" and it seems like almost all of the tests just need a working constructor call and not an actual parent. Makes sense of course since none of the prior tests could have used a parent that wasn't there and usually that field is only used by the new method you're adding :) Interestingly, the practice of using |
Hi Steve, no worries!
The The corresponding |
Ah, right, that makes sense. So the first one you can fix by adding def it_provides_access_to_the_category_axis(self, cat_ax_fixture):
chart, category_axis_, CategoryAxis_, catAx = cat_ax_fixture
category_axis = chart.category_axis
CategoryAxis_.assert_called_once_with(catAx, chart.axes) ## <<< here
assert category_axis is category_axis_ The same approach should fix the second test as well. It points up an interesting question though, and that is whether the implementation of I think before long we'll want an implementation more like this: @property
def category_axis(self):
"""
The first category axis of this chart. Raises |ValueError| if no
category axes are defined.
"""
try:
return self.axes.list_by_type(MSO_AXIS_TYPE.CATEGORY)[0]
except IndexError:
raise ValueError('chart has no category axis') but that can wait until after you get it working :) Basically the idea being there shouldn't be more than one way to get the first category axis because it would represent duplication in the code. We would want the Axes object to be in charge of all axis getting and the .category_axis property of chart would delegate that job to its Axes object instead of handling it directly as it does today. So we'll need to noodle a bit on what the broader API of Axes will be. The Anyway, we can get to all that in due time, step by step :) |
Thanks to that snippet, the tests are all passing again. Instead of
It's really a matter of how useful the What do you think should be next? |
Oooh, that's an idea. I think the |
Changing the category and value axis methods (with
I don't suppose you know how to rewrite those tests off the top of your head..? 😕 |
Well, they would need to be rewritten. They use mocks so changing the interaction used to get the result will need a different test. A mocked test tests the interaction rather than the final result. First, it would take two tests, one for the success case and one for the error case. For the success case you need to mock the Chart.axes property to return an Axes mock, then have the Axes mock return a type of I know that's a little bit tricky probably if you're not used to working with the Python mock library :) Also I think there will need to be some sort of AXIS_TYPE enumeration, we wouldn't want to use a raw XML attribute string value in there. It would be perfectly fine to leave it the way it was (calling CategoryAxis() directly) for now and we can get it upgraded when we actually close in on a set of commits. I'll leave that up to you. I think if we can get it working that's probably the first priority for most folks. We can attend to the careful craftsmanship of it as a second step. We might need more of my attention for that, but the actual incorporation of these into the main branch always seems to take a lot of that one way or the other :) |
Is the API for the second y axis avaiable now? I checked the chart and series source withtout clue. |
Is this feature something that I can use? If so, is there anyway you could post an example of how to plot on the secondary axis or direct me to some documentation. Thank you! |
No. This is a feature request. No one's working on it as far as I know. Looks like Alex made some progress on it a couple years ago. You can check out his fork, although it's 400-odd commits behind this master, so a lot of the latest features would be missing. |
@scanny is there any work around to get two axis charts ? large part of our application depends upon python-pptx.i will be able to look into more detail of a permanent fix once i have some time off from work. Thanks |
@anekix I don't know of any. There was some work done on this as you can see from the prior posts, so you might be able to pull from there. If your team has budget to devote to it you might consider sponsoring this feature. |
Hi @scanny I have a simple problem , i am trying to add a reference line on the head of a bar keeping my value axis line same. For example I want to add that blue line on the top of my reference bar. |
@aishwarya91 Please post support questions on StackOverflow, using the 'python-pptx' tag. |
Hi Have posted On StackOverflow. Thanks |
I would like to try and workout a solution for the secondary axis on a simple combo chart. Is there any documentation you are aware of that could get me up to speed on:
Sorry for a newbie dev post, but this feature would really help me out, so I want to try and work it out for a simple bar/line combo chart (bars on one axis, lines on other). Thanks. |
The analysis pages on Charts here are probably a good place to start: |
So, do you plan to implement it in the future ? |
No current plans to implement. |
Tracking more than one value axis is necessary for e.g. scatter plots and manipulating charts with a secondary axis.
In the
oxml/chart/chart.py
file, thecatAx
andvalAx
should beZeroOrMore
arrays of axes instead ofZeroOrOne
values that store only the first axis in a chart. I think it would be nice to preserve the behavior today where you get the first axis with e.g.chart.value_axis
and only expose the multiple axes via some new API likechart.value_axes_list
.There will need to be provisions for working with axis IDs and assigning the axis IDs for each plot within a chart. The
pptx.chart.axis._BaseAxis
will need to be able to setc:crossAx
,c:crosses
, andc:axPos
values.The text was updated successfully, but these errors were encountered: