65.9K
CodeProject 正在变化。 阅读更多。
Home

生成商业图表的应用程序

starIconstarIconemptyStarIconemptyStarIconemptyStarIcon

2.00/5 (1投票)

2018年5月15日

CPOL

6分钟阅读

viewsIcon

7966

downloadIcon

171

B4A 生成商业图表的应用程序

引言

通过图表可视化数据可以更清晰地理解现象,前提是我们选择合适的图表,并且对于某些类型,我们不期望精确的对应关系。

一个图表可以包含一个或多个数值数据集,这些数据集可以被视为一个坐标,而另一个坐标则是一个隐含的递增数字,对应于一个枚举或时间点。

某些图表可以接受多个数据集,如果它们具有兼容的比例,就可以一起绘制。两个非同质的数据集可以在散点图中排列,其中第一个数据集生成的点成为一个气泡,其尺寸与第二个数据集中的相应数据成比例。

如果我们有一个分区数据集,或者说,一个百分比数据集,那么最佳显示方式是饼图或环形图。

当然,这个简要的检查并不详尽,它涵盖了附带文档中可能找到的所有图表或变体,但它足以说明所开发的程序。


下表是一个总结。

图表类型 数据类型
Area 具有相同比例的数据集 线性现象的值
Bar 具有相同比例的数据集 时间或枚举数据
Bubble 具有不同比例的数据集 气泡代替点,尺寸与第二个数据集成比例
Line 具有相同比例的数据集 通常在固定点采集的现象的值
差值 相同比例数据集的组合 两个数据集的差值
饼图、环形图 数据集的分区 可能的第二个数据集创建可变扇区
Point 具有相同比例的数据集 通常在固定点采集的现象的值
维恩图 两个数据集及其交集

使用程序

该程序是一个“沙盒”,允许使用我的 B4A 表单生成器 来尝试脚本 Chart.bas,该生成器用于输入生成图表所需的数据。Chart.bas 是一个活动模块,它包含两个函数,每个函数都有一个参数,即一个描述图表的 string

CallSubDelayed2(Charts,"startChart",param)	' for portrait chart
CallSubDelayed2(Charts,"changeOrientation",param)	' for landscape chart

程序的工作原理

图表的创建基于一个描述,其中组成图表的对象后面跟着它们的属性以及可能的值;如果一个属性有多个值,例如,颜色或标签列表,则必须用撇号括起来。对象的顺序不重要;但是,如果存在多种图表类型,某些属性将取自第一个图表。图表描述使用正则表达式进行解析(有关机械原理的解释,请参阅 正则表达式使用示例 段),解析后,使用 B4A 对象 canvas 生成图表。

生成图表的算法没有任何特别的困难,除了创建饼图切片和表示维恩图。您可以在附带文档的 **技术参考** 章节中找到涉及的数学知识。

饼图切片

在开发此程序所使用的 B4A 版本中,无法绘制弧线,只能绘制圆形,因此可以通过裁剪适当的区域来获得切片,绘制圆形并移除裁剪区域。

在上图右侧,虚线围合了用于获得切片的裁剪区域。裁剪区域需要以笛卡尔坐标表示的点,这意味着对于点 B,需要计算圆和直线 y = tan(θ)*x 的交点,而点 A 是直线

  • y = tan(α+θ)*x
  • y = (-1/ tan(θ))*x

相反,使用极坐标,任务就容易多了,事实上,这三个点的坐标是

  • C(0, 0)
  • B(R, θ)
  • A(R*SQRT(1+tan(α)* tan(α))), θ+α) (AB 的长度是 R*tan(α),根据勾股定理 CA2 = R2 + R*tan(α)2

通过关系式:x = ρ * cos(φ)y = ρ * sin(φ),可以轻松地将它们转换为笛卡尔坐标。

在左侧的图像中,有一个带有爆炸的切片的雷达饼图示例。生成图表的脚本是:Chart Pie color 'Silver' Border labels 'Medicine Letters Mathematics Physics' Data 'Graduate distribution' '100 110 20 77,6.8 4.1 7.9 3.1' Color 'Olive aqua orange purple' Title 'Pie Radar' color Olive Align CENTER explode '2' show '%'
Sub drawSlice(Canvas As Canvas, center() As Int, _
Radius As Int, startDegree As Float, _
sliceAngle As Float, Color As Int, explode As Int) 
	Dim cx As Int = center(0)
	Dim cy As Int = center(1)
	' explode contains 0 or 10dip if the 
        ' slice must be exploded
	cx = cx + explode * CosD(startDegree+0.5*sliceAngle)
	cy = cy + explode * SinD(startDegree+0.5*sliceAngle)
	Dim p As Path
	Do While sliceAngle > 0
		Dim slDg As Float = 72
		If slDg > sliceAngle Then slDg = sliceAngle
		p.Initialize(cx, cy)
		p.LineTo(cx+Radius*CosD_
                (startDegree),cy+Radius*SinD(startDegree))
		Dim ip As Float = Radius*Sqrt_
                (1+TanD(slDg)*TanD(slDg))	' hypotenuse
		p.LineTo(cx+ip*CosD(slDg+startDegree),_
                cy+ip*SinD(slDg+startDegree))
		p.lineTo(cx, cy)
		Canvas.ClipPath(p)
		Canvas.DrawCircle_
                (cx, cy, Radius, Color, True, 0)
		Canvas.RemoveClip
		sliceAngle = sliceAngle - slDg
		startDegree = startDegree + slDg
	Loop
End Sub

维恩图

维恩图可以被看作是数字到图形表示的类比对应,就像饼图中的扇形与相对百分比成正比一样。

因此,在维恩图中,这种比例关系仅在存在两个数据集及其交集的情况下成立;对于三个数据集,这种对应关系仅适用于所有数据集及其两两交集,而不适用于三者交集。在某些情况下,上述比例关系是不可能的。

在下面的片段中,交集区域用于查找两个中心的距离,这是通过二分法完成的

...
	Dim intersectArea As Float = itemdata(0).data(2)
	Dim x1 As Float = r1+r2			' at end contains the distance
	Dim x2 As Float = 0
	Do While Abs(intersectArea - a(0)) > 0.001
		Log(intersectArea - a(0))
		Dim d As Float = 0.5*(x1+x2)
		a = areaOfIntersection(r1,d, r2)	' returns area and ascissa of 
                                                        ' intersection of circles
		If a(0) < intersectArea Then  x1 = d	' the distance must be decreased
		If a(0) >= intersectArea Then x2 = d	' the distance must be augmented
	Loop
...
private Sub areaOfIntersection(r1 As Float, d As Float, r2 As Float) As Float() 
	' first circle is on origin, all center are on abscisse returns area and 
        ' center of intersection of circles
	Dim rr1 As Float = r1 * r1
	Dim rr2 As Float = r2 * r2
	Dim d2 As Float = d * d
	Dim phi As Float = 2 * (ACos((rr1 + d2 - rr2) / (2 * r1 * d)))	' cos(α) = (a2+b2-c2)/(2*a*b)
	Dim theta As Float = 2 * (ACos((rr2 + d2 - rr1) / (2 * r2 * d)))
	Dim a(2) As Float
	a(0) = 0.5 * (rr1 * (theta - Sin(theta)) + rr2 * (phi - Sin(phi)))
	a(1) = r1 * Cos(phi * 0.5)	' center of intersection
	Return a
End Sub

正则表达式使用示例

在网站 https://regex101.com/ 上,您可以尝试您的正则表达式。

B4A 具有 Matcher 对象,其 Match 属性包含与搜索匹配的字符 string

解析颜色列表

颜色可以是符号名称之一,即 Colors 对象名称加上一些其他名称,后面可能跟着透明度值或十六进制值,此外,颜色可以重复,例如饼图切片的颜色:color 'red 127 green x7f00007f',即透明红色、绿色和透明暗蓝色。

正则表达式是:([xX][0-9a-fA-F]{1,8})|([a-zA-Z]+\s+\d{1,3})|([a-zA-Z]+);符号 | 分隔颜色选项,(...) 允许记住找到的值

  • ([xX][0-9a-fA-F]{1,8}) - 这捕获一个十六进制值,最多 8 位数字,前提是它以 x 开头
  • ([a-zA-Z]+\s+\d{1,3}) - 这捕获一个后跟最多三位数字的单词
  • ([a-zA-Z]+) - 这捕获一个单词

请注意,顺序对于捕获不同的颜色编码很重要,并且在提取后,必须验证值。上面有一个用于提取(可能的)值的函数。

Public Sub splitColors(c As String) As List	' contains the list of colors
	Dim Match As Matcher = Regex.Matcher(reSplitColors,c)
	Dim l1 As List
	l1.initialize
	Do While Match.Find
		l1.Add(interpColor(Match.Match))
	Loop
	Return l1
End Sub

使用正则表达式 reSplitColors 匹配 string c 的结果是一个列表,其中包含所有匹配的颜色。

解析标签列表

标签可以用空格分隔,如果标签包含空格,则分隔符是逗号;因此,正则表达式是 \s*([^,]+)\s*,|([^\s,]+)

这种情况与前一种情况的处理方式略有不同,因为识别模式可能包含一个不需要提取的分隔符。提取的 string(即括号内的内容)存储在 Group(n) 属性中,其中 n 是正则表达式的第 n 个替代项。

...
Dim reSplitTokens As String = "\s*([^,]+)\s*,|([^\s,]+)"	' split tokens 
                                             'on space or comma \s*([^,]+)\s*,|([^\s,]+)
...
itemData.labels = getItems(replace(getItem("labels","")),reSplitTokens)
...
Sub getItems(s As String, re As String) As String()	' return an array of item with 
                                                        ' removed possibly delimiters
	Dim list As List
	list.Initialize
	Dim Mtch As Matcher
	Mtch = Regex.Matcher2(re,Regex.CASE_INSENSITIVE,s)
	Do While Mtch.Find
		For i=1 To Mtch.GroupCount
			If Mtch.Group(i) <> Null Then list.Add(removeDelimiters(Mtch.Group(i)))
		Next
	Loop
	Return list2Array(list)
End Sub

注释

JavaScript 和 B4A Canvas

JavaScript canvas 是一个丰富的对象,其核心是变换矩阵[1]

在编写此软件时,我与 B4A canvas 的限制进行了斗争,即

  • 没有绘制圆形扇区的原始方法[2]
  • 裁剪区域仅为多边形[2]
  • 线条只能是连续的
  • 没有什么原生的方法来平滑线条

注意事项

该程序仍处于初期阶段,除了显而易见的和简单的实现之外,并非所有数据都得到了完美的控制,因此请谨慎使用(自担风险),并严厉批评。显然,欢迎提出建议。

最后,该程序不提供广告,也不读取您的个人数据。

  1. ^请参阅 JavaScript 课程章节
  2. ^在 B4A 的最新版本中,可以将裁剪区域设置为扇区。
© . All rights reserved.