Currently, I'm using jspdf latest version and jspdf-AutoTable 2.1.0 latest version in order to create a PDF with a very complicated table.
It works like a charm in Chrome and FireFox (big surprise!) but in IE10, it's rendering the pdf awfully (another big surprise!)
This is the output of one of the most extensive tables in pdf on chrome (Currently empty)
This is the pdf that IE10 renders
As you can see, it's not wrapping the column header as it should, nor it's expanding and showing the first columns cells and cropping the text inside them.
In order to mantain my custom table style, with it's content correct styling, I adjusted and created my own GetTableJSON method, so I can retrieve and store each individual cell style and apply it later on the createdHeaderCell and createdCell hooks
This is the complete code used in order to create this pdf with it's custom style
function DownloadSchedulePDF() {
var orientation = landscape ? 'l' : 'p'
var doc = new jsPDF(orientation, 'pt', paperFormat);
doc.text('Header', 40, 50);
var res = GetTableJSON($(".scheduleGrid table"));
tableCellsStyles = res.styles;
doc.autoTable(res.columns, res.data, {
theme: 'plain',
startY: 60,
pageBreak: 'auto',
margin: 20,
width: 'auto',
styles: {
lineWidth: 0.01,
lineColor: 0,
fillStyle: 'DF',
halign: 'center',
valign: 'middle',
columnWidth: 'auto',
overflow: 'linebreak'
},
createdHeaderCell: function (cell, data) {
ApplyCellStyle(cell, "th", data.column.dataKey);
},
createdCell: function (cell, data) {
ApplyCellStyle(cell, data.row.index, data.column.dataKey);
},
drawHeaderCell: function (cell, data) {
//ApplyCellStyle(cell, "th", data.column.dataKey);
//data.table.headerRow.cells[data.column.dataKey].styles = cell.styles;
//ApplyCellStyle(data.table.headerRow.cells[data.column.dataKey], "th", data.column.dataKey);
},
drawCell: function (cell, data) {
if (cell.raw === undefined)
return false;
if(cell.raw.indexOf("*") > -1) {
var text = cell.raw.split("*")[0];
var times = cell.raw.split("*")[1];
doc.rect(cell.x, cell.y, cell.width, cell.height * times, 'FD');
doc.autoTableText(text, cell.x + cell.width / 2, cell.y + cell.height * times / 2, {
halign: 'center',
valign: 'middle'
});
return false;
}
}
});
doc.save("schedule " + selectedDate.toLocaleDateString() + ".pdf");
}
function ApplyCellStyle(cell, x, y) {
if(!pdfInColor)
return;
var styles = tableCellsStyles[x + "-" + y];
if (styles === undefined)
return;
cell.styles.cellPadding = styles.cellPadding
cell.styles.fillColor = styles.fillColor;
cell.styles.textColor = styles.textColor;
cell.styles.font = styles.font;
cell.styles.fontStyle = styles.fontStyle;
}
Object.vals = function (o) {
return Object.values
? Object.values(o)
: Object.keys(o).map(function (k) { return o[k]; });
}
// direct copy of the plugin method adjusted in order to retrieve and store each cell style
function GetTableJSON (tableElem, includeHiddenElements) {
includeHiddenElements = includeHiddenElements || false;
var columns = {}, rows = [];
var cellsStyle = {};
var header = tableElem.rows[0];
for (var k = 0; k < header.cells.length; k++) {
var cell = header.cells[k];
var style = window.getComputedStyle(cell);
cellsStyle["th-" + k] = AdjustStyleProperties(style);
if (includeHiddenElements || style.display !== 'none') {
columns[k] = cell ? cell.textContent.trim() : '';
}
}
for (var i = 1; i < tableElem.rows.length; i++) {
var tableRow = tableElem.rows[i];
var style = window.getComputedStyle(tableRow);
if (includeHiddenElements || style.display !== 'none') {
var rowData = [];
for (var j in Object.keys(columns)) {
var cell = tableRow.cells[j];
style = window.getComputedStyle(cell);
if (includeHiddenElements || style.display !== 'none') {
var val = cell
? cell.hasChildNodes() && cell.childNodes[0].tagName !== undefined
? cell.childNodes[0].textContent + (cell.getAttribute("rowSpan") ? "*" + cell.getAttribute("rowSpan") : '')
: cell.textContent.trim()
: '';
cellsStyle[(i-1) + "-" + j] = cell
? cell.hasChildNodes() && cell.childNodes[0].tagName !== undefined
? AdjustStyleProperties(window.getComputedStyle(cell.childNodes[0]))
: AdjustStyleProperties(window.getComputedStyle(cell))
: {};
rowData.push(val);
}
}
rows.push(rowData);
}
}
return {columns: Object.vals(columns), rows: rows, data: rows, styles: cellsStyle}; // data prop deprecated
};
function AdjustStyleProperties(style) {
return {
cellPadding: parseInt(style.padding),
fontSize: parseInt(style.fontSize),
font: style.fontFamily, // helvetica, times, courier
lineColor: ConvertToRGB(style.borderColor),
lineWidth: parseInt(style.borderWidth) / 10,
fontStyle: style.fontStyle, // normal, bold, italic, bolditalic
overflow: 'linebreak', // visible, hidden, ellipsize or linebreak
fillColor: ConvertToRGB(style.backgroundColor),
textColor: ConvertToRGB(style.color),
halign: 'center', // left, center, right
valign: 'middle', // top, middle, bottom
fillStyle: 'DF', // 'S', 'F' or 'DF' (stroke, fill or fill then stroke)
rowHeight: parseInt(style.height),
columnWidth: parseInt(style.width) // 'auto', 'wrap' or a number
//columnWidth: 'auto'
};
}
function ConvertToRGB(value) {
if (value === undefined || value === '' || value === "transparent")
value = [255, 255, 255];
else if (value.indexOf("rgb") > -1)
value = value.replace(/[^\d,]/g, '').split(',').map(function (x) { return parseInt(x) });
else if (value.indexOf("#") > -1)
value = value.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i
, function (m, r, g, b) { return '#' + r + r + g + g + b + b })
.substring(1).match(/.{2}/g)
.map(function (x) { return parseInt(x, 16) });
else if (Array.isArray(value))
return value;
else {
var canvas, context;
canvas = document.createElement('canvas');
canvas.height = 1;
canvas.width = 1;
context = canvas.getContext('2d');
context.fillStyle = 'rgba(0, 0, 0, 0)';
// We're reusing the canvas, so fill it with something predictable
context.clearRect(0, 0, 1, 1);
context.fillStyle = value;
context.fillRect(0, 0, 1, 1);
var imgData = context.getImageData(0, 0, 1, 1);
value = imgData.data.slice
? imgData.data.slice(0, 3)
: [imgData.data[0], imgData.data[1], imgData.data[2]];
}
return value;
}
Edit:
As requested, this is the table HTML and the JSON for the tableCellStyles variable
https://jsfiddle.net/6ewqnwty/
Due to the size of the table and the amount of characters for the HTML and the JSON, I set them in a separate fiddle.
Edit 2:
I just made the fiddle runnable, being able to reproduce the issue.
https://jsfiddle.net/6ewqnwty/1/
Not perfectly as I have it in my application, I'm able to retrieve the pdf with the styling, only missing the columns headers text, but at least the issue when downloading the pdf on IE is still present
Running your example I get NaN for cellPadding. This is also probably the reason it does not work with the latest version. You could do something simple such as adding:
cell.styles.cellPadding = styles.cellPadding || 5;
in ApplyCellStyle. The reason you get NaN is that IE apparantly returns empty string instead of '0px' which chrome etc does.
Also note that you would not need to parse the html table yourself if you upgrade since cell.raw is set to the html element in the latest version. This means that you could parse the style with something like window.getComputedStyle(cell.raw).
Related
You have a html table and you want to show sparkline charts from your data, exactly as in this example (from highcharts demos):
https://codepen.io/_dario/pen/rNBOGVR
Highcharts suggested code follows:
/**
* Create a constructor for sparklines that takes some sensible defaults and merges in the individual
* chart options. This function is also available from the jQuery plugin as $(element).highcharts('SparkLine').
*/
Highcharts.SparkLine = function(a, b, c) {
var hasRenderToArg = typeof a === 'string' || a.nodeName,
options = arguments[hasRenderToArg ? 1 : 0],
defaultOptions = {
chart: {
renderTo: (options.chart && options.chart.renderTo) || this,
backgroundColor: null,
borderWidth: 0,
type: 'area',
margin: [2, 0, 2, 0],
width: 120,
height: 20,
style: {
overflow: 'visible'
},
// small optimalization, saves 1-2 ms each sparkline
skipClone: true
},
title: {
text: ''
},
credits: {
enabled: false
},
xAxis: {
labels: {
enabled: false
},
title: {
text: null
},
startOnTick: false,
endOnTick: false,
tickPositions: []
},
yAxis: {
endOnTick: false,
startOnTick: false,
labels: {
enabled: false
},
title: {
text: null
},
tickPositions: [0]
},
legend: {
enabled: false
},
tooltip: {
hideDelay: 0,
outside: true,
shared: true
},
plotOptions: {
series: {
animation: false,
lineWidth: 1,
shadow: false,
states: {
hover: {
lineWidth: 1
}
},
marker: {
radius: 1,
states: {
hover: {
radius: 2
}
}
},
fillOpacity: 0.25
},
column: {
negativeColor: '#910000',
borderColor: 'silver'
}
}
};
options = Highcharts.merge(defaultOptions, options);
return hasRenderToArg ?
new Highcharts.Chart(a, options, c) :
new Highcharts.Chart(options, b);
};
var start = +new Date(),
$tds = $('td[data-sparkline]'),
fullLen = $tds.length,
n = 0;
// Creating 153 sparkline charts is quite fast in modern browsers, but IE8 and mobile
// can take some seconds, so we split the input into chunks and apply them in timeouts
// in order avoid locking up the browser process and allow interaction.
function doChunk() {
var time = +new Date(),
i,
len = $tds.length,
$td,
stringdata,
arr,
data,
chart;
for (i = 0; i < len; i += 1) {
$td = $($tds[i]);
stringdata = $td.data('sparkline');
arr = stringdata.split('; ');
data = $.map(arr[0].split(', '), parseFloat);
chart = {};
if (arr[1]) {
chart.type = arr[1];
}
$td.highcharts('SparkLine', {
series: [{
data: data,
pointStart: 1
}],
tooltip: {
headerFormat: '<span style="font-size: 10px">' + $td.parent().find('th').html() + ', Q{point.x}:</span><br/>',
pointFormat: '<b>{point.y}.000</b> USD'
},
chart: chart
});
n += 1;
// If the process takes too much time, run a timeout to allow interaction with the browser
if (new Date() - time > 500) {
$tds.splice(0, i + 1);
setTimeout(doChunk, 0);
break;
}
// Print a feedback on the performance
if (n === fullLen) {
$('#result').html('Generated ' + fullLen + ' sparklines in ' + (new Date() - start) + ' ms');
}
}
}
doChunk();
However, in my use case, the data in the table (and the data-sparkline attribute) are not hard-coded like in the example, but loaded and displayed via an AJAX call, similar to below.
//here a table row gets compiled
var tableRow = '<tr id="row_' + word.id + '">';
//this is where the sparkline data go
tableRow += '<td class="has-sparkline"></td></tr>';
//the row gets appended to tbody
$('#wordstable tbody').append(tableRow);
//finally the sparkline data are attached
//data are a simple string such as "1,2,3,4,5"
var rowId = '#row_'+word.id;
var rowIdTd = rowId + ' td.has-sparkline';
$(rowIdTd).data('sparkline',word.sparkline);
This breaks the example logic and I can't have Highcharts "see" the data.
No particular error is returned (as the data, as far as Highcharts is concerned, just isn't there, so there's nothing to do).
The doChunk bit just does all the processing in advance, and when you add your row it is no longer processing. One way of dealing with this is pulling out the part that creates a single chart into a separate function (makeChart) and when you are doing your processing you use that part directly to create your sparkline.
For example, doChunk with split out makeChart:
function makeChart(td) {
$td = td;
stringdata = $td.data('sparkline');
arr = stringdata.split('; ');
data = $.map(arr[0].split(', '), parseFloat);
chart = {};
if (arr[1]) {
chart.type = arr[1];
}
$td.highcharts('SparkLine', {
series: [{
data: data,
pointStart: 1
}],
tooltip: {
headerFormat: '<span style="font-size: 10px">' + $td.parent().find('th').html() + ', Q{point.x}:</span><br/>',
pointFormat: '<b>{point.y}.000</b> USD'
},
chart: chart
});
}
// Creating 153 sparkline charts is quite fast in modern browsers, but IE8 and mobile
// can take some seconds, so we split the input into chunks and apply them in timeouts
// in order avoid locking up the browser process and allow interaction.
function doChunk() {
var time = +new Date(),
i,
len = $tds.length,
$td,
stringdata,
arr,
data,
chart;
for (i = 0; i < len; i += 1) {
makeChart($($tds[i]));
n += 1;
// If the process takes too much time, run a timeout to allow interaction with the browser
if (new Date() - time > 500) {
$tds.splice(0, i + 1);
setTimeout(doChunk, 0);
break;
}
// Print a feedback on the performance
if (n === fullLen) {
$('#result').html('Generated ' + fullLen + ' sparklines in ' + (new Date() - start) + ' ms');
}
}
}
And then a basic example of your ajax-code:
function ajaxIsh() {
var word = {
name: 'Bird', // is the word
id: 'bird',
sparkline: '1, 2, 3, 4, 5'
};
//here a table row gets compiled
var tableRow = '<tr id="row_' + word.id + '">';
//this is where the sparkline data go
tableRow += '<th>'+word.name+'</th><td class="has-sparkline"></td></tr>';
//the row gets appended to tbody
$('#table-sparkline tbody').append(tableRow);
//finally the sparkline data are attached
//data are a simple string such as "1,2,3,4,5"
var rowId = '#row_'+word.id;
var rowIdTd = rowId + ' td.has-sparkline';
$(rowIdTd).data('sparkline',word.sparkline);
makeChart($(rowIdTd));
}
See this JSFiddle demonstration of it in action.
I am trying to get user inputs and then draw a bubble chart with 100 bubbles. How can I change the background color of bubbles to different colors(up to 10 colors)?
Below is my javascript code,
<script>
function generateChart() {
var my_arr = [];
var Stakeholders = [];
$('td').each(function () {
my_arr.push($(this).children().val());
});
var length = my_arr.length;
for (var i = 0; i < length - 2; i++) {
var Stakeholder = new Object();
Stakeholder.name = my_arr[i] || 'Unknown';
Stakeholder.x = parseFloat(my_arr[i + 1] || 5);
Stakeholder.y = parseFloat(my_arr[i + 2] || 5);
Stakeholders.push(Stakeholder);
i += 2;
}
drawChart(Stakeholders);
};
function drawChart(Stakeholders) {
Highcharts.chart('container', {
chart: {
type: 'bubble',
plotBorderWidth: 1,
zoomType: 'xy',
spacingTop: 40,
spacingRight: 40,
spacingBottom: 40,
spacingLeft: 40,
borderWidth: 1
},
plotOptions: {
column: {
colorByPoint: true
}
},
series: [{
data: Stakeholders
}]
});
};
</script>
I should have added a property to Stakeholder:
var colors = ['#98d9c2', '#ffd9ce', '#db5461', '#f5853f', '#b497d6', '#dc965a', '#FF9655', '#FFF263', '#6AF9C4', "000"];
for (var i = 0; i < length - 2 ; i++) {
var Stakeholder = new Object();
var color = parseInt(Math.random() * 10);
Stakeholder.name = my_arr[i] || 'Unknown';
Stakeholder.x = parseFloat(my_arr[i + 1]);
Stakeholder.y = parseFloat(my_arr[i + 2]);
Stakeholder.z = 5;
Stakeholder.color = colors[color];
Stakeholders.push(Stakeholder);
i += 2;
}
I'm not sure if you want to assign specific colors to specific bubbles or just randomly assign colors, but you can add a color: 'somecolor' property to each bubble object in the series.
Fiddle here (see lines 96-110).
Or, you could create an array of colors, loop through your bubble series, and randomly assign a color to each bubble object.
Hope this helps.
I have a Custom object (fabric.DefaultSensorGroup) which based on fabric.Group. This custom objects contains a fabric.Text and various number of other custom objects (fabric.DefaultSensor).
If I add/remove objects to the group, the "borders" of this group don't resize according to the containing objects.
DefaultSensor:
fabric.DefaultSensor = fabric.util.createClass(fabric.Group, {
type : 'defaultSensor',
initialize : function(options) {
options || (options = {});
// rect base
var rectBase = new fabric.Rect({
left : 0,
top : 0,
fill : options.sensorFillColor || '#aaf',
width : 350,
height : 30,
stroke : '#000',
strokeWidth : 1,
rx : 5,
ry : 5,
shadow : 'rgba(0,0,0,1) 3px 3px 3px'
});
// text for sensor name and unit
var nameUnitText = new fabric.Text('n/a', {
left : 5,
top : 2,
fontSize : 20,
fontFamily : 'Arial'
});
// text for sensor value
var valueText = new fabric.Text('n/a', {
top : 2,
fontSize : 20,
fontFamily : 'Arial',
});
this.callSuper('initialize', [ rectBase, nameUnitText, valueText ],
options);
this.set('sensorId', options.sensorId || '');
this.set('sensorName', options.sensorName || 'n/a');
this.set('sensorNameFontSize', options.sensorNameFontSize || 20);
this.set('dataSeriesName', options.dataSeriesName || 'n/a');
this.set('sensorUnit', options.sensorUnit || 'n/a');
this.set('sensorValue', options.sensorValue || 'n/a');
this.set('sensorFillColor', options.sensorFillColor || '#aaf');
this.set('sensorDeviceId', options.sensorDeviceId || '');
this.set('sensorProjectId', options.sensorProjectId || '');
this.set('sensorDataSeriesId', options.sensorDataSeriesId || '');
this.set('fractionalDigits', options.fractionalDigits || 2);
},
render : function(ctx, noTransform) {
this.refreshSensorData();
this.callSuper('render', ctx, noTransform);
},
refreshSensorData : function() {
// set fill of base rect
this.getObjects()[0].set('fill', this.get('sensorFillColor'));
// set sensor name and unit text from current values
this.getObjects()[1].set('text', this.get('dataSeriesName') + ' ['
+ this.get('sensorUnit') + ']');
// set sensor name and unit text from current values
this.getObjects()[1].set('fontSize', this.get('sensorNameFontSize'));
// set sensor value text from current value
this.getObjects()[2].set('text', this.get('sensorValue') + '');
// set new width of base rect arcording to content width + 30 padding
this.getObjects()[0].set('width', this.getObjects()[1].get('width')
+ this.getObjects()[2].get('width') + 30);
// align base rect according to new width
this.getObjects()[0].set('left', -1 * this.getObjects()[0].get('width')
/ 2);
this.getObjects()[0].set('top', -1 * this.getObjects()[0].get('height')
/ 2);
// adapt group width according to new content width
this.set('width', this.getObjects()[0].get('width'));
this.setCoords();
// set new left of sensorlabel + 3 padding
this.getObjects()[1].set('left', (-1 * (this.getObjects()[0]
.get('width') / 2)) + 3);
var topPadding = (this.getObjects()[0].get('height') - this
.getObjects()[1].get('height')) / 2;
this.getObjects()[1].set('top', (-1 * (this.getObjects()[0]
.get('height') / 2))
+ topPadding);
// position sensor value text according to the text width (right
// aligned)
this.getObjects()[2].set('left',
(this.getObjects()[0].get('width') / 2)
- this.getObjects()[2].get('width') - 5);
},
toObject : function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
sensorId : this.get('sensorId'),
sensorName : this.get('sensorName'),
sensorNameFontSize : this.get('sensorNameFontSize'),
sensorContainerWidth : this.get('sensorContainerWidth'),
dataSeriesName : this.get('dataSeriesName'),
sensorUnit : this.get('sensorUnit'),
sensorValue : this.get('sensorValue'),
sensorFillColor : this.get('sensorFillColor'),
sensorDeviceId : this.get('sensorDeviceId'),
sensorProjectId : this.get('sensorProjectId'),
sensorDataSeriesId : this.get('sensorDataSeriesId'),
fractionalDigits : this.get('fractionalDigits')
});
},
_render : function(ctx) {
this.callSuper('_render', ctx);
}
});
fabric.DefaultSensor.fromObject = function(object, callback) {
fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) {
delete object.objects;
callback && callback(new fabric.DefaultSensor(object));
});
};
fabric.DefaultSensor.selected = function(object) {
detailsbar.addColorSetter(object, 'sensorFillColor', webLU.gs(
'label.Color', 'Color'));
detailsbar.addOpacitySetter(object, 'opacity', webLU.gs('label.Opacity',
'Opacity'));
detailsbar.addSensorSelectSetter(object, webLU.gs('label.SensorSelection',
'Sensor selection'));
detailsbar.addDecimalPlacesSetter(object, 'fractionalDigits', webLU.gs(
'label.DecimalPlaces', 'Decimal places'));
detailsbar.addRangeSetter(object, 'sensorNameFontSize', webLU.gs(
'label.SensorNameFontSize', 'Sensorname Font Size'), 10, 20);
};
fabric.DefaultSensor.async = true;
DefaultSensorGroup:
fabric.DefaultSensorGroup = fabric.util.createClass(fabric.Group,
{
type : 'defaultSensorGroup',
groupWidth: 40,
groupHeight : 50,
initialize : function(objects, options)
{
options || (options =
{});
var objectsExtended = this.buildGraphicObjects(objects)
this.callSuper('initialize', objectsExtended, options);
this.set('sensorGroupName', options.sensorGroupName || 'undefinedSensorGroup');
this.set('sensorSelection', objects || []);
this.set('fractionalDigits', options.fractionalDigits || 2);
var blub = this.getObjects();
for(var i in blub) {
this.groupWidth = Math.max(this.groupWidth, blub[i].get('width'));
this.groupHeight = Math.max(this.groupHeight, blub[i].get('height'));
}
this.setWidth(this.groupWidth);
this.setHeight(this.groupHeight);
},
buildGraphicObjects : function(objects)
{
// rect base
var rectBase = new fabric.Rect(
{
left : 0,
top : 0,
fill : '#C0C0C0',
width : 370,
height : 40 + (objects.length * 35),
stroke : '#000',
strokeWidth : 1,
rx : 5,
ry : 5,
shadow : 'rgba(0,0,0,1) 3px 3px 3px'
});
// group name text
var groupNameText = new fabric.Text('n/a',
{
left : 5,
top : 0,
fontSize : 20,
textAlign : 'center',
fontFamily : 'Arial'
});
var objectsExtended = [ groupNameText ];
var sensorTopPosition = groupNameText.getHeight() + groupNameText.getLeft();
// extend sensor group base objects (rect and group name text) with
// sensor objects and set position of sensor objects
if (objects)
{
for (var i = 0; i < objects.length; i++)
{
objectsExtended.push(objects[i].set(
{
left : 10,
top : sensorTopPosition
}));
sensorTopPosition += objects[i].getHeight();
}
}
return objectsExtended;
},
render : function(ctx, noTransform)
{
this.refreshGroupData();
this.callSuper('render', ctx, noTransform);
},
refreshGroupData : function()
{
this.getObjects()[0].set('text', this.get('sensorGroupName'));
},
toObject : function()
{
return fabric.util.object.extend(this.callSuper('toObject'),
{
sensorGroupName : this.get('sensorGroupName'),
sensorSelection : this.get('sensorSelection'),
fractionalDigits : this.get('fractionalDigits')
});
},
addSensorToSelection : function(fabricSensor)
{
fabricSensor.set('fractionalDigits', this.get('fractionalDigits'));
this.get('sensorSelection').push(fabricSensor);
this.initialize(this.get('sensorSelection'), this.toObject());
},
removeSensorFromSelection : function(index)
{
this.get('sensorSelection').splice(index, 1);
this.initialize(this.get('sensorSelection'), this.toObject());
},
_render : function(ctx)
{
this.callSuper('_render', ctx);
}
});
fabric.DefaultSensorGroup.fromObject = function(object, callback)
{
fabric.util.enlivenObjects(object.objects, function(enlivenedObjects)
{
delete object.objects;
var sensors = [];
if (object.sensorSelection)
{
for (var i = 0; i < object.sensorSelection.length; i++)
{
sensors.push(new fabric.DefaultSensor(object.sensorSelection[i]).set('fractionalDigits', object.fractionalDigits));
}
}
callback && callback(new fabric.DefaultSensorGroup(sensors, object));
});
};
fabric.DefaultSensorGroup.selected = function(object)
{
detailsbar.addTextSetter(object, 'sensorGroupName', webLU.gs('label.Name', 'Name'));
detailsbar.addOpacitySetter(object, 'opacity', webLU.gs('label.Opacity', 'Opacity'));
detailsbar.addSensorGroupSensorSelectionSetter(object, webLU.gs('label.SensorSelection', 'Sensor selection'));
detailsbar.addDecimalPlacesSetter(object, 'fractionalDigits', webLU.gs('label.DecimalPlaces', 'Decimal places'));
};
fabric.DefaultSensorGroup.async = true;
When you add your objects, do you add them with group.add() or group.addWithUpdate()?
Becase the add function just throws them into the group without recalculating the new border size. But with group.addWithUpdate() it recalculats the size of the bounding box of the group.
I am working in SVG editor 2.7 version. Here I need to change selection area for path tags in SVG using Javascript.
For example refer this image below:
Here I analysis about SVG editor, <g> tags will generated automatically and create only one selection area which is provided only rectangular shape only.
Here I need to change selection area for path section.
Below code is based on select.js :
(function() {'use strict';
if (!svgedit.select) {
svgedit.select = {};
}
var svgFactory_;
var config_;
var selectorManager_; // A Singleton
var gripRadius = svgedit.browser.isTouch() ? 5 : 4;
// Class: svgedit.select.Selector
// Private class for DOM element selection boxes
//
// Parameters:
// id - integer to internally indentify the selector
// elem - DOM element associated with this selector
svgedit.select.Selector = function(id, elem) {
// this is the selector's unique number
this.id = id;
// this holds a reference to the element for which this selector is being used
this.selectedElement = elem;
// this is a flag used internally to track whether the selector is being used or not
this.locked = true;
// this holds a reference to the <g> element that holds all visual elements of the selector
this.selectorGroup = svgFactory_.createSVGElement({
'element': 'g',
'attr': {'id': ('selectorGroup' + this.id)}
});
// this holds a reference to the path rect
this.selectorRect = this.selectorGroup.appendChild(
svgFactory_.createSVGElement({
'element': 'path',
'attr': {
'id': ('selectedBox' + this.id),
'fill': 'none',
'stroke': '#22C',
'stroke-width': '1',
'stroke-dasharray': '5,5',
// need to specify this so that the rect is not selectable
'style': 'pointer-events:none'
}
})
);
// this holds a reference to the grip coordinates for this selector
this.gripCoords = {
'nw': null,
'n' : null,
'ne': null,
'e' : null,
'se': null,
's' : null,
'sw': null,
'w' : null
};
this.reset(this.selectedElement);
};
// Function: svgedit.select.Selector.reset
// Used to reset the id and element that the selector is attached to
//
// Parameters:
// e - DOM element associated with this selector
svgedit.select.Selector.prototype.reset = function(e) {
this.locked = true;
this.selectedElement = e;
this.resize();
this.selectorGroup.setAttribute('display', 'inline');
};
// Function: svgedit.select.Selector.updateGripCursors
// Updates cursors for corner grips on rotation so arrows point the right way
//
// Parameters:
// angle - Float indicating current rotation angle in degrees
svgedit.select.Selector.prototype.updateGripCursors = function(angle) {
var dir,
dir_arr = [],
steps = Math.round(angle / 45);
if (steps < 0) {steps += 8;}
for (dir in selectorManager_.selectorGrips) {
dir_arr.push(dir);
}
while (steps > 0) {
dir_arr.push(dir_arr.shift());
steps--;
}
var i = 0;
for (dir in selectorManager_.selectorGrips) {
selectorManager_.selectorGrips[dir].setAttribute('style', ('cursor:' + dir_arr[i] + '-resize'));
i++;
}
};
// Function: svgedit.select.Selector.showGrips
// Show the resize grips of this selector
//
// Parameters:
// show - boolean indicating whether grips should be shown or not
svgedit.select.Selector.prototype.showGrips = function(show) {
// TODO: use suspendRedraw() here
var bShow = show ? 'inline' : 'none';
selectorManager_.selectorGripsGroup.setAttribute('display', bShow);
var elem = this.selectedElement;
this.hasGrips = show;
if (elem && show) {
this.selectorGroup.appendChild(selectorManager_.selectorGripsGroup);
this.updateGripCursors(svgedit.utilities.getRotationAngle(elem));
}
};
// Function: svgedit.select.Selector.resize
// Updates the selector to match the element's size
svgedit.select.Selector.prototype.resize = function() {
console.log(this.selectorGroup);
var selectedBox = this.selectorRect,
selectorRubberBand = this.getRubberBandBox,
mgr = selectorManager_,
selectedGrips = mgr.selectorGrips,
selected = this.selectedElement,
sw = selected.getAttribute('stroke-width'),
current_zoom = svgFactory_.currentZoom();
$('#current_zoom').val(current_zoom);
console.log(mgr);
var offset = 1/current_zoom;
if (selected.getAttribute('stroke') !== 'none' && !isNaN(sw)) {
offset += (sw/2);
}
var tagName = selected.tagName;
if (tagName === 'text') {
offset += 2/current_zoom;
}
// loop and transform our bounding box until we reach our first rotation
var tlist = svgedit.transformlist.getTransformList(selected);
var m = svgedit.math.transformListToTransform(tlist).matrix;
// This should probably be handled somewhere else, but for now
// it keeps the selection box correctly positioned when zoomed
m.e *= current_zoom;
m.f *= current_zoom;
var bbox = svgedit.utilities.getBBox(selected);
if (tagName === 'g' && !$.data(selected, 'gsvg')) {
// The bbox for a group does not include stroke vals, so we
// get the bbox based on its children.
var stroked_bbox = svgFactory_.getStrokedBBox(selected.childNodes);
if (stroked_bbox) {
bbox = stroked_bbox;
}
}
// apply the transforms
var l = bbox.x, t = bbox.y, w = bbox.width, h = bbox.height;
bbox = {x:l, y:t, width:w, height:h};
var x_varadd=70;
var y_varadd=40;
var cen_x=l;
var cen_y=t;
var dummyvarrr=0;
var gethide_curtselid=$("#hide_curtselid").val();
var gethide_curtselwidth=$("#hide_curtdrawx_width").val(w);
var gethide_curtselheight=$("#hide_curtdrawy_height").val(h);
$("#hide_curtdrawx_id").val(l);
$("#hide_curtdrawy_id").val(t);
var gethide_curtselid12=$("#hide_curtdrawx_id").val();
var gethide_curtselid13=$("#hide_curtdrawy_id").val();
offset *= current_zoom;
var nbox = svgedit.math.transformBox(l*current_zoom, t*current_zoom, w*current_zoom, h*current_zoom, m),
aabox = nbox.aabox,
nbax = aabox.x - offset,
nbay = aabox.y - offset,
nbaw = aabox.width + (offset * 2),
nbah = aabox.height + (offset * 2);
var gethide_curtselwidth=$("#hide_curtdrawx_width").val(nbaw);
var gethide_curtselheight=$("#hide_curtdrawy_height").val(nbah);
$("#hide_curtdrawx_id").val(nbax);
$("#hide_curtdrawy_id").val(nbay);
// now if the shape is rotated, un-rotate it
var cx = nbax + nbaw/2,
cy = nbay + nbah/2;
var angle = svgedit.utilities.getRotationAngle(selected);
if (angle) {
var rot = svgFactory_.svgRoot().createSVGTransform();
rot.setRotate(-angle, cx, cy);
var rotm = rot.matrix;
nbox.tl = svgedit.math.transformPoint(nbox.tl.x, nbox.tl.y, rotm);
nbox.tr = svgedit.math.transformPoint(nbox.tr.x, nbox.tr.y, rotm);
nbox.bl = svgedit.math.transformPoint(nbox.bl.x, nbox.bl.y, rotm);
nbox.br = svgedit.math.transformPoint(nbox.br.x, nbox.br.y, rotm);
// calculate the axis-aligned bbox
var tl = nbox.tl;
var minx = tl.x,
miny = tl.y,
maxx = tl.x,
maxy = tl.y;
var min = Math.min, max = Math.max;
minx = min(minx, min(nbox.tr.x, min(nbox.bl.x, nbox.br.x) ) ) - offset;
miny = min(miny, min(nbox.tr.y, min(nbox.bl.y, nbox.br.y) ) ) - offset;
maxx = max(maxx, max(nbox.tr.x, max(nbox.bl.x, nbox.br.x) ) ) + offset;
maxy = max(maxy, max(nbox.tr.y, max(nbox.bl.y, nbox.br.y) ) ) + offset;
nbax = minx;
nbay = miny;
nbaw = (maxx-minx);
nbah = (maxy-miny);
}
var sr_handle = svgFactory_.svgRoot().suspendRedraw(100);
var dstr = 'M' + nbax + ',' + nbay
+ ' L' + (nbax+nbaw) + ',' + nbay
+ ' ' + (nbax+nbaw) + ',' + (nbay+nbah)
+ ' ' + nbax + ',' + (nbay+nbah) + 'z';
// alert(dstr);
selectedBox.setAttribute('d', dstr);
var xform = angle ? 'rotate(' + [angle, cx, cy].join(',') + ')' : '';
this.selectorGroup.setAttribute('transform', xform);
// TODO(codedread): Is this if needed?
// if (selected === selectedElements[0]) {
this.gripCoords = {
'nw': [nbax, nbay],
'ne': [nbax+nbaw, nbay],
'sw': [nbax, nbay+nbah],
'se': [nbax+nbaw, nbay+nbah],
'n': [nbax + (nbaw)/2, nbay],
'w': [nbax, nbay + (nbah)/2],
'e': [nbax + nbaw, nbay + (nbah)/2],
's': [nbax + (nbaw)/2, nbay + nbah]
};
var width_rect = nbaw-offset;
var height_rect = nbah-offset;
var conversation =$('#measurement_det').val();
// alert(conversation);
if(conversation =='Meter') {
var width_gets = (parseFloat(width_rect)/100).toFixed(2)+'m';
var height_gets = (parseFloat(height_rect)/100).toFixed(2)+'m';
}
if(conversation =='Feet') {
var width_gets = (parseFloat(width_rect)/100)*3.2808399;
var feet_w = Math.floor(width_gets);
var inches_w = Math.round((width_gets - feet_w) * 12);
var width_gets = feet_w + "'" + inches_w + '"';
var width_gets = width_gets+'ft';
var height_gets = (parseFloat(height_rect)/100)*3.2808399;
var feet_h = Math.floor(height_gets);
var inches_h = Math.round((height_gets - feet_h) * 12);
var height_gets = feet_h + "'" + inches_h + '"';
var height_gets = height_gets+'ft';
}
if(selected.tagName=='rect')
{
var rect_text_id=selected.parentNode.id;
var rect_text_id1=rect_text_id.split('_');
}
if(selected.tagName=='g')
{
var sslice = selected.childNodes;
if(sslice[0].tagName=='rect')
{
var rect_text_id=sslice[0].parentNode.id;
var rect_text_id1=rect_text_id.split('_');
}
}
var dir;
for (dir in this.gripCoords) {
var coords = this.gripCoords[dir];
if((dir =='n') || (dir =='e') || (dir =='s') || (dir =='w')) {
/* selectedGrips[dir].setAttribute('cx', coords[0]);
selectedGrips[dir].setAttribute('cy', coords[1]); */
/* $('#selectorGrip_resize_n').html('');
$('#selectorGrip_resize_s').html(''); */
if(selected.childNodes[0] && selected.childNodes[0].tagName=='rect')
{
$('#meter_range_rect'+rect_text_id1[3]+'_n').text(width_gets);
$('#meter_range_rect'+rect_text_id1[3]+'_n').attr({'font-weight':''});
$('#meter_range_rect'+rect_text_id1[3]+'_s').text(width_gets);
$('#meter_range_rect'+rect_text_id1[3]+'_s').attr({'font-weight':''});
}
if(dir =='w') {
selectedGrips[dir].setAttribute('transform', 'rotate(-90,'+coords[0]+','+coords[1]+')');
// $('#selectorGrip_resize_w').html('');
if(selected.childNodes[0] && selected.childNodes[0].tagName=='rect')
{
$('#meter_range_rect'+rect_text_id1[3]+'_w').text(height_gets);
$('#meter_range_rect'+rect_text_id1[3]+'_w').attr({'font-weight':''});
}
}
if(dir =='e') {
selectedGrips[dir].setAttribute('transform', 'rotate(-90,'+coords[0]+','+coords[1]+')');
// $('#selectorGrip_resize_e').html('');
if(selected.childNodes[0] && selected.childNodes[0].tagName=='rect')
{
$('#meter_range_rect'+rect_text_id1[3]+'_e').text(height_gets);
$('#meter_range_rect'+rect_text_id1[3]+'_e').attr({'font-weight':''});
}
}
selectedGrips[dir].setAttribute('cx', coords[0]);
selectedGrips[dir].setAttribute('cy', coords[1]);
}
else {
selectedGrips[dir].setAttribute('cx', coords[0]);
selectedGrips[dir].setAttribute('cy', coords[1]);
}
}
// we want to go 20 pixels in the negative transformed y direction, ignoring scale
mgr.rotateGripConnector.setAttribute('x1', nbax + (nbaw)/2);
mgr.rotateGripConnector.setAttribute('y1', nbay);
mgr.rotateGripConnector.setAttribute('x2', nbax + (nbaw)/2);
mgr.rotateGripConnector.setAttribute('y2', nbay - (gripRadius*5));
mgr.rotateGrip.setAttribute('cx', nbax + (nbaw)/2);
mgr.rotateGrip.setAttribute('cy', nbay - (gripRadius*5));
// }
svgFactory_.svgRoot().unsuspendRedraw(sr_handle);
};
// Class: svgedit.select.SelectorManager
svgedit.select.SelectorManager = function() {
// this will hold the <g> element that contains all selector rects/grips
this.selectorParentGroup = null;
// this is a special rect that is used for multi-select
this.rubberBandBox = null;
// this will hold objects of type svgedit.select.Selector (see above)
this.selectors = [];
// this holds a map of SVG elements to their Selector object
this.selectorMap = {};
// this holds a reference to the grip elements
this.selectorGrips = {
'nw': null,
'n' : null,
'ne': null,
'e' : null,
'se': null,
's' : null,
'sw': null,
'w' : null
};
this.selectorGripsGroup = null;
this.rotateGripConnector = null;
this.rotateGrip = null;
this.initGroup();
};
// Function: svgedit.select.SelectorManager.initGroup
// Resets the parent selector group element
svgedit.select.SelectorManager.prototype.initGroup = function() {
// remove old selector parent group if it existed
if (this.selectorParentGroup && this.selectorParentGroup.parentNode) {
this.selectorParentGroup.parentNode.removeChild(this.selectorParentGroup);
}
// create parent selector group and add it to svgroot
this.selectorParentGroup = svgFactory_.createSVGElement({
'element': 'g',
'attr': {'id': 'selectorParentGroup'}
});
this.selectorGripsGroup = svgFactory_.createSVGElement({
'element': 'g',
'attr': {'display': 'none'}
});
this.selectorParentGroup.appendChild(this.selectorGripsGroup);
svgFactory_.svgRoot().appendChild(this.selectorParentGroup);
this.selectorMap = {};
this.selectors = [];
this.rubberBandBox = null;
// add the corner grips
var dir;
for (dir in this.selectorGrips) {
var grip = svgFactory_.createSVGElement({
'element': 'circle',
'attr': {
'id': ('selectorGrip_resize_' + dir),
'fill': '#04739E',
'r': gripRadius,
'style': ('cursor:' + dir + '-resize'),
// This expands the mouse-able area of the grips making them
// easier to grab with the mouse.
// This works in Opera and WebKit, but does not work in Firefox
// see https://bugzilla.mozilla.org/show_bug.cgi?id=500174
'stroke-width': 2,
'pointer-events': 'all'
}
});
/* if((dir =='n') || (dir =='e') || (dir =='s') || (dir =='w')) {
var grip = svgFactory_.createSVGElement({
'element': 'text',
'attr': {
'id': ('selectorGrip_resize_' + dir),
'fill': '#3F7F00',
// 'r': gripRadius,
'style': ('cursor:' + dir + '-resize'),
// This expands the mouse-able area of the grips making them
// easier to grab with the mouse.
// This works in Opera and WebKit, but does not work in Firefox
// see https://bugzilla.mozilla.org/show_bug.cgi?id=500174
'stroke-width': 2,
'pointer-events': 'all'
}
});
grip.textContent = '';
}else {
var grip = svgFactory_.createSVGElement({
'element': 'circle',
'attr': {
'id': ('selectorGrip_resize_' + dir),
'fill': '#04739E',
'r': gripRadius,
'style': ('cursor:' + dir + '-resize'),
// This expands the mouse-able area of the grips making them
// easier to grab with the mouse.
// This works in Opera and WebKit, but does not work in Firefox
// see https://bugzilla.mozilla.org/show_bug.cgi?id=500174
'stroke-width': 2,
'pointer-events': 'all'
}
});
}
*/
$.data(grip, 'dir', dir);
$.data(grip, 'type', 'resize');
this.selectorGrips[dir] = this.selectorGripsGroup.appendChild(grip);
}
// add rotator elems
this.rotateGripConnector = this.selectorGripsGroup.appendChild(
svgFactory_.createSVGElement({
'element': 'line',
'attr': {
'id': ('selectorGrip_rotateconnector'),
'stroke': '#22C',
'stroke-width': '0'
}
})
);
this.rotateGrip = this.selectorGripsGroup.appendChild(
svgFactory_.createSVGElement({
'element': 'circle',
'attr': {
'id': 'selectorGrip_rotate',
'fill': 'url(#rotate)',
'r': '15',
'stroke': '#22C',
'stroke-width': 2,
'style': "cursor:url(" + config_.imgPath + "rotate.png) 12 12, auto;"
}
})
);
$.data(this.rotateGrip, 'type', 'rotate');
if ($('#canvasBackground').length) {return;}
var dims = config_.dimensions;
var canvasbg = svgFactory_.createSVGElement({
'element': 'svg',
'attr': {
'id': 'canvasBackground',
'width': dims[0],
'height': dims[1],
'x': 0,
'y': 0,
'overflow': (svgedit.browser.isWebkit() ? 'none' : 'visible'), // Chrome 7 has a problem with this when zooming out
'style': 'pointer-events:none'
}
});
var rect = svgFactory_.createSVGElement({
'element': 'rect',
'attr': {
'width': '100%',
'height': '100%',
'x': 0,
'y': 0,
'stroke-width': 1,
'stroke': '#000',
'fill': '#FFF',
'style': 'pointer-events:none'
}
});
// Both Firefox and WebKit are too slow with this filter region (especially at higher
// zoom levels) and Opera has at least one bug
// if (!svgedit.browser.isOpera()) rect.setAttribute('filter', 'url(#canvashadow)');
canvasbg.appendChild(rect);
svgFactory_.svgRoot().insertBefore(canvasbg, svgFactory_.svgContent());
};
// Function: svgedit.select.SelectorManager.requestSelector
// Returns the selector based on the given element
//
// Parameters:
// elem - DOM element to get the selector for
svgedit.select.SelectorManager.prototype.requestSelector = function(elem) {
if (elem == null) {return null;}
var i,
N = this.selectors.length;
// If we've already acquired one for this element, return it.
if (typeof(this.selectorMap[elem.id]) == 'object') {
this.selectorMap[elem.id].locked = true;
$("#hide_curtselid").val(elem.id);
return this.selectorMap[elem.id];
}
for (i = 0; i < N; ++i) {
if (this.selectors[i] && !this.selectors[i].locked) {
this.selectors[i].locked = true;
this.selectors[i].reset(elem);
this.selectorMap[elem.id] = this.selectors[i];
return this.selectors[i];
}
}
// if we reached here, no available selectors were found, we create one
this.selectors[N] = new svgedit.select.Selector(N, elem);
this.selectorParentGroup.appendChild(this.selectors[N].selectorGroup);
this.selectorMap[elem.id] = this.selectors[N];
return this.selectors[N];
};
// Function: svgedit.select.SelectorManager.requestSelectors
// Returns the selector based on the given Rect and line and Path
//
// Parameters:
// elem - DOM element to get the selector for
svgedit.select.SelectorManager.prototype.requestSelectors = function(elem) {
if (elem == null) {return null;}
var i,
N = this.selectors.length;
// console.log(elem);
// If we've already acquired one for this element, return it.
if (typeof(this.selectorMap[elem.id]) == 'object') {
this.selectorMap[elem.id].locked = true;
$("#hide_curtselid").val(elem.id);
return this.selectorMap[elem.id];
}
for (i = 0; i < N; ++i) {
if (this.selectors[i] && !this.selectors[i].locked) {
this.selectors[i].locked = true;
this.selectors[i].reset(elem);
this.selectorMap[elem.id] = this.selectors[i];
return this.selectors[i];
}
}
// if we reached here, no available selectors were found, we create one
this.selectors[N] = new svgedit.select.Selector(N, elem);
this.selectorParentGroup.appendChild(this.selectors[N].selectorGroup);
this.selectorMap[elem.id] = this.selectors[N];
return this.selectors[N];
};
// Function: svgedit.select.SelectorManager.releaseSelector
// Removes the selector of the given element (hides selection box)
//
// Parameters:
// elem - DOM element to remove the selector for
svgedit.select.SelectorManager.prototype.releaseSelector = function(elem) {
if (elem == null) {return;}
var i,
N = this.selectors.length,
sel = this.selectorMap[elem.id];
for (i = 0; i < N; ++i) {
if (this.selectors[i] && this.selectors[i] == sel) {
if (sel.locked == false) {
// TODO(codedread): Ensure this exists in this module.
console.log('WARNING! selector was released but was already unlocked');
}
delete this.selectorMap[elem.id];
sel.locked = false;
sel.selectedElement = null;
sel.showGrips(false);
// remove from DOM and store reference in JS but only if it exists in the DOM
try {
sel.selectorGroup.setAttribute('display', 'none');
} catch(e) { }
break;
}
}
};
// Function: svgedit.select.SelectorManager.getRubberBandBox
// Returns the rubberBandBox DOM element. This is the rectangle drawn by the user for selecting/zooming
svgedit.select.SelectorManager.prototype.getRubberBandBox = function() {
if (!this.rubberBandBox) {
this.rubberBandBox = this.selectorParentGroup.appendChild(
svgFactory_.createSVGElement({
'element': 'path',
'attr': {
'id': 'selectorRubberBand',
'fill': '#22C',
'fill-opacity': 0.15,
'stroke': '#22C',
'stroke-width': 10.5,
'display': 'block',
'style': 'pointer-events:none'
}
})
);
}
return this.rubberBandBox;
};
/**
* Interface: svgedit.select.SVGFactory
* An object that creates SVG elements for the canvas.
*
* interface svgedit.select.SVGFactory {
* SVGElement createSVGElement(jsonMap);
* SVGSVGElement svgRoot();
* SVGSVGElement svgContent();
*
* Number currentZoom();
* Object getStrokedBBox(Element[]); // TODO(codedread): Remove when getStrokedBBox() has been put into svgutils.js
* }
*/
/**
* Function: svgedit.select.init()
* Initializes this module.
*
* Parameters:
* config - an object containing configurable parameters (imgPath)
* svgFactory - an object implementing the SVGFactory interface (see above).
*/
svgedit.select.init = function(config, svgFactory) {
config_ = config;
svgFactory_ = svgFactory;
selectorManager_ = new svgedit.select.SelectorManager();
};
/**
* Function: svgedit.select.getSelectorManager
*
* Returns:
* The SelectorManager instance.
*/
svgedit.select.getSelectorManager = function() {
return selectorManager_;
};
}());
Dynamic create <g> tags based
<g id="selectorParentGroup" >
<rect id="selectorRubberBand" fill="#22C" fill-opacity="0.15" stroke="#22C" stroke-width="10.5" display="none" style="pointer-events:none" x="2034" y="909.800048828125" width="0" height="0"/>
<path id="selectedBox0" fill="none" stroke="#22C" stroke-dasharray="5,5" style="pointer-events:none" d="M2057,948.716552734375 L2280.5,948.716552734375 2280.5,1092 2057,1092z"/>
</g>
Just check both attached images, we are on the first stage (image) we to make it us the second stage - Selection area should be in over boundary as in secondary image (should be as image right side). Kindly assist me.
All suggestion will be highly appreciated.
I'm using chart.js to display a Doughnut chart on my site, I'd like to be able to populate the values in the chart dynamically with an array of content pulled from somewhere else on the page. Chart.js stores it's values for the Doughnut chart in this data structure
var doughnutData = [
{
value: 10,
color:"#F7464A"
},
{
value : 10,
color : "#46BFBD"
},
{
value : 20,
color : "#FDB45C"
},
{
value : 50,
color : "#949FB1"
}
];
How can I populate the values of this data structure dynamically in Javascript?
I already know how I'm going to generate the random colors using this (for anyone interested)
'#'+Math.floor(Math.random()*16777215).toString(16)
The issue you're going to have is that actual randomness isn't really perceived as random, so you're going to have to figure out a way to check the color similarity between each of these colors to make sure they don't appear to be indistinguishable. I've done this before with three colors (below). What I would suggest instead is creating a list of colors that you already know are dissimilar and randomly choosing from that list instead.
I tried to keep my colors within a certain range:
function getRandomColor() {
var color = '';
while (!color.match(/(#[c-e].)([e-f][a-f])([9-c].)/)) {
color = '#' + Math.floor(Math.random() * (Math.pow(16,6))).toString(16);
}
return color;
}
function getColorSimilarityIndex(c1, c2) {
var index = 0;
for (i = 1; i <= 6; i++) {
if (i == 1 || i == 5) {
if (c1.substring(i, i + 1) === c2.substring(i, i + 1)) {
index++;
}
}
}
return index;
}
var color1 = getRandomColor();
var color2 = getRandomColor();
var color3 = getRandomColor();
while (getColorSimilarityIndex(color2, color1) > 0) {
color2 = getRandomColor();
}
while (getColorSimilarityIndex(color3, color1) > 0 || getColorSimilarityIndex(color3, color2) > 0) {
color3 = getRandomColor();
}
You have to build the array with the appropriate elements in it like the below:
var getcolor = function (num) {
if (num == 0)
return "#F38630";
if (num == 1)
return "#E0E4CC";
if (num == 2)
return "#69D2E7";
if (num == 3)
return "#003399";
if (num == 4)
return "#969696";
};
var piedata = []
for (i = 0; i <= self.countViolationsByManager().length - 1; i++) {
piedata.push({
value: self.countViolationsByManager()[i].Count(),
label: self.countViolationsByManager()[i].MoneyManagerName(),
color: getcolor(i)
});
}
var pieoptions = {
}
var ctx = $("#myPieChart").get(0).getContext("2d");
var myNewChart = new Chart(ctx);
new Chart(ctx).Pie(piedata, pieoptions);