from openpyxl import Workbook
from openpyxl.chart import (
ScatterChart,
Reference,
Series,
)
wb = Workbook()
ws = wb.active
rows = [
['Size', 'Batch 1', 'Batch 2'],
[2, 40, 30],
[3, 40, 25],
[4, 50, 30],
[5, 30, 25],
[6, 25, 35],
[7, 20, 40],
]
for row in rows:
ws.append(row)
chart = ScatterChart()
chart.scatterStyle = "marker"
chart.title = "Scatter Chart"
chart.style = 13
chart.x_axis.title = 'Size'
chart.y_axis.title = 'Percentage'
xvalues = Reference(ws, min_col=1, min_row=2, max_row=7)
for i in range(2, 4):
values = Reference(ws, min_col=i, min_row=1, max_row=7)
series = Series(values, xvalues, title_from_data=True)
chart.series.append(series)
ws.add_chart(chart, "A10")
wb.save("scatter.xlsx") # from ...\chart\_chart.py
width = 15 # in cm, approx 5 rows (Actually it should say columns here...)
height = 7.5 # in cm, approx 14 rowschart.width = 15
chart.height = 7.5
# from ...\utils\units.py
#constants
DEFAULT_ROW_HEIGHT = 15. # Default row height measured in point size.
BASE_COL_WIDTH = 13 # in characters
DEFAULT_COLUMN_WIDTH = 51.85 # in points, should be characters
I think that top-left is currently hard-wired into the code but it's
fairly easy to fix this. Just haven't got round to it yet.
# standard width openpyxl=2.117 cm/10.71 chars/ 80 pixels
# standard height openpyxl =0.529 cm/15 points/20 pixels
COLUMN_WIDTH_CM = 2.117
ROW_HEIGHT_CM = 0.529There is a worksheet
method `point_pos` that tries to calculate position but it's basically a
game you can't win.
This can probably be changed to a default anchor type that can be
overwritten if required. Haven't got round to this yet as it also affects
images.
def _write(self):
""" create required structure and the serialise """
anchors = []
for idx, obj in enumerate(self.charts + self.images, 1):
if isinstance(obj, ChartBase):
rel = Relationship(type="chart", target='../charts/chart%s.xml' % obj._id)
row, col = coordinate_to_tuple(obj.anchor)
anchor = OneCellAnchor()
anchor._from.row = row -1
anchor._from.col = col -1
anchor.ext.width = cm_to_EMU(obj.width)
anchor.ext.height = cm_to_EMU(obj.height)
anchor.graphicFrame = self._chart_frame(idx)
elif isinstance(obj, Image):
rel = Relationship(type="image", target='../media/image%s.png' % obj._id)
anchor = obj.drawing.anchor anchor.pic = self._picture_frame(idx)
anchors.append(anchor)
self._rels.append(rel)
self.oneCellAnchor = anchors
tree = self.to_tree()
tree.set('xmlns', SHEET_DRAWING_NS)
return treedef _write(self):
""" create required structure and the serialise """
one_cell_anchors = []
two_cell_anchors = []
absolute_anchors = []
for idx, obj in enumerate(self.charts + self.images, 1):
if isinstance(obj, ChartBase):
rel = Relationship(type="chart", target='../charts/chart%s.xml' % obj._id)
anchor=obj.anchor
anchor.graphicFrame = self._chart_frame
elif isinstance(obj, Image):
rel = Relationship(type="image", target='../media/image%s.png' % obj._id)
anchor = obj.drawing.anchor
anchor.pic = self._picture_frame(idx)
if isinstance(anchor, OneCellAnchor):
one_cell_anchors.append(anchor)
elif isinstance(anchor, TwoCellAnchor):
two_cell_anchors.append(anchor)
else:
#AbsoluteAnchor
absolute_anchors.append(anchor)
self._rels.append(rel)
self.oneCellAnchor = one_cell_anchors
self.twoCellAnchor = two_cell_anchors
self.absoluteAnchor= absolute_anchors
tree = self.to_tree()
tree.set('xmlns', SHEET_DRAWING_NS)
return treedef add_chart(self, chart, anchor=None, anchor_top_left=None, anchor_bottom_right=None, pos_x=None, pos_y=None, ext_x=None, ext_y=None):
"""
Add a chart to the sheet
Optionally provide a cell for the top-left anchor,
or the top-left and bottom-ight anchor
or absolute x- and y-positions together with width and hight
"""
chart_anchor=None
if anchor is not None:
row, col = coordinate_to_tuple(anchor)
chart_anchor = OneCellAnchor()
chart_anchor._from.row = row -1
chart_anchor._from.col = col -1
chart_anchor.ext.width = cm_to_EMU(obj.width)
chart_anchor.ext.height = cm_to_EMU(obj.height)
elseif anchor_top_left is not None or anchor_bottom_right is not None:
if anchor_top_left is not None and anchor_bottom_right is not None:
row1, col1 = coordinate_to_tuple(anchor_top_left)
row2, col2 = coordinate_to_tuple(anchor_bottom_right)
chart_anchor=TwoCellAnchor(_from=AnchorMarker(col=col1,row=row1), to=AnchorMarker(col=col2, row=row2), editAs="twoCell")
else:
msg = "If you provide a value for "\
"'anchor_top_left' you must also provide a value for 'anchor_bottom_right'"
raise InsufficientCoordinatesException(msg)
elif pos_x is not None or pos_y is not None or ext_x is not None or ext_y is not None:
if pos_x is not None or pos_y is not None or ext_x is not None and ext_y is not None:
pos = Point2D(pos_x, pos_y)
ext = PositiveSize2D(ext_x, ext_y)
chart_anchor=AbsoluteAnchor(pos=pos, ext=ext)
else:
msg = "If you provide a value for "\
"'pos_x' you must also provide a value for 'pos_y' *and* 'ext_x' *and* 'ext_y'"
raise InsufficientCoordinatesException(msg)
if chart_anchor is not None
chart.anchor = chart_anchor
self._charts.append(chart)
self._parent._charts.append(ref(chart))I file bugs on the specification and wrote a code generator, which I'll be
talking about at next month's PyCon France in Pau, and without which the
new charting stuff simply wouldn't be possible. And at least one other
person seems to have been able to use the generator and live!
Your code is far too complex. This needs thinking through in terms of the
API for client code. It's probably simpler to leave this as it is and
allow client code to overwrite.
ws.add_chart(my_chart) # default is a oneCellAnchor for which should
suffice in most cases
my_chart.anchor = TwoCellAnchor(…) # when you need something speical.
Please feel free to make a PR (with tested tests! ;-) )