diff --git a/kivyblocks/baseWidget.py b/kivyblocks/baseWidget.py index b5ff064..ebd54cf 100755 --- a/kivyblocks/baseWidget.py +++ b/kivyblocks/baseWidget.py @@ -54,6 +54,7 @@ from .widgetExt.jsoncodeinput import JsonCodeInput from .widgetExt.inputext import FloatInput,IntegerInput, \ StrInput,SelectInput, BoolInput, AmountInput from .widgetExt.messager import Messager +from .charts.bar import Bar if platform == 'android': from .widgetExt.phonebutton import PhoneButton diff --git a/kivyblocks/charts/bar.py b/kivyblocks/charts/bar.py new file mode 100644 index 0000000..01f1b33 --- /dev/null +++ b/kivyblocks/charts/bar.py @@ -0,0 +1,85 @@ +from kivy.uix.widget import Widget +from kivy.graphics import Color, Rectangle +from kivyblocks.colorcalc import * +from kivyblocks.charts.chart import Chart,ChartPart + +class BarPart(ChartPart): + def __init__(self,widget,pos,size,color,data_offset): + self.widget = widget + self.pos = pos + self.size = size + self.color = color + self.data_offset = data_offset + + def collide_point(self,x,y): + minx = self.widget.pos[0] + self.pos[0] + maxx = minx + self.size[0] + miny = self.widget.pos[1] + self.pos[1] + maxy = miny + self.size[1] + if minx <= x and x <= maxx and \ + miny <= y and y <= maxy: + return True + return False + + def draw(self): + with self.widget.canvas: + Color(*self.color) + Rectangle(pos=(self.widget.pos[0] + self.pos[0], + self.widget.pos[1] + self.pos[1]), + size = self.size) + + def mark(self): + rcolor = reverseColor(self.color) + rcolor.append(0.8) + with self.widget.canvas.after: + Color(*reverseColor(self.color)) + Rectangle(pos=(self.widget.pos[0] + self.pos[0], + self.widget.pos[1] + self.pos[1]), + size = self.size) + +class Bar(Chart): + """ + a BAR class + """ + def __init__(self,**options): + """ + options={ + width, + height, + title, + keyField, + valueField, + color1: + color2: + data=[ + { + name, + value, + },{ + } + ] + } + """ + self.data = None + super().__init__(**options) + + def data2part(self): + data = self.data + kvs = [ [i[self.options['keyField']],i[self.options['valueField']]] for i in data ] + m = max([i[1] for i in kvs ]) + cnt = len(kvs) + points = divide([0,0],[self.width,0],cnt) + color1='8833ee' + color2='ed8234' + colors = divideColor(color1,color2,cnt-1) + for i in range(cnt): + h = self.height * kvs[i][1] / m + c = colors[i] + part = BarPart(self, + points[i], + (points[i+1][0] - points[i][0],h), + colors[i], + i + ) + self.chartparts.append(part) + diff --git a/kivyblocks/charts/chart.py b/kivyblocks/charts/chart.py new file mode 100644 index 0000000..f7aef87 --- /dev/null +++ b/kivyblocks/charts/chart.py @@ -0,0 +1,79 @@ +from kivy.app import App +from kivy.graphics import Color, Rectangle +from kivy.uix.widget import Widget +from kivyblocks.colorcalc import toArrayColor + +class ChartPart(object): + def __init__(self,pos,width,height): + self.pos = pos + self.width = width + self.height = height + + def collide_point(self,x,y): + raise NotImplementedError + + def draw(self): + raise NotImplementedError + + def mark(self): + raise NotImplementedError + +class Chart(Widget): + def __init__(self,**options): + super().__init__() + self.options = options + self.bg_color = toArrayColor(self.options.get('bg_color',[0.3,0.3,0.3,1])) + self.markpart = None + self.bind(size=self.onSize,pos=self.onSize) + self.register_event_type("on_press") + + def unmark(self): + self.canvas.after.clear() + + def getData(self): + url = self.options.get('url') + if url: + hc = App.get_running_app().hc + params = self.options.get('params',{}) + d = hc.get(url,parms=param) + self.data = d.get('data',[]) + else: + self.data = self.options.get('data',[]) + self.chartparts = [] + + def on_touch_down(self,touch): + if touch.is_mouse_scrolling: + return False + if not self.collide_point(touch.x,touch.y): + return False + self.markpart = None + self.unmark() + for part in self.chartparts: + if part.collide_point(touch.x,touch.y): + self.markPart(part) + self.dispatch('on_press',self,self.data[part.data_offset]) + return super(Chart, self).on_touch_down(touch) + + def on_press(self,o,data): + print('data=',data) + + def draw(self): + with self.canvas.before: + Color(*self.bg_color) + Rectangle(pos=self.pos,size=self.size) + + self.canvas.clear() + for part in self.chartparts: + part.draw() + + def markPart(self,part): + self.markpart = part + part.mark() + + def onSize(self,o,v): + self.build() + + def build(self): + self.getData() + self.data2part() + self.draw() diff --git a/kivyblocks/charts/geometry.py b/kivyblocks/charts/geometry.py new file mode 100644 index 0000000..166d640 --- /dev/null +++ b/kivyblocks/charts/geometry.py @@ -0,0 +1,81 @@ +import math +import numpy as np + +class Point(object): + def __init__(self, x=0, y=0): + super().__init__() + self.x = x + self.y = y + +class Line(object): # 直线由两个点组成 + def __init__(self, p1, p2): + if isinstance(p1,list) or isinstance(p1,tuple): + self.p1 = Point(*p1) + else: + self.p1 = p1 + if isinstance(p2,list) or isinstance(p2,tuple): + self.p2 = Point(*p2) + else: + self.p2 = p2 + + def vector(self): + return self.p1.x - self.p2.x,self.p1.y - self.p2.y + + def lenght(self): + return math.sqrt(pow((self.p1.x - self.p2.x), 2) + + pow((self.p1.y - self.p2.y), 2)) + + def get_cross_angle(self, l): + # 向量a + arr_a = np.array(self.vector()) + # 向量b + arr_b = np.array(l.vector()) + cos_value = float(arr_a.dot(arr_b)) / (np.sqrt(arr_a.dot(arr_a)) \ + * np.sqrt(arr_b.dot(arr_b))) + # 注意转成浮点数运算 + return np.arccos(cos_value) * (180 / np.pi) + # 两个向量的夹角的角度, + # 余弦值:cos_value, np.cos(para), + # 其中para是弧度,不是角度 + +class EllipseUtils(object): + def __init__(self,pos,size): + self.pos = Point(pos) + self.size = size + self.slides = [] + super().__init__() + + def split(self,data,colors): + self.slides = [] + kvs = [ [i[self.options['keyField']],i[self.options['valueField']]] for i in data ] + total = sum([i[1] for i in kvs ]) + start_degree = 0 + cnt = len(kvs) + for i in range(cnt): + degree = start_degree + 360 * kvs[i][1] / total + self.slides.append((degress,colors[i])) + start_degree = degree + + def isInside(self,a,b,x,y): + if a>b: + return x**x / a**a + y**y/b**b <= 1 + return x**x / b**b + y**y / a**a <= 1 + + def collide_point(self,x,y): + a = float(self.size[0] / 2) + b = float(self.size[1] / 2) + x = x - self.pos[0] + y = y - self.pos[1] + if not self.isInside(a,b,x,y): + return -1 + start_degress = 0 + l = Line((a,b),(a,b*2)) + l1 = Line((a,b),(x,y)) + degress = l.get_cross_angle(l1) + if x < a: + degress = 360 - degress + for i in range(len(self.slides)): + if start_degress <= degress and degress < self.slides[i][0]: + return i + start_degress += self.slides[i][0] + return -1 diff --git a/kivyblocks/charts/pie.py b/kivyblocks/charts/pie.py new file mode 100644 index 0000000..fc11ce8 --- /dev/null +++ b/kivyblocks/charts/pie.py @@ -0,0 +1,66 @@ + +import math +import numpy as np +from kivy.uix.widget import Widget +from kivyblocks.charts.geometry import EllipseUtils + +class PiePart(ChartPart): + def collide_point(self,x,y): + if not self.isInSideEllipse(x,y): + return False + return self.isInSidePart(x,y) + + def isInSideEllipse(self,x,y): + a = self.width / 2 + b = self.height / 2 + v = x ** x / a ** a + y**y / b ** b + if v <= 1: + return True + return False + +class Pie(Widget): + def __init__(self,**options): + """ + options={ + width, + height, + title, + keyField, + valueField, + data=[ + { + name, + value, + },{ + } + ] + } + """ + self.options = options + self.initflag = False + super().__init__() + self.bind(size=self.onSize,pos=self.onSize) + + def data2pie(self): + data = self.options.get('data',[]) + kvs = [ [i[self.options['keyField']],i[self.options['valueField']]] for i in data ] + total = sum([i[1] for i in kvs ]) + start_degree = 0 + cnt = len(kvs) + color1='8833ee' + color2='ed8234' + colors = divideColor(color1,color2,cnt-1) + self.canvas.clear() + for i in range(cnt): + degree = start_degree + 360 * kvs[i][1] / total + with self.canvas: + Color(*colors[i]) + Ellipse(pos=self.pos, + size=self.size, + angle_start=start_degree, + angle_end= degree) + start_degree = degree + + def onSize(self,o,v): + self.data2pie() + diff --git a/kivyblocks/colorcalc.py b/kivyblocks/colorcalc.py index 9223ab4..aaabf01 100644 --- a/kivyblocks/colorcalc.py +++ b/kivyblocks/colorcalc.py @@ -7,8 +7,9 @@ import math def toArrayColor(color): if isinstance(color,str): - sc = color - return get_color_from_hex(sc) + color = get_color_from_hex(color) + if len(color) == 3: + color.append(1) return color def distance(color1,color2): @@ -18,6 +19,7 @@ def distance(color1,color2): math.pow(abs(color2[1] - color1[1]),2) + \ math.pow(abs(color2[2] - color1[2]),2) d = math.sqrt(x) + return d def reverseColor(color): center = [0.5,0.5,0.5] @@ -28,8 +30,8 @@ def reverseColor(color): point= [0,0,0] for i in range(3): m = color[i] - center[i] - m1 = m * d1 / d - point[i] = color[i] - m1 if m < 0? color[i] + m1 + m1 = abs(m) * d1 / d + point[i] = color[i] - m1 if m < 0 else color[i] + m1 return point #DIVIDE @@ -50,121 +52,3 @@ def divide(point1,point2,divide_cnt): ps.append(p) ps.append(point2) return ps - -class Bar(Widget): - """ - a BAR class - """ - def __init__(self,**options): - """ - options={ - width, - height, - title, - keyField, - valueField, - data=[ - { - name, - value, - },{ - } - ] - } - """ - self.options = options - self.initflag = False - super().__init__() - self.bind(size=self.onSize,pos=self.onSize) - - def data2bar(self): - data = self.options.get('data',[]) - kvs = [ [i[self.options['keyField']],i[self.options['valueField']]] for i in data ] - m = max([i[1] for i in kvs ]) - cnt = len(kvs) - points = divide([0,0],[self.width,0],cnt) - color1='8833ee' - color2='ed8234' - colors = divideColor(color1,color2,cnt-1) - self.canvas.clear() - for i in range(cnt): - h = self.height * kvs[i][1] / m - with self.canvas: - Color(*colors[i]) - Rectangle(pos=points[i], - size=(points[i+1][0] - points[i][0],h)) - - def onSize(self,o,v): - self.build() - - def build(self): - self.initflag = True - self.data2bar() - -class Pie(Widget): - def __init__(self,**options): - """ - options={ - width, - height, - title, - keyField, - valueField, - data=[ - { - name, - value, - },{ - } - ] - } - """ - self.options = options - self.initflag = False - super().__init__() - self.bind(size=self.onSize,pos=self.onSize) - - def data2pie(self): - data = self.options.get('data',[]) - kvs = [ [i[self.options['keyField']],i[self.options['valueField']]] for i in data ] - total = sum([i[1] for i in kvs ]) - start_degree = 0 - cnt = len(kvs) - color1='8833ee' - color2='ed8234' - colors = divideColor(color1,color2,cnt-1) - self.canvas.clear() - for i in range(cnt): - degree = start_degree + 360 * kvs[i][1] / total - with self.canvas: - Color(*colors[i]) - Ellipse(pos=self.pos, - size=self.size, - angle_start=start_degree, - angle_end= degree) - start_degree = degree - - def onSize(self,o,v): - self.data2pie() - -class MyApp(App): - def build(self): - d = { - "keyField":"name", - "valueField":"value", - "data":[ - {'name':'you','value':102.0}, - {'name':'me','value':42.0}, - {'name':'she','value':92.0}, - {'name':'she','value':52.0}, - {'name':'she','value':42.0}, - {'name':'she','value':82.0}, - {'name':'she','value':17.0}, - {'name':'he','value':77.0} - ] - } - - return Pie(**d) - -if __name__ == '__main__': - MyApp().run()