This commit is contained in:
yumoqing 2020-03-06 11:22:53 +08:00
parent 192ceb7c68
commit 1cf4c0a421
6 changed files with 318 additions and 122 deletions

View File

@ -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

85
kivyblocks/charts/bar.py Normal file
View File

@ -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)

View File

@ -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()

View File

@ -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

66
kivyblocks/charts/pie.py Normal file
View File

@ -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()

View File

@ -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()