Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed bug handling 1 and 0 #1

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The API is very simple. First you need to find an element to insert the graph on
Then you instantiate the class:

var mychart = new DonutChart(chart_div, {
r: 60,
radius: 60,
stroke: 16,
scale: 100,
items: [
Expand All @@ -31,5 +31,5 @@ And if you need to, you can update the values:
]
})


Have fun!
- Have fun!
https://raw.githubusercontent.com/ismaell/js-donut-chart
265 changes: 163 additions & 102 deletions donut-chart.js
Original file line number Diff line number Diff line change
@@ -1,107 +1,168 @@
/**
* Generate a donut chart.
*
* @param {object} parent The parent element to place the chart within.
* @param {object} spec The chart details { radius, stroke, scale, items } (See README).
*/
function DonutChart(parent, spec) {
var __polar2xy = function(a, r) {
return {
x: Math.cos(a * 2 * Math.PI) * r,
y: -Math.sin(a * 2 * Math.PI) * r,
}
}

var __gen_arc_path = function(cx, cy, r, start, offset) {
var end = __polar2xy(start + offset, r)
start = __polar2xy(start, r)
return [
"M", cx + start.x, cy + start.y,
"A", r, r, 0, +(offset > .5), 0, cx + end.x, cy + end.y,
].join(" ")
}

var __gen_chart_item = function(out, c, r, prev, cur, i, stroke) {
out.push(["path", {
d: __gen_arc_path(c, c, r, prev, cur),
class: "chart-item-" + i,
fill: "transparent",
"stroke-width": stroke,
}])
}

var __gen_chart = function(chart) {
var prev = 0, out = []
// FIXME get radius and stroke-width from CSS
var c = chart.r, r = chart.r - chart.stroke / 2
for (var i in chart.items) {
cur = chart.items[i]
__gen_chart_item(out, c, r, prev, cur.value, i, chart.stroke)
prev += cur.value
}
if (prev < 1) {
__gen_chart_item(out, c, r, prev, 1 - prev, "bg", chart.stroke)
}
return out
}

var __create_tag_tree = function(elem) {
var root = document.createElementNS("http://www.w3.org/2000/svg", elem[0])
var attr = elem[1]
// Set attributes
for (var i in attr) {
var a = document.createAttribute(i)
a.value = attr[i]
root.setAttributeNode(a)
}
// Create children nodes
if (elem.length > 2) {
var children = elem[2]
for (var i in children) {
var c = __create_tag_tree(children[i])
root.appendChild(c)
}
}
return root
}


/* Transformation matrix (rotate and mirror) to correct orientation:
* \[
* \left[
* \begin{array}{ccc}
* 0 & -1 & 0 \\
* -1 & 0 & 0 \\
* 0 & 0 & 1
* \end{array}
* \right]
* \]
*/
var correct_orientation = "matrix(0 -1 -1 0 0 0)"

var __gen_code = function(spec) {
return __create_tag_tree(
["svg", {
transform: correct_orientation,
class: "chart-donut",
width: spec.r * 2,
height: spec.r * 2,
}, __gen_chart(spec)])
}

var __is_dict = function(v) {
return v && typeof v === "object" && !(v instanceof Array)
}

DonutChart.prototype.update = function(spec) {
// Merge the new spec
for (var i in spec) {
this.spec[i] = spec[i]

/* Transformation matrix (rotate and mirror) to correct orientation:
* \[
* \left[
* \begin{array}{ccc}
* 0 & -1 & 0 \\
* -1 & 0 & 0 \\
* 0 & 0 & 1
* \end{array}
* \right]
* \]
*/
var correct_orientation = "matrix(0 -1 -1 0 0 0)";

var __get_def_radius_from_parent = function (parent) {
if (parent.clientWidth < parent.clientHeight)
return parent.clientWidth /2
else
return parent.clientHeight /2
}

var code = __gen_code(this.spec)
// TODO can we switch the elements in place?
if (this.element != undefined) {
this.element.remove()
var __polar2xy = function (a, radius) {
return {
x: Math.cos(a * 2 * Math.PI) * radius,
y: -Math.sin(a * 2 * Math.PI) * radius
};
};

var __gen_arc_path = function (cx, cy, radius, start, offset) {
var end = __polar2xy(start + offset, radius);
start = __polar2xy(start, radius);
return [
"M",
cx + start.x,
cy + start.y,
"A",
radius,
radius,
0,
+(offset > 0.5),
0,
cx + end.x,
cy + end.y
].join(" ");
};

var __gen_chart_item = function (out, c, radius, prev, cur, i, stroke) {
out.push([
"path",
{
d: __gen_arc_path(c, c, radius, prev, cur),
class: "chart-item-" + i,
fill: "transparent",
"stroke-width": stroke
}
]);
};

var __gen_chart = function (chart) {
var prev = 0,
out = [];
// FIXME get radius and stroke-width from CSS
var c = chart.radius,
radius = chart.radius - chart.stroke / 2;
for (var i in chart.items) {
cur = chart.items[i];
__gen_chart_item(out, c, radius, prev, cur.value, i, chart.stroke);
prev += cur.value;
}
if (prev < 1) {
__gen_chart_item(out, c, radius, prev, 1 - prev, "bg", chart.stroke);
}
return out;
};

var __create_tag_tree = function (elem) {
var root = document.createElementNS("http://www.w3.org/2000/svg", elem[0]);
var attr = elem[1];
// Set attributes
for (var i in attr) {
var a = document.createAttribute(i);
a.value = attr[i];
root.setAttributeNode(a);
}
// Create children nodes
if (elem.length > 2) {
var children = elem[2];
for (var i in children) {
var c = __create_tag_tree(children[i]);
root.appendChild(c);
}
}
return root;
};

// Need to fix spec values that will cause a broken SVG
var __fix_spec_items = function (spec) {
if (spec.items) {
for (var i in spec.items) {
if (spec.items[i].value == 1) spec.items[i].value = 0.9999999
if (spec.items[i].value == 0) spec.items[i].value = 0.0000001
}
}
return spec
}
this.element = this.parent.appendChild(code)
}

this.parent = parent
this.spec = spec
this.update({})
}
var __gen_code = function (spec) {
return __create_tag_tree([
"svg",
{
transform: correct_orientation,
class: "chart-donut",
width: spec.radius * 2,
height: spec.radius * 2
},
__gen_chart(spec)
]);
};

var __is_dict = function (v) {
return v && typeof v === "object" && !(v instanceof Array);
};

/**
* Updates the state of the chart.
*
* @param {object} spec The chart items to update { items } (See README).
*/
DonutChart.prototype.update = function (spec) {
// Merge the new spec - A value of 1 will fail so need to make it as big as possible
__fix_spec_items(spec)
for (var i in spec) {
this.spec[i] = spec[i];
}

if (!this.parent) {
throw new Error("No parent defined for the chart.")
} else {
var code = __gen_code(this.spec);
// TODO can we switch the elements in place?
if (this.element) this.element.remove();
this.element = this.parent.appendChild(code);
}
};

if (!parent) throw new TypeError("The parent is required.")
if (!spec) throw new TypeError("The chart spec is required.")
if (!spec.items) throw new Error("The spec must have items provided.")
__fix_spec_items(spec)

// Set defaults if spec elements not provided.
if (!spec.radius) spec.radius = __get_def_radius_from_parent(parent)
if (!spec.stroke) spec.stroke = 25
if (!spec.scale) spec.scale = 100

this.parent = parent;
this.spec = spec;
this.element = null;

this.update({});
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't change the indentation in the same commit. Also, what's the rationale for changing it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not aware that I changed the indentation at all. I did move some methods around in the page order but I don't think I changed the indentation at all....

}
6 changes: 4 additions & 2 deletions test.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<html>
<head>
<style>
.chart-item-bg {stroke: #ddd}
.chart-item-bg {stroke: #000000}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am guessing this change slipped thru.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was intentional. Switching the stroke to black made it much clearer to debug at the very very small levels that I was dealing with where it would break trying to cover off the closest acceptable fraction close to 1 / 0. I wanted the highest possible contrast.

.chart-item-0 {stroke: #dd0000}
.chart-item-1 {stroke: #00cc00}
.chart-item-2 {stroke: #0000ff}
Expand All @@ -14,17 +14,19 @@
<script type="text/javascript">
var chart_div = document.getElementById("mychart")
var mychart = new DonutChart(chart_div, {
r: 60,
radius: 60,
stroke: 16,
scale: 100,
items: [
//{ label: "ALL", value: 1 },
{ label: "A", value: .2 },
{ label: "B", value: .1 },
{ label: "C", value: .5 },
]
})
mychart.update({
items: [
//{ label: "ALL", value: 1 }
{ label: "J", value: .4 },
{ label: "Q", value: .3 },
]
Expand Down