Skip to content
322 changes: 316 additions & 6 deletions flotr2.js
Original file line number Diff line number Diff line change
Expand Up @@ -2609,6 +2609,7 @@ Graph.prototype = {
textEnabled : this.textEnabled,
htmlText : this.options.HtmlText,
text : this._text, // TODO Is this necessary?
labelText : series.label,
element : this.el,
data : series.data,
color : series.color,
Expand All @@ -2622,10 +2623,21 @@ Graph.prototype = {
options = flotr.merge(type, options);

// Fill
options.fillStyle = this.processColor(
type.fillColor || series.color,
{opacity: type.fillOpacity}
);
var color = type.fillColor || series.color;
var colorType = color.substring( 0, color.indexOf('('));
if (colorType == 'rgba') {
// Color already has alpha, use it
var colorAlpha = color.substring( color.lastIndexOf(',')+1, color.lastIndexOf(')')).replace(/ /g,''); // Get the alpha value (if it exists), and remove whitespace
options.fillStyle = this.processColor(
color,
{opacity: colorAlpha}
);
} else {
options.fillStyle = this.processColor(
color,
{opacity: type.fillOpacity}
);
}

return options;
},
Expand Down Expand Up @@ -4765,7 +4777,7 @@ function isImage (i) {
var
_ = Flotr._;

Flotr.defaultPieLabelFormatter = function (total, value) {
Flotr.defaultPieLabelFormatter = function (total, value, labelText) {
return (100 * value / total).toFixed(2)+'%';
};

Expand Down Expand Up @@ -4809,7 +4821,8 @@ Flotr.addType('pie', {
startAngle = this.startAngle || (2 * Math.PI * options.startAngle), // TODO: this initial startAngle is already in radians (fixing will be test-unstable)
endAngle = startAngle + measure,
bisection = startAngle + measure / 2,
label = options.labelFormatter(this.total, value),
//label = options.labelFormatter(this.total, value),
label = options.labelFormatter(this.total, value, options.labelText),
//plotTickness = Math.sin(series.pie.viewAngle)*series.pie.spliceThickness / vScale;
explodeCoeff = explode + radius + 4,
distX = Math.cos(bisection) * explodeCoeff,
Expand Down Expand Up @@ -4968,6 +4981,262 @@ Flotr.addType('pie', {
});
})();

/**
* Donut
*
* Formats the donuts labels.
* @param {Object} slice - Slice object
* @return {String} Formatted donut label string
*/
(function () {

var
_ = Flotr._;

Flotr.defaultDonutLabelFormatter = function (total, value, labelText) {
return (100 * value / total).toFixed(2)+'%';
};

Flotr.addType('donut', {
options: {
show: false, // => setting to true will show bars, false will hide
lineWidth: 1, // => in pixels
fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill
fillColor: null, // => fill color
fillOpacity: 0.6, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
explode: 6, // => the number of pixels the splices will be far from the center
sizeRatio: 0.6, // => the size ratio of the donut relative to the plot
startAngle: Math.PI/4, // => the first slice start angle
labelFormatter: Flotr.defaultDonutLabelFormatter,
epsilon: 0.1, // => how close do you have to get to hit empty slice
sliceThickness: 20 // => thickness of each slice in the donut
},

startAngle: [],
total: [],

draw : function (options) {

var startAngle = [];
var endAngle = [];

var
data = options.data,
context = options.context,
lineWidth = options.lineWidth,
shadowSize = options.shadowSize,
sizeRatio = options.sizeRatio,
height = options.height,
width = options.width,
explode = options.explode,
color = options.color,
fill = options.fill,
fillStyle = options.fillStyle,
radius = Math.min(width, height) * sizeRatio / 2,
thickness = options.sliceThickness,
value = data[0][1],
layer = data[0][0],
html = [],
vScale = 1,
measure = Math.PI * 2 * value / this.total[layer];
startAngle[layer] = this.startAngle[layer] || (2 * Math.PI * options.startAngle); // TODO: this initial startAngle is already in radians (fixing will be test-unstable)
endAngle[layer] = startAngle[layer] + measure;
var
bisection = startAngle[layer] + measure / 2,
label = options.labelFormatter(this.total[layer], value, options.labelText),
explodeCoeff = explode + radius + 4,
distX = Math.cos(bisection) * explodeCoeff,
distY = Math.sin(bisection) * explodeCoeff,
textAlign = distX < 0 ? 'right' : 'left',
textBaseline = distY > 0 ? 'top' : 'bottom',
style,
x, y;

context.save();
context.translate(width / 2, height / 2);
context.scale(1, vScale);

x = Math.cos(bisection) * explode;
y = Math.sin(bisection) * explode;

// Shadows
if (shadowSize > 0) {
this.plotSlice(x + shadowSize, y + shadowSize, radius, thickness, layer, startAngle[layer], endAngle[layer], context);
if (fill) {
context.fillStyle = 'rgba(0,0,0,0.1)';
context.fill();
}
}

if (value > 0) {
this.plotSlice(x, y, radius, thickness, layer, startAngle[layer], endAngle[layer], context);
if (fill) {
context.fillStyle = fillStyle;
context.fill();
}
context.lineWidth = lineWidth;
context.strokeStyle = color;
context.stroke();
}

style = {
size : options.fontSize * 1.2,
color : options.fontColor,
weight : 1.5
};

if (label) {
if (options.htmlText || !options.textEnabled) {
// iterate through slices and check for overlap
_.each(this.slices, function (slice){
// Get old bisection
sliceBisect = slice.start + (slice.end-slice.start) / 2;
// Check if close
if (sliceBisect-bisection < 0 && sliceBisect-bisection > -0.3) {
// Values close, move bisection up
bisection += 0.3;
adjustValues();
} else if (sliceBisect-bisection < 0.3 && sliceBisect-bisection >= 0) {
// Values close, move bisection down
bisection -= 0.3;
adjustValues();
}

function adjustValues() {
// Adjust new values
distX = Math.cos(bisection) * explodeCoeff;
distY = Math.sin(bisection) * explodeCoeff;
textAlign = distX < 0 ? 'right' : 'left';
textBaseline = distY > 0 ? 'top' : 'bottom';
}
});

divStyle = 'position:absolute;' + textBaseline + ':' + (height / 2 + (textBaseline === 'top' ? distY : -distY)) + 'px;';
divStyle += textAlign + ':' + (width / 2 + (textAlign === 'right' ? -distX : distX)) + 'px;';
html.push('<div style="', divStyle, '" class="flotr-grid-label">', label, '</div>');
}
else {
style.textAlign = textAlign;
style.textBaseline = textBaseline;
Flotr.drawText(context, label, distX, distY, style);
}
}

if (options.htmlText || !options.textEnabled) {
var div = Flotr.DOM.node('<div style="color:' + options.fontColor + '" class="flotr-labels"></div>');
Flotr.DOM.insert(div, html.join(''));
Flotr.DOM.insert(options.element, div);
}

context.restore();

// New start angle
this.startAngle[layer] = endAngle[layer];
this.slices = this.slices || [];
this.slices.push({
radius : radius,
x : x,
y : y,
explode : explode,
start : startAngle[layer],
end : endAngle[layer]
});
},
plotSlice : function (x, y, radius, thickness, layer, startAngle, endAngle, context) {
// Start path
context.beginPath();
// Move to inside arc start pt
context.moveTo(x+Math.cos(startAngle)*(radius-thickness*(layer+1)), y+Math.sin(startAngle)*(radius-thickness*(layer+1)));
// Line to outside arc start pt
context.lineTo(x+Math.cos(startAngle)*(radius-thickness*(layer)), y+Math.sin(startAngle)*(radius-thickness*(layer)));
// Outside arc
context.arc(x, y, radius-thickness*(layer), startAngle, endAngle, false);
// Line to inside arc end pt
context.lineTo(x+Math.cos(endAngle)*(radius-thickness*(layer+1)), y+Math.sin(endAngle)*(radius-thickness*(layer+1)));
// Inside arc
context.arc(x, y, radius-thickness*(layer+1), endAngle, startAngle, true);
// Close
context.closePath();
},
hit : function (options) {

var
data = options.data[0],
args = options.args,
index = options.index,
mouse = args[0],
n = args[1],
slice = this.slices[index],
x = mouse.relX - options.width / 2,
y = mouse.relY - options.height / 2,
r = Math.sqrt(x * x + y * y),
theta = Math.atan(y / x),
circle = Math.PI * 2,
explode = slice.explode || options.explode,
start = slice.start % circle,
end = slice.end % circle,
epsilon = options.epsilon;

if (x < 0) {
theta += Math.PI;
} else if (x > 0 && y < 0) {
theta += circle;
}

if (r < slice.radius + explode && r > explode) {
if (
(theta > start && theta < end) || // Normal Slice
(start > end && (theta < end || theta > start)) || // First slice
// TODO: Document the two cases at the end:
(start === end && ((slice.start === slice.end && Math.abs(theta - start) < epsilon) || (slice.start !== slice.end && Math.abs(theta-start) > epsilon)))
) {

// TODO Decouple this from hit plugin (chart shouldn't know what n means)
n.x = data[0];
n.y = data[1];
n.sAngle = start;
n.eAngle = end;
n.index = 0;
n.seriesIndex = index;
n.fraction = data[1] / this.total[layer];
}
}
},
drawHit: function (options) {
var
context = options.context,
slice = this.slices[options.args.seriesIndex];

context.save();
context.translate(options.width / 2, options.height / 2);
this.plotSlice(slice.x, slice.y, slice.radius, thickness, layer, slice.start, slice.end, context);
context.stroke();
context.restore();
},
clearHit : function (options) {
var
context = options.context,
slice = this.slices[options.args.seriesIndex],
padding = 2 * options.lineWidth,
radius = slice.radius + padding;

context.save();
context.translate(options.width / 2, options.height / 2);
context.clearRect(
slice.x - radius,
slice.y - radius,
2 * radius + padding,
2 * radius + padding
);
context.restore();
},
extendYRange : function (axis, data) {
var layer = data[0][0];
this.total[layer] = (this.total[layer] || 0) + data[0][1];
}
});
})();

/** Points **/
Flotr.addType('points', {
options: {
Expand Down Expand Up @@ -7205,3 +7474,44 @@ Flotr.addPlugin('titles', {
}
});
})();

(function () {

var D = Flotr.DOM;

Flotr.addPlugin('statusGraphTitle', {
callbacks: {
'flotr:afterdraw': function() {
this.statusGraphTitle.drawStatusTitle();
}
},
/**
* Draws the title
*/
drawStatusTitle : function () {
var
options = this.options,
margin = options.grid.labelMargin,
ctx = this.ctx,
a = this.axes;

var style = {
size: options.statusGraphTitle.fontSize,
color: options.statusGraphTitle.color,
textAlign: 'center',
weight: 1.5,
textBaseline: 'middle'
};

// Draw text
if (options.statusGraphTitle.text) {
Flotr.drawText(
ctx, options.statusGraphTitle.text,
this.plotOffset.left + this.plotWidth/2,
this.plotHeight/2,
style
);
}
}
});
})();
2 changes: 1 addition & 1 deletion flotr2.min.js

Large diffs are not rendered by default.

Loading