diff --git a/cms/apexchart.py b/cms/apexchart.py
new file mode 100644
index 0000000..97f93a7
--- /dev/null
+++ b/cms/apexchart.py
@@ -0,0 +1,185 @@
+import numpy as np
+import pandas as pd
+
+class chart :
+ @staticmethod
+ def get(_data,_config) :
+ # r = {}
+ # for _key in _config :
+ # r[_key] = {'about':_config[_key]['about'],'chart':[]}
+ # _pointers = _config[_key]['apply']
+ # _pointers = [_pointers] if type(_pointers) == str else _pointers
+ # r[_key]['chart'] += [getattr(chart,_name)(_data,_config[_key]) for _name in _pointers if hasattr(chart,_name)]
+ # return [r]
+ r = {}
+ for _key in _config :
+ _options = _config[_key]['options']
+ r[_key] = {'about':_config[_key]['about'],'css':_config[_key]['css'],'charts':[]}
+ _charts = []
+ for _itemOption in _options :
+ _type = _itemOption['type']
+ if hasattr(chart,_type) :
+ _pointer = getattr(chart,_type)
+
+ _chartOption = _pointer(_data,_itemOption)
+ _tag = 'options' if _type != 'scalar' else 'html'
+ if 'title' in _itemOption and _itemOption['type'] != 'scalar' :
+ _chartOption['title'] = {'text':_itemOption['title'],'align':'center'}
+ _chartOption['legend'] = {'position':'bottom','itemMargin':{'horizontal':4,'vertical':10}}
+
+ _chartOption['chart']['height'] = 300
+ # _chartOption['chart']['height'] = '100%'
+ # _chartOption['responsive'] = [{'breakpoint':480,'options':{'chart':{'width':300}}}]
+ _charts.append ({"type":_type,_tag:_chartOption})
+ if _charts :
+ r[_key]['charts'] = _charts
+
+ # for _pointer in _pointers :
+ # r[_key]['chart'].append(_pointer(_data,_config[_key]))
+
+ # _pointers = [getattr(chart,_name) for _name in _config[_key]['options']['apply'] if hasattr(chart,_name)]
+ # r[_key] = {'about':_config[_key]['about'],'chart':[]}
+ # for _pointer in _pointers :
+ # r[_key]['chart'].append(_pointer(_data,_config[_key]))
+
+
+ return r
+ @staticmethod
+ def format_digit (value):
+ if value > 1000000 :
+ return np.divide(value,1000000).round(2).astype(str) + ' M'
+ elif value > 1000 :
+ return np.divide(value,1000).round(2).astype(str)+ ' K'
+ return value
+ @staticmethod
+ def scalar(_data,_config) :
+ """
+ Only focusing on axis.y
+ """
+ _columns = _config['axis']['y']
+ if _data.shape[0] > 1 :
+ _apply = 'sum' if 'apply' not in _config else _config['apply']
+ # values = _data[_columns].sum().values.tolist()
+ values = getattr(_data[_columns],_apply)().values.round(3).tolist()
+ else:
+ values = _data[_columns].values.tolist()[0]
+
+ _html = [f'
{chart.format_digit(values[_index])}
{_columns[_index].replace("_"," ") }
' for _index in np.arange(len(values))]
+ return ' '.join(_html)
+ @staticmethod
+ def donut(_data,_config):
+ options = {"chart":{"type":"donut"}}
+ _yaxis = _config['axis']['y']
+ _apply = 'sum' if 'apply' not in _config else _config['apply']
+ # options['series'] = _data[_yaxis].sum().values.tolist()
+ options['series'] = getattr(_data[_yaxis],_apply)().values.round(3).tolist()
+ options['labels'] = [_name.replace('_',' ').upper() for _name in _yaxis]
+ options["dataLabels"]= {
+ "enabled": False
+ }
+ return options
+ @staticmethod
+ def column(_data,_config):
+ if 'apply' in _config :
+ _fn = _config['apply']
+ _yaxis = _config['axis']['y']
+ _values = getattr(_data[_yaxis],_fn)().values #.sum()
+ _data = (pd.DataFrame([dict(zip(_yaxis,_values))]))
+
+
+
+ options = chart.barStacked(_data,_config)
+ options['chart'] = {'type':'bar'}
+ if 'title' in _config :
+ options['title'] = {'text':_config['title'],'align':'center','style':{'text-transform':'upperCase','fontSize':'18px'}}
+ pass
+
+ options['stroke'] = {'show':True,'width':2,'colors':['transparent']}
+ if _data.shape[0] == 1:
+ options['xaxis']['categories'] = [_name.replace('_',' ').upper() for _name in _config['axis']['x']]
+ # options['plotOptions'] = {'bar':{'columnWidth':'55%'}}
+ return options
+ @staticmethod
+ def barStacked(_data,_config):
+ options = {"series":[], "chart": {
+ "type": 'bar','stacked':True}
+ }
+ # options['plotOptions'] = {'bar':{'horizontal':True}}
+ # options['legend'] = {'position':'bottom'} # {'position':'right','horizontalAlign':'left','offsetX':40}
+
+ _xaxis = _data[_config['axis']['x']].values.tolist()
+ options["xaxis"]={"categories":_xaxis}
+ for _col in _config['axis']['y'] :
+ options['series'] += [{'name':_col.replace('_',' ').upper(), 'data':_data[_col].tolist()}]
+ return options
+ @staticmethod
+ def radialBar (_data,_config) :
+
+ _options = {
+ "series": _config["axis"]["y"],
+ "labels": _config["axis"]["x"],
+ "chart": {
+ "type": 'radialBar',
+ "offsetY": -20,
+ "sparkline": {
+ "enabled": True
+ }
+ },
+ # "plotOptions": {
+ # "radialBar": {
+ # "startAngle": -90,
+ # "endAngle": 90,
+ # "track": {
+ # "background": "#e7e7e7",
+ # "strokeWidth": '97%',
+ # "margin": 5,
+ # "dropShadow": {
+ # "enabled": True,
+ # "top": 2,
+ # "left": 0,
+ # "color": '#999',
+ # "opacity": 1,
+ # "blur": 2
+ # }
+ # },
+ # "dataLabels": {
+
+ # "name": {
+ # "show": False
+ # },
+ # "value": {
+ # "offsetY": -2,
+ # "fontSize": '18px'
+ # }
+ # }
+ # }
+ # },
+ # "grid": {
+ # "padding": {
+ # "top":10
+ # }
+ # },
+
+
+ }
+ return _options
+ @staticmethod
+ def barGrouped (_data,_config):
+ """
+ """
+ options = {"series":[],"chart":{"type":"bar"},"plotOptions": {
+ "bar": {
+ "horizontal": True,
+ "dataLabels": {
+ "position": 'top',
+ }}},
+
+ }
+ _yaxis = _config["axis"]["y"]
+ for _name in _yaxis :
+ options["series"] += [{'name':_name.replace('_',' ').upper(),"data":_data[_name].tolist()}]
+ #
+ # _xaxis
+ _xaxis = _config['axis']['x']
+ options['xaxis'] = {'categories':_data[_xaxis].values.tolist()}
+ return options