| import pandas |
| from collections import defaultdict |
| import copy |
| import json |
| from pandas.io.json import dumps |
| |
| |
| class BaseHighchart(object): |
| stockchart = False |
| tooltip_formatter = "" |
| target_div = 'chart' |
| @property |
| def javascript_cmd(self): |
| js = dumps(self.chart) |
| js = ( |
| js.replace('"{{TOOLTIP_FORMATTER}}"', self.tooltip_formatter) |
| .replace("\n", " ") |
| ) |
| if self.stockchart: |
| return "new Highcharts.StockChart(%s);" % js |
| return "new Highcharts.Chart(%s);" %js |
| |
| |
| class Highchart(BaseHighchart): |
| def __init__( |
| self, df, |
| chart_type="spline", |
| target_div="#chart", |
| polar=False, |
| width=None, |
| height=None, |
| show_legend=True, |
| stockchart=False, |
| title=None, |
| tooltip=None, |
| sort_columns=False, |
| secondary_y=None, |
| mark_right=False, |
| compare=False, |
| stacked=False, |
| logx=False, |
| logy=False, |
| xlim=None, |
| ylim=None, |
| sort_legend_y=False, |
| grid=False, |
| zoom=None): |
| self.df = df |
| self.chart_type = chart_type |
| self.chart = chart = {} |
| self.stockchart = stockchart |
| self.sort_columns = sort_columns |
| self.secondary_y = secondary_y or [] |
| self.mark_right = mark_right |
| self.compare = compare |
| self.logx = logx |
| self.logy = logy |
| self.xlim = xlim |
| self.ylim = ylim |
| self.zoom = zoom |
| self.polar = polar |
| self.grid = grid |
| self.stacked = stacked |
| self.sort_legend_y = sort_legend_y |
| |
| chart['chart'] = {} |
| chart['chart']["type"] = chart_type |
| chart['chart']['renderTo'] = target_div |
| |
| if width: |
| chart['chart']["width"] = width |
| if height: |
| chart['chart']["height"] = height |
| |
| chart['chart']['polar'] = polar |
| |
| chart["legend"] = { |
| "enabled": show_legend |
| } |
| chart["title"] = {"text": title} |
| |
| if tooltip: |
| chart['tooltip'] = tooltip |
| if sort_legend_y: |
| if 'tooltip' not in chart: |
| chart['tooltip'] = { |
| 'formatter': "{{TOOLTIP_FORMATTER}}" |
| } |
| if self.zoom: |
| chart["zoomType"] = self.zoom |
| |
| self.serialize_series() |
| self.serialize_xaxis() |
| self.serialize_yaxis() |
| |
| self.chart = chart |
| |
| @property |
| def tooltip_formatter(self): |
| if self.compare == 'percent': |
| tf = """ |
| function() { |
| var s = '<b>' + Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', new Date(this.x))+'</b><br/>'; |
| var sortedPoints = this.points.sort(function(a, b){ |
| return ((a.point.change > b.point.change) ? -1 : ((a.point.change < b.point.change) ? 1 : 0)); |
| }); |
| $.each(sortedPoints , function(i, point) { |
| s += '<span style="color:'+ point.series.color +'">\u25CF</span> ' + point.series.name + ': ' + f(point.y) + ' (<b>' + Highcharts.numberFormat(point.point.change, 2) + '%</b>)' + '<br/>'; |
| }); |
| |
| return s; |
| } |
| """ |
| else: |
| tf = """ |
| function() { |
| var s = '<b>' + Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', new Date(this.x))+'</b><br/>'; |
| var sortedPoints = this.points.sort(function(a, b){ |
| return ((a.y > b.y) ? -1 : ((a.y < b.y) ? 1 : 0)); |
| }); |
| $.each(sortedPoints , function(i, point) { |
| s += '<span style="color:'+ point.series.color +'">\u25CF</span> ' + point.series.name + ': ' + f(point.y) + '<br/>'; |
| }); |
| |
| return s; |
| } |
| """ |
| return tf |
| |
| |
| def serialize_series(self): |
| df = self.df |
| chart = self.chart |
| if self.sort_columns: |
| df = df.sort_index() |
| series = df.to_dict('series') |
| chart["series"] = [] |
| for name, data in series.items(): |
| if df[name].dtype.kind not in "biufc": |
| continue |
| sec = name in self.secondary_y |
| d = { |
| "name": name if not sec or self.mark_right else name + " (right)", |
| "yAxis": int(sec), |
| "data": zip(df.index, data.tolist()) |
| } |
| if self.polar: |
| d['data'] = [v for k, v in d['data']] |
| if self.compare: |
| d['compare'] = self.compare # either `value` or `percent` |
| if self.chart_type in ("area", "column", "bar") and self.stacked: |
| d["stacking"] = 'normal' |
| #if kwargs.get("style"): |
| # d["dashStyle"] = pd2hc_linestyle(kwargs["style"].get(name, "-")) |
| chart["series"].append(d) |
| |
| def serialize_xaxis(self): |
| df = self.df |
| x_axis = { |
| 'ordinal': False, |
| } |
| if df.index.name: |
| x_axis["title"] = {"text": df.index.name} |
| if df.index.dtype.kind in "M": |
| x_axis["type"] = "datetime" |
| if df.index.dtype.kind == 'O': |
| x_axis['categories'] = sorted( |
| list(df.index)) if self.sort_columns else list(df.index) |
| if self.grid: |
| x_axis["gridLineWidth"] = 1 |
| x_axis["gridLineDashStyle"] = "Dot" |
| if self.logx: |
| x_axis["type"] = 'logarithmic' |
| if self.xlim: |
| x_axis["min"] = self.xlim[0] |
| x_axis["max"] = self.xlim[1] |
| self.chart['xAxis'] = x_axis |
| |
| def serialize_yaxis(self): |
| yAxis = {} |
| chart = self.chart |
| if self.grid: |
| yAxis["gridLineWidth"] = 1 |
| yAxis["gridLineDashStyle"] = "Dot" |
| if self.logy: |
| yAxis["type"] = 'logarithmic' |
| if self.ylim: |
| yAxis["min"] = self.ylim[0] |
| yAxis["max"] = self.ylim[1] |
| chart["yAxis"] = [yAxis] |
| if self.secondary_y: |
| yAxis2 = copy.deepcopy(yAxis) |
| yAxis2["opposite"] = True |
| chart["yAxis"].append(yAxis2) |
| |
| |
| class HighchartBubble(BaseHighchart): |
| def __init__(self, df, target_div='chart', height=800): |
| self.df = df |
| self.chart = { |
| 'chart': { |
| 'type': 'bubble', |
| 'zoomType': 'xy' |
| }, |
| 'title': {'text': None}, |
| 'plotOptions': { |
| 'bubble': { |
| 'tooltip': { |
| 'headerFormat': '<b>{series.name}</b><br>', |
| 'pointFormat': '<b>{point.name}</b>: {point.x}, {point.y}, {point.z}' |
| } |
| } |
| }, |
| } |
| chart = self.chart |
| chart['series'] = self.series() |
| chart['chart']['renderTo'] = target_div |
| if height: |
| chart['chart']["height"] = height |
| |
| def series(self): |
| #df = self.df[['name', 'x', 'y', 'z']] |
| df = self.df |
| series = defaultdict(list) |
| for row in df.to_dict(orient='records'): |
| series[row['group']].append(row) |
| l = [] |
| for k, v in series.items(): |
| l.append({'data': v, 'name': k}) |
| return l |