打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
在 SVG 中呈现动态图形

我在 IBM Hursley UK 做测试和开发时遇到这样一个问题。于是我便写了一些代码,以便能够精细地使用 SVG 绘制柱状图、线图和散点图。但是每次测试所要处理的数据都要跨越多个数量级。使用同一比例来绘制每个图是没有用处的,很多图要么非常大,要么非常小,数据要么拥挤在一个角上,要么分散在整个页面上。

我需要一种能够自动确定每组数据使用的最佳比例的方法。

注意,要查看本文中的 SVG 图,需要一个 SVG 查看器,可以从 参考资料中找到该查看器(还有一个 .zip 压缩包,它包括所有相关的文件)。

一点高中数学知识

从问题中我发现,自动缩放图形的最佳方式是观察需要绘制的数据集,确定数据集中的最大值,并使用该值作为其他将绘制的数据的比例。

最好通过 实际的例子来说明我采用的技术。假设测试生成三个值:A、B 和 C。每次测试后都需要将这些值绘制到柱状图中,但是每次测试中数据的范围都是动态变化的。

第一次测试生成的数据,如下所示:

  • A=100
  • B=50
  • C=25

在这次测试中,A 的值最大,为 100。第一项任务(也是最重要的任务)是计算图形的 比例因子,就是说在绘图时每单位的值在高度上要占用多少像素。比例因子可以用图轴的高度除以数据的最大值来获得。通过下面的计算就可以清楚如何获得比例因子。

  • 为了简单起见,Y 轴的高度设为 1000 像素,这样计算起来很方便。
  • 数据集中的最大值是 100(A)。
  • 对于该图,比例因子应该是: 1000/100 = 10
  • 因此对于这个柱状图,每单位的值在高度上用 10 个像素表示。
  • 要得到每个条的高度,只需要用比例因子乘上数据集中的值即可,就是说每个条的高度为:

    图 1. 使用比例因子,最大值为 100(A)

假设下一次测试得到如下所示的值:

  • A=2000
  • B=5000
  • C=800

要记住,Y 轴仍然是 1000 个像素高。这次测试中的最大值在 B 栏,为 5000。 因此比例因子就是 1000/5000=0.2

因此每个条的高度为:


图 2. 使用比例因子,最大值为 5000(B)

可以看出,因为条的高度是相对于最大值计算得到的,条的高度不会超过 1000 像素,因此图不会画得太高。SVG 动态缩放的关键就是比例因子。要记住,比例因子是根据最大值计算得到的,而其他数据都根据比例因子值来计算。

现在已经介绍了比例因子这个重要概念,下面来说明如何使用它。下面的例子是一个小型的 Java 语言程序,从命令行中得到一组数据,然后按照 上例所述的方式绘制 SVG 柱状图。虽然使用 Java 代码进行编写,但其中的关键是数学计算。需要的话,可以参照这里的 Java 代码用其他任何语言改写这个程序。

本文不再列出完整的代码,仅仅介绍其中的要点。建议您下载这个例子(请参阅 参考资料),然后使用带有对行进行编号功能的编辑器打开 SVG_barchart.java 文件。

首先,第 38-48 行确定传入的数据的最大值。

最重要的是第 51 行,它计算 Y 轴的比例因子:


清单 1. 计算 Y 轴比例因子
	51.	YAxisScalingFactor = 1000/(double)largestNumber; 

第 58 和第 59 行创建了 SVG 输出文件。SVG 使用 SVGout.write(-SVG-) 写入该文件。主要必须使用各种转义字符,如双引号用 \" 表示。有时候这样使 SVG 难以阅读,但必需如此,否则 Java 编译器就不能正确地解释它,代码也无法编译。此外,还要注意每个 SVG 文本行都以 \n 开始,这样可以保证使用 SVG 查看器的“查看源代码”功能时,SVG 更容易阅读。

第 70-79 行绘制 X 和 Y 轴。X 轴是从 [x=0, y=1000] 到 [x=1000, y=1000] 的一条直线,Y 轴是从 [x=0, y=0] 到 [x=0, y=1000],这样就建立了一个简单的笛卡儿坐标系。

第 82 和 83 行在 Y 轴的顶端和中间各划一条水平虚线,这样做只是为了让查看图时更方便。

第 86 和 87 行在顶端和中间标记 Y 轴。注意,要使用前面计算的最大值来标记 Y 轴。


清单 2. 标记 Y 轴
86.	SVGout.write("\n <text style=\"fill:black; stroke:none\" 	x=\"-10\" y=\"0\" >" + largestNumber + "</text>");87.	SVGout.write("\n <text style=\"fill:black; stroke:none\" 	x=\"-10\" y=\"500\" >" + (largestNumber/2) + "</text>");

值是动态变化的,因此这里使用 largestNumber 来标记轴。如果需要,添加其他的基准线和标记也很方便。

绘制柱状图从第 90 行开始:


清单 3. 绘制条
90.// The graph is ready to be rendered with the values.91.for(i=0;i<barChartValues.length;i++){92.  93. // Calculate the Y position. First work out how high the bar 94. // will be by multiplying the value by the scaling factor.94. // calculated earlier95. double barHeight = 96.    Integer.parseInt(barChartValues[i]) * YAxisScalingFactor;97.		98. System.out.println("Bar Height is =" + barHeight);99.    100. // You now have the height that the bar will be. Need to work 101. // out now where to place the bar. With Y values running 102. // positively down, and the Y-axis being 1000 pixels tall, 103. // simply subtract the  bar height from 1000 to get the position104. // of where to place the bar.105. 106. double YStart = 1000 - barHeight;107.                108. // Each of the bars is 100 pixels wide. So to space them out 109. //(with a 10-pixel gap between them), multiply the readings position110. // in the array by 110.111.	 112. double XPosition = (i*110);113.    114. // Generate some random numbers for your bar colours115. int randomRed = random.nextInt(255);116. int randomGreen = random.nextInt(255);117. int randomBlue = random.nextInt(255);118.    119. // You now have all your values ready. Draw the rectangle. 120. SVGout.write("\n<rect x=\""+XPosition+"\" y=\""+121.  YStart+"\" width =\""+ 100 +"\" height=\""+barHeight+122.  "\" style=\"fill:rgb("+randomRed+"123.  ,"+randomGreen+","+randomBlue+");\" /> ");124.    	                             125.}   

可以下载并自己来运行该程序,传递不同量级的数据,查看图形的变化和自动缩放功能,图 3 给出了一些例子。


图 3. 动态生成的 SVG 条形图

线图

目前为止,我们已经介绍了如何动态缩放柱状图。但这并不是惟一的图形,某些形式的数据,特别是基于时间的数据(如股票价格或者地震数据),最好用线图来表示。也可以使用与柱状图类似的方法缩放线图。

假设您监控数据库中的行数。 每 30 秒读一次行数,并记录在一个数组中,最后得到包含以下 10 个值的数组:

10,20,30,50,90,25,45,60,70,10

这次测试同样需要计算每次测试的比例因子。但是您可能不希望局限在只能绘制包含特定个数据的图,因此需要同时在 X 轴和 Y 轴两个方向上进行缩放。

绘制线图最好的办法是使用称为 polyline 的 SVG 元素。polyline 接受成对的 X 和 Y 坐标值并在相邻的两点之间划一条线,比如:

<polyline points="0 0, 10 10, 20 20">

将绘制一条线,其中三个点在 [X=0,Y=0]、[X=10,Y=10] 和 [X=20,Y=20]。这就是需要缩放和计算的点。

与上面的描述相同,也提供了一个 Java 示例程序,根据输入的数据缩放和呈现折线图(请参阅 参考资料)。

然后分析代码中的要点。

首先计算 X 轴的比例因子。它由要呈现的读入次数决定,因此要用 X 轴的像素数除以读入的次数。比方说,如果读取了 50 次,X 轴的比例因子就是 1000/50 = 20。因此每次读取操作的结果在 X 轴上间隔 20 像素进行绘制。读取的结果以数组传入时,只需要用数组的元素个数去除以 1000 即可。


清单 4. 计算 X 轴的比例因子
40. XAxisScalingFactor = 1000/(double)valuesToPlot.length;

第 41-49 行确定数组中的最大值。

第 54-100 行准备输出文件并绘制 X 和 Y 坐标轴。

Y 轴比例因子是在第 104 行计算的,方法和上面的柱状图使用的方法一样(请参阅 清单 2)。

第 108 到 125 行呈现了要绘制的线图:


图 5. 绘制折线
108. // Render the line109. SVGout.write("\n<polyline points=\"0 1000,");110.    	    111. for(i=0;i<valuesToPlot.length;i++){112.    113.  // Calculate the X position by determining which 114.  //value in the array you are dealing with.115.  XValue = ((i+1)*XAxisScalingFactor);116.117.  YValue = Integer.parseInt(valuesToPlot[i]);118.  YValue = YValue*YAxisScalingFactor;119.  YValue = 1000-YValue;120.    121.  // You now have your polyline point.122.  SVGout.write(" " + XValue + " " + YValue +",\n");123.                124. }           	           125. // Close off the polyline.126. SVGout.write("\" style=\"stroke:red; stroke-width: 3; fill : 127. none;\"/>");

第 109 行是 polyline 元素的起点,它与坐标轴的原点重合。

然后依次处理数组中的值。首先计算 X,用元素在数组中的位置乘上 X 轴比例因子。比如,如果数组中有 50 个元素,X 轴比例因子就是:

  • XAxisScalingFactor = 1000/(double)valuesToPlot.length;
  • XAxisScalingFactor = 1000/(double)50;
  • XAxisScalingFactor = 20

因此,两个值以 20 像素的间距绘制,全部数组的计算结果为:

  • 第 1 个点: x=20,<1st value>
  • 第 2 个点: x=40,<2nd value>
  • 第 3 个点: x=60,<3rd value>
  • ...
  • ...
  • 第 49 个点: x=980,<49th value>
  • 第 50 个点: x=1000,<50th value>

注意,这里使用了 (i+1),因为数组中第一个元素的索引号是 0,要从第一个元素开始,必须加上 1

然后计算 Y 值:

  1. 第 117 行从数组中提取数据并转化成整数。
  2. 第 118 行将该值乘以 Y 轴的比例因子,以确定其位置。
  3. 最后从 1000 中减去这个值,这是因为 Y 轴是从页面的上方向下增长,X 轴从 Y 轴的第 1000 个像素的位置开始绘制,因此,要确定计算得到的 Y 值在 X 轴的上方多远的位置上,就必须从 1000 中减去它。

现在计算得到了 X 和 Y 位置,然后要将该坐标点添加到 polyline 参数表中。

处理完数组中的值后封闭折线并应用适当的样式。

试一试以下这个例子:传入不同量级的数据。您会看到图形也能根据传入的最大值自动缩放。还要试一试传入不同数量的数据,比如 10 个或者 1000 个。X 轴将会按照传入的数据个数自动缩放。图 4 给出了一些例子。


图 4. 动态生成的 SVG 线图

散点图

最后介绍的是散点图。这种图形适用于在 X 维和 Y 维上都相差很大的数据。我们采用与前面相同的方法,但是这一次要绘制的数据同时提供 X 和 Y 坐标值。

该例中,数据以 (X-value),(Y-value) 的形式传入,比如: [1,1]、[3,5]、[50,2] 和 [10,34]。

我同样提供了一个 Java 实例程序以供下载(请参阅 参考资料),请分析下面这些代码。

第 31-57 行确定 X 和 Y 的最大值。

第 60-61 行计算 X 轴和 Y 轴的比例因子。

第 67-119 行绘制坐标轴,这里画了 4 条辅助虚线,并相应地做了标记。

第 124-157 行绘制数据:


清单 6. 计算和绘制散点图
122. // The axis and the guide lines are ready; now draw the data.123. SVGout.write("\n    <g style=\"fill:none; 124. stroke:red; stroke-width:3\">");125.126. for(i=0;i<dataToPlot.length;i++){127.                 128.    // Get the value out of the array.129.	String value = dataToPlot[i];130.                131.	// The data is in the form (X-Value),(Y-Value), so find132.	// the comma and get the values on either side of it.133.	index = value.indexOf (',');134.	String X_Pos = value.substring(0,index);135.	String Y_Pos = value.substring(index+1);136.137.	// Change them to numbers138.	XValue = Integer.parseInt(X_Pos);139.	YValue = Integer.parseInt(Y_Pos);140.141.	// Calculate the point's position by using the scaling 142.	// factor calculated earlier143.	XValue = XValue*XAxisScalingFactor;     144.	YValue = YValue*YAxisScalingFactor;145.	YValue = 1000-YValue;146.147.	// You now have your point. As it's a scatter plot, it 148.	// would look nice with an X, so use the point to draw 149	// a line from the top left to the bottom right, and from150.	// the top right to the bottom left.151. 152.	SVGout.write("\n  <line x1=\""+ (int)(XValue-5) + 153		"\" y1=\""+(int)(YValue+5)+ "\" x2=\""+(int)(XValue+5)+154.	"\" y2=\""+(int)(YValue-5)+"\" />"); 155.	SVGout.write("\n  <line x1=\""+ (int)(XValue+5) + 156.	"\" y1=\""+(int)(YValue+5)+ "\" x2=\""+(int)(XValue-5)+157.	"\" y2=\""+(int)(YValue-5)+"\" />");    158.}

首先要定义这一组的样式(第 123 行)。

然后开始处理数据,第 129 行获得了数据。

根据逗号分隔 X 和 Y 值(第 133-135 行),并将这些值转化成数值(第 138-139 行)。

计算点的位置(第 143 - 145 行)。

现在可以画点了,但是这一次不是简单地画一个点,而是要用 X 来标记。也就是说要画两条线,一条从左上角到右下角,一条从右上角到左下角,偏移量都是 5 个像素,从而得到以计算点为中心的 X。

下载并运行这个例子。


图 5. 动态生成的散点图

进一步改进

为了简化和保持代码的可读性,我在上述例子中使用了最少的 Java 代码生成 SVG。当然,您还可以改进这些例子,增加新的颜色、效果和信息。例子中包含两个柱状图生成器,一个像前面所述的那样绘制简单的矩形(参见 图 3)。另一个稍微复杂一点,使用斜线和点填充图形,但计算条形的方法不变,如图 6 所示。


图 6. 改进的柱状图

图 7 是一个线图,显示了测试的数据库中的一些信息,添加了一个徽标、阴影效果和测试的统计表。


图 7. 改进的 SVG 线图

图 8 是一个散点图,测试过程中我希望比较从数据库中删除一条记录所用的时间,一组数据使用 JDBC 执行这项操作,另一组则使用准备好的语句执行该操作。两组数据都提供了徽标、阴影效果和状态表。


图 8. 改进的 SVG 散点图

这些图的完整 SVG 版本包含在示例文件中。

结束语

本文通过例子说明了如何根据数据缩放 SVG 图形。通过这些技术和提供的示例代码,您可以呈现自己的图形,并根据需要定制图形。


下载

描述名字大小下载方法
Sample code and SVG example filesx-svggrph_examples.zip51 KBHTTP

关于下载方法的信息


参考资料

关于作者

Brian Venn 是 IBM 认证的解决方案专家,目前在 IBM Hursley Park in MQ Development 工作。您可以通过 http://www.360doc.com/mailto:vennb@uk.ibm.com?subject=Render dynamic graphs in SVG&cc=dwxed@us.ibm.com 与他联系

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
使用R语言进行纹理填充
万物归宗:分组的思路
编程之美 求数组的子数组之和的最大值
d3.js 之SVG:矢量化图形绘制
vlookup函数从入门到精通,只看这一篇就够了
模拟赛考0分?这3条信奥赛题目组成的规律你知道吗
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服