diff --git a/rowers/forms.py b/rowers/forms.py index 412087b1..4e199518 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -327,6 +327,11 @@ formaxlabelsmultiflex.pop('distance') formaxlabelsmultiflex['workoutid'] = 'Workout' parchoicesmultiflex = list(sorted(formaxlabelsmultiflex.items(), key = lambda x:x[1])) +from utils import palettes +#palettechoices = { key:key for key, value in palettes.iteritems() } + +palettechoices = tuple((p,p) for p in palettes.keys()) + class MultiFlexChoiceForm(forms.Form): xparam = forms.ChoiceField(choices=parchoicesmultiflex, @@ -352,7 +357,10 @@ class MultiFlexChoiceForm(forms.Form): includereststrokes = forms.BooleanField(initial=False, required=False, label='Include Rest Strokes') - + palette = forms.ChoiceField(choices=palettechoices, + label = 'Color Scheme', + initial='monochrome_blue') + class ChartParamChoiceForm(forms.Form): plotchoices = ( ('line','Line Plot'), diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 47d446cd..b6f19b46 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1,3 +1,4 @@ +import colorsys from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage from rowingdata import rower as rrower from rowingdata import main as rmain @@ -10,7 +11,7 @@ from django.utils import timezone from bokeh.palettes import Dark2_8 as palette import itertools from bokeh.plotting import figure, ColumnDataSource, Figure,curdoc -from bokeh.models import CustomJS,Slider, TextInput +from bokeh.models import CustomJS,Slider, TextInput,BoxAnnotation from bokeh.charts import Histogram,HeatMap,Area,BoxPlot from bokeh.resources import CDN,INLINE from bokeh.embed import components @@ -70,11 +71,9 @@ watermarkh = 35 watermarkanchor = 'bottom_right' def errorbar(fig, x, y, source=ColumnDataSource(), - xerr=False, yerr=False, color='red', + xerr=False, yerr=False, color='black', point_kwargs={}, error_kwargs={}): - fig.circle(x, y, source=source, name='data',color=color, - **point_kwargs) xvalues = source.data[x] yvalues = source.data[y] @@ -96,7 +95,12 @@ def errorbar(fig, x, y, source=ColumnDataSource(), for px, py, err, color in zip(xvalues, yvalues, xerrvalues, colorvalues): x_err_x.append((px - err, px + err)) x_err_y.append((py, py)) - err_color.append(color) + (r, g, b) = tuple(int(color[i:i+2],16) for i in (1, 3, 5)) + h,s,v = colorsys.rgb_to_hsv(r/255., g/255., b/255.) + v = v*0.8 + r, g, b = colorsys.hsv_to_rgb(h, s, v) + color2 = "#%02x%02x%02x" % (int(255.*r), int(255.*g), int(255*b)) + err_color.append(color2) fig.multi_line(x_err_x, x_err_y, color=err_color, name='xerr', @@ -113,13 +117,22 @@ def errorbar(fig, x, y, source=ColumnDataSource(), for px, py, err, color in zip(xvalues, yvalues, yerrvalues, colorvalues): y_err_x.append((px, px)) y_err_y.append((py - err, py + err)) - err_color.append(color) + (r, g, b) = tuple(int(color[i:i+2],16) for i in (1, 3, 5)) + h,s,v = colorsys.rgb_to_hsv(r/255., g/255., b/255.) + v = v*0.8 + r, g, b = colorsys.hsv_to_rgb(h, s, v) + color2 = "#%02x%02x%02x" % (int(255.*r), int(255.*g), int(255*b)) + err_color.append(color2) fig.multi_line(y_err_x, y_err_y, color=err_color, name='yerr',**error_kwargs) except TypeError: pass - + + fig.circle(x, y, source=source, name='data',color=color, + **point_kwargs) + + def tailwind(bearing,vwind,winddir): """ Calculates head-on head/tailwind in direction of rowing @@ -1176,7 +1189,7 @@ def interactive_chart(id=0,promember=0): def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='', ploterrorbars=False, - title=None,binsize=1): + title=None,binsize=1,colorlegend=[]): if datadf.empty: return ['','

No non-zero data in selection

'] @@ -1246,18 +1259,17 @@ def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='', datadf, ) - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,resize' if groupby != 'date': hover = HoverTool(names=['data'], tooltips = [ - (groupby,'@groupval{1.1}') + (groupby,'@groupval{1.1}'), ]) else: hover = HoverTool(names=['data'], tooltips = [ - (groupby,'@groupval') + (groupby,'@groupval'), ]) hover.mode = 'mouse' @@ -1268,7 +1280,7 @@ def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='', plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, tools=TOOLS, toolbar_location="above", - toolbar_sticky=False) + toolbar_sticky=False) #,plot_width=500,plot_height=500) # add watermark plot.extra_y_ranges = {"watermark": watermarkrange} @@ -1287,17 +1299,48 @@ def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='', x_range_name = "watermark", y_range_name = "watermark", ) - + errorbar(plot,xparam,yparam,source=source, xerr=ploterrorbars, yerr=ploterrorbars, point_kwargs={ - 'line_color':None, + 'line_color':"#969696", 'size':"groupsize", 'fill_color':"color", + 'fill_alpha':1.0, }, ) + + + for nr, gvalue, color in colorlegend: + box = BoxAnnotation(bottom=500-20*nr,left=550,top=520-20*nr, + right=570, + bottom_units='screen', + top_units='screen', + left_units='screen', + right_units='screen', + fill_color=color, + fill_alpha=1.0, + line_color=color) + legendlabel = Label(x=571,y=503-20*nr,x_units='screen', + y_units='screen', + text = "{gvalue:3.0f}".format(gvalue=gvalue), + background_fill_alpha=1.0, + text_color='black', + text_font_size=value("0.7em")) + plot.add_layout(box) + plot.add_layout(legendlabel) + + if colorlegend: + legendlabel = Label(x=372,y=300,x_units='screen', + y_units='screen', + text = 'group legend', + text_color='black', + text_font_size=value("0.7em"), + angle=90, + angle_units='deg') + if xparam == 'workoutid': plot.xaxis.axis_label = 'Workout' else: diff --git a/rowers/utils.py b/rowers/utils.py index 526dd327..c6052de6 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -1,9 +1,59 @@ import math import numpy as np import pandas as pd +import colorsys lbstoN = 4.44822 +def trcolors(r1,g1,b1,r2,g2,b2): + r1 = r1/255. + r2 = r2/255. + g1 = g1/255. + g2 = g2/255. + b2 = b2/255. + b1 = b1/255. + h1,s1,v1 = colorsys.rgb_to_hsv(r1,g1,b1) + h2,s2,v2 = colorsys.rgb_to_hsv(r2,g2,b2) + + + return 360*h1,360*(h2-h1),s1,(s2-s1),v1,(v2-v1) + +palettes = { + 'monochrome_blue':(207,-4,0.06,0.89,1.0,-0.38), + 'gold_sunset':(47,-31,.26,-0.12,0.94,-0.5), + 'blue_red':(207,-200,.85,0,.74,-.24), + 'blue_green':(207,-120,.85,0,.75,.25), + 'cyan_green':(192,-50,.08,.65,.98,-.34), + 'cyan_purple':trcolors(237,248,251,136,65,157), + 'green_blue':trcolors(240,249,232,8,104,172), + 'orange_red':trcolors(254,240,217,179,0,0), + 'cyan_blue':trcolors(241,238,246,4,90,141), + 'cyan_green':trcolors(246,239,247,1,108,89), + 'cyan_magenta':trcolors(241,238,246,152,0,67), + 'beige_magenta':trcolors(254,235,226,122,1,119), + 'yellow_green':trcolors(255,255,204,0,104,55), + 'yellow_blue':trcolors(255,255,205,37,52,148), + 'autumn':trcolors(255,255,212,153,52,4), + 'yellow_red':trcolors(255,255,178,189,0,39) +} + + +def range_to_color_hex(groupcols,palette='monochrome_blue'): + + try: + plt = palettes[palette] + except KeyErro: + plt = palettes['monochrome_blue'] + + rgb = [colorsys.hsv_to_rgb((plt[0]+plt[1]*x)/360., + plt[2]+plt[3]*x, + plt[4]+plt[5]*x) for x in groupcols] + + RGB = [(int(255.*r),int(255.*g),int(255.*b)) for (r, g, b) in rgb] + colors = ["#%02x%02x%02x" % (r, g, b) for (r, g, b) in RGB] + + return colors + def str2bool(v): return v.lower() in ("yes", "true", "t", "1") diff --git a/rowers/views.py b/rowers/views.py index c0e1d69f..2e600225 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -272,7 +272,7 @@ def splitstdata(lijst): from utils import ( geo_distance,serialize_list,deserialize_list,uniqify, - str2bool + str2bool,range_to_color_hex ) import datautils @@ -3435,6 +3435,11 @@ def multiflex_view(request,userid=0, ploterrorbars = options['ploterrorbars'] except KeyError: ploterrorbars = False + + try: + palette = options['palette'] + except KeyError: + palette = 'blue_green' workstrokesonly = not includereststrokes @@ -3454,7 +3459,9 @@ def multiflex_view(request,userid=0, request.session['ploterrorbars'] = ploterrorbars request.session['includereststrokes'] = includereststrokes workstrokesonly = not includereststrokes - + palette = chartform.cleaned_data['palette'] + request.session['palette'] = palette + groupby = chartform.cleaned_data['groupby'] binsize = chartform.cleaned_data['binsize'] if binsize <= 0: @@ -3482,6 +3489,8 @@ def multiflex_view(request,userid=0, request.session['ploterrorbars'] = ploterrorbars request.session['includereststrokes'] = includereststrokes workstrokesonly = not includereststrokes + palette = chartform.cleaned_data['palette'] + request.session['palette'] = palette groupby = chartform.cleaned_data['groupby'] binsize = chartform.cleaned_data['binsize'] @@ -3570,9 +3579,12 @@ def multiflex_view(request,userid=0, yerror = groups.std()[yparam] groupsize = groups.count()[xparam] - #groupsize = 15.*np.log10(1+99.*groupsize/float(max(groupsize))) - groupsize = 30.*np.sqrt(groupsize/float(max(groupsize))) - + if len(groupsize) == 0: + messages.error(request,'No data in selection') + url = reverse(user_multiflex_select) + return HttpResponseRedirect(url) + else: + groupsize = 30.*np.sqrt(groupsize/float(groupsize.max())) df = pd.DataFrame({ xparam:xvalues, @@ -3604,12 +3616,19 @@ def multiflex_view(request,userid=0, if aantal == 1: groupcols = np.array([1.]) - groupcols *= 100. - rgb = [colorsys.hsv_to_rgb(float(x/100.), 1.0, 1.0) for x in groupcols] - RGB = [(int(255.*r),int(255.*g),int(255.*b)) for (r, g, b) in rgb] - colors = ["#%02x%02x%02x" % (r, g, b) for (r, g, b) in RGB] + + colors = range_to_color_hex(groupcols,palette=palette) + df['color'] = colors + clegendx = np.arange(0,1.2,.2) + legcolors = range_to_color_hex(clegendx,palette=palette) + clegendy = df['groupval'].min()+clegendx*(df['groupval'].max()-df['groupval'].min()) + + + colorlegend = zip(range(6),clegendy,legcolors) + + if userid == 0: extratitle = '' else: @@ -3622,7 +3641,8 @@ def multiflex_view(request,userid=0, groupby, extratitle=extratitle, ploterrorbars=ploterrorbars, - binsize=binsize) + binsize=binsize, + colorlegend=colorlegend)