创建随内容动态缩放的SVG图形

创建随内容动态缩放的SVG图形
BRIAN VENN 2008-04-17 来源:
文章首页
创建随内容动态缩放的SVG图形
第二页
页1 共2页
  本文通过例子说明了如何根据数据缩放 SVG 图形。通过这些技术和提供的示例代码,您可以呈现自己的图形,并根据需要定制图形。

  Scalable Vector Graphics(可缩放向量图形,SVG)是一种基于 XML 的语言,可用于绘制二维图形。它能够快速地呈现图形,因此很自然可用于表示图形这样的数据。但是如果要表示的数据相差很大,比如今天的图形数据是从 0 到 10,明天的数据就变成 0 到 1000000,以同样的比例绘制这样的数据是没有意义的。理想情况下,需要根据内容来缩放 SVG 图形。作者 Brian Venn 将介绍如何实现这一点。

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

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

1 一点高中数学知识


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

  最好通过 实际的例子来说明我采用的技术。假设测试生成三个值: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)


图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 earlier
95. 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 position
104. // 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 position
110. // in the array by 110.
111.
112. double XPosition = (i*110);
113.
114. // Generate some random numbers for your bar colours
115. 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条形图


2 线图

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

  假设您监控数据库中的行数。 每 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 line
109. 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 值:

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

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

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


创建随内容动态缩放的SVG图形 - 第二页
BRIAN VENN 2008-04-17 来源:
文章首页
创建随内容动态缩放的SVG图形
第二页
页2 共2页
3 散点图


  最后介绍的是散点图。这种图形适用于在 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 find
132. // 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 numbers
138. 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 earlier
143. 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 from
150. // 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。

下载并运行这个例子。


4 进一步改进

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


图6 改进的柱状图


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


图7 改进的SVG线图



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


图8 改进的SVG散点图



  这些图的完整SVG版本包含在示例文件中,http://download.boulder.ibm.com/ibmdl/pub/software/dw/library/x-svggrph_examples.zip

5 结束语

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

(THE END)

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值