下面通过一个实例了解一下SVG重用的过程。报表是我们经常需要用到的图形元素,首先我们从ADOBE SVG站点上得到了一个SVG图表的例子,这是用Adobe Illustrator 9.0画的DEMO,做的非常漂亮,我们的目的是希望重用其中的报表图形。
大致思路如下,分析其中的SVG图形,样式以及JAVASCRIPT代码,并分离出我们使用的主要接口,然后通过这些接口,将自己的数据显示出来。
通过分析,确定该例子包括:
1,一个饼图,鼠标在上面移动可以显示该部分的数据和比例
2,一个柱状图,鼠标移动可以显示高度坐标
3,背景和标题
我们的工作流程如下:
1,确定接口,为简单起见,我们仅仅使用四个接口,剩下的操作对我们来说是透明的,并不需要关心他们的具体内容
Initialize 初始化图形,为避免歧义,将原来的函数改名为InitialCharts
SetTitle 设置标题
AddChartValue 添加数值
ClearChart 将图形数值清除
2,将代码和SVG元素分离,将所有的样式和CSS定义与SVG放在一起,这样在装载SVG时就可以不丢失原有的样式了。文件分别保存为Reusablecharts.js和Reusablecharts.svg
3,创建myCharts.svg, 并装载Reusablecharts.js和Reusablecharts.svg图形到该文档。这些在以前的例子中都演示过,就不详细介绍了。
4,确定我们要显示的数据源,这里可以简单的编几个数据,也可以复杂一点,从网上或数据库中取得数据。这里还是使用我的BLOG RSS
5,显示报表
使用AddChartValue将XMLHTTPREQUEST取得RSS数据加入报表
对Reusablecharts.js中代码做一些必要的改动:
1,将原来初始化函数中的SVGDocument移动到新的SVG初始化函数中
2,由于动态加载SVG时,SVGDocument进行了优化,空的(例如只有回车符)TEXT节点被优化为无子节点的TEXT节点,例如
<text id="XPos" x="0" y="10">
</text>成为<text id="XPos" x="0" y="10"/>
所以代码在静态SVG时可以通过,而动态加载时有问题,例如在原代码中的
NewItem = SVGDocument.createTextNode(Value + Percent)
SVGDocument.getElementById("labelamount").replaceChild(NewItem, SVGDocument.getElementById("labelamount").getFirstChild())
就会报“无效对象”错误。
所以改为
if(SVGDocument.getElementById("labelamount").hasChildNodes() == true)
SVGDocument.getElementById("labelamount").getFirstChild().setData( Value + Percent);
else
{
NewItem = SVGDocument.createTextNode(Value + Percent)
SVGDocument.getElementById("labelamount").appendChild(NewItem);
}
这样的地方有几处。
我们一共添加了3个变量,7个函数,修改了几个函数后,就可以使用了。如果想去掉其中背景和辅助图形,只保留报表图形也很简单。
最后的代码如下:
MyCharts.svg:
<?
xml version
=
"
1.0
"
encoding
=
"
iso-8859-1
"
?>
<
svg onload
=
"
myInit(evt)
"
width
=
"
100%
"
height
=
"
100%
"
>
<
title
>
SVG REPORT REUSE DEMO
</
title
>
<
script xlink:href
=
"
ReusableCharts.js
"
/>
<
script type
=
"
text/javascript
"
>
<!
[CDATA[

var
svgns
=
"
http://www.w3.org/2000/svg
"
;
var
dataUrl
=
"
http://blog.youkuaiyun.com/firefight/Rss.aspx
"
;
//
var svgUrl = "http://localhost/svg/ReusableCharts.svg";
var
svgUrl
=
"
http://bbs.xml.org.cn/UploadFile/20068141358690824.txt
"
;
function
myInit(evt)

...
{
SVGDocument = evt.getTarget().getOwnerDocument();

//Get reusable SVG charts
getSvgCharts();
//Set charts entry points
InitialCharts();
//Get data from blog RSS
getChartData();
//Refresh data every 10 seconds
setTimeout("getChartData()",10000);
}
function
getDataByXmlHttp(url)

...
{
var xmlhttp;
var error;
var s = null;
//Create XMLHTTP object
eval('try {xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {xmlhttp = null;error=e;}');
if(null != xmlhttp)

...{
xmlhttp.Open("GET", url, false);
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlhttp.Send();
s = xmlhttp.responseText;
}
return s;
}
function
getSvgCharts()

...
{
var s = getDataByXmlHttp(svgUrl);
//alert(s);
//Include into local svg document
var newNode = parseXML(s, SVGDocument);
//alert(printNode(newNode));
var svgElem = SVGDocument.getElementById("newRoot");
svgElem.appendChild(newNode);
//svgElem = SVGDocument.getElementById("labelamount");
//alert(printNode(svgElem));
}
function
getChartData()

...
{
var s = getDataByXmlHttp(dataUrl);
//alert(s);
var xml_dom = new ActiveXObject("MSXML2.DOMDocument");
//alert(xmlhttp.responseText);
xml_dom.loadXML(s);
drawText(xml_dom);
}
function
drawText(xml)

...
{
//Remove all
ClearChart();
//Set char title
SetTitle("My Blog Comments Statistic");
//Get data
var nodes = xml.selectNodes("/rss/channel/item");
var count = 0;
while(nodes[count]!=null)

...{
//Get artical and comment time values from my blog RSS
var title = nodes[count].getElementsByTagName("title");
var commentTimes = nodes[count].getElementsByTagName("slash:comments");
var times = Number( commentTimes[0].text )*10 + 5;
//Add data to chart
AddChartValue(times, title[0].text ,false);
count++;
}
//Update date time
yy_datetime();
}
function
setTextNodeValue(id, value)

...
{
if(SVGDocument.getElementById( id ).hasChildNodes() == true)
SVGDocument.getElementById( id ).getFirstChild().setData( value );
else

...{
NewItem = SVGDocument.createTextNode( value )
SVGDocument.getElementById( id ).appendChild(NewItem);
}
}
function
yy_datetime()

...
{
var datetime,temp,date,time;
datetime = new Date().toLocaleString();
temp = datetime.lastIndexOf(" ");
date = datetime.substring(0,temp);
time = datetime.substring(temp+1,datetime.length);
setTextNodeValue("updateTime", "Update time: " + date + " " + time);
}

//
]]></script>
<!--
Fix svg item place here
-->
<
svg id
=
"
skeleton
"
x
=
"
50
"
y
=
"
50
"
>
<
text id
=
"
updateTime
"
x
=
"
300
"
y
=
"
480
"
/>
</
svg
>
<!--
Dynamic svg item place here
-->
<
svg id
=
"
newRoot
"
/>
</
svg
>
ReusableCharts.js:
ColorArray
=
new
Array(
"
#FF9966
"
,
"
#C2A00E
"
,
"
#71A214
"
,
"
#3399CC
"
,
"
#CC99FF
"
,
"
#FF9999
"
,
"
#00CC99
"
,
"
#C2CC0E
"
,
"
#71CC14
"
,
"
#CC68CC
"
,
"
#66FFCC
"
)
ActiveSegment
=
"
Pie
"
CurrentColor
=
0
;
SVGDocument
=
null
BarChartHeight
=
150
BarChartWidth
=
250
PieChartSize
=
140
MoveDistance
=
40
Values
=
new
Array()
Names
=
new
Array()
PieElements
=
new
Array()
BarElements
=
new
Array()
BarTexts
=
new
Array()
DeleteList
=
new
Array()
PieTotalSize
=
0
;
BarTotalSize
=
0
;
MaxSize
=
0
;
AngleFactor
=
Math.pow(
2
, .
5
)
Removed
=
false


function
InitialCharts()

...
{
//Get entry port
ParentGroup1 = SVGDocument.getElementById("slices")
Grandparent1 = SVGDocument.getElementById("piechart")
ParentGroup2 = SVGDocument.getElementById("bars")
Grandparent2 = SVGDocument.getElementById("barchart")
}
function
ClearChart()

...
{
for (var I = 0; I < PieElements.length; I++)

...{
ParentGroup1.removeChild(PieElements[I])
ParentGroup2.removeChild(BarElements[I])
SVGDocument.getElementById("labels").removeChild(Names[I])
}
PieElements = new Array()
BarElements = new Array()
BarTexts = new Array()
Values = new Array()
Names = new Array()
PieTotalSize = 0;
BarTotalSize = 0;
MaxSize = 0;
if (!(Removed))

...{
Grandparent1.removeChild(ParentGroup1)
Grandparent2.removeChild(ParentGroup2)
}
Removed = true
}
function
AddChartValue(Value, Name, Repress)

...
{
Value = Value * 1
if ((Value <= 0) || (isNaN(Value)))

...{
alert("Negative, textual or null values are not allowed")
return;
}
Color = ColorArray[CurrentColor]
CurrentColor++
if (CurrentColor >= ColorArray.length) CurrentColor = 0
if (!(Removed))

...{
Grandparent1.removeChild(ParentGroup1)
Grandparent2.removeChild(ParentGroup2)
}
DeleteList[DeleteList.length] = false
Values[Values.length] = Value * 1
Names[Names.length] = SVGDocument.createElement("text")
PieElements[PieElements.length] = SVGDocument.createElement("path")
PieElements[PieElements.length - 1].setAttribute("style", "stroke:black;fill:" + Color)
PieElements[PieElements.length - 1].setAttribute("onmouseover", "DisplayInfo('" + Name + "', '" + Value + "')")
PieElements[PieElements.length - 1].setAttribute("onmouseout", "DisplayInfo(' ', ' ')")
ParentGroup1.appendChild(PieElements[PieElements.length - 1])
BarElements[BarElements.length] = SVGDocument.createElement("path")
BarTexts[BarTexts.length] = SVGDocument.createElement("text")
BarElements[BarElements.length - 1].setAttribute("style", "stroke:black;fill:" + Color)
BarElements[BarElements.length - 1].setAttribute("onmouseover", "BarTexts[" + (BarTexts.length - 1) + "].getStyle().setProperty('visibility', 'show')")
BarElements[BarElements.length - 1].setAttribute("onmouseout", "BarTexts[" + (BarTexts.length - 1) + "].getStyle().setProperty('visibility', 'hidden')")
ParentGroup2.appendChild(BarElements[BarElements.length - 1])
BarTexts[BarTexts.length - 1].setAttribute("style", "text-anchor:end;font-weight:bold;font-size:13;visibility:hidden")
BarTexts[BarTexts.length - 1].setAttribute("x", "188")
BarTexts[BarTexts.length - 1].appendChild(SVGDocument.createTextNode(Value + " -"))
SVGDocument.getElementById("barsidetext").appendChild(BarTexts[BarTexts.length - 1])
Names[Names.length - 1].appendChild(SVGDocument.createTextNode(Name + ""))
Names[Names.length - 1].setAttribute("transform", "rotate(45)")
SVGDocument.getElementById("labels").appendChild(Names[Names.length - 1])
Refresh()
if (Repress)
Removed = true
else

...{
Removed = false
Grandparent1.appendChild(ParentGroup1)
Grandparent2.appendChild(ParentGroup2)
}
}
function
Refresh()

...
{
PieTotalSize = 0
BarTotalSize = 0
MaxSize = 0
for (var I = 0; I < Values.length; I++)

...{
PieTotalSize += Values[I]
BarTotalSize++
if (Values[I] > MaxSize)
MaxSize = Values[I]
}
PieStart = 0
BarStart = 0
if (PieTotalSize > 0)
for (var I = 0; I < Values.length; I++)

...{
PieStart = DrawPieSegment(PieStart, Values[I] / PieTotalSize, PieElements[I], I)
BarStart = DrawBarSegment(BarStart, Values[I] / MaxSize, BarElements[I], BarTexts[I], Names[I])
}
SVGDocument.getElementById("max").replaceChild(SVGDocument.createTextNode(MaxSize + ""), SVGDocument.getElementById("max").getFirstChild())
SVGDocument.getElementById("min").replaceChild(SVGDocument.createTextNode("0"), SVGDocument.getElementById("min").getFirstChild())
}
function
DeleteSegment()

...
{
if (ActiveSegment == "Pie")

...{
NewValues = new Array()
NewNames = new Array()
NewPieElements = new Array()
NewBarElements = new Array()
NewBarTexts = new Array()
NewDeleteList = new Array()
SomethingDeleted = false
CurrentCopySpot = 0
for (var I = 0; I < PieElements.length; I++)

...{
if (!(DeleteList[I]))

...{
NewValues[CurrentCopySpot] = Values[I]
NewNames[CurrentCopySpot] = Names[I]
NewPieElements[CurrentCopySpot] = PieElements[I]
NewBarElements[CurrentCopySpot] = BarElements[I]
NewBarTexts[CurrentCopySpot] = BarTexts[I]
NewDeleteList[CurrentCopySpot] = DeleteList[I]
NewBarElements[CurrentCopySpot].setAttribute("onmouseover", "BarTexts[" + CurrentCopySpot + "].getStyle().setProperty('visibility', 'show')")
NewBarElements[CurrentCopySpot].setAttribute("onmouseout", "BarTexts[" + CurrentCopySpot + "].getStyle().setProperty('visibility', 'hidden')")
CurrentCopySpot++
}
else

...{
SomethingDeleted = true
PieElements[I].getParentNode().removeChild(PieElements[I])
BarElements[I].getParentNode().removeChild(BarElements[I])
BarTexts[I].getParentNode().removeChild(BarTexts[I])
Names[I].getParentNode().removeChild(Names[I])
}
}
if (SomethingDeleted)

...{
Values = NewValues
Names = NewNames
PieElements = NewPieElements
BarElements = NewBarElements
BarTexts = NewBarTexts
DeleteList = NewDeleteList
Refresh()
}
else
alert("To delete segments, click on the segments to be deleted to pull them out, then click on the Delete button")
}
else

...{
NewValues = new Array()
NewNames = new Array()
NewPieElements = new Array()
NewBarElements = new Array()
NewBarTexts = new Array()
NewDeleteList = new Array()
SomethingDeleted = false
CurrentCopySpot = 0
for (var I = 0; I < PieElements.length - 1; I++)

...{
NewValues[I] = Values[I]
NewNames[I] = Names[I]
NewPieElements[I] = PieElements[I]
NewBarElements[I] = BarElements[I]
NewBarTexts[I] = BarTexts[I]
NewDeleteList[I] = DeleteList[I]
NewBarElements[I].setAttribute("onmouseover", "BarTexts[" + CurrentCopySpot + "].getStyle().setProperty('visibility', 'show')")
NewBarElements[I].setAttribute("onmouseout", "BarTexts[" + CurrentCopySpot + "].getStyle().setProperty('visibility', 'hidden')")
CurrentCopySpot++
}
if (PieElements.length > 0)

...{
SomethingDeleted = true
PieElements[PieElements.length - 1].getParentNode().removeChild(PieElements[PieElements.length - 1])
BarElements[PieElements.length - 1].getParentNode().removeChild(BarElements[PieElements.length - 1])
BarTexts[PieElements.length - 1].getParentNode().removeChild(BarTexts[PieElements.length - 1])
Names[PieElements.length - 1].getParentNode().removeChild(Names[PieElements.length - 1])
}
if (SomethingDeleted)

...{
Values = NewValues
Names = NewNames
PieElements = NewPieElements
BarElements = NewBarElements
BarTexts = NewBarTexts
DeleteList = NewDeleteList
Refresh()
}
}
}
parent.deleteOuties
=
DeleteSegment
function
DrawBarSegment(Start, Height, Element, Text, Label)

...
{
XOffset3D = 10
YOffset3D = 5
PathData = "M" + (Start + (BarChartWidth / BarTotalSize)) + ",0"
PathData = PathData + "h" + (BarChartWidth / BarTotalSize * -1)
PathData = PathData + "v" + (Height * BarChartHeight * -1)
PathData = PathData + "l" + XOffset3D + ",-" + YOffset3D
PathData = PathData + "h" + (BarChartWidth / BarTotalSize)
PathData = PathData + "v" + (Height * BarChartHeight)
PathData = PathData + "l-" + XOffset3D + "," + YOffset3D
PathData = PathData + "v" + (Height * BarChartHeight * -1)
PathData = PathData + "h" + (BarChartWidth / BarTotalSize * -1)
PathData = PathData + "h" + (BarChartWidth / BarTotalSize)
PathData = PathData + "l" + XOffset3D + ",-" + YOffset3D
PathData = PathData + "l-" + XOffset3D + "," + YOffset3D
Element.setAttribute("d", PathData)
Label.setAttribute("x", Start / AngleFactor)
Label.setAttribute("y", Start * -1 / AngleFactor)
Text.setAttribute("y", (275 - BarChartHeight * Height))
return Start + BarChartWidth / BarTotalSize
}
function
DrawPieSegment(Start, Size, Element, ID)

...
{
PathData = "M0,0L"
PathData = PathData + PieChartSize * Math.sin(Start * Math.PI * 2) + "," + PieChartSize * Math.cos(Start * Math.PI * 2)
if (Size >= .5)
PathData = PathData + "A" + PieChartSize + " " + PieChartSize + " 1 1 0 " + PieChartSize * Math.sin((Start + Size) * Math.PI * 2) + "," + PieChartSize * Math.cos((Start + Size) * Math.PI * 2)
else
PathData = PathData + "A" + PieChartSize + " " + PieChartSize + " 0 0 0 " + PieChartSize * Math.sin((Start + Size) * Math.PI * 2) + "," + PieChartSize * Math.cos((Start + Size) * Math.PI * 2)
PathData = PathData + "z"
Element.setAttribute("d", PathData)
if (Start > 0)
Element.setAttribute("onclick", "MoveSegment(evt, " + (Start + Size / 2) + ", true, " + ID + ")")
else

...{
DeleteList[ID] = true;
Angle = Start + Size / 2
X = MoveDistance * Math.sin(Angle * 2 * Math.PI)
Y = MoveDistance * Math.cos(Angle * 2 * Math.PI)
Element.setAttribute("transform", "translate(" + X + "," + Y + ")")
Element.setAttribute("onclick", "MoveSegment(evt, " + ((Start + Size / 2) * -1) + ", false, " + ID + ")")
}
return Start + Size
}
function
MoveSegment(MouseEvent, Angle, CanBeDeleted, ID)

...
{
Element = MouseEvent.getTarget()
if (Angle < 0)

...{
X = 0
Y = 0
}
else

...{
X = MoveDistance * Math.sin(Angle * 2 * Math.PI)
Y = MoveDistance * Math.cos(Angle * 2 * Math.PI)
}
DeleteList[ID] = CanBeDeleted
Element.setAttribute("transform", "translate(" + X + "," + Y + ")")
Element.setAttribute("onclick", "MoveSegment(evt, " + (Angle * -1) + ", " + (!CanBeDeleted) + ", " + ID + ")")
}
function
DisplayInfo(Text, Value)

...
{
if (Text != " ")
Percent = " (" + Math.round(Value / PieTotalSize * 10000) / 100 + "%)"
else
Percent = ""
if(SVGDocument.getElementById("labelamount").hasChildNodes() == true)
SVGDocument.getElementById("labelamount").getFirstChild().setData( Value + Percent);
else

...{
NewItem = SVGDocument.createTextNode(Value + Percent)
SVGDocument.getElementById("labelamount").appendChild(NewItem);
}
if(SVGDocument.getElementById("labelitem").hasChildNodes() == true)
SVGDocument.getElementById("labelitem").getFirstChild().setData(Text + "");
else

...{
NewItem = SVGDocument.createTextNode(Text + "")
SVGDocument.getElementById("labelitem").appendChild(NewItem);
}

if (Text + Value == " ")
s = " ";
else
s = ":";

if(SVGDocument.getElementById("labelcolon").hasChildNodes() == true)
SVGDocument.getElementById("labelcolon").getFirstChild().setData( s );
else

...{
NewItem = SVGDocument.createTextNode( s )
SVGDocument.getElementById("labelcolon").appendChild(NewItem);
}

//Original code has some problems

/**//*NewItem = SVGDocument.createTextNode(Value + Percent)
SVGDocument.getElementById("labelamount").replaceChild(NewItem, SVGDocument.getElementById("labelamount").getFirstChild())
NewItem = SVGDocument.createTextNode(Text + "")
SVGDocument.getElementById("labelitem").replaceChild(NewItem, SVGDocument.getElementById("labelitem").getFirstChild())
if (Text + Value == " ")
NewItem = SVGDocument.createTextNode(" ")
else
NewItem = SVGDocument.createTextNode(":")
SVGDocument.getElementById("labelcolon").replaceChild(NewItem, SVGDocument.getElementById("labelcolon").getFirstChild())*/
}
function
SetTitle(Text)

...
{
if(SVGDocument.getElementById("title").hasChildNodes() == true)
SVGDocument.getElementById("title").getFirstChild().setData( Text + "" );
else

...{
NewItem = SVGDocument.createTextNode( Text + "" )
SVGDocument.getElementById("title").appendChild(NewItem);
}

//Original code has some problems

/**//*NewItem = SVGDocument.createTextNode(Text + "")
SVGDocument.getElementById("title").replaceChild(NewItem, SVGDocument.getElementById("title").getFirstChild())
*/
}
function
SetAxis(Text)

...
{
NewItem = SVGDocument.createTextNode(Text + "")
SVGDocument.getElementById("axis").replaceChild(NewItem, SVGDocument.getElementById("axis").getFirstChild())
NewItem = SVGDocument.createTextNode(Text + "")
SVGDocument.getElementById("subtitle").replaceChild(NewItem, SVGDocument.getElementById("subtitle").getFirstChild())
}
function
ShowPie()

...
{
ActiveSegment = "Pie"
SVGDocument.getElementById("showpierect").getStyle().setProperty("fill", "blue");
SVGDocument.getElementById("showbarrect").getStyle().setProperty("fill", "#FC8D4F");
SVGDocument.getElementById("barchart").getStyle().setProperty("visibility", "hidden");
SVGDocument.getElementById("subtitle").getStyle().setProperty("visibility", "show");
SVGDocument.getElementById("piechart").getStyle().setProperty("visibility", "show");
SVGDocument.getElementById("pieitemlabel").getStyle().setProperty("visibility", "show");
SVGDocument.getElementById("barsidetext").getStyle().setProperty("visibility", "hidden");
}
function
ShowBar()

...
{
ActiveSegment = "Bar"
SVGDocument.getElementById("showpierect").getStyle().setProperty("fill", "#FC8D4F");
SVGDocument.getElementById("showbarrect").getStyle().setProperty("fill", "blue");
SVGDocument.getElementById("barchart").getStyle().setProperty("visibility", "show");
SVGDocument.getElementById("subtitle").getStyle().setProperty("visibility", "hidden");
SVGDocument.getElementById("piechart").getStyle().setProperty("visibility", "hidden");
SVGDocument.getElementById("pieitemlabel").getStyle().setProperty("visibility", "hidden");
SVGDocument.getElementById("barsidetext").getStyle().setProperty("visibility", "show");
}
parent.clearChart
=
ClearChart
parent.addChartValue
=
AddChartValue
parent.setTitle
=
SetTitle
parent.setAxis
=
SetAxis
ReusableCharts.svg放在http://bbs.xml.org.cn/UploadFile/20068141358690824.txt
效果如下:


是不是比我自己画的柱状图好看多了,而且功能强大,更重要的是并没有编写多少代码。而且可以供更多人使用。
从这个例子可以看出,在拿到一个SVG文档后,重用其中我们感兴趣的部分是比较简单的。
在该例子中,可重用的代码中全部采用根据ID进行定位的方法(getElementById),为重用代码提供了方便和可能。
为避免重用部分的ID与用户的ID冲突,以及今后嵌套包含时判断是否已经引入该部分图形,所有的可重用部分的ID应当有自己的命名空间,以保证ID的唯一性。