Skip to content
Merged
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ After you have initialized the component, use it everywhere you want in your app
dash="60 0.9"
animation="reverse 700 400"
:noData="false"
:loading="false"
:loading="false"
:loader="{ color: 'green' }"
fontColor="white"
:half="false"
:gap="10"
Expand Down Expand Up @@ -115,6 +116,7 @@ This table below provides a quick overview over all available options. To gain m
| **[`legendFormatter`](#legendformatter)** [![npm](https://img.shields.io/badge/v1.3.0-blue?style=flat-square)](#legendformatter) | Function | Function that returns formatted value | |
| **[`animation`](#animation)** | String | "default \| rs \| loop \| reverse \| bounce [duration delay]" | "default 1000 400"|
| **[`loading`](#loading)** | Boolean | |false|
| **[`loader`](#loading)** | Object | { [thickness, color, lineMode, line, opacity ]} | |
| **[`determinate`](#determinate)** | Boolean | |false|
| **[`noData`](#nodata)** | Boolean | |false|
| **[`angle`](#angle)** | Number | any Number |-90|
Expand Down Expand Up @@ -406,6 +408,21 @@ Forces loading state. The component provides an indeterminate loading state for

<br>

- ### `loader`

With this option defined as Object you can customize the loading circle that is shown in the states
[loading](#loading) and [determinate](#determinate). Accepted properties are [`color`](#color), [`thickness`](#thickness), [`line`](#line),
[`lineMode`](#linemode) and `opactity`. `opacity` is specific for loading circle and can be any valid CSS opacity value. If the option is not specified, the loading circle replicates the progress circle with a 0.55 default value for `opacity`.

###### Example: :scroll:

```vue
<vue-ellipse-progress :loader="{ color: 'green', lineMode: 'in 10', opacity: '0.6' }" />

```

<br>

- ### `determinate`

Provides a determinate loading state that indicates that your data loading is still in progress but allows to show the **[`progress`](#progress)**.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vue-ellipse-progress",
"version": "2.0.0-alpha.2",
"version": "2.0.0-alpha.3",
"private": false,
"description": "A Vue.js component to create beautiful animated circular progress bars",
"main": "./dist/vue-ellipse-progress.umd.min.js",
Expand Down
2 changes: 2 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
:thickness="20"
:empty-thickness="10"
dot="10 red"
:loader="{ thickness: 40, color: 'red' }"
line-mode="bottom"
:no-data="noData"
:determinate="determinate"
Expand All @@ -73,6 +74,7 @@
line-mode="out 20"
:no-data="noData"
:determinate="determinate"
:loader="{ thickness: 40, color: 'red' }"
>
<template v-slot:legend-caption>
<p slot="legend-caption">TASKS DONE</p>
Expand Down
24 changes: 3 additions & 21 deletions src/components/Circle/Circle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,7 @@
</circle>
<fade-in-transition>
<g v-if="isLoading">
<g class="ep-circle--loading__container" :style="{ opacity: `${options.loading ? 1 : 0.45}` }">
<circle
class="ep-circle--loading animation__loading"
:r="radius"
:cx="position"
:cy="position"
fill="transparent"
:stroke="computedColor"
:stroke-width="thickness"
:stroke-linecap="options.line"
:stroke-dasharray="circumference"
:style="{
transitionTimingFunction: styles.transitionTimingFunction,
transformOrigin: styles.transformOrigin,
'--ep-loading-stroke-offset': styles['--ep-loading-stroke-offset'],
'--ep-circumference': styles['--ep-circumference'],
}"
>
</circle>
</g>
<circle-loader :options="options.loader" />
</g>
</fade-in-transition>
<circle
Expand All @@ -67,10 +48,11 @@
<script>
import CircleMixin from "./circleMixin";
import FadeInTransition from "../FadeInTransition.vue";
import CircleLoader from "./CircleLoader.vue";

export default {
name: "CircleProgress",
components: { FadeInTransition },
components: { CircleLoader, FadeInTransition },
mixins: [CircleMixin],
computed: {
position() {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Circle/CircleContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import CircleProgress from "./Circle.vue";
import CircleDot from "./CircleDot.vue";

export default {
name: "EpCircleContainer",
name: "CircleContainer",
components: { CircleDot, CircleProgress, HalfCircleProgress, Gradient },
props: {
options: {
Expand Down
44 changes: 44 additions & 0 deletions src/components/Circle/CircleLoader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<template>
<g class="ep-circle--loader__container" :style="{ opacity: opacity }">
<circle
class="ep-circle--loader animation__loading"
:r="radius"
:cx="position"
:cy="position"
fill="transparent"
:stroke="computedColor"
:stroke-width="thickness"
:stroke-linecap="options.line"
:stroke-dasharray="circumference"
:style="{
transitionTimingFunction: styles.transitionTimingFunction,
transformOrigin: styles.transformOrigin,
'--ep-loading-stroke-offset': styles['--ep-loading-stroke-offset'],
'--ep-circumference': styles['--ep-circumference'],
}"
>
</circle>
</g>
</template>

<script>
import CircleMixin from "./circleMixin";

export default {
name: "CircleLoader",
mixins: [CircleMixin],
computed: {
position() {
return this.options.size / 2;
},
circumference() {
return this.radius * 2 * Math.PI;
},
opacity() {
return this.options.opacity && this.options.opacity >= 0 ? this.options.opacity : 0.55;
},
},
};
</script>

<style scoped lang="scss"></style>
23 changes: 3 additions & 20 deletions src/components/Circle/HalfCircle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,7 @@
</path>
<fade-in-transition>
<g v-if="isLoading">
<g :style="{ opacity: `${options.loading ? 1 : 0.45}` }">
<path
:stroke-width="thickness"
class="ep-half-circle--loading animation__loading"
:d="path"
:fill="computedColorFill"
:stroke="computedColor"
:stroke-dasharray="circumference"
:stroke-linecap="options.line"
:style="{
transitionTimingFunction: styles.transitionTimingFunction,
transformOrigin: styles.transformOrigin,
'--ep-loading-stroke-offset': styles['--ep-loading-stroke-offset'],
'--ep-circumference': styles['--ep-circumference'],
'--ep-negative-circumference': styles['--ep-negative-circumference'],
}"
>
</path>
</g>
<half-circle-loader :options="options.loader" />
</g>
</fade-in-transition>

Expand All @@ -63,10 +45,11 @@
<script>
import CircleMixin from "./circleMixin";
import FadeInTransition from "../FadeInTransition.vue";
import HalfCircleLoader from "./HalfCircleLoader.vue";

export default {
name: "HalfCircleProgress",
components: { FadeInTransition },
components: { HalfCircleLoader, FadeInTransition },
mixins: [CircleMixin],
computed: {
circumference() {
Expand Down
57 changes: 57 additions & 0 deletions src/components/Circle/HalfCircleLoader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<template>
<g :style="{ opacity: opacity }">
<path
:stroke-width="thickness"
class="ep-half-circle--loader animation__loading"
:d="path"
:fill="computedColorFill"
:stroke="computedColor"
:stroke-dasharray="circumference"
:stroke-linecap="options.line"
:style="{
transitionTimingFunction: styles.transitionTimingFunction,
transformOrigin: styles.transformOrigin,
'--ep-loading-stroke-offset': styles['--ep-loading-stroke-offset'],
'--ep-circumference': styles['--ep-circumference'],
'--ep-negative-circumference': styles['--ep-negative-circumference'],
}"
>
</path>
</g>
</template>
<script>
import CircleMixin from "./circleMixin";

export default {
name: "HalfCircleLoader",
mixins: [CircleMixin],
computed: {
circumference() {
return (this.radius * 2 * Math.PI) / 2;
},
path() {
return ` M ${this.position}, ${this.options.size / 2} a ${this.radius},${this.radius} 0 1,1 ${this.radius * 2},0`;
},
emptyPath() {
return ` M ${this.emptyPosition}, ${this.options.size / 2} a ${this.emptyRadius},${this.emptyRadius} 0 1,1 ${
this.emptyRadius * 2
},0`;
},
position() {
return this.options.size / 2 - this.radius;
},
emptyPosition() {
return this.options.size / 2 - this.emptyRadius;
},
opacity() {
return this.options.opacity && this.options.opacity >= 0 ? this.options.opacity : 0.55;
},
},
};
</script>

<style scoped lang="scss">
g.ep-half-circle {
transform-origin: 50% 50%;
}
</style>
24 changes: 13 additions & 11 deletions src/components/VueEllipseProgress.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { getNumberIfValid, isValidNumber } from "../utils";
import props from "./interface";
import CircleContainer from "./Circle/CircleContainer.vue";
import Counter from "./Counter.vue";
import parseOptions from "./optionsParser";
import { parseOptions, calcThickness, lineModeParser } from "./optionsParser";

export default {
name: "VueEllipseProgress",
Expand Down Expand Up @@ -84,17 +84,19 @@ export default {
const previousCircles = [];
for (let i = 0; i < this.circlesData.length; i++) {
const options = this.circlesData[i];
normalizedCircles.push({
...parseOptions({
index: i,
id: i,
...options,
globalDot: this.dot,
globalGap: this.gap,
globalThickness: this.thickness,
previousCircles: [...previousCircles],
}),
const parsedOptions = parseOptions({
index: i,
id: i,
...options,
globalDot: this.dot,
globalGap: this.gap,
globalThickness: this.thickness,
previousCircles: [...previousCircles],
});
const loaderOptions = { ...parsedOptions, ...parsedOptions.loader };
loaderOptions.thickness = calcThickness(loaderOptions.thickness, parsedOptions.size);
loaderOptions.lineMode = parsedOptions.loader.lineMode ? lineModeParser(loaderOptions) : parsedOptions.lineMode;
normalizedCircles.push({ ...parsedOptions, loader: loaderOptions });
const { gap, thickness, dot } = normalizedCircles[i];
previousCircles.push({ gap, thickness, dot });
}
Expand Down
21 changes: 20 additions & 1 deletion src/components/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ const colorConfig = (defaultColor = "transparent") => ({
},
});

export default {
const validateLoaderProps = (loaderOptions) =>
Object.keys(loaderOptions).every((p) => options[p].validator(loaderOptions[p]));

const options = {
data: {
type: Array,
required: false,
Expand Down Expand Up @@ -164,4 +167,20 @@ export default {
type: Function,
required: false,
},
loader: {
type: Object,
required: false,
default: () => ({}),
validator: (value) => {
const propsAllowed = Object.keys(value).every((prop) =>
["thickness", "color", "lineMode", "line", "opacity"].includes(prop)
);
if (propsAllowed) {
return validateLoaderProps(value);
}
return false;
},
},
};

export default options;
6 changes: 3 additions & 3 deletions src/components/optionsParser.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getNumberIfValid, isValidNumber } from "@/utils";

const lineModeParser = (options) => {
export const lineModeParser = (options) => {
const lineModeConfig = options.lineMode.trim().split(" ");
const mode = options.multiple ? "multiple" : lineModeConfig[0];
return {
Expand Down Expand Up @@ -49,12 +49,12 @@ const dotParser = (dot) => {
};
};

const calcThickness = (thickness, size) => {
export const calcThickness = (thickness, size) => {
const value = parseFloat(thickness);
return thickness.toString().includes("%") ? (value * size) / 100 : value;
};

export default (options) => {
export const parseOptions = (options) => {
const dot = dotParser(options.dot);
const globalDot = dotParser(options.globalDot);
return {
Expand Down
4 changes: 2 additions & 2 deletions src/styles/animationsUsage.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
}
}

.ep-circle--loading {
.ep-circle--loader {
&.animation__loading {
animation-name: ep-progress--loading, ep-progress--loading__rotation;
animation-iteration-count: infinite !important;
Expand All @@ -26,7 +26,7 @@
}
}

.ep-half-circle--loading {
.ep-half-circle--loader {
&.animation__loading {
animation-name: ep-half-progress--loading;
animation-iteration-count: infinite !important;
Expand Down