Is it possible to brute force this 5*5 Tic-Tac-Toe game? - tic-tac-toe

So I made up this game, but I'm not able to find a good strategy to always win.
It's very similar to the original 3×3 Tic-Tac-Toe; but with changes.
So you draw a 5×5 board. Then each player takes turns putting a cross and circle. We then count the "scores".
This is a completed game that I drew. The scores are made by counting every 3-in-a-row strike. So for example in the top row, cross player gets 1 point.
You then do the counting horizontally, vertically, and both ways diagonally; for each row, column and diagonal.
For a 4-in-a-row, you get two points, as you can look at it as 2 different 3-in-a-rows. Similarly, a 5-in-a-row would get 3 points.
In the example game, cross wins as it get 9 whereas circle gets only 7.
I play this a lot; but it's always hard to tell where to put your next move. I've noticed that starting first gives you a significant advantage. To compensate for this, I lower the points of the player who started first by one.
Is it possible to brute force a computer to learn the best move for every game?
Thanks in advance!
Side note: If someone can program this as a simple game with random computer moves, that'd be great. I'm just getting into programming and I'm having a hard time figuring how to do it.

Brute forced
The snippet below will brute force all end games for player X and player O
There are 33,542,145 permutations to test of which ~ 5,000,000 are valid end games. To prevent the page locking up it splits the task into groups of 250,000 permutations
Best score for X is 16 and for O is 14. Run the snippet to see example end game layout and other details.
I did not include any handicap.
This can only do end games as it uses bit fields to keep performance high. However bit fields have no room of empty board positions.
It can be optimized to do both players at the same time by inverting the bits for each game permutation
const tag = (tag, props = {}) => Object.assign(document.createElement(tag), props);
const append = (par, ...sibs) => sibs.reduce((p, sib) => (p.appendChild(sib), p), par);
const log = (el, data, add = true) => add ? append(el, tag("div", {textContent: data.toString()})) : el.textContent = data.toString();
const scores = {
[0b00111] : 1,
[0b01110] : 1,
[0b11100] : 1,
[0b11101] : 1,
[0b10111] : 1,
[0b01111] : 2,
[0b11110] : 2,
[0b11111] : 3,
};
const rotateMat = [
0, 5, 10, 15, 20,
1, 6, 11, 16, 21,
2, 7, 12, 17, 22,
3, 8, 13, 18, 23,
4, 9, 14, 19, 24
];
const diagonMat = [
2, 8, 14, -1, -1,
1, 7, 13, 19, -1,
0, 6, 12, 18, 24,
5, 11, 17, 23, -1,
10, 16, 22, -1, -1,
];
const diagonMatUp = [
10, 6, 2, -1, -1,
15, 11, 7, 3, -1,
20, 16, 12, 8, 4,
21, 17, 13, 9, -1,
22, 18, 14,-1, -1,
];
function transform(board, matrix) {
var i = 25, b = 0;
while (i--) { matrix[i] > -1 && (board & (1 << matrix[i])) && (b |= 1 << i) }
return b;
}
function scoreLines(board) {
var i = 5, score = 0, l = 0;
while (i--) { score += scores[(board >> (i * 5)) & 0b11111] ?? 0 }
return score;
}
function score(board) {
return scoreLines(board) +
scoreLines(transform(board, rotateMat)) +
scoreLines(transform(board, diagonMat)) +
scoreLines(transform(board, diagonMatUp));
}
function isValidGame(board, side) {
var c = 0, i = 25, bits = side + 1;
while (i-- && c < bits) { (board & (1 << i)) && c++ }
return c === side;
}
function showBoard(board, score, side) {
var i = 5;
log(games, "------------------------");
log(games, "End game score: " + score + " player: " + (side===13 ? "X" : "O"));
log(games, "Example end game");
while (i--) {
const line = ((board >> (i * 5)) & 0b11111).toString(2).padStart(5, "0");
const lined = side === 13 ?
line.replace(/1/g,"X").replace(/0/g,"O") :
line.replace(/1/g,"O").replace(/0/g,"X");
log(games, lined);
}
}
function brute(side = 13) {
function doSet(i) {
var ii = 251357;
while (i >= min && ii--) {
if (isValidGame(i, side)) {
gameCount ++;
const s = score(i);
if (s >= maxScore) {
if (s > maxScore) {
bestEndGames.length = 0
game = i;
}
maxScore = s;
bestEndGames.push(i);
}
}
i--;
}
if (i >= min) {
setTimeout(doSet, 8, i);
log(progress, status + " is: " + maxScore + " tested: " + ((max - min) - (i - min)) + " of " + (max - min) + " permutations", false);
} else {
log(games, status + " is: " + maxScore + " of " + gameCount + " end games");
log(games, "Number of end games with best score: " + bestEndGames.length);
showBoard(game, maxScore, side);
if (side === 13) { setTimeout(brute, 1000, 12) }
else { log(progress, "Done", false) }
}
}
var game, gameCount = 0;
var maxScore = 0;
const bestEndGames = [];
const status = "Best score player: " + (side===13 ? "X" : "O");
const [min, max] = side === 13 ? [0b1111111111111, 0b1111111111111000000000000] : [0b111111111111, 0b1111111111110000000000000];
doSet(max)
}
brute(13);
<div id="progress"></div>
<div id="games"></div>
To score a game the next snippet will do so. A bit of a hack, enter game into input fields and will spit out scores.
const tag = (tag, props = {}) => Object.assign(document.createElement(tag), props);
const append = (par, ...sibs) => sibs.reduce((p, sib) => (p.appendChild(sib), p), par);
const log = (el, data, add = true) => add ? append(el, tag("div", {textContent: data.toString()})) : el.textContent = data.toString();
const scores = {
[0b00111] : 1,
[0b01110] : 1,
[0b11100] : 1,
[0b11101] : 1,
[0b10111] : 1,
[0b01111] : 2,
[0b11110] : 2,
[0b11111] : 3,
};
const rotateMat = [
0, 5, 10, 15, 20,
1, 6, 11, 16, 21,
2, 7, 12, 17, 22,
3, 8, 13, 18, 23,
4, 9, 14, 19, 24
];
const diagonMat = [
2, 8, 14, -1, -1,
1, 7, 13, 19, -1,
0, 6, 12, 18, 24,
5, 11, 17, 23, -1,
10, 16, 22, -1, -1,
];
const diagonMatUp = [
10, 6, 2, -1, -1,
15, 11, 7, 3, -1,
20, 16, 12, 8, 4,
21, 17, 13, 9, -1,
22, 18, 14,-1, -1,
];
function transform(board, matrix) {
var i = 25, b = 0;
while (i--) { matrix[i] > -1 && (board & (1 << matrix[i])) && (b |= 1 << i) }
return b;
}
function scoreLines(board) {
var i = 5, score = 0, l = 0;
while (i--) { score += scores[(board >> (i * 5)) & 0b11111] ?? 0 }
return score;
}
function score(board) {
return scoreLines(board) +
scoreLines(transform(board, rotateMat)) +
scoreLines(transform(board, diagonMat)) +
scoreLines(transform(board, diagonMatUp));
}
function isValidGame(board, side, asStr) {
var c = 0, i = 25, bits = side + 1;
while (i-- && c < bits) { (
(asStr[24-i] !== "-" && asStr[24-i] !== " ") && board & (1 << i)) && c++
}
return [c <= side, c];
}
function showBoard(board, asStr) {
var i = 0;
var j = 0;
while (i < 5) {
const line = ((board >> ((4-i) * 5)) & 0b11111).toString(2).padStart(5, "0");
const lined = line.replace(/1/g,"X").replace(/0/g,"O");
var str = "";
j = 0;
while (j < 5) {
str += (asStr[i * 5 + j] !== "-" && asStr[i * 5 + j] !== " ") ? lined[j] : ".";
j++;
}
log(games, str);
i++;
}
}
gameVal1.addEventListener("input", testGame);
gameVal2.addEventListener("input", testGame);
gameVal3.addEventListener("input", testGame);
gameVal4.addEventListener("input", testGame);
gameVal5.addEventListener("input", testGame);
function testGame() {
var board = gameVal1.value.slice(0,5).padEnd(5, "-");
board += gameVal2.value.slice(0,5).padEnd(5, "-");
board += gameVal3.value.slice(0,5).padEnd(5, "-");
board += gameVal4.value.slice(0,5).padEnd(5, "-");
board += gameVal5.value.slice(0,5).padEnd(5, "-");
board = board.replace(/[^OX\- ]/gi,"-");
const X = eval("0B" + board.replace(/X/gi,"1").replace(/O|-| /gi,"0"));
const O = eval("0B" + board.replace(/X|-| /gi,"0").replace(/O/gi,"1"));
const [vx, movesX] = isValidGame(X, 13, board);
const [vo, movesY] = isValidGame(O, 12, board);
vx && log(resultX, "Player X score: " + score(X) + " moves: " + movesX, false);
vo && log(resultO, "Player O score: " + score(O) + " moves: " + movesY, false);
games.innerHTML = "";
showBoard(X, board);
}
testGame()
input {
font-family: monospace;
font-size: large;
}
.fixFont {
font-family: monospace;
font-size: large;
}
#games {
position: absolute;
top: 28px;
left: 80px;
border: 1px solid black;
padding: 0px 3px;
letter-spacing: 3px;
}
#resultX {
position: absolute;
top: 40px;
left: 160px;
}
#resultO {
position: absolute;
top: 60px;
left: 160px;
}
<div class="fixFont">
Enter "x" "x" "O" or "o". Empty slots "-" or space<br>
<input type="text" id="gameVal1" value="XXXXX" size="5"><br>
<input type="text" id="gameVal2" value="XXXXO" size="5"><br>
<input type="text" id="gameVal3" value="XXXXO" size="5"><br>
<input type="text" id="gameVal4" value="OOOOO" size="5"><br>
<input type="text" id="gameVal5" value="OOOOO" size="5"><br>
<div id="resultX"></div>
<div id="resultO"></div>
<div id="games"></div>
</div>

Related

jsPDF + html2PDF - how do I append html code to footer

I know how to append my footer with text but now I need to append dynamic HTML code. I can't find anything in the documentation or in any forum.
const page_margin_left = 20;
const page_margin_right = 15;
const page_margin_bottom = 20;
const page_margin_top = 5;
html2pdf()
.set({
margin: [page_margin_top, page_margin_left, page_margin_bottom, page_margin_right],
filename: 'test.pdf',
image: { type: 'jpeg', quality: 1 },
html2canvas: { dpi: 192, scale: 2, letterRendering: true, useCORS: true },
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
//pageBreak: {mode: ['avoid-all', 'css'], avoid: ['.pi-row']},
})
.from(print_target_container[0].innerHTML)
.toPdf().get('pdf').then(function (pdf) {
var totalPages = pdf.internal.getNumberOfPages();
var pageHeight = pdf.internal.pageSize.height || pdf.internal.pageSize.getHeight();
var pageWidth = pdf.internal.pageSize.width || pdf.internal.pageSize.getWidth();
var footerText = offer_template_page_footer;
for (i = 1; i <= totalPages; i++) {
if (i > 1) {
pdf.setPage(i);
pdf.setFontSize(8);
pdf.setTextColor(150);
pdf.line(25, pageHeight - 15, pageWidth - page_margin_right, pageHeight - 15); // horizontal line x1, y1, x2, y2, style
pdf.text(i + ' (' + totalPages + ')\n' + footerText, pageWidth / 2, pageHeight - 10, {align: 'center'});
//pdf.text(i + ' (' + totalPages + ')\n' + offer_template_page_footer, pdf.internal.pageSize.getWidth() / 2, pdf.internal.pageSize.getHeight() - 8);
}
}
}).save();

onEdit in Script vs onEdit trigger / Column Width Script Not Working For Editors

I'm a beginner and created a script to expand columns on 4 different tabs (A Sheet, B Sheet, C Sheet, D Sheet).
For myself, the script author, the script works fine on all tabs.
For invited editors, the script seems to work fine on the first tab (A Sheet) but does not work on the other tabs (B Sheet, C Sheet, D Sheet).
I tried adding a onEdit trigger which seems to fix this problem but now I'm receiving "Service using too much computer time for one day" errors.
On 8/27 I received a notice that the "Service using too much computer time for one day" error occurred 3 times. When I check My Executions logs I only see about 17 seconds of trigger time.
My questions are:
1) If I have onEdit(e) in my script code do I need an onEdit trigger?
2) Do I need to add onEdit(e) in my script code in more places such as above the B, C and D script sections?
3) Why does the script work for me, the author, but not the users with editing permissions.
4) Why do I receive "Service using too much computer time for one day" error notifications when my total execution duration time is well below the limit of 90min/day.
5) Can someone point me to good formating rules for script?
Thanks!
function onEdit(e) {
var app = SpreadsheetApp;
var ss = app.getActiveSpreadsheet();
var Sh1 = ss.getSheetByName("A Sheet");
var clmnwdth = Sh1.getRange("G16:G27").getValues();
var maxi = Math.max.apply(Math, clmnwdth);
var clmn6 = Sh1.getRange("AB13:AE15");
var clmn56 = Sh1.getRange("X13:AE15");
var clmn456 = Sh1.getRange("T13:AE15");
var clmn3456 = Sh1.getRange("P13:AE15");
var clmn23456 = Sh1.getRange("L13:AE15");
var clmn2 = Sh1.getRange("L13:O15");
var clmn23 = Sh1.getRange("L13:S15");
var clmn234 = Sh1.getRange("L13:W15");
var clmn2345 = Sh1.getRange("L13:AA15");
Logger.log(maxi);
if (maxi == 0) {
Sh1.setColumnWidths( 12, 20, 5),clmn23456.setFontColor("white");
} else if (maxi == 1) {
Sh1.setColumnWidths( 12, 20, 5),clmn23456.setFontColor("white");
} else if (maxi == 2) {
Sh1.setColumnWidths( 16, 16, 5),Sh1.setColumnWidths( 12, 4, 100),clmn3456.setFontColor("white"),clmn2.setFontColor("black");
} else if (maxi == 3) {
Sh1.setColumnWidths( 20, 12, 5),Sh1.setColumnWidths( 12, 8, 100),clmn456.setFontColor("white"),clmn23.setFontColor("black");
} else if (maxi == 4) {
Sh1.setColumnWidths( 24, 8, 5),Sh1.setColumnWidths( 12, 12, 100),clmn56.setFontColor("white"),clmn234.setFontColor("black");
} else if (maxi == 5) {
Sh1.setColumnWidths( 28, 4, 5),Sh1.setColumnWidths( 12, 16, 100),clmn6.setFontColor("white"),clmn2345.setFontColor("black");
} else {
Sh1.setColumnWidths( 12, 20, 100),clmn23456.setFontColor("black");
}
var BSh1 = ss.getSheetByName("B Sheet");
var Bclmnwdth = BSh1.getRange("G16:G27").getValues();
var Bmaxi = Math.max.apply(Math, Bclmnwdth);
var Bclmn6 = BSh1.getRange("AB13:AE15");
var Bclmn56 = BSh1.getRange("X13:AE15");
var Bclmn456 = BSh1.getRange("T13:AE15");
var Bclmn3456 = BSh1.getRange("P13:AE15");
var Bclmn23456 = BSh1.getRange("L13:AE15");
var Bclmn2 = BSh1.getRange("L13:O15");
var Bclmn23 = BSh1.getRange("L13:S15");
var Bclmn234 = BSh1.getRange("L13:W15");
var Bclmn2345 = BSh1.getRange("L13:AA15");
Logger.log(Bmaxi);
if (Bmaxi == 0) {
BSh1.setColumnWidths( 12, 20, 5),Bclmn23456.setFontColor("white");
} else if (Bmaxi == 1) {
BSh1.setColumnWidths( 12, 20, 5),Bclmn23456.setFontColor("white");
} else if (Bmaxi == 2) {
BSh1.setColumnWidths( 16, 16, 5),BSh1.setColumnWidths( 12, 4, 100),Bclmn3456.setFontColor("white"),Bclmn2.setFontColor("black");
} else if (Bmaxi == 3) {
BSh1.setColumnWidths( 20, 12, 5),BSh1.setColumnWidths( 12, 8, 100),Bclmn456.setFontColor("white"),Bclmn23.setFontColor("black");
} else if (Bmaxi == 4) {
BSh1.setColumnWidths( 24, 8, 5),BSh1.setColumnWidths( 12, 12, 100),Bclmn56.setFontColor("white"),Bclmn234.setFontColor("black");
} else if (Bmaxi == 5) {
BSh1.setColumnWidths( 28, 4, 5),BSh1.setColumnWidths( 12, 16, 100),Bclmn6.setFontColor("white"),Bclmn2345.setFontColor("black");
} else {
BSh1.setColumnWidths( 12, 20, 100),Bclmn23456.setFontColor("black");
}
var CSh1 = ss.getSheetByName("C Sheet");
var Cclmnwdth = CSh1.getRange("G16:G27").getValues();
var Cmaxi = Math.max.apply(Math, Cclmnwdth);
var Cclmn6 = CSh1.getRange("AB13:AE15");
var Cclmn56 = CSh1.getRange("X13:AE15");
var Cclmn456 = CSh1.getRange("T13:AE15");
var Cclmn3456 = CSh1.getRange("P13:AE15");
var Cclmn23456 = CSh1.getRange("L13:AE15");
var Cclmn2 = CSh1.getRange("L13:O15");
var Cclmn23 = CSh1.getRange("L13:S15");
var Cclmn234 = CSh1.getRange("L13:W15");
var Cclmn2345 = CSh1.getRange("L13:AA15");
Logger.log(Cmaxi);
if (Cmaxi == 0) {
CSh1.setColumnWidths( 12, 20, 5),Cclmn23456.setFontColor("white");
} else if (Cmaxi == 1) {
CSh1.setColumnWidths( 12, 20, 5),Cclmn23456.setFontColor("white");
} else if (Cmaxi == 2) {
CSh1.setColumnWidths( 16, 16, 5),CSh1.setColumnWidths( 12, 4, 100),Cclmn3456.setFontColor("white"),Cclmn2.setFontColor("black");
} else if (Cmaxi == 3) {
CSh1.setColumnWidths( 20, 12, 5),CSh1.setColumnWidths( 12, 8, 100),Cclmn456.setFontColor("white"),Cclmn23.setFontColor("black");
} else if (Cmaxi == 4) {
CSh1.setColumnWidths( 24, 8, 5),CSh1.setColumnWidths( 12, 12, 100),Cclmn56.setFontColor("white"),Cclmn234.setFontColor("black");
} else if (Cmaxi == 5) {
CSh1.setColumnWidths( 28, 4, 5),CSh1.setColumnWidths( 12, 16, 100),Cclmn6.setFontColor("white"),Cclmn2345.setFontColor("black");
} else {
CSh1.setColumnWidths( 12, 20, 100),Cclmn23456.setFontColor("black");
}
var DSh1 = ss.getSheetByName("D Sheet");
var Dclmnwdth = DSh1.getRange("G16:G27").getValues();
var Dmaxi = Math.max.apply(Math, Dclmnwdth);
var Dclmn6 = DSh1.getRange("AB13:AE15");
var Dclmn56 = DSh1.getRange("X13:AE15");
var Dclmn456 = DSh1.getRange("T13:AE15");
var Dclmn3456 = DSh1.getRange("P13:AE15");
var Dclmn23456 = DSh1.getRange("L13:AE15");
var Dclmn2 = DSh1.getRange("L13:O15");
var Dclmn23 = DSh1.getRange("L13:S15");
var Dclmn234 = DSh1.getRange("L13:W15");
var Dclmn2345 = DSh1.getRange("L13:AA15");
Logger.log(Dmaxi);
if (Dmaxi == 0) {
DSh1.setColumnWidths( 12, 20, 5),Dclmn23456.setFontColor("white");
} else if (Dmaxi == 1) {
DSh1.setColumnWidths( 12, 20, 5),Dclmn23456.setFontColor("white");
} else if (Dmaxi == 2) {
DSh1.setColumnWidths( 16, 16, 5),DSh1.setColumnWidths( 12, 4, 100),Dclmn3456.setFontColor("white"),Dclmn2.setFontColor("black");
} else if (Dmaxi == 3) {
DSh1.setColumnWidths( 20, 12, 5),DSh1.setColumnWidths( 12, 8, 100),Dclmn456.setFontColor("white"),Dclmn23.setFontColor("black");
} else if (Dmaxi == 4) {
DSh1.setColumnWidths( 24, 8, 5),DSh1.setColumnWidths( 12, 12, 100),Dclmn56.setFontColor("white"),Dclmn234.setFontColor("black");
} else if (Dmaxi == 5) {
DSh1.setColumnWidths( 28, 4, 5),DSh1.setColumnWidths( 12, 16, 100),Dclmn6.setFontColor("white"),Dclmn2345.setFontColor("black");
} else {
DSh1.setColumnWidths( 12, 20, 100),Dclmn23456.setFontColor("black");
}
}

How to display count value of each category of Y axis in a graph using Morris.Bar function?

I am displaying data in graphical format and I am using Morris.Bar function in my cshtml page. The Y axis has categories namely: Performance, Maintainability, Others, Portability, Reliability and Security.
I am using the following function:
Morris.Bar({
element: 'category-bar-chart',
data: JSON.parse(''[{"y":"Performance","a":23},{"y":"Maintainability","a":106},{"y":"Others","a":98},{"y":"Portability","a":27},{"y":"Reliability","a":87},{"y":"Security","a":14}]'),'),
xkey: 'y',
ykeys: ['a'],
labels: ['Violation'],
xLabelAngle: 43,
});
But currently it is not displaying the value for each category at the top of each bar. May I know what property I can add to get the values at the top of each bar?
There's no built-in parameter to display the value on top of each Bar.
But you can extend Morris to add this parameter. I've extended Morris, adding a labelTop property for Bar charts. If set to true, a label with the value is added on top of each Bar (I restricted this property for non stacked Bar, as there's multiple values with stacked Bar).
Usage:
labelTop: true
Please try the snippet below to see a working example:
(function() {
var $, MyMorris;
MyMorris = window.MyMorris = {};
$ = jQuery;
MyMorris = Object.create(Morris);
MyMorris.Bar.prototype.defaults["labelTop"] = false;
MyMorris.Bar.prototype.drawLabelTop = function(xPos, yPos, text) {
var label;
return label = this.raphael.text(xPos, yPos, text).attr('font-size', this.options.gridTextSize).attr('font-family', this.options.gridTextFamily).attr('font-weight', this.options.gridTextWeight).attr('fill', this.options.gridTextColor);
};
MyMorris.Bar.prototype.drawSeries = function() {
var barWidth, bottom, groupWidth, idx, lastTop, left, leftPadding, numBars, row, sidx, size, spaceLeft, top, ypos, zeroPos;
groupWidth = this.width / this.options.data.length;
numBars = this.options.stacked ? 1 : this.options.ykeys.length;
barWidth = (groupWidth * this.options.barSizeRatio - this.options.barGap * (numBars - 1)) / numBars;
if (this.options.barSize) {
barWidth = Math.min(barWidth, this.options.barSize);
}
spaceLeft = groupWidth - barWidth * numBars - this.options.barGap * (numBars - 1);
leftPadding = spaceLeft / 2;
zeroPos = this.ymin <= 0 && this.ymax >= 0 ? this.transY(0) : null;
return this.bars = (function() {
var _i, _len, _ref, _results;
_ref = this.data;
_results = [];
for (idx = _i = 0, _len = _ref.length; _i < _len; idx = ++_i) {
row = _ref[idx];
lastTop = 0;
_results.push((function() {
var _j, _len1, _ref1, _results1;
_ref1 = row._y;
_results1 = [];
for (sidx = _j = 0, _len1 = _ref1.length; _j < _len1; sidx = ++_j) {
ypos = _ref1[sidx];
if (ypos !== null) {
if (zeroPos) {
top = Math.min(ypos, zeroPos);
bottom = Math.max(ypos, zeroPos);
} else {
top = ypos;
bottom = this.bottom;
}
left = this.left + idx * groupWidth + leftPadding;
if (!this.options.stacked) {
left += sidx * (barWidth + this.options.barGap);
}
size = bottom - top;
if (this.options.verticalGridCondition && this.options.verticalGridCondition(row.x)) {
this.drawBar(this.left + idx * groupWidth, this.top, groupWidth, Math.abs(this.top - this.bottom), this.options.verticalGridColor, this.options.verticalGridOpacity, this.options.barRadius, row.y[sidx]);
}
if (this.options.stacked) {
top -= lastTop;
}
this.drawBar(left, top, barWidth, size, this.colorFor(row, sidx, 'bar'), this.options.barOpacity, this.options.barRadius);
_results1.push(lastTop += size);
if (this.options.labelTop && !this.options.stacked) {
label = this.drawLabelTop((left + (barWidth / 2)), top - 10, row.y[sidx]);
textBox = label.getBBox();
_results.push(textBox);
}
} else {
_results1.push(null);
}
}
return _results1;
}).call(this));
}
return _results;
}).call(this);
};
}).call(this);
Morris.Bar({
element: 'category-bar-chart',
data: [
{ "y": "Performance", "a": 23 },
{ "y": "Maintainability", "a": 106 },
{ "y": "Others", "a": 98 },
{ "y": "Portability", "a": 27 },
{ "y": "Reliability", "a": 87 },
{ "y": "Security", "a": 14 }],
xkey: 'y',
ykeys: ['a'],
labels: ['Violation'],
xLabelAngle: 43,
labelTop: true
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css" rel="stylesheet" />
<div id="category-bar-chart"></div>

Chart.js dynamic bar width

I have a requirement to render a set of time series data of contiguous blocks.
I need to describe a series of bars which could span many hours, or just minutes, with their own Y value.
I'm not sure if ChartJS is what I should be using for this, but I have looked at extending the Bar type, but it seems very hard coded for each bar to be the same width. The Scale Class internally is used for labels, chart width etc, not just the bars themselves.
I am trying to achieve something like this that works in Excel: http://peltiertech.com/variable-width-column-charts/
Has anyone else had to come up with something similar?
I found I needed to do this and the answer by #potatopeelings was great, but out of date for version 2 of Chartjs. I did something similar by creating my own controller/chart type via extending bar:
//controller.barw.js
module.exports = function(Chart) {
var helpers = Chart.helpers;
Chart.defaults.barw = {
hover: {
mode: 'label'
},
scales: {
xAxes: [{
type: 'category',
// Specific to Bar Controller
categoryPercentage: 0.8,
barPercentage: 0.9,
// grid line settings
gridLines: {
offsetGridLines: true
}
}],
yAxes: [{
type: 'linear'
}]
}
};
Chart.controllers.barw = Chart.controllers.bar.extend({
/**
* #private
*/
getRuler: function() {
var me = this;
var scale = me.getIndexScale();
var options = scale.options;
var stackCount = me.getStackCount();
var fullSize = scale.isHorizontal()? scale.width : scale.height;
var tickSize = fullSize / scale.ticks.length;
var categorySize = tickSize * options.categoryPercentage;
var fullBarSize = categorySize / stackCount;
var barSize = fullBarSize * options.barPercentage;
barSize = Math.min(
helpers.getValueOrDefault(options.barThickness, barSize),
helpers.getValueOrDefault(options.maxBarThickness, Infinity));
return {
fullSize: fullSize,
stackCount: stackCount,
tickSize: tickSize,
categorySize: categorySize,
categorySpacing: tickSize - categorySize,
fullBarSize: fullBarSize,
barSize: barSize,
barSpacing: fullBarSize - barSize,
scale: scale
};
},
/**
* #private
*/
calculateBarIndexPixels: function(datasetIndex, index, ruler) {
var me = this;
var scale = ruler.scale;
var options = scale.options;
var isCombo = me.chart.isCombo;
var stackIndex = me.getStackIndex(datasetIndex);
var base = scale.getPixelForValue(null, index, datasetIndex, isCombo);
var size = ruler.barSize;
var dataset = me.chart.data.datasets[datasetIndex];
if(dataset.weights) {
var total = dataset.weights.reduce((m, x) => m + x, 0);
var perc = dataset.weights[index] / total;
var offset = 0;
for(var i = 0; i < index; i++) {
offset += dataset.weights[i] / total;
}
var pixelOffset = Math.round(ruler.fullSize * offset);
var base = scale.isHorizontal() ? scale.left : scale.top;
base += pixelOffset;
size = Math.round(ruler.fullSize * perc);
size -= ruler.categorySpacing;
size -= ruler.barSpacing;
}
base -= isCombo? ruler.tickSize / 2 : 0;
base += ruler.fullBarSize * stackIndex;
base += ruler.categorySpacing / 2;
base += ruler.barSpacing / 2;
return {
size: size,
base: base,
head: base + size,
center: base + size / 2
};
},
});
};
Then you need to add it to your chartjs instance like this:
import Chart from 'chart.js'
import barw from 'controller.barw'
barw(Chart); //add plugin to chartjs
and finally, similar to the other answer, the weights of the bar widths need to be added to the data set:
var data = {
labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G'],
datasets: [
{
label: "My First dataset",
fillColor: "rgba(220,220,220,0.5)",
strokeColor: "rgba(220,220,220,0.8)",
highlightFill: "rgba(220,220,220,0.7)",
highlightStroke: "rgba(220,220,220,1)",
data: [65, 59, 80, 30, 56, 65, 40],
weights: [1, 0.9, 1, 2, 1, 4, 0.3]
},
]
};
This will hopefully get someone onto the right track. What I have certainly isn't perfect, but if you make sure you have the right number of weight to data points, you should be right.
Best of luck.
This is based on the #Shane's code, I just posted to help, since is a common question.
calculateBarIndexPixels: function (datasetIndex, index, ruler) {
const options = ruler.scale.options;
const range = options.barThickness === 'flex' ? computeFlexCategoryTraits(index, ruler, options) : computeFitCategoryTraits(index, ruler, options);
const barSize = range.chunk;
const stackIndex = this.getStackIndex(datasetIndex, this.getMeta().stack);
let center = range.start + range.chunk * stackIndex + range.chunk / 2;
let size = range.chunk * range.ratio;
let start = range.start;
const dataset = this.chart.data.datasets[datasetIndex];
if (dataset.weights) {
//the max weight should be one
size = barSize * dataset.weights[index];
const meta = this.chart.controller.getDatasetMeta(0);
const lastModel = index > 0 ? meta.data[index - 1]._model : null;
//last column takes the full bar
if (lastModel) {
//start could be last center plus half of last column width
start = lastModel.x + lastModel.width / 2;
}
center = start + size * stackIndex + size / 2;
}
return {
size: size,
base: center - size / 2,
head: center + size / 2,
center: center
};
}
For Chart.js you can create a new extension based on the bar class to do this. It's a bit involved though - however most of it is a copy paste of the bar type library code
Chart.types.Bar.extend({
name: "BarAlt",
// all blocks that don't have a comment are a direct copy paste of the Chart.js library code
initialize: function (data) {
// the sum of all widths
var widthSum = data.datasets[0].data2.reduce(function (a, b) { return a + b }, 0);
// cumulative sum of all preceding widths
var cumulativeSum = [ 0 ];
data.datasets[0].data2.forEach(function (e, i, arr) {
cumulativeSum.push(cumulativeSum[i] + e);
})
var options = this.options;
// completely rewrite this class to calculate the x position and bar width's based on data2
this.ScaleClass = Chart.Scale.extend({
offsetGridLines: true,
calculateBarX: function (barIndex) {
var xSpan = this.width - this.xScalePaddingLeft;
var x = this.xScalePaddingLeft + (cumulativeSum[barIndex] / widthSum * xSpan) - this.calculateBarWidth(barIndex) / 2;
return x + this.calculateBarWidth(barIndex);
},
calculateBarWidth: function (index) {
var xSpan = this.width - this.xScalePaddingLeft;
return (xSpan * data.datasets[0].data2[index] / widthSum);
}
});
this.datasets = [];
if (this.options.showTooltips) {
Chart.helpers.bindEvents(this, this.options.tooltipEvents, function (evt) {
var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];
this.eachBars(function (bar) {
bar.restore(['fillColor', 'strokeColor']);
});
Chart.helpers.each(activeBars, function (activeBar) {
activeBar.fillColor = activeBar.highlightFill;
activeBar.strokeColor = activeBar.highlightStroke;
});
this.showTooltip(activeBars);
});
}
this.BarClass = Chart.Rectangle.extend({
strokeWidth: this.options.barStrokeWidth,
showStroke: this.options.barShowStroke,
ctx: this.chart.ctx
});
Chart.helpers.each(data.datasets, function (dataset, datasetIndex) {
var datasetObject = {
label: dataset.label || null,
fillColor: dataset.fillColor,
strokeColor: dataset.strokeColor,
bars: []
};
this.datasets.push(datasetObject);
Chart.helpers.each(dataset.data, function (dataPoint, index) {
datasetObject.bars.push(new this.BarClass({
value: dataPoint,
label: data.labels[index],
datasetLabel: dataset.label,
strokeColor: dataset.strokeColor,
fillColor: dataset.fillColor,
highlightFill: dataset.highlightFill || dataset.fillColor,
highlightStroke: dataset.highlightStroke || dataset.strokeColor
}));
}, this);
}, this);
this.buildScale(data.labels);
// remove the labels - they won't be positioned correctly anyway
this.scale.xLabels.forEach(function (e, i, arr) {
arr[i] = '';
})
this.BarClass.prototype.base = this.scale.endPoint;
this.eachBars(function (bar, index, datasetIndex) {
// change the way the x and width functions are called
Chart.helpers.extend(bar, {
width: this.scale.calculateBarWidth(index),
x: this.scale.calculateBarX(index),
y: this.scale.endPoint
});
bar.save();
}, this);
this.render();
},
draw: function (ease) {
var easingDecimal = ease || 1;
this.clear();
var ctx = this.chart.ctx;
this.scale.draw(1);
Chart.helpers.each(this.datasets, function (dataset, datasetIndex) {
Chart.helpers.each(dataset.bars, function (bar, index) {
if (bar.hasValue()) {
bar.base = this.scale.endPoint;
// change the way the x and width functions are called
bar.transition({
x: this.scale.calculateBarX(index),
y: this.scale.calculateY(bar.value),
width: this.scale.calculateBarWidth(index)
}, easingDecimal).draw();
}
}, this);
}, this);
}
});
You pass in the widths like below
var data = {
labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G'],
datasets: [
{
label: "My First dataset",
fillColor: "rgba(220,220,220,0.5)",
strokeColor: "rgba(220,220,220,0.8)",
highlightFill: "rgba(220,220,220,0.7)",
highlightStroke: "rgba(220,220,220,1)",
data: [65, 59, 80, 30, 56, 65, 40],
data2: [10, 20, 30, 20, 10, 40, 10]
},
]
};
and you call it like so
var ctx = document.getElementById('canvas').getContext('2d');
var myLineChart = new Chart(ctx).BarAlt(data);
Fiddle - http://jsfiddle.net/moye0cp4/

export to excel datagrid adobe flex3

I found the following code here only I kept the same in my Adobe Flex project but after clicking on the export to Excel button I can't see any Excel opening in addition to that my datagrid is filling with the column names which I intended to give to my Excel.
public function roExport_export_Result(e:ResultEvent):void
{
if(e.result.length != 0)
{
btnExportToExcel.enabled = true;
var arrExportResult:Array = e.result as Array;
xlsFile = new ExcelFile();
var sheet:Sheet = new Sheet();
sheet.resize(arrExportResult.length+1,14);
sheet.setCell(0,0,'Id');
sheet.setCell(0,1,'Full Name');
sheet.setCell(0,2,'Gender');
sheet.setCell(0,3,'Birth Date');
sheet.setCell(0,4,'College Name');
sheet.setCell(0,5,'Qualification');
sheet.setCell(0,6,'Email Id');
sheet.setCell(0,7,'Mobile');
sheet.setCell(0,8,'Position Applied For');
sheet.setCell(0,9,'Technology Interested');
sheet.setCell(0,10,'User Name');
sheet.setCell(0,11,'Password');
sheet.setCell(0,12,'Exam Date');
sheet.setCell(0,13,'Percentage');
sheet.setCell(0,14,'IsActive');
for(var i:int=0;i<arrExportResult.length;i++)
{
sheet.setCell(i+1, 0, arrExportResult[i].Id);
sheet.setCell(i+1, 1, arrExportResult[i].FullName);
if(arrExportResult[i].Gender == 1)
{
arrExportResult[i].Gender = "Male"
}
else
{
arrExportResult[i].Gender = "Female";
}
sheet.setCell(i+1, 2, arrExportResult[i].Gender);
var date:String = arrExportResult[i].BirthDate.date.toString();
var month:String = (arrExportResult[i].BirthDate.month + 1).toString();
var year:String = arrExportResult[i].BirthDate.fullYear.toString();
var bDate:String = date + "/" + month + "/" + year;
arrExportResult[i].BirthDate = bDate;
sheet.setCell(i+1, 3, arrExportResult[i].BirthDate);
sheet.setCell(i+1, 4, arrExportResult[i].CollegeId);
sheet.setCell(i+1, 5, arrExportResult[i].QualificationId);
sheet.setCell(i+1, 6, arrExportResult[i].EmailId);
sheet.setCell(i+1, 7, arrExportResult[i].Mobile);
sheet.setCell(i+1, 8, arrExportResult[i].PositionName);
sheet.setCell(i+1, 9, arrExportResult[i].TechForTraining);
sheet.setCell(i+1, 10, arrExportResult[i].UserName);
sheet.setCell(i+1, 11, arrExportResult[i].Password);
var date:String = arrExportResult[i].CreatedDate.date.toString();
var month:String = (arrExportResult[i].CreatedDate.month + 1).toString();
var year:String = arrExportResult[i].CreatedDate.fullYear.toString();
var hour:String = arrExportResult[i].CreatedDate.hours.toString();
var min:String = arrExportResult[i].CreatedDate.minutes.toString();
var sec:String = arrExportResult[i].CreatedDate.seconds.toString();
var cDate:String = date + "/" + month + "/" + year + " " + hour + ":" + min + ":" + sec;
arrExportResult[i].CreatedDate = cDate;
sheet.setCell(i+1, 12, arrExportResult[i].CreatedDate);
sheet.setCell(i+1, 13, arrExportResult[i].Percentage);
sheet.setCell(i+1, 14, arrExportResult[i].IsActive);
}
dataGridResult.dataProvider = arrExportResult;
xlsFile.sheets.addItem(sheet);
bytes = xlsFile.saveToByteArray();
}
else
{
arrExportResult = new Array();
dataGridResult.dataProvider = arrExportResult;
btnExportToExcel.enabled = false;
xlsFile = new ExcelFile();
var sheet:Sheet = new Sheet();
Alert.show("No Records Found",parentApplication.alertTitle);
}
}
After you get the byte Array you have to use a FileReference.save(bytes); call to pop up the window that prompts the user for where to save the data... as Flextras/Jeffry says the data grid is probably changing due to the dataGridResult.dataProvider line. If you're doing this as an AIR based desktop app you can use the File and FileStream class to write out the bytes directly without prompting the user.
So long as you have FP 10+ targetted
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/FileReference.html#save()
For AIR:
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/com/adobe/livecycle/content/File.html
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/filesystem/FileStream.html#writeBytes()