This example illustrates how to create a SVG Donut Chart with Rust WebAssembly web-sys and wasm-bindgen.
Live WebAssembly Donut Chart Demo
The first part of the code is structured to be as simple and straight forward as possible. All parameters, CSS settings, and labels are hardcoded with the aim of making the source code easy to understand. Subsequently, the code will be refactored to support all the required parameters and also as a W3C compliant web component.
The basic steps of creating the donut chart is described below:
1. Create a "figure" element (with create-element) and add a "svg" element containing a "rect" as background.
2. Create two concentrix circles in svg (create-element_ns) and lay them over each other to produce the effect of a donut chart with a ring and a hole.
3. Create additional circles and use the CSS "stroke-dasharray" and "stroke-dashoffset" effect to create the different segments of the donut chart. For example, the first circle is used to paint a donut chart segment representing 40% of the chart. The second circle is used to draw the segment representing the next 20% of the chart in another color. And finally, a third segment for the rest of the 40% in a different color. A detailed explanation of "stroke-dasharray" and "stroke-dashoffset" is available in a later section.
4. Create a "g" element to group the "svg text" objects that appears within the donut chart.
5. Finally, add a "figcaption" and "ul" (HTML unordered list) to provide a legend for the chart. The different steps are highlighted with comments below.
pub fn rust_webassembly_svg_donut_chart()-> Result<(), JsValue>
{
let window = web_sys::window().expect("global window does not exists");
let document = window.document().expect("expecting a document on window");
let body = document.body().expect("document expect to have have a body");
//Step 1
let figure = document.create_element("figure")?;
let div = document.create_element("div")
.unwrap()
.dyn_into::<web_sys::HtmlDivElement>()
.unwrap();
div.set_attribute("class","doughnut-main")?;
let svg = document
.create_element_ns(Some("http://www.w3.org/2000/svg"),
"svg")?;
svg.set_attribute("width","300px")?;
svg.set_attribute("height","300px")?;
svg.set_attribute("viewBox","0 0 42 42")?;
let rect = document
.create_element_ns(Some("http://www.w3.org/2000/svg"),
"rect")?;
rect.set_attribute("width","100%")?;
rect.set_attribute("height","100%")?;
rect.set_attribute("fill","white")?;
//Step 2
let hole = document
.create_element_ns(Some("http://www.w3.org/2000/svg"),
"circle")?;
hole.set_attribute("class","hole")?;
hole.set_attribute("cx","21")?;
hole.set_attribute("cy","21")?;
hole.set_attribute("r","15.91549430918954")?;
hole.set_attribute("fill","#fff")?;
let ring = document
.create_element_ns(Some("http://www.w3.org/2000/svg"),
"circle")?;
ring.set_attribute("class","ring")?;
ring.set_attribute("cx","21")?;
ring.set_attribute("cy","21")?;
ring.set_attribute("r","15.91549430918954")?;
ring.set_attribute("fill","transparent")?;
ring.set_attribute("stroke","#d2d3d4")?;
ring.set_attribute("stroke-width","3")?;
//Step 3
let seg1 = document
.create_element_ns(Some("http://www.w3.org/2000/svg"),
"circle")?;
seg1.set_attribute("cx","21")?;
seg1.set_attribute("cy","21")?;
seg1.set_attribute("r","15.91549430918954")?;
seg1.set_attribute("fill","transparent")?;
seg1.set_attribute("stroke","#ce4b99")?;
seg1.set_attribute("stroke-width","5")?;
seg1.set_attribute("stroke-dasharray","40 60")?;
seg1.set_attribute("stroke-dashoffset","25")?;
let seg2 = document
.create_element_ns(Some("http://www.w3.org/2000/svg"),
"circle")?;
seg2.set_attribute("cx","21")?;
seg2.set_attribute("cy","21")?;
seg2.set_attribute("r","15.91549430918954")?;
seg2.set_attribute("fill","transparent")?;
seg2.set_attribute("stroke","#27A844")?;
seg2.set_attribute("stroke-width","5")?;
seg2.set_attribute("stroke-dasharray","20 80")?;
seg2.set_attribute("stroke-dashoffset","85")?;
let seg3 = document
.create_element_ns(Some("http://www.w3.org/2000/svg"),
"circle")?;
seg3.set_attribute("cx","21")?;
seg3.set_attribute("cy","21")?;
seg3.set_attribute("r","15.91549430918954")?;
seg3.set_attribute("fill","transparent")?;
seg3.set_attribute("stroke","#377bbc")?;
seg3.set_attribute("stroke-width","5")?;
seg3.set_attribute("stroke-dasharray","40 60")?;
seg3.set_attribute("stroke-dashoffset","65")?;
//Step 4
let g = document
.create_element_ns(Some("http://www.w3.org/2000/svg"), "g")?;
g.set_attribute("class","doughnut-text")?;
let g_text1 = document
.create_element_ns(Some("http://www.w3.org/2000/svg"), "text")?;
g_text1.set_attribute("x","50%")?;
g_text1.set_attribute("y","50%")?;
g_text1.set_attribute("class","doughnut-number")?;
g_text1.set_text_content(Some("100"));
g.append_child(&g_text1)?;
let g_text2 = document
.create_element_ns(Some("http://www.w3.org/2000/svg"), "text")?;
g_text2.set_attribute("x","50%")?;
g_text2.set_attribute("y","50%")?;
g_text2.set_attribute("class","doughnut-label")?;
g_text2.set_text_content(Some("Sales"));
g.append_child(&g_text2)?;
//Step 5
let figcaption = document.create_element("figcaption")?;
figcaption.set_attribute("class","doughnut-key")?;
let ul = document.create_element("ul")
.unwrap()
.dyn_into::<web_sys::HtmlUListElement>()
.unwrap();
ul.set_attribute("class","doughnut-key-list")?;
ul.set_attribute("aria-hidden","true")?;
ul.style()
.set_property("list-style-type", "none")?;
let li1 = document.create_element("li")?;
let span1a = document.create_element("span")?;
span1a.set_attribute("class","round-dot dot-red")?;
let span1b = document.create_element("span")?;
span1b.set_text_content(Some("App Store (40)"));
li1.append_child(&span1a).unwrap();
li1.append_child(&span1b).unwrap();
ul.append_child(&li1).unwrap();
let li2 = document.create_element("li")?;
let span2a = document.create_element("span")?;
span2a.set_attribute("class","round-dot dot-green").unwrap();
let span2b = document.create_element("span")?;
span2b.set_text_content(Some("Website (20)"));
li2.append_child(&span2a).unwrap();
li2.append_child(&span2b).unwrap();
ul.append_child(&li2).unwrap();
let li3 = document.create_element("li")?;
let span3a = document.create_element("span")?;
span3a.set_attribute("class","round-dot dot-blue").unwrap();
let span3b = document.create_element("span")?;
span3b.set_text_content(Some("Partners (40)"));
li3.append_child(&span3a).unwrap();
li3.append_child(&span3b).unwrap();
ul.append_child(&li3).unwrap();
figcaption.append_child(&ul).unwrap();
svg.append_child(&rect).unwrap();
svg.append_child(&hole).unwrap();
svg.append_child(&ring).unwrap();
svg.append_child(&seg1).unwrap();
svg.append_child(&seg2).unwrap();
svg.append_child(&seg3).unwrap();
svg.append_child(&g).unwrap();
div.append_child(&svg).unwrap();
figure.append_child(&div).unwrap();
figure.append_child(&figcaption).unwrap();
body.append_child(&figure).unwrap();
Ok(())
}
The donut chart uses the attributes "stroke-dasharray" and "stroke-dashoffset" for drawing the different segments.
The following illustrates the HTML/SVG output of the donut chart program above.
<figure id="donut-chart"></figure>
<figure>
<div class="doughnut-main">
<svg width="100%" height="100%" viewBox="0 0 42 42">
<rect width="100%" height="100%" fill="white" />
<circle class="hole" cx="21" cy="21" r="15.91549430918954"
fill="#fff"></circle>
<circle class="ring" cx="21" cy="21"
r="15.91549430918954" fill="transparent"
stroke="#d2d3d4" stroke-width="3"></circle>
<circle cx="21" cy="21" r="15.91549430918954"
fill="transparent" stroke="#ce4b99"
stroke-width="5" stroke-dasharray="40 60" stroke-dashoffset="25">
<title>App Store</title>
<desc>40% (40 out of 100)</desc>
</circle>
<circle cx="21" cy="21" r="15.91549430918954"
fill="transparent" stroke="#27A844"
stroke-width="5" stroke-dasharray="20 80" stroke-dashoffset="85">
<title>Website</title>
<desc>20% (20 out of 100)</desc>
</circle>
<circle cx="21" cy="21" r="15.91549430918954"
fill="transparent" stroke="#377bbc"
stroke-width="5" stroke-dasharray="40 60" stroke-dashoffset="65">
<title>Partners</title>
<desc>40% (40 out of 100)</desc>
</circle>
<g class="doughnut-text">
<text x="50%" y="50%" class="doughnut-number">
100
</text>
<text x="50%" y="50%" class="doughnut-label">
Sales
</text>
</g>
</svg>
</div>
<figcaption class="doughnut-key">
<ul class="doughnut-key-list" aria-hidden="true"
style="list-style-type: none;">
<li>
<span class="round-dot dot-red"></span> <span>App Store (40)</span>
</li>
<li>
<span class="round-dot dot-green"></span> Website (20)
</li>
<li>
<span class="round-dot dot-blue"></span> Partners (40)
</li>
</ul>
</figcaption>
</figure>
The CSS Style Sheet values used by the SVG donut chart.
.doughnut-text {
fill: #000;
transform: translateY(0.25em);
}
.doughnut-number {
font-size: 0.5em;
line-height: 1;
text-anchor: middle;
transform: translateY(-0.25em);
}
.doughnut-label {
font-size: 0.2em;
text-transform: uppercase;
text-anchor: middle;
transform: translateY(0.3em);
}
figure {
display: flex;
justify-content: space-around;
flex-direction: column;
margin-left: -15px;
margin-right: -15px;
}
.doughnut-main,
.doughnut-legend {
flex: 1;
padding-left: 15px;
padding-right: 15px;
align-self: flex-start;
}
.doughnut-main svg {
height: auto;
}
.doughnut-legend {
min-width: calc(8 / 12);
}
.doughnut-legend [class*="dot-"] {
margin-right: 6px;
}
.doughnut-legend-list {
margin: 0;
padding: 0;
list-style: none;
}
.doughnut-legend-list li {
margin: 0 0 8px;
padding: 0;
}
.dot-red {
background-color: #c84b95;
}
.dot-green {
background-color: #27A844;
}
.dot-blue {
background-color: #3779b8;
}
.round-dot {
margin: 5px;
display: inline-block;
vertical-align: middle;
width: 26px;
height: 26px;
border-radius: 50%;
}