Преглед на файлове

ability to create donut chart

tags/1.2.0
ratnajeetshyamkunwar преди 6 години
родител
ревизия
8d00770207
променени са 17 файла, в които са добавени 385 реда и са изтрити 20 реда
  1. +176
    -4
      dist/frappe-charts.esm.js
  2. +1
    -1
      dist/frappe-charts.min.cjs.js
  3. +1
    -1
      dist/frappe-charts.min.cjs.js.map
  4. +1
    -1
      dist/frappe-charts.min.esm.js
  5. +1
    -1
      dist/frappe-charts.min.esm.js.map
  6. +1
    -1
      dist/frappe-charts.min.iife.js
  7. +1
    -1
      dist/frappe-charts.min.iife.js.map
  8. +1
    -1
      docs/assets/js/frappe-charts.min.js
  9. +1
    -1
      docs/assets/js/frappe-charts.min.js.map
  10. +12
    -1
      docs/assets/js/index.min.js
  11. +1
    -1
      docs/assets/js/index.min.js.map
  12. +3
    -2
      docs/index.html
  13. +3
    -1
      src/js/chart.js
  14. +154
    -0
      src/js/charts/DonutChart.js
  15. +14
    -0
      src/js/objects/ChartComponents.js
  16. +2
    -1
      src/js/utils/constants.js
  17. +12
    -2
      src/js/utils/draw.js

+ 176
- 4
dist/frappe-charts.esm.js Целия файл

@@ -166,7 +166,8 @@ const DEFAULT_COLORS = {
line: DEFAULT_CHART_COLORS,
pie: DEFAULT_CHART_COLORS,
percentage: DEFAULT_CHART_COLORS,
heatmap: HEATMAP_COLORS_GREEN
heatmap: HEATMAP_COLORS_GREEN,
donut: DEFAULT_CHART_COLORS
};

// Universal constants
@@ -516,13 +517,14 @@ function makeSVGGroup(className, transform='', parent=undefined) {



function makePath(pathStr, className='', stroke='none', fill='none') {
function makePath(pathStr, className='', stroke='none', fill='none', strokeWidth=0) {
return createSVG('path', {
className: className,
d: pathStr,
styles: {
stroke: stroke,
fill: fill
fill: fill,
'stroke-width': strokeWidth
}
});
}
@@ -537,6 +539,15 @@ function makeArcPathStr(startPosition, endPosition, center, radius, clockWise=1)
${arcEndX} ${arcEndY} z`;
}

function makeArcStrokePathStr(startPosition, endPosition, center, radius, clockWise=1){
let [arcStartX, arcStartY] = [center.x + startPosition.x, center.y + startPosition.y];
let [arcEndX, arcEndY] = [center.x + endPosition.x, center.y + endPosition.y];

return `M${arcStartX} ${arcStartY}
A ${radius} ${radius} 0 0 ${clockWise ? 1 : 0}
${arcEndX} ${arcEndY}`;
}

function makeGradient(svgDefElem, color, lighter = false) {
let gradientId ='path-fill-gradient' + '-' + color + '-' +(lighter ? 'lighter' : 'default');
let gradientDef = renderVerticalGradient(svgDefElem, gradientId);
@@ -1878,6 +1889,20 @@ class ChartComponent {
}

let componentConfigs = {
donutSlices: {
layerClass: 'donut-slices',
makeElements(data) {
return data.sliceStrings.map((s, i) => {
let slice = makePath(s, 'donut-path', data.colors[i], 'none', data.strokeWidth);
slice.style.transition = 'transform .3s;';
return slice;
});
},

animateElements(newData) {
return this.store.map((slice, i) => animatePathStr(slice, newData.sliceStrings[i]));
},
},
pieSlices: {
layerClass: 'pie-slices',
makeElements(data) {
@@ -3677,6 +3702,152 @@ class AxisChart extends BaseChart {
// removeDataPoint(index = 0) {}
}

class DonutChart extends AggregationChart {
constructor(parent, args) {
super(parent, args);
this.type = 'donut';
this.initTimeout = 0;
this.init = 1;

this.setup();
}

configure(args) {
super.configure(args);
this.mouseMove = this.mouseMove.bind(this);
this.mouseLeave = this.mouseLeave.bind(this);

this.hoverRadio = args.hoverRadio || 0.1;
this.config.startAngle = args.startAngle || 0;

this.clockWise = args.clockWise || false;
this.strokeWidth = args.strokeWidth || 30;
}

calc() {
super.calc();
let s = this.state;
this.radius = (this.height > this.width ? this.center.x - (this.strokeWidth / 2) : this.center.y - (this.strokeWidth / 2));

const { radius, clockWise } = this;

const prevSlicesProperties = s.slicesProperties || [];
s.sliceStrings = [];
s.slicesProperties = [];
let curAngle = 180 - this.config.startAngle;

s.sliceTotals.map((total, i) => {
const startAngle = curAngle;
const originDiffAngle = (total / s.grandTotal) * FULL_ANGLE;
const diffAngle = clockWise ? -originDiffAngle : originDiffAngle;
const endAngle = curAngle = curAngle + diffAngle;
const startPosition = getPositionByAngle(startAngle, radius);
const endPosition = getPositionByAngle(endAngle, radius);

const prevProperty = this.init && prevSlicesProperties[i];

let curStart,curEnd;
if(this.init) {
curStart = prevProperty ? prevProperty.startPosition : startPosition;
curEnd = prevProperty ? prevProperty.endPosition : startPosition;
} else {
curStart = startPosition;
curEnd = endPosition;
}
const curPath = makeArcStrokePathStr(curStart, curEnd, this.center, this.radius, this.clockWise);

s.sliceStrings.push(curPath);
s.slicesProperties.push({
startPosition,
endPosition,
value: total,
total: s.grandTotal,
startAngle,
endAngle,
angle: diffAngle
});

});
this.init = 0;
}

setupComponents() {
let s = this.state;

let componentConfigs = [
[
'donutSlices',
{ },
function() {
return {
sliceStrings: s.sliceStrings,
colors: this.colors,
strokeWidth: this.strokeWidth,
};
}.bind(this)
]
];

this.components = new Map(componentConfigs
.map(args => {
let component = getComponent(...args);
return [args[0], component];
}));
}

calTranslateByAngle(property){
const{radius,hoverRadio} = this;
const position = getPositionByAngle(property.startAngle+(property.angle / 2),radius);
return `translate3d(${(position.x) * hoverRadio}px,${(position.y) * hoverRadio}px,0)`;
}

hoverSlice(path,i,flag,e){
if(!path) return;
const color = this.colors[i];
if(flag) {
transform(path, this.calTranslateByAngle(this.state.slicesProperties[i]));
path.style.stroke = lightenDarkenColor(color, 50);
let g_off = getOffset(this.svg);
let x = e.pageX - g_off.left + 10;
let y = e.pageY - g_off.top - 10;
let title = (this.formatted_labels && this.formatted_labels.length > 0
? this.formatted_labels[i] : this.state.labels[i]) + ': ';
let percent = (this.state.sliceTotals[i] * 100 / this.state.grandTotal).toFixed(1);
this.tip.setValues(x, y, {name: title, value: percent + "%"});
this.tip.showTip();
} else {
transform(path,'translate3d(0,0,0)');
this.tip.hideTip();
path.style.stroke = color;
}
}

bindTooltip() {
this.container.addEventListener('mousemove', this.mouseMove);
this.container.addEventListener('mouseleave', this.mouseLeave);
}

mouseMove(e){
const target = e.target;
let slices = this.components.get('donutSlices').store;
let prevIndex = this.curActiveSliceIndex;
let prevAcitve = this.curActiveSlice;
if(slices.includes(target)) {
let i = slices.indexOf(target);
this.hoverSlice(prevAcitve, prevIndex,false);
this.curActiveSlice = target;
this.curActiveSliceIndex = i;
this.hoverSlice(target, i, true, e);
} else {
this.mouseLeave();
}
}

mouseLeave(){
this.hoverSlice(this.curActiveSlice,this.curActiveSliceIndex,false);
}
}

// import MultiAxisChart from './charts/MultiAxisChart';
const chartTypes = {
bar: AxisChart,
@@ -3684,7 +3855,8 @@ const chartTypes = {
// multiaxis: MultiAxisChart,
percentage: PercentageChart,
heatmap: Heatmap,
pie: PieChart
pie: PieChart,
donut: DonutChart,
};

function getChartByType(chartType = 'line', parent, options) {


+ 1
- 1
dist/frappe-charts.min.cjs.js
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 1
- 1
dist/frappe-charts.min.cjs.js.map
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 1
- 1
dist/frappe-charts.min.esm.js
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 1
- 1
dist/frappe-charts.min.esm.js.map
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 1
- 1
dist/frappe-charts.min.iife.js
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 1
- 1
dist/frappe-charts.min.iife.js.map
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 1
- 1
docs/assets/js/frappe-charts.min.js
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 1
- 1
docs/assets/js/frappe-charts.min.js.map
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 12
- 1
docs/assets/js/index.min.js Целия файл

@@ -46,6 +46,12 @@ var HEATMAP_COLORS_YELLOW = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001

// Universal constants

/**
* Returns the value of a number upto 2 decimal places.
* @param {Number} d Any number
*/


/**
* Returns whether or not two given arrays are equal.
* @param {Array} arr1 First array
@@ -114,7 +120,6 @@ var MONTH_NAMES_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",



// https://stackoverflow.com/a/11252167/6495043


function clone(date) {
@@ -155,6 +160,8 @@ function addDays(date, numberOfDays) {
date.setDate(date.getDate() + numberOfDays);
}

// Composite Chart
// ================================================================================
var reportCountList = [152, 222, 199, 287, 534, 709, 1179, 1256, 1632, 1856, 1850];

var lineCompositeData = {
@@ -324,6 +331,9 @@ var demoConfig = {
}
};

// import { lineComposite, barComposite } from './demoConfig';
// ================================================================================

var Chart = frappe.Chart; // eslint-disable-line no-undef

var lc = demoConfig.lineComposite;
@@ -670,3 +680,4 @@ document.querySelector('.export-heatmap').addEventListener('click', function ()
});

}());
//# sourceMappingURL=index.min.js.map

+ 1
- 1
docs/assets/js/index.min.js.map
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 3
- 2
docs/index.html Целия файл

@@ -65,7 +65,7 @@ redirect_to: "https://frappe.io/charts"
},

title: "My Awesome Chart",
type: 'axis-mixed', // or 'bar', 'line', 'pie', 'percentage'
type: 'axis-mixed', // or 'bar', 'line', 'pie', 'percentage', 'donut'
height: 300,
colors: ['purple', '#ffa3ef', 'light-blue'],

@@ -82,6 +82,7 @@ redirect_to: "https://frappe.io/charts"
<div class="btn-group aggr-type-buttons margin-top mx-auto" role="group">
<button type="button" class="btn btn-sm btn-secondary active" data-type='axis-mixed'>Mixed</button>
<button type="button" class="btn btn-sm btn-secondary" data-type='pie'>Pie Chart</button>
<button type="button" class="btn btn-sm btn-secondary" data-type='donut'>Donut Chart</button>
<button type="button" class="btn btn-sm btn-secondary" data-type='percentage'>Percentage Chart</button>
</div>
<div class="btn-group export-buttons margin-top mx-auto" role="group">
@@ -233,7 +234,7 @@ redirect_to: "https://frappe.io/charts"
// default: 0
},

// Pie/Percentage charts
// Pie/Percentage/Donut charts
maxLegendPoints: 6, // default: 20
maxSlices: 10, // default: 20



+ 3
- 1
src/js/chart.js Целия файл

@@ -5,6 +5,7 @@ import PercentageChart from './charts/PercentageChart';
import PieChart from './charts/PieChart';
import Heatmap from './charts/Heatmap';
import AxisChart from './charts/AxisChart';
import DonutChart from './charts/DonutChart';

const chartTypes = {
bar: AxisChart,
@@ -12,7 +13,8 @@ const chartTypes = {
// multiaxis: MultiAxisChart,
percentage: PercentageChart,
heatmap: Heatmap,
pie: PieChart
pie: PieChart,
donut: DonutChart,
};

function getChartByType(chartType = 'line', parent, options) {


+ 154
- 0
src/js/charts/DonutChart.js Целия файл

@@ -0,0 +1,154 @@
import AggregationChart from './AggregationChart';
import { getComponent } from '../objects/ChartComponents';
import { getOffset } from '../utils/dom';
import { getPositionByAngle } from '../utils/helpers';
import { makeArcStrokePathStr } from '../utils/draw';
import { lightenDarkenColor } from '../utils/colors';
import { transform } from '../utils/animation';
import { FULL_ANGLE } from '../utils/constants';

export default class DonutChart extends AggregationChart {
constructor(parent, args) {
super(parent, args);
this.type = 'donut';
this.initTimeout = 0;
this.init = 1;

this.setup();
}

configure(args) {
super.configure(args);
this.mouseMove = this.mouseMove.bind(this);
this.mouseLeave = this.mouseLeave.bind(this);

this.hoverRadio = args.hoverRadio || 0.1;
this.config.startAngle = args.startAngle || 0;

this.clockWise = args.clockWise || false;
this.strokeWidth = args.strokeWidth || 30;
}

calc() {
super.calc();
let s = this.state;
this.radius = (this.height > this.width ? this.center.x - (this.strokeWidth / 2) : this.center.y - (this.strokeWidth / 2));

const { radius, clockWise } = this;

const prevSlicesProperties = s.slicesProperties || [];
s.sliceStrings = [];
s.slicesProperties = [];
let curAngle = 180 - this.config.startAngle;

s.sliceTotals.map((total, i) => {
const startAngle = curAngle;
const originDiffAngle = (total / s.grandTotal) * FULL_ANGLE;
const diffAngle = clockWise ? -originDiffAngle : originDiffAngle;
const endAngle = curAngle = curAngle + diffAngle;
const startPosition = getPositionByAngle(startAngle, radius);
const endPosition = getPositionByAngle(endAngle, radius);

const prevProperty = this.init && prevSlicesProperties[i];

let curStart,curEnd;
if(this.init) {
curStart = prevProperty ? prevProperty.startPosition : startPosition;
curEnd = prevProperty ? prevProperty.endPosition : startPosition;
} else {
curStart = startPosition;
curEnd = endPosition;
}
const curPath = makeArcStrokePathStr(curStart, curEnd, this.center, this.radius, this.clockWise);

s.sliceStrings.push(curPath);
s.slicesProperties.push({
startPosition,
endPosition,
value: total,
total: s.grandTotal,
startAngle,
endAngle,
angle: diffAngle
});

});
this.init = 0;
}

setupComponents() {
let s = this.state;

let componentConfigs = [
[
'donutSlices',
{ },
function() {
return {
sliceStrings: s.sliceStrings,
colors: this.colors,
strokeWidth: this.strokeWidth,
};
}.bind(this)
]
];

this.components = new Map(componentConfigs
.map(args => {
let component = getComponent(...args);
return [args[0], component];
}));
}

calTranslateByAngle(property){
const{radius,hoverRadio} = this;
const position = getPositionByAngle(property.startAngle+(property.angle / 2),radius);
return `translate3d(${(position.x) * hoverRadio}px,${(position.y) * hoverRadio}px,0)`;
}

hoverSlice(path,i,flag,e){
if(!path) return;
const color = this.colors[i];
if(flag) {
transform(path, this.calTranslateByAngle(this.state.slicesProperties[i]));
path.style.stroke = lightenDarkenColor(color, 50);
let g_off = getOffset(this.svg);
let x = e.pageX - g_off.left + 10;
let y = e.pageY - g_off.top - 10;
let title = (this.formatted_labels && this.formatted_labels.length > 0
? this.formatted_labels[i] : this.state.labels[i]) + ': ';
let percent = (this.state.sliceTotals[i] * 100 / this.state.grandTotal).toFixed(1);
this.tip.setValues(x, y, {name: title, value: percent + "%"});
this.tip.showTip();
} else {
transform(path,'translate3d(0,0,0)');
this.tip.hideTip();
path.style.stroke = color;
}
}

bindTooltip() {
this.container.addEventListener('mousemove', this.mouseMove);
this.container.addEventListener('mouseleave', this.mouseLeave);
}

mouseMove(e){
const target = e.target;
let slices = this.components.get('donutSlices').store;
let prevIndex = this.curActiveSliceIndex;
let prevAcitve = this.curActiveSlice;
if(slices.includes(target)) {
let i = slices.indexOf(target);
this.hoverSlice(prevAcitve, prevIndex,false);
this.curActiveSlice = target;
this.curActiveSliceIndex = i;
this.hoverSlice(target, i, true, e);
} else {
this.mouseLeave();
}
}

mouseLeave(){
this.hoverSlice(this.curActiveSlice,this.curActiveSliceIndex,false);
}
}

+ 14
- 0
src/js/objects/ChartComponents.js Целия файл

@@ -69,6 +69,20 @@ class ChartComponent {
}

let componentConfigs = {
donutSlices: {
layerClass: 'donut-slices',
makeElements(data) {
return data.sliceStrings.map((s, i) => {
let slice = makePath(s, 'donut-path', data.colors[i], 'none', data.strokeWidth);
slice.style.transition = 'transform .3s;';
return slice;
});
},

animateElements(newData) {
return this.store.map((slice, i) => animatePathStr(slice, newData.sliceStrings[i]));
},
},
pieSlices: {
layerClass: 'pie-slices',
makeElements(data) {


+ 2
- 1
src/js/utils/constants.js Целия файл

@@ -98,7 +98,8 @@ export const DEFAULT_COLORS = {
line: DEFAULT_CHART_COLORS,
pie: DEFAULT_CHART_COLORS,
percentage: DEFAULT_CHART_COLORS,
heatmap: HEATMAP_COLORS_GREEN
heatmap: HEATMAP_COLORS_GREEN,
donut: DEFAULT_CHART_COLORS
};

// Universal constants


+ 12
- 2
src/js/utils/draw.js Целия файл

@@ -98,13 +98,14 @@ export function wrapInSVGGroup(elements, className='') {
return g;
}

export function makePath(pathStr, className='', stroke='none', fill='none') {
export function makePath(pathStr, className='', stroke='none', fill='none', strokeWidth=0) {
return createSVG('path', {
className: className,
d: pathStr,
styles: {
stroke: stroke,
fill: fill
fill: fill,
'stroke-width': strokeWidth
}
});
}
@@ -119,6 +120,15 @@ export function makeArcPathStr(startPosition, endPosition, center, radius, clock
${arcEndX} ${arcEndY} z`;
}

export function makeArcStrokePathStr(startPosition, endPosition, center, radius, clockWise=1){
let [arcStartX, arcStartY] = [center.x + startPosition.x, center.y + startPosition.y];
let [arcEndX, arcEndY] = [center.x + endPosition.x, center.y + endPosition.y];

return `M${arcStartX} ${arcStartY}
A ${radius} ${radius} 0 0 ${clockWise ? 1 : 0}
${arcEndX} ${arcEndY}`;
}

export function makeGradient(svgDefElem, color, lighter = false) {
let gradientId ='path-fill-gradient' + '-' + color + '-' +(lighter ? 'lighter' : 'default');
let gradientDef = renderVerticalGradient(svgDefElem, gradientId);


Зареждане…
Отказ
Запис