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/>. # ~ along with ZAZ. If not, see <https://www.gnu.org/licenses/>.
import base64 import base64
import csv
import ctypes import ctypes
import datetime import datetime
import errno 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.datatransfer import XTransferable, DataFlavor
from com.sun.star.table.CellContentType import EMPTY, VALUE, TEXT, FORMULA 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.text.TextContentAnchorType import AS_CHARACTER
from com.sun.star.lang import XEventListener from com.sun.star.lang import XEventListener
@ -183,6 +185,7 @@ log = logging.getLogger(__name__)
_start = 0 _start = 0
_stop_thread = {} _stop_thread = {}
TIMEOUT = 10 TIMEOUT = 10
SECONDS_DAY = 60 * 60 * 24
CTX = uno.getComponentContext() CTX = uno.getComponentContext()
@ -197,7 +200,7 @@ def create_instance(name, with_context=False):
return instance return instance
def _get_app_config(node_name, key=''): def get_app_config(node_name, key=''):
name = 'com.sun.star.configuration.ConfigurationProvider' name = 'com.sun.star.configuration.ConfigurationProvider'
service = 'com.sun.star.configuration.ConfigurationAccess' service = 'com.sun.star.configuration.ConfigurationAccess'
cp = create_instance(name, True) 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/' # ~ 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] LANG = LANGUAGE.split('-')[0]
NAME = TITLE = _get_app_config('org.openoffice.Setup/Product', 'ooName') NAME = TITLE = get_app_config('org.openoffice.Setup/Product', 'ooName')
VERSION = _get_app_config('org.openoffice.Setup/Product','ooSetupVersion') 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): def mri(obj):
@ -303,6 +312,10 @@ def today():
return datetime.date.today() return datetime.date.today()
def time():
return datetime.datetime.now().time()
def get_date(year, month, day, hour=-1, minute=-1, second=-1): def get_date(year, month, day, hour=-1, minute=-1, second=-1):
if hour > -1 or minute > -1 or second > -1: if hour > -1 or minute > -1 or second > -1:
h = hour h = hour
@ -471,6 +484,27 @@ def array_to_dict(values):
# ~ Custom classes # ~ 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): class LOObjectBase(object):
@ -710,6 +744,20 @@ class LOCalc(LODocument):
def __contains__(self, item): def __contains__(self, item):
return item in self.obj.Sheets 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 @property
def active(self): def active(self):
return LOCalcSheet(self._cc.getActiveSheet(), self) return LOCalcSheet(self._cc.getActiveSheet(), self)
@ -847,6 +895,23 @@ class LOCalc(LODocument):
self.cell_style[name] = obj self.cell_style[name] = obj
return LOCellStyle(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): class LOCalcSheets(object):
@ -932,6 +997,17 @@ class LOCalcSheet(object):
def code_name(self, value): def code_name(self, value):
self._obj.CodeName = 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): def activate(self):
self.doc.activate(self.obj) self.doc.activate(self.obj)
return return
@ -943,6 +1019,10 @@ class LOCalcSheet(object):
def visible(self, value): def visible(self, value):
self.obj.IsVisible = value self.obj.IsVisible = value
@property
def is_protected(self):
return self._obj.isProtected()
@property @property
def password(self): def password(self):
return '' return ''
@ -961,6 +1041,9 @@ class LOCalcSheet(object):
def get_cursor(self, cell): def get_cursor(self, cell):
return self.obj.createCursorByRange(cell) return self.obj.createCursorByRange(cell)
def exists_chart(self, name):
return name in self.obj.Charts.ElementNames
@property @property
def events(self): def events(self):
return self._events return self._events
@ -1005,17 +1088,40 @@ class LOWriter(LODocument):
@property @property
def selection(self): def selection(self):
# ~ sel = self._cc.getSelection()
sel = self.obj.getCurrentSelection() sel = self.obj.getCurrentSelection()
return LOTextRange(sel[0]) 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): def insert_content(self, cursor, data, replace=False):
self.text.insertTextContent(cursor, data, replace) self.text.insertTextContent(cursor, data, replace)
return return
# ~ tt = doc.createInstance('com.sun.star.text.TextTable')
# ~ tt.initialize(5, 2)
# ~ f = doc.createInstance('com.sun.star.text.TextFrame') # ~ f = doc.createInstance('com.sun.star.text.TextFrame')
# ~ f.setSize(Size(10000, 500)) # ~ f.setSize(Size(10000, 500))
@ -1284,14 +1390,22 @@ class LOCellRange(object):
self.obj.setString(data) self.obj.setString(data)
elif isinstance(data, (int, float)): elif isinstance(data, (int, float)):
self.obj.setValue(data) 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 @property
def data(self): def data(self):
return self.obj.getDataArray() return self.obj.getDataArray()
@data.setter @data.setter
def data(self, values): def data(self, values):
# ~ if not isinstance(data[0], tuple):
# ~ data = tuple(zip(data))
if isinstance(values, list): if isinstance(values, list):
values = tuple(values) values = tuple(values)
self.obj.setDataArray(values) self.obj.setDataArray(values)
@ -1305,6 +1419,15 @@ class LOCellRange(object):
values = tuple(values) values = tuple(values)
self.obj.setFormulaArray(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 @property
def columns(self): def columns(self):
return self._obj.Columns.Count return self._obj.Columns.Count
@ -1359,6 +1482,21 @@ class LOCellRange(object):
def sheet(self): def sheet(self):
return LOCalcSheet(self.obj.Spreadsheet, self.doc) 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 @property
def draw_page(self): def draw_page(self):
return self.sheet.obj.getDrawPage() return self.sheet.obj.getDrawPage()
@ -1397,6 +1535,13 @@ class LOCellRange(object):
for r in cursor.queryEmptyCells()] for r in cursor.queryEmptyCells()]
return tuple(rangos) 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 @property
def cell_style(self): def cell_style(self):
return self.obj.CellStyle return self.obj.CellStyle
@ -1440,9 +1585,57 @@ class LOCellRange(object):
return return
def clear(self, what=31): 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) self.obj.clearContents(what)
return 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): class EventsListenerBase(unohelper.Base, XEventListener):
@ -2010,6 +2203,443 @@ def add_listeners(events, control, name=''):
return 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): class LODialog(object):
def __init__(self, **properties): def __init__(self, **properties):
@ -2388,21 +3018,6 @@ def get_cell(*args):
return cell 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(): def active_cell():
return get_cell() return get_cell()
@ -2411,6 +3026,10 @@ def create_dialog(properties):
return LODialog(**properties) return LODialog(**properties)
def create_window(kwargs):
return LOWindow(**kwargs)
# ~ Export ok # ~ Export ok
def get_config_path(name='Work'): def get_config_path(name='Work'):
""" """
@ -2876,7 +3495,6 @@ def get_size_screen():
return res.strip() return res.strip()
# ~ Export ok
def get_clipboard(): def get_clipboard():
df = None df = None
text = '' text = ''
@ -3582,8 +4200,19 @@ def server_smtp_test(config):
return server.error return server.error
def create_window(kwargs): def import_csv(path, **kwargs):
return LOWindow(**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): class LIBOServer(object):