Skip to content

Commit 32e9764

Browse files
author
Guillaume Martigny
committed
Release v1.0.0
1 parent 99de88c commit 32e9764

File tree

10 files changed

+218
-108
lines changed

10 files changed

+218
-108
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.idea/
22
node_modules/
33
dist/
4+
/circular-progress-bar.css

.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
.eslintrc
22
webpack.config.js
3+
circular-progress-bar.less
4+
media/

circular-progress-bar.js

Lines changed: 85 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import "./circular-progress-bar.less";
1+
import "./circular-progress-bar.css";
22

33
const classesPrefix = "circular-progress-bar";
44

55
/**
66
* Create a new html element
7-
* @param {String} classes - Some css classes
7+
* @param {String} [classes] - Some css classes
88
* @param {HTMLElement} [parent] - Parent to append the element
99
* @return {HTMLDivElement}
1010
*/
@@ -17,9 +17,28 @@ const wrap = (classes, parent) => {
1717
return element;
1818
};
1919

20+
/**
21+
* Get item from an array without overflow
22+
* @param {Array} array - Any array
23+
* @param {Number} index - Any positive number
24+
* @return {*}
25+
*/
2026
const getLooped = (array, index) => array[index % array.length];
2127

22-
const toDeg = percent => percent * (360 / 100);
28+
/**
29+
* Tell if two number are equals
30+
* @param {Number} n1 - Any positive number
31+
* @param {Number} n2 - Any positive number
32+
* @return {Boolean}
33+
*/
34+
const equals = (n1, n2) => Math.abs(n1 - n2) < Number.EPSILON;
35+
36+
/**
37+
* Change percentage into degree
38+
* @param {Number} percent - Any percentage
39+
* @return {Number}
40+
*/
41+
const toDegree = percent => percent * (360 / 100);
2342

2443
/**
2544
* Class for CircularProgressBar's Bar
@@ -30,10 +49,10 @@ class Bar {
3049
* Bar constructor
3150
*/
3251
constructor () {
33-
this.html = wrap("bar");
52+
this.node = wrap("bar");
3453

3554
this._nodes = (new Array(2)).fill().map(() => {
36-
const clip = wrap("clip", this.html);
55+
const clip = wrap("clip", this.node);
3756
return {
3857
clip,
3958
part: wrap("part", clip),
@@ -43,7 +62,7 @@ class Bar {
4362
/**
4463
* @private
4564
*/
46-
this._value = 0;
65+
this._value = undefined;
4766
}
4867

4968
/**
@@ -62,24 +81,27 @@ class Bar {
6281
* @param {Number} [offset=0] - Starting position in %
6382
*/
6483
update (value, time, color, offset = 0) {
65-
const rotate = `rotate3d(0, 0, 1, ${toDeg(value / 2) - 179}deg)`;
66-
this._nodes.forEach((node) => {
67-
node.clip.style.transitionDuration = `${time}ms`;
68-
node.part.style.transitionDuration = `${time}ms`;
69-
node.part.style.transform = rotate;
70-
node.part.style.backgroundColor = color;
71-
});
84+
if (!equals(value, this._value)) {
85+
const half = value / 2;
86+
const rotate = `rotate3d(0,0,1,${toDegree(half) - 180}deg)`;
87+
this._nodes.forEach((node) => {
88+
node.clip.style.transitionDuration = `${time}ms`;
89+
node.part.style.transitionDuration = `${time}ms`;
90+
node.part.style.transform = rotate;
91+
node.part.style.backgroundColor = color;
92+
});
7293

73-
this._nodes[0].clip.style.transform = `rotate3d(0, 0, 1, ${toDeg(offset)}deg)`;
74-
this._nodes[1].clip.style.transform = `rotate3d(0, 0, 1, ${toDeg((value / 2) + offset)}deg)`;
75-
this._value = value;
94+
this._nodes[0].clip.style.transform = `rotate3d(0,0,1,${toDegree(offset) + 0.3}deg)`;
95+
this._nodes[1].clip.style.transform = `rotate3d(0,0,1,${toDegree(half + offset)}deg)`;
96+
this._value = value;
97+
}
7698
}
7799

78100
/**
79101
* Delete the bar from the DOM
80102
*/
81103
remove () {
82-
this.html.remove();
104+
this.node.remove();
83105
}
84106
}
85107

@@ -90,27 +112,28 @@ class Bar {
90112
export default class CircularProgressBar {
91113
/**
92114
* CircularProgressBar constructor
93-
* @param {Number|Array<Number>} [value=0] - Starting value or a set of values
94-
* @param {CPBOptions} [options] - Some options
115+
* @param {Number|Array<Number>} [value=0] - Starting value or set of values
116+
* @param {CPBOptions} [options] - Component's options
95117
*/
96118
constructor (value = 0, options) {
97119
this.options = Object.assign(CircularProgressBar.defaultOptions, options);
98120

99-
this.html = wrap();
121+
this.node = wrap();
100122
const size = `${this.options.size}px`;
101-
this.html.style.width = size;
102-
this.html.style.height = size;
103-
this.html.style.backgroundColor = this.options.background;
123+
this.node.style.width = size;
124+
this.node.style.height = size;
125+
this.node.style.backgroundColor = this.options.background;
104126

105-
this.wrapper = wrap("wrapper", this.html);
127+
this.wrapper = wrap("wrapper", this.node);
106128

107-
this.valueNode = wrap("value", this.html);
129+
this.valueNode = wrap("value", this.node);
108130
this.valueNode.style.backgroundColor = this.options.valueBackground;
109-
const valueSize = `${this.options.size - (this.options.barsWidth * 2)}px`;
131+
const borderWidth = this.options.size * (this.options.barsWidth / 100);
132+
const valueSize = `${this.options.size - (borderWidth * 2)}px`;
110133
this.valueNode.style.width = valueSize;
111134
this.valueNode.style.height = valueSize;
112135
this.valueNode.style.lineHeight = valueSize;
113-
const valueOffset = `${this.options.barsWidth}px`;
136+
const valueOffset = `${borderWidth}px`;
114137
this.valueNode.style.top = valueOffset;
115138
this.valueNode.style.left = valueOffset;
116139
this.valueTextNode = wrap("text", this.valueNode);
@@ -126,40 +149,45 @@ export default class CircularProgressBar {
126149
}
127150

128151
/**
129-
* Change value with only one bar
152+
* Change value using only one bar
130153
* @param {Number} value - Any value
131154
*/
132155
set value (value) {
133156
this.values = [value];
134157
}
135158

136159
/**
137-
* Change values with multiple bars
160+
* Change values using multiple bars
138161
* @param {Array<Number>} values - Any set of value
139162
*/
140163
set values (values) {
141-
if (this.options.showValue) {
142-
this.valueNode.style.visibility = "";
164+
const opts = this.options;
165+
this.valueNode.style.visibility = opts.showValue ? "" : "hidden";
166+
if (opts.showValue) {
143167
const sum = values.reduce((acc, value) => acc + value, 0);
144-
const used = (values.length === 1 ? values[0] : sum);
145-
const displayed = this.options.valueUnit === "%" ? (used / this.options.max) * 100 : used;
146-
this.valueTextNode.textContent = displayed.toFixed(this.options.valueDecimals) + this.options.valueUnit;
147-
}
148-
else {
149-
this.valueNode.style.visibility = "hidden";
168+
const used = Math.min(values.length === 1 ? values[0] : sum, opts.max);
169+
if (used === opts.max && opts.valueWhenDone) {
170+
if (this.valueTextNode.textContent !== opts.valueWhenDone) {
171+
setTimeout(() => this.valueTextNode.textContent = opts.valueWhenDone, opts.transitionTime);
172+
}
173+
}
174+
else {
175+
const displayed = opts.valueUnit === "%" ? (used / opts.max) * 100 : used;
176+
this.valueTextNode.textContent = displayed.toFixed(opts.valueDecimals) + opts.valueUnit;
177+
}
150178
}
151179

152180
let offset = 0;
153181
let lastIndex = 0;
154182
values.forEach((value, index) => {
155183
let bar = this._bars[index];
156184
if (!bar) {
157-
bar = new Bar(this.options.colors[index]);
185+
bar = new Bar(opts.colors[index]);
158186
this._bars.push(bar);
159-
this.wrapper.appendChild(bar.html);
187+
this.wrapper.appendChild(bar.node);
160188
}
161-
const percentage = (value / this.options.max) * 100;
162-
bar.update(percentage, this.options.transitionTime, getLooped(this.options.colors, index), offset);
189+
const percentage = (Math.min(value, opts.max) / opts.max) * 100;
190+
bar.update(percentage, opts.transitionTime, getLooped(opts.colors, index), offset);
163191
offset += percentage;
164192
lastIndex = index;
165193
});
@@ -183,26 +211,34 @@ export default class CircularProgressBar {
183211
}
184212

185213
/**
186-
* Append the component to another element
214+
* Append the component to the DOM
187215
* @param {HTMLElement} parent - Another DOM element
188216
*/
189217
appendTo (parent) {
190-
parent.appendChild(this.html);
218+
parent.appendChild(this.node);
219+
}
220+
221+
/**
222+
* Remove it from the DOM
223+
*/
224+
remove () {
225+
this.node.remove();
191226
}
192227

193228

194229
/**
195230
* @typedef {Object} CPBOptions
196231
* @prop {Number} [size=150] - Component diameter in pixels
197-
* @prop {Number} [barsWidth=10] - Width of bars
232+
* @prop {Number} [barsWidth=7] - Width of bars in % of size
198233
* @prop {Number} [max=100] - Value for a full 360° rotation
199-
* @prop {Boolean} [showValue=true] - Whether or not to display current value inside (if multiple value, sum is displayed)
234+
* @prop {Boolean} [showValue=true] - Whether or not to display current value (if multiple value, sum is displayed)
200235
* @prop {Number} [valueDecimals=0] - Number of decimals to display
201236
* @prop {String} [valueUnit="%"] - Unit used for display (if set to "%", value is calculated over max)
202237
* @prop {String} [valueBackground="#333"] - Background color for value
203238
* @prop {Array<String>} [colors] - Set of colors to use for bars
204-
* @prop {String} [background="#666"] - Background color where there's no bar
239+
* @prop {String} [background="rgba(0, 0, 0, .3)"] - Background color where there's no bar
205240
* @prop {Number} [transitionTime=500] - Transition duration
241+
* @prop {String} [valueWhenDone=""] - Text to display when at 100%
206242
*/
207243
/**
208244
* Returns the default options of the component
@@ -211,15 +247,16 @@ export default class CircularProgressBar {
211247
static get defaultOptions () {
212248
return {
213249
size: 150,
214-
barsWidth: 10,
250+
barsWidth: 7,
215251
max: 100,
216252
showValue: true,
217253
valueDecimals: 0,
218254
valueUnit: "%",
219255
valueBackground: "#333",
220-
colors: ["#ffa114", "#4714ff", "#ff14c8", "#c8ff14", "#ff203a", "#3aff20", "#204dff"],
256+
colors: ["#0095ff", "#ffa114", "#4714ff", "#ff14c8", "#c8ff14", "#204dff", "#ff203a", "#3aff20"],
221257
background: "rgba(0, 0, 0, .3)",
222258
transitionTime: 500,
259+
valueWhenDone: "",
223260
};
224261
}
225262
}

circular-progress-bar.less

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,22 @@
1515
// wrap all bars
1616
.@{prefix}-wrapper {
1717
.full();
18+
overflow: hidden;
1819

1920
// one for every bar
2021
.@{prefix}-bar {
2122
.full();
2223

23-
.@{prefix}-clip {
24+
.@{prefix}-clip, .@{prefix}-part {
2425
.full();
2526
clip-path: inset(0 0 0 50%);
2627
transition: transform ease-out;
2728

28-
.@{prefix}-part {
29-
.full();
30-
clip-path: inset(0 0 0 50%);
31-
transition-timing-function: ease-out;
32-
transition-property: transform, background-color;
33-
// Bar's color is here
34-
}
29+
}
30+
.@{prefix}-clip .@{prefix}-part {
31+
border-radius: 50%;
32+
transition-property: transform, background-color;
33+
// Bar's color is here
3534
}
3635
}
3736
}
@@ -40,9 +39,9 @@
4039
position: absolute;
4140
border-radius: 50%;
4241
text-align: center;
43-
font-family: monospace;
4442

4543
.@{prefix}-text {
44+
font-family: monospace;
4645
color: #FFF;
4746
mix-blend-mode: difference;
4847
}

0 commit comments

Comments
 (0)