Add charts

This commit is contained in:
Mauricio Baeza 2019-10-12 20:16:00 -05:00
parent aa905e19f3
commit 114d10fc14
1 changed files with 657 additions and 28 deletions

View File

@ -18,6 +18,7 @@
# ~ along with ZAZ. If not, see <https://www.gnu.org/licenses/>.
import base64
import csv
import ctypes
import datetime
import errno
@ -71,6 +72,7 @@ from com.sun.star.awt.KeyFunction import QUIT
from com.sun.star.datatransfer import XTransferable, DataFlavor
from com.sun.star.table.CellContentType import EMPTY, VALUE, TEXT, FORMULA
from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK
from com.sun.star.text.TextContentAnchorType import AS_CHARACTER
from com.sun.star.lang import XEventListener
@ -183,6 +185,7 @@ log = logging.getLogger(__name__)
_start = 0
_stop_thread = {}
TIMEOUT = 10
SECONDS_DAY = 60 * 60 * 24
CTX = uno.getComponentContext()
@ -197,7 +200,7 @@ def create_instance(name, with_context=False):
return instance
def _get_app_config(node_name, key=''):
def get_app_config(node_name, key=''):
name = 'com.sun.star.configuration.ConfigurationProvider'
service = 'com.sun.star.configuration.ConfigurationAccess'
cp = create_instance(name, True)
@ -214,10 +217,16 @@ def _get_app_config(node_name, key=''):
# ~ FILTER_PDF = '/org.openoffice.Office.Common/Filter/PDF/Export/'
LANGUAGE = _get_app_config('org.openoffice.Setup/L10N/', 'ooLocale')
LANGUAGE = get_app_config('org.openoffice.Setup/L10N/', 'ooLocale')
LANG = LANGUAGE.split('-')[0]
NAME = TITLE = _get_app_config('org.openoffice.Setup/Product', 'ooName')
VERSION = _get_app_config('org.openoffice.Setup/Product','ooSetupVersion')
NAME = TITLE = get_app_config('org.openoffice.Setup/Product', 'ooName')
VERSION = get_app_config('org.openoffice.Setup/Product','ooSetupVersion')
nd = '/org.openoffice.Office.Calc/Calculate/Other/Date'
d = get_app_config(nd, 'DD')
m = get_app_config(nd, 'MM')
y = get_app_config(nd, 'YY')
DATE_OFFSET = datetime.date(y, m, d).toordinal()
def mri(obj):
@ -303,6 +312,10 @@ def today():
return datetime.date.today()
def time():
return datetime.datetime.now().time()
def get_date(year, month, day, hour=-1, minute=-1, second=-1):
if hour > -1 or minute > -1 or second > -1:
h = hour
@ -471,6 +484,27 @@ def array_to_dict(values):
# ~ Custom classes
class ObjectBase(object):
def __init__(self, obj):
self._obj = obj
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
def __getitem__(self, index):
return self.obj[index]
@property
def obj(self):
return self._obj
@obj.setter
def obj(self, value):
self._obj = value
class LOObjectBase(object):
@ -710,6 +744,20 @@ class LOCalc(LODocument):
def __contains__(self, item):
return item in self.obj.Sheets
@property
def headers(self):
return self._cc.ColumnRowHeaders
@headers.setter
def headers(self, value):
self._cc.ColumnRowHeaders = value
@property
def tabs(self):
return self._cc.SheetTabs
@tabs.setter
def tabs(self, value):
self._cc.SheetTabs = value
@property
def active(self):
return LOCalcSheet(self._cc.getActiveSheet(), self)
@ -847,6 +895,23 @@ class LOCalc(LODocument):
self.cell_style[name] = obj
return LOCellStyle(obj)
def clear_undo(self):
self.obj.getUndoManager().clear()
return
def filter_by_color(self, cell=None):
if cell is None:
cell = self.selection.first
cr = cell.current_region
col = cell.column - cr.column
rangos = cell.get_column(col).visible
for r in rangos:
for row in range(r.rows):
c = r[row, 0]
if c.back_color != cell.back_color:
c.rows_visible = False
return
class LOCalcSheets(object):
@ -932,6 +997,17 @@ class LOCalcSheet(object):
def code_name(self, value):
self._obj.CodeName = value
@property
def color(self):
return self._obj.TabColor
@color.setter
def color(self, value):
self._obj.TabColor = get_color(value)
@property
def active(self):
return self.doc.selection.first
def activate(self):
self.doc.activate(self.obj)
return
@ -943,6 +1019,10 @@ class LOCalcSheet(object):
def visible(self, value):
self.obj.IsVisible = value
@property
def is_protected(self):
return self._obj.isProtected()
@property
def password(self):
return ''
@ -961,6 +1041,9 @@ class LOCalcSheet(object):
def get_cursor(self, cell):
return self.obj.createCursorByRange(cell)
def exists_chart(self, name):
return name in self.obj.Charts.ElementNames
@property
def events(self):
return self._events
@ -1005,17 +1088,40 @@ class LOWriter(LODocument):
@property
def selection(self):
# ~ sel = self._cc.getSelection()
sel = self.obj.getCurrentSelection()
return LOTextRange(sel[0])
def write(self, data, cursor=None):
cursor = cursor or self.selection.cursor.getEnd()
if data.startswith('\n'):
c = data.split('\n')
for i in range(len(c)-1):
self.text.insertControlCharacter(cursor, PARAGRAPH_BREAK, False)
else:
self.text.insertString(cursor, data, False)
return
def insert_table(self, data, cursor=None):
cursor = cursor or self.selection.cursor.getEnd()
table = self.obj.createInstance('com.sun.star.text.TextTable')
rows = len(data)
cols = len(data[0])
table.initialize(rows, cols)
self.insert_content(cursor, table)
table.DataArray = data
return WriterTable(table)
def create_chart(self, tipo, cursor=None):
cursor = cursor or self.selection.cursor.getEnd()
chart = LOChart(None, tipo)
chart.cursor = cursor
chart.doc = self
return chart
def insert_content(self, cursor, data, replace=False):
self.text.insertTextContent(cursor, data, replace)
return
# ~ tt = doc.createInstance('com.sun.star.text.TextTable')
# ~ tt.initialize(5, 2)
# ~ f = doc.createInstance('com.sun.star.text.TextFrame')
# ~ f.setSize(Size(10000, 500))
@ -1284,14 +1390,22 @@ class LOCellRange(object):
self.obj.setString(data)
elif isinstance(data, (int, float)):
self.obj.setValue(data)
elif isinstance(data, datetime.datetime):
d = data.toordinal()
t = (data - datetime.datetime.fromordinal(d)).seconds / SECONDS_DAY
self.obj.setValue(d - DATE_OFFSET + t)
elif isinstance(data, datetime.date):
d = data.toordinal()
self.obj.setValue(d - DATE_OFFSET)
elif isinstance(data, datetime.time):
d = (data.hour * 3600 + data.minute * 60 + data.second) / SECONDS_DAY
self.obj.setValue(d)
@property
def data(self):
return self.obj.getDataArray()
@data.setter
def data(self, values):
# ~ if not isinstance(data[0], tuple):
# ~ data = tuple(zip(data))
if isinstance(values, list):
values = tuple(values)
self.obj.setDataArray(values)
@ -1305,6 +1419,15 @@ class LOCellRange(object):
values = tuple(values)
self.obj.setFormulaArray(values)
@property
def column(self):
a = self.address
if hasattr(a, 'Column'):
c = a.Column
else:
c = a.StartColumn
return c
@property
def columns(self):
return self._obj.Columns.Count
@ -1359,6 +1482,21 @@ class LOCellRange(object):
def sheet(self):
return LOCalcSheet(self.obj.Spreadsheet, self.doc)
@property
def charts(self):
return self.obj.Spreadsheet.Charts
@property
def ps(self):
ps = Rectangle()
s = self.obj.Size
p = self.obj.Position
ps.X = p.X
ps.Y = p.Y
ps.Width = s.Width
ps.Height = s.Height
return ps
@property
def draw_page(self):
return self.sheet.obj.getDrawPage()
@ -1397,6 +1535,13 @@ class LOCellRange(object):
for r in cursor.queryEmptyCells()]
return tuple(rangos)
@property
def back_color(self):
return self._obj.CellBackColor
@back_color.setter
def back_color(self, value):
self._obj.CellBackColor = get_color(value)
@property
def cell_style(self):
return self.obj.CellStyle
@ -1440,9 +1585,57 @@ class LOCellRange(object):
return
def clear(self, what=31):
# ~ http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1CellFlags.html
self.obj.clearContents(what)
return
@property
def rows_visible(self):
return self._obj.getRows().IsVisible
@rows_visible.setter
def rows_visible(self, value):
self._obj.getRows().IsVisible = value
@property
def columns_visible(self):
return self._obj.getColumns().IsVisible
@columns_visible.setter
def columns_visible(self, value):
self._obj.getColumns().IsVisible = value
def get_column(self, index=0, first=False):
ca = self.address
ra = self.current_region.address
if hasattr(ca, 'Column'):
col = ca.Column
else:
col = ca.StartColumn + index
start = 1
if first:
start = 0
if hasattr(ra, 'Row'):
row_start = ra.Row + start
row_end = ra.Row + 1
else:
row_start = ra.StartRow + start
row_end = ra.EndRow + 1
return LOCellRange(self.sheet[row_start:row_end, col:col+1].obj, self.doc)
def import_csv(self, path, **kwargs):
data = import_csv(path, **kwargs)
self.copy_from(data)
return
def export_csv(self, path, **kwargs):
data = self.current_region.data
export_csv(path, data, **kwargs)
return
def create_chart(self, tipo):
chart = LOChart(None, tipo)
chart.cell = self
return chart
class EventsListenerBase(unohelper.Base, XEventListener):
@ -2010,6 +2203,443 @@ def add_listeners(events, control, name=''):
return
class WriterTable(ObjectBase):
def __init__(self, obj):
super().__init__(obj)
def __getitem__(self, key):
obj = super().__getitem__(key)
return WriterTableRange(obj, key, self.name)
@property
def name(self):
return self.obj.Name
@name.setter
def name(self, value):
self.obj.Name = value
class WriterTableRange(ObjectBase):
def __init__(self, obj, index, table_name):
self._index = index
self._table_name = table_name
super().__init__(obj)
self._is_cell = hasattr(self.obj, 'CellName')
def __getitem__(self, key):
obj = super().__getitem__(key)
return WriterTableRange(obj, key, self._table_name)
@property
def value(self):
return self.obj.String
@value.setter
def value(self, value):
self.obj.String = value
@property
def data(self):
return self.obj.getDataArray()
@data.setter
def data(self, values):
if isinstance(values, list):
values = tuple(values)
self.obj.setDataArray(values)
@property
def rows(self):
return len(self.data)
@property
def columns(self):
return len(self.data[0])
@property
def name(self):
if self._is_cell:
name = '{}.{}'.format(self._table_name, self.obj.CellName)
elif isinstance(self._index, str):
name = '{}.{}'.format(self._table_name, self._index)
else:
c1 = self.obj[0,0].CellName
c2 = self.obj[self.rows-1,self.columns-1].CellName
name = '{}.{}:{}'.format(self._table_name, c1, c2)
return name
def get_cell(self, *index):
return self[index]
def get_column(self, index=0, start=1):
return self[start:self.rows,index:index+1]
def get_series(self):
class Serie():
pass
series = []
for i in range(self.columns):
serie = Serie()
serie.label = self.get_cell(0,i).name
serie.data = self.get_column(i).data
serie.values = self.get_column(i).name
series.append(serie)
return series
class ChartFormat(object):
def __call__(self, obj):
for k, v in self.__dict__.items():
if hasattr(obj, k):
setattr(obj, k, v)
class LOChart(object):
BASE = 'com.sun.star.chart.{}Diagram'
def __init__(self, obj, tipo=''):
self._obj = obj
self._type = tipo
self._name = ''
self._table = None
self._data = ()
self._data_series = ()
self._cell = None
self._cursor = None
self._doc = None
self._title = ChartFormat()
self._subtitle = ChartFormat()
self._legend = ChartFormat()
self._xaxistitle = ChartFormat()
self._yaxistitle = ChartFormat()
self._xaxis = ChartFormat()
self._yaxis = ChartFormat()
self._xmaingrid = ChartFormat()
self._ymaingrid = ChartFormat()
self._xhelpgrid = ChartFormat()
self._yhelpgrid = ChartFormat()
self._area = ChartFormat()
self._wall = ChartFormat()
self._dim3d = False
self._series = ()
self._labels = ()
return
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.insert()
@property
def obj(self):
return self._obj
@obj.setter
def obj(self, value):
self._obj = value
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
@property
def type(self):
return self._type
@type.setter
def type(self, value):
self._type = value
@property
def table(self):
return self._table
@table.setter
def table(self, value):
self._table = value
@property
def data(self):
return self._data
@data.setter
def data(self, value):
self._data = value
@property
def cell(self):
return self._cell
@cell.setter
def cell(self, value):
self._cell = value
self.doc = value.doc
@property
def cursor(self):
return self._cursor
@cursor.setter
def cursor(self, value):
self._cursor = value
@property
def doc(self):
return self._doc
@doc.setter
def doc(self, value):
self._doc = value
@property
def width(self):
return self._width
@width.setter
def width(self, value):
self._width = value
@property
def height(self):
return self._height
@height.setter
def height(self, value):
self._height = value
@property
def title(self):
return self._title
@property
def subtitle(self):
return self._subtitle
@property
def legend(self):
return self._legend
@property
def xaxistitle(self):
return self._xaxistitle
@property
def yaxistitle(self):
return self._yaxistitle
@property
def xaxis(self):
return self._xaxis
@property
def yaxis(self):
return self._yaxis
@property
def xmaingrid(self):
return self._xmaingrid
@property
def ymaingrid(self):
return self._ymaingrid
@property
def xhelpgrid(self):
return self._xhelpgrid
@property
def yhelpgrid(self):
return self._yhelpgrid
@property
def area(self):
return self._area
@property
def wall(self):
return self._wall
@property
def dim3d(self):
return self._dim3d
@dim3d.setter
def dim3d(self, value):
self._dim3d = value
@property
def series(self):
return self._series
@series.setter
def series(self, value):
self._series = value
@property
def data_series(self):
return self._series
@data_series.setter
def data_series(self, value):
self._data_series = value
@property
def labels(self):
return self._labels
@labels.setter
def labels(self, value):
self._labels = value
def _add_series_writer(self, chart):
dp = self.doc.create_instance('com.sun.star.chart2.data.DataProvider')
chart.attachDataProvider(dp)
chart_type = chart.getFirstDiagram().getCoordinateSystems()[0].getChartTypes()[0]
self._data_series = self.table[self.data].get_series()
series = [self._create_serie(dp, s) for s in self._data_series[1:]]
chart_type.setDataSeries(tuple(series))
chart_data = chart.getData()
chart_data.ComplexRowDescriptions = self._data_series[0].data
return
def _get_series(self):
rango = self._data_series
class Serie():
pass
series = []
for i in range(0, rango.columns, 2):
serie = Serie()
serie.label = rango[0, i+1].name
serie.xvalues = rango.get_column(i).name
serie.values = rango.get_column(i+1).name
series.append(serie)
return series
def _add_series_calc(self, chart):
dp = self.doc.create_instance('com.sun.star.chart2.data.DataProvider')
chart.attachDataProvider(dp)
chart_type = chart.getFirstDiagram().getCoordinateSystems()[0].getChartTypes()[0]
series = self._get_series()
series = [self._create_serie(dp, s) for s in series]
chart_type.setDataSeries(tuple(series))
return
def _create_serie(self, dp, data):
serie = create_instance('com.sun.star.chart2.DataSeries')
rango = data.values
is_x = hasattr(data, 'xvalues')
if is_x:
xrango = data.xvalues
rango_label = data.label
lds = create_instance('com.sun.star.chart2.data.LabeledDataSequence')
values = self._create_data(dp, rango, 'values-y')
lds.setValues(values)
if data.label:
label = self._create_data(dp, rango_label, '')
lds.setLabel(label)
xlds = ()
if is_x:
xlds = create_instance('com.sun.star.chart2.data.LabeledDataSequence')
values = self._create_data(dp, xrango, 'values-x')
xlds.setValues(values)
if is_x:
serie.setData((lds, xlds))
else:
serie.setData((lds,))
return serie
def _create_data(self, dp, rango, role):
data = dp.createDataSequenceByRangeRepresentation(rango)
if not data is None:
data.Role = role
return data
def _from_calc(self):
ps = self.cell.ps
ps.Width = self.width
ps.Height = self.height
charts = self.cell.charts
data = ()
if self.data:
data = (self.data.address,)
charts.addNewByName(self.name, ps, data, True, True)
self.obj = charts.getByName(self.name)
chart = self.obj.getEmbeddedObject()
chart.setDiagram(chart.createInstance(self.BASE.format(self.type)))
if not self.data:
self._add_series_calc(chart)
return chart
def _from_writer(self):
obj = self.doc.create_instance('com.sun.star.text.TextEmbeddedObject')
obj.setPropertyValue('CLSID', '12DCAE26-281F-416F-a234-c3086127382e')
obj.Name = self.name
obj.setSize(Size(self.width, self.height))
self.doc.insert_content(self.cursor, obj)
self.obj = obj
chart = obj.getEmbeddedObject()
tipo = self.type
if self.type == 'Column':
tipo = 'Bar'
chart.Diagram.Vertical = True
chart.setDiagram(chart.createInstance(self.BASE.format(tipo)))
chart.DataSourceLabelsInFirstColumn = True
if isinstance(self.data, str):
self._add_series_writer(chart)
else:
chart_data = chart.getData()
labels = [r[0] for r in self.data]
data = [(r[1],) for r in self.data]
chart_data.setData(data)
chart_data.RowDescriptions = labels
if tipo == 'Pie':
chart.setDiagram(chart.createInstance(self.BASE.format('Bar')))
chart.setDiagram(chart.createInstance(self.BASE.format('Pie')))
return chart
def insert(self):
if not self.cell is None:
chart = self._from_calc()
elif not self.cursor is None:
chart = self._from_writer()
diagram = chart.Diagram
if self.type == 'Bar':
diagram.Vertical = True
if hasattr(self.title, 'String'):
chart.HasMainTitle = True
self.title(chart.Title)
if hasattr(self.subtitle, 'String'):
chart.HasSubTitle = True
self.subtitle(chart.SubTitle)
if self.legend.__dict__:
chart.HasLegend = True
self.legend(chart.Legend)
if self.xaxistitle.__dict__:
diagram.HasXAxisTitle = True
self.xaxistitle(diagram.XAxisTitle)
if self.yaxistitle.__dict__:
diagram.HasYAxisTitle = True
self.yaxistitle(diagram.YAxisTitle)
if self.dim3d:
diagram.Dim3D = True
if self.series:
data_series = chart.getFirstDiagram(
).getCoordinateSystems(
)[0].getChartTypes()[0].DataSeries
for i, serie in enumerate(data_series):
for k, v in self.series[i].items():
if hasattr(serie, k):
setattr(serie, k, v)
return self
class LODialog(object):
def __init__(self, **properties):
@ -2388,21 +3018,6 @@ def get_cell(*args):
return cell
# ~ def get_range(self, obj, address):
# ~ if not obj.ImplementationName == 'ScTableSheetObj':
# ~ obj = obj.getCurrentController().getActiveSheet()
# ~ if isinstance(address, tuple):
# ~ if len(address) == 1:
# ~ address = (slice(0, None), address[0])
# ~ elif len(address) == 4:
# ~ address = (
# ~ slice(address[0], address[1]),
# ~ slice(address[2], address[3])
# ~ )
# ~ return obj[address]
def active_cell():
return get_cell()
@ -2411,6 +3026,10 @@ def create_dialog(properties):
return LODialog(**properties)
def create_window(kwargs):
return LOWindow(**kwargs)
# ~ Export ok
def get_config_path(name='Work'):
"""
@ -2876,7 +3495,6 @@ def get_size_screen():
return res.strip()
# ~ Export ok
def get_clipboard():
df = None
text = ''
@ -3582,8 +4200,19 @@ def server_smtp_test(config):
return server.error
def create_window(kwargs):
return LOWindow(**kwargs)
def import_csv(path, **kwargs):
"""
See https://docs.python.org/3.5/library/csv.html#csv.reader
"""
with open(path) as f:
rows = tuple(csv.reader(f, **kwargs))
return rows
def export_csv(path, data, **kwargs):
with open(path, 'w') as f:
writer = csv.writer(f, **kwargs)
writer.writerows(data)
return
class LIBOServer(object):