JavaScript method to determine correct path is confusing - javascript

I'm learning algorithms and doing JavaScript exercise questions, and I don't understand how one reaches the correct answer for a particular algorithm.
The question provided in the exercise is:
Have the function CorrectPath(str) read the str parameter being
passed, which will represent the movements made in a 5x5 grid of cells
starting from the top left position. The characters in the input
string will be entirely composed of: r, l, u, d, ?. Each of the
characters stand for the direction to take within the grid, for
example: r = right, l = left, u = up, d = down. Your goal is to
determine what characters the question marks should be in order for a
path to be created to go from the top left of the grid all the way to
the bottom right without touching previously travelled on cells in the
grid.
For example, the input drdr??rrddd? should ouptut drdruurrdddd
I've not found a solution on my own. I'm taking a look at a solution provided, and I'm bothered because:
A. pure functions are not used to manipulate values within the CorrectPath function (note the addX() and addY() methods contained within). I'm not convinced the solution provided is using best practices, especially coming from a functional programming background.
B. I don't understand how the steps taken, specifically in the while block and the succeeding for block, are taken to reach the correct answer and why sometimes the missingLetters array has letters remaining and other times not
The working solution provided is below
function CorrectPath(str) {
let x = 0, //start x coord
y = 0, //start y coord
missingLetters = []
const unknowns = str.match(/\W/g)
function addX() {
while(x !== 4) {
if (x > 4) {
x--;
missingLetters.push('l');
} else {
x++;
missingLetters.push('r');
}
}
}
function addY() {
while (y !== 4) {
if (y > 4) {
y--;
missingLetters.push('u');
} else {
y++;
missingLetters.push('d');
}
}
}
//tallies current number of x and y movements
for (let i=0; i<str.length; i++) {
switch (str[i]) {
case 'd':
y += 1;
break;
case 'u':
y -= 1;
break;
case 'l':
x -= 1;
break;
case 'r':
x += 1;
break;
}
}
if (x > y) { addX(); addY(); }
if (y >= x) { addY(); addX(); }
while (missingLetters.length < unknowns.length) {
var pos = missingLetters.length - 1;
if (missingLetters[pos] === 'r') {x += 1; missingLetters.push('r'); addX()}
if (missingLetters[pos] === 'l') {x -= 1; missingLetters.push('l'); addX()}
if (missingLetters[pos] === 'd') {y += 1; missingLetters.push('d'); addY()}
if (missingLetters[pos] === 'u') {y -= 1; missingLetters.push('u'); addY()}
}
var newStr = str.split('');
for (var j=0; j<str.length; j++) {
if (newStr[j] === '?') {
newStr[j] = missingLetters.shift()
}
}
return newStr.join('');
}
CorrectPath(readline());

Here's a solution I found
const dirMap = {
u: { x: 0, y: -1 },
r: { x: 1, y: 0 },
d: { x: 0, y: 1 },
l: { x: -1, y: 0 }
}
function CorrectPath(pathString) {
const map = Array(5*5)
return trace(pathString, map)
}
function trace(path, [...map], x = 0, y = 0, newPath = "") {
const steps = path.split(""),
nextMove = steps.shift()
if (nextMove === undefined) {
if (5 * y + x === (5*5-1)) return newPath
return "Bad move"
}
if (nextMove === "?") {
const moves = availableMoves(x,y,map)
if (!moves.length) return "Bad move"
for(let i = 0; i<moves.length; i++) {
let move = moves[i],
trySteps = [move,...steps].join("")
res = trace(trySteps,map,x,y,newPath)
if (!res || res === "Bad move") continue
else return res
}
return "Bad move"
} else {
if (!canMove(nextMove, x, y, map)) return "Bad move"
const pos = dirMap[nextMove],
newX = pos.x + x,
newY = pos.y + y
newPath += nextMove
map[5*newY+newX] = nextMove
return trace(steps.join(""),map,newX,newY,newPath)
}
}
function availableMoves(x,y,map) {
const steps = []
Object.keys(dirMap).forEach(z => {
if (canMove(z,x,y,map)) steps.push(z)
})
return steps
}
function canMove(dir, xPath, yPath, map) {
const pos = dirMap[dir],
x = pos.x + xPath,
y = pos.y + yPath
if (x > 4 || x < 0 || y > 4 || y < 0) return false
if (map[5*y+x] !== undefined) return false
return true
}
CorrectPath(readline());

Related

How to clip movements to a bounding box?

I'm struggling to find the logic to output a series of pen positions based on some input format.
The pen's position data should be converted in a way that maintains a state of 'pen-down' and 'pen-up' (I have that working), and that clips any pen-movement beyond the rectangle area for the input (x = -8192, y = -8192) .. (8191, 8191). I could not make that clipping work.
See below instructions for how that clipping is supposed to happen:
Instructions
Example of expected output
inputData:
F0A0417F40004000417FC067086708804001C0670840004000187818784000804000
inputData:
F0A0417F41004000417FC067086708804001C067082C3C18782C3C804000
//For decode inputData//
let commands =[] , command , allPos = "", lastPos;
function proDecode(hex) {
for (let i = 0, len; i < hex.length; i+=len) {
// Opcodes take 1 byte (i.e. 2 hex characters), and
// numbers take 2 bytes (4 characters)
len = hex[i] >= '8' ? 2:4;
let num = hex.slice(i,i+len)
if (len === 2) { // opcode
command = []
commands.push(command)
}
else{
num = penDecode(num);
console.log(num);
}
command.push(num)
}
console.log(commands);
return commands;
}
//for outPut//
unction proOutput(commands){
let isPenDown = false;
let x = 0, y = 0;
let output = "";
let color = ""
for (let [opcode, ...args] of commands) {
if (opcode === 'F0') {
x = y = 0;
isPenDown = false;
color = "CO" + 0 + 0 + 0 + 255 + ";\n";
output += "CLR;\n";
} else if (opcode === '80') {
isPenDown = args[0] > 0;
output += "PEN " + (isPenDown ? "DOWN" : "UP") + ";\n";
} else if (opcode === 'A0') {
color = "CO " + args.join(" ") + ";\n";
output += color
} else if (opcode === 'C0') {
let allPos = "", lastPos;
for (let i = 0; i < args.length; i+=2) {
x += args[i];
y += args[i+1];
if(x <-8192){
x= -8192
} else if (x>8191){
x= 8191
lastPos = ` (${x}, ${y})`;
}else if (y<-8192){
y= -8192
}else if (y>8191){
y= 8191
output += "y PEN UP" + ";\n";
} else{
}
lastPos = ` (${x}, ${y})`;
if (isPenDown) allPos += lastPos;
}
output += "MV" + (allPos || lastPos) + ";\n";
} // else: ignore unknown commands
}
Expected Result
Create the same output of the example above.
You would need to find the intersection point with the bounding box whenever the coordinates move out of bounds (or come back within bounds). In some cases a line segment could even both enter and exit the box, giving two intersections.
To calculate an intersection point between a line segment (from x1, y1 to x2, y2) and a horizontal line at y, you would use this formula for the x-coordinate:
x = x1 + (y - y1) * (x2 - x1) / (y2 - y1)
Obviously the y coordinate of the intersection point is already given by y.
There would be no intersection when one of the following conditions is true:
y1 < y and y2 < y
y1 > y and y2 > y
y1 = y2
So to determine whether a line segment crosses one of the four sides of the bounding box (-8192, -8192) to (8191, 8191), you would check for 4 intersections. You could then find 0, 1 or 2 of such intersections.
Here is the code, which I adapted from my answer to your previous question (on hex encoding):
function intersectionWithAxis(x1, y1, x2, y2, y) {
if (y1 < y && y2 < y || y1 > y && y2 > y || y1 === y2) return []; // No intersection
return [Math.round(x1 + (y - y1) * (x2 - x1) / (y2 - y1)), y];
}
function decode(hex) {
let commands = [];
for (let i = 0, len, command; i < hex.length; i += len) {
// Opcodes take 1 byte (i.e. 2 hex characters), and
// numbers take 2 bytes (4 characters)
len = hex[i] >= "8" ? 2 : 4;
let num = parseInt(hex.slice(i, i+len), 16);
if (len === 2) { // Opcode
command = []; // start a new command
commands.push(command);
} else { // Number. Encoded in offset-binary, using 2 x 7 bits
num = ((num & 0x7F00) >> 1) + (num & 0x7F) - 0x2000;
}
command.push(num); // Push opcode or argument in current command
}
return commands;
}
function disassemble(hex) {
let isPenDown = false;
let isPenInBox = true;
let x = 0, y = 0;
let output = "";
let commands = decode(hex);
for (let [opcode, ...args] of commands) {
if (opcode === 0xF0) {
x = y = 0;
isPenDown = false;
isPenInBox = true;
output += "CLR;\n";
} else if (opcode === 0x80) {
isPenDown = args[0] > 0;
if (isPenInBox) output += "PEN " + (isPenDown ? "DOWN" : "UP") + ";\n";
} else if (opcode === 0xA0) {
output += "CO " + args.join(" ") + ";\n";
} else if (opcode === 0xC0) {
if (!isPenDown) {
for (let i = 0; i < args.length; i += 2) {
let [dx, dy] = args.slice(i, i + 2);
x += dx;
y += dy;
}
output += `MV (${x}, ${y});\n`;
} else {
let buffer = "";
for (let i = 0; i < args.length; i += 2) {
let [dx, dy] = args.slice(i, i + 2);
let toX = x + dx;
let toY = y + dy;
// Get intersections with top, bottom, left and right side of box:
let intersections = [
intersectionWithAxis(x, y, toX, toY, -8192),
intersectionWithAxis(x, y, toX, toY, 8191),
intersectionWithAxis(y, x, toY, toX, -8192).reverse(),
intersectionWithAxis(y, x, toY, toX, 8191).reverse()
].filter(p =>
// Only keep the intersection points
p.length && p.every(x => x >= -8192 && x <= 8191)
);
if (intersections.length === 0) { // Position remains at same side of the box (inside or outside)
if (isPenInBox) buffer += ` (${x}, ${y})`;
} else if (intersections.length === 1) { // Moving from outside to inside of box, or vice versa
// Flush previous positions to output, move to the intersection point, and toggle the pen
output += `MV${buffer} (${intersections[0].join(", ")});\nPEN ${(isPenInBox ? "UP" : "DOWN")};\n`
isPenInBox = !isPenInBox;
// Start new series with positions
buffer = isPenInBox ? ` (${toX}, ${toY})` : "";
} else { // Moving from outside the box, through the box, and outside again
output += `MV (${intersections[0].join(", ")});\nPEN DOWN;\nMV (${intersections[1].join(", ")});\nPEN UP;\n`;
}
x = toX;
y = toY;
}
// Flush previous positions to output
if (buffer) output += `MV${buffer};\n`;
}
} // else: ignore unknown commands
}
return output;
}
// Two samples as in question:
console.log(disassemble("F0A0417F40004000417FC067086708804001C0670840004000187818784000804000"));
console.log("---");
console.log(disassemble("F0A0417F41004000417FC067086708804001C067082C3C18782C3C804000"));
The code you posted in your question has some issues (which is why I used the original code without your changes to it):
It is bad practice to use global variables like commands, command, allPos and lastPos: you change their values within your functions, which gives those functions side effects;
The color variable is set in the following statement, but this value is never used:
color = "CO" + 0 + 0 + 0 + 255 + ";\n";
Also, that value does not follow the required syntax (spaces are missing between the RGB values). Anyway, it serves no purpose in your function, so you can omit it.
The following string does not include the value of y, but a literal "y":
output += "y PEN UP" + ";\n";
Also, generating the output at that spot in the code is wrong: you would need to flush the already gathered coordinates to the output first. And outputting y or its value at this point is not in line with the output syntax either.

Javascript even and odd range

I an trying to solve an online quiz but i don't seem to be able to pass all the tests. here is the question
Given two numbers X and Y, write a function that:
1 returns even numbers between X and Y, if X is greater than Y else it returns odd numbers between x and y
For instance, take the integers 10 and 2 . the function would return all the even numbers between 2 and 10.
Examples:
12, 0 => [2,4,6,8,10]
2, 12 => [3, 5, 7, 9, 11]
0, 0 => [ ]
Here is my code:
function number_game(x, y){
let numbers = [];
if (x > y){
for (let i = y; i <= x; i++){
if (i > y){
numbers.push(i);
}
}
}else{
for (let i = x; i <= y; i++){
if (i > x){
numbers.push(i);
}
}
}
const result = numbers.filter(function(num){
return x > y ? num % 2 === 0: num % 2 === 1;
});
return result;
}
While not written optimally, your code is essentially OK, except that it includes the higher number in the result. You're skipping the lower number with your if (i > y) test, although it would be simpler to just start your loop at y + 1.
To exclude the higher number, simply change the repetition criteria from <= to <.
It would also be simpler to perform the even or odd test in those loops.
function number_game(x, y) {
let numbers = [];
if (x > y) {
for (let i = y + 1; i < x; i++) {
if (i % 2 == 0) {
numbers.push(i);
}
}
} else {
for (let i = x + 1; i < y; i++) {
if (i % 2 == 1) {
numbers.push(i);
}
}
}
return numbers;
}
console.log(number_game(12, 0));
console.log(number_game(2, 12));
console.log(number_game(0, 0));
console.log(number_game(3, 13));
console.log(number_game(1, 1));
Because I'm such a damn sucker for code golfing:
const number_game = (x, y) => {
const min = Math.min(x, y), max = Math.max(x, y);
return Array.from(Array(max - min), (_, i) => i + min).slice(1)
.filter(v => v % 2 == (x < y));
};
Perhaps something like this could help.
function number_game(x, y) {
let result = [];
let min=0, max=0;
if(x==y) {
return result;
} else if (x > y) {
min = y;
max = x;
} else {
min = x;
max = y;
}
for (let i = min; i <= max; i++){
if (i%2===0 && x > y && i!=min && i!=max) {
result.push(i);
}
if (i%2===1 && x < y && i!=min && i!=max) {
result.push(i);
}
}
return result;
}
console.log(number_game(12,0));
console.log(number_game(2,12));
console.log(number_game(0,0));
console.log(number_game(13,1));
console.log(number_game(3,13));
console.log(number_game(1,1));
console.log(number_game(1,1000));
console.log(number_game(3,1300));
Instead of generating all the numbers, and then filtering them, you can generate just the numbers that you need:
function number_game(x, y) {
const start = Math.min(x, y);
const end = Math.max(x, y);
const base = x > y ? 2 - start % 2 : start % 2 + 1; // how much you need to add, to get from start to the first number in the result
const numbers = [];
for(let i = start + base; i < end; i+= 2) numbers.push(i);
return numbers;
}
console.log(JSON.stringify(number_game(9, 1)));
console.log(JSON.stringify(number_game(1, 9)));
console.log(JSON.stringify(number_game(12, 2)));
console.log(JSON.stringify(number_game(2, 12)));
console.log(JSON.stringify(number_game(12, 1)));
console.log(JSON.stringify(number_game(1, 12)));
console.log(JSON.stringify(number_game(2, 2)));
function returnOddOrEven(x,y){
// return empty array if both x and y are equal to 0
let mixedArr = [];
if (x ===0 && y===0){
return [];
}
// first condition of x greater than y
else if ( x > y){
for (var i = 1; i < x; i++){
if( i % 2 === 0){
mixedArr.push(i)
}
}
}
// second condition of y > x
else if( y > x){
for (var i = 1; i < y; i++){
if(i > 1 && i % 2 === 1){
mixedArr.push(i)
}
}
}
return mixedArr;
}
function number_game(x, y) {
var numArray = new Array();
if (x > y) {
for (i=y+1; i<x; i++) {
if (i%2 == 0) {
numArray[numArray.length] = i;
}
}
} else {
for (i=x+1; i<y; i++) {
if (i%2 != 0) {
numArray[numArray.length] = i;
}
}
}
return numArray;
}

(Javascript - Arrays) Get most left and most right connected character

I have an array (2d-matrix) and I'd like to get the x/y values for the
most left & top connected '1'-character
most right & bottom connected '1'-character
EDIT 2.0:
I call my function with the parameters x/y, that are the coordinates of my start-'1'-character.
10001
00001
11111
01110 --> (x: 1, y: 3)
And my function checks the column above & and the column right so if there is a character '1' it counts x or y (wherever the column has been found) plus 1.
My function starts at a specific point (e.g. y: 2, x: 0)
var array = [
'00000',
'01111', --> get position of the most top & right '1'-character (x: 4, y: 1)
'11000',
'00000'
]
This is the function to get the top-right end of '1'-characters:
var array = [
'00000',
'01111',
'11000',
'00000'
]
Array.prototype.get_top_right = function(x_pos, y_pos) {
var matrix = this, y1= y_pos;
for (var x1 = x_pos; x1 < this[0].length; x1++) {
try {
if (matrix[(y1-1)][x1] == '1') y1--;
else if (matrix[y1][(x1+1)] != '1') break;
} catch(e) {}
}; return [x1,y1]
}
var result=array.get_top_right(0,2)
console.log(result)
Ok. The function above seems to work fine, but now I'd like to turn around the process to get the last bottom-left connected '1'-character of my array / 2D-matrix.
var array = [
'00000',
'01111',
'11000', --> get position of the most bottom & left '1'-character (x: 0, y: 2)
'00000'
]
I have no clue how to edit the function above to get the left&bottom match as result instead of the most right&top match like you can see above.
Edit 1.0 My function I've coded till yet is not working but looks like this:
Array.prototype.get_bottom_left = function(x_pos, y_pos) {
var matrix = this, y2= y_pos;
for (var x2 = x_pos; x2 > 0; x2--) {
try {
if (matrix[(y2+1)][x2] == '1') y2++;
if (matrix[y2][(x2-1)] != '1') break;
} catch(e) {}
}; return [x2,y2]
}
Using this function above and the error_array below to get the bottom left connected character of the array will lead to a browser crash. Nice!
var error_array = [
'000000',
'000011',
'111110',
'111111'
]
However, I hope somebody can help me updating my function...
Thanks a million in advance,
Greetings - hans.
I created two versions of get_bottom_left method:
bottom_left_up which traversing from (x, y) point to the left and to the up
bottom_left_down which traversing from (x, y) point to the right and to the down.
Here's the implementation:
Array.prototype.bottom_left_up = function(x, y) {
if(this[y][x] === '0') {
return;
}
while(y >= 0) {
while(--x >= 0) {
if(this[y][x] === '0') {
return [x + 1, y];
}
}
if(--y === -1 || this[y][this[y].length - 1] === '0') {
return [0, y + 1];
}
x = this[y].length;
}
};
Array.prototype.bottom_left_down = function(x, y) {
if(this[y][x] === '0') {
return;
}
while(y < this.length) {
if(this[y].indexOf('0', x) !== -1) {
return [x, y];
}
if(++y === this.length || this[y][0] === '0') {
return [x, y - 1];
}
x = 0;
}
};
You see, there are no out-of-range protections, it could be added with no problem separately. Let's test the logic:
var array = [
'00000',
'01111',
'11000',
'00000'
];
console.log(array.bottom_left_up(2, 1)); // [1, 1]
console.log(array.bottom_left_down(2, 1)); // [0, 2]
var array2 = [
'000000',
'000011',
'111110',
'111111'
];
console.log(array2.bottom_left_up(3, 3)); // [0, 3]
console.log(array2.bottom_left_down(3, 3)); // [3, 3]
Regarding methods protection, I would not use try-catch, I would suggest something like:
function(x, y) {
x = parseInt(x, 10);
y = parseInt(y, 10);
if(!this.length || isNaN(x) || isNaN(y) || x < 0 || y < 0 || x >= this.length || y >= this[0].length) {
return;
}
// ...
}
So you will get 'undefined' in 3 cases: empty array, bad params, not found.
Here is a function that seems to do the trick with any size of a matrix but I'm not quite sure if you wanted always to find the first "1" in the matrix even if it was alone on the line and on the right side..
Using your arrays as test parameters
var array = [
'00000',
'01111',
'11000',
'00100'];
var error_array = [
'000000',
'000011',
'111111',
'111111',
'000000',
'000010',
'100000'
]
getLeftBottom(error_array);
function getLeftBottom(testArray)
{
var firstFound;
for (var index = testArray.length; index > 0; index--)
{
console.log(testArray[index-1]);
str = testArray[index-1];
firstFound = str.indexOf("1");
if (firstFound !== -1)
{
console.log("Coordinates: x = {0}, y = {1}", firstFound, index) // Apparently this doesn't work as I expected in JS but you can still see the coordinates
break;
}
}
}

Javascript while loop (Card deck simulation)

I am having an issue with the following code that simulates a card deck.
The deck is created properly (1 array containing 4 arrays (suits) containing 13 elements each (face values)) and when I use the G.test(); function it is correctly pulling 13 random cards but then returns 39x "Empty" (A total of 52).
I hate to ask for help, but I have left the problem overnight and then some and I still cannot find the reason that this is happening. I appreciate any and all insight that can be offered.
var G = {};
G.cards = [[], [], [], []];
G.newCard = function(v) { //currently a useless function, tried a few things
return v;
};
G.deck = {
n: function() { //new deck
var x; var list = [];
list.push(G.newCard("A"));
for (x = 2; x <= 10; x += 1) {
list.push(G.newCard(x.toString()));
}
list.push(G.newCard("J"), G.newCard("Q"), G.newCard("K"));
for (x = 0; x < G.cards.length; x += 1) {
G.cards[x] = list;
}
},
d: function() { //random card - returns suit & value
var s; var c; var v; var drawn = false; var n;
s = random(0, G.cards.length);
c = random(0, G.cards[s].length);
n = 0;
while (!drawn) {
if (G.cards[s].length > 0) {
if (G.cards[s][c]) {
v = G.cards[s].splice(c, 1);
drawn = true;
} else {
c = random(0, G.cards[s].length);
}
} else {
s = (s + 1 >= G.cards.length) ? 0 : s + 1;
n += 1;
console.log(s);
if (n >= G.cards.length) {
console.log(n);
return "Empty";
}
}
}
return {s: s, v: v[0]};
},
}; //G.deck
G.test = function() {
var x; var v;
G.deck.n();
for (x = 0; x < 52; x += 1) {
v = G.deck.d();
console.log(v);
}
};
Replace
for (x = 0; x < G.cards.length; x += 1) {
G.cards[x] = list;
}
with
for (x = 0; x < G.cards.length; x += 1) {
G.cards[x] = list.slice();
}
as this prevents all elements of G.cards[x] binding to the same (single) array instance.
If all elements bind to the same instance, mutating one element equals mutating all elements. list.slice() creates a new copy of list and thus a new array instance to prevent the aforementioned issue.
I won't go through your code, but I built a code that will do what you wanted. I only built this for one deck and not multiple deck play. There are two functions, one will generate the deck, and the other will drawn cards from the deck, bases on how many hands you need and how many cards you wanted for each hand. One a card is drawn, it will not be re-drawn. I might publish a short article for how a card dealing program work or similar in the short future at http://kevinhng86.iblog.website.
function random(min, max){
return Math.floor(Math.random() * (max - min)) + min;
}
function deckGenerate(){
var output = [];
var face = {1: "A", 11: "J", 12: "Q", 13: "K"};
// Heart Space Diamond & Club;
var suit = ["H", "S", "D", "C"];
// Delimiter between card identification and suit identification.
var d = "-";
for(i = 0; i < 4; i++){
output[i] = [];
for(ind = 0; ind < 13; ind++ ){
card = (ind + 1);
output[i][ind] = (card > 10) || (card === 1)? face[card] + d + suit[i] : card.toString() + d + suit[i];
}
}
return output;
}
function randomCard(deck, hand, card){
var output = [];
var randS = 0;
var randC = 0;
if( hand * card > 52 ) throw("Too many card, I built this for one deck only");
for(i = 0; i < hand; i++){
output[i] = [];
for(ind = 0; ind < card; ind++){
randS = random(0, deck.length);
randC = random(0, deck[randS].length);
output[i][ind] = deck[randS][randC];
deck[randS].splice(randC,1);
if(deck[randS].length === 0) deck.splice(randS,1);
}
}
document.write( JSON.stringify(deck, null, 2) );
return output;
}
var deck = deckGenerate()
document.write( JSON.stringify(deck, null, 2) );
document.write("<br><br>");
var randomhands = randomCard(deck, 5, 8);
document.write("<br><br>");
document.write("<br><br>");
document.write( JSON.stringify(randomhands, null, 2) );

d3js - Create custom scale - Positive and negative logarithm

I'm currently working on a d3 project and I'm trying to display bar charts with a huge range of values, both positive and negative.
I saw online a walkaround using d3.scale.sqrt() or displaying two log scale but I was wondering if I could create my own scale.
What I have in mind is a mix between a log scale for negative values, a linear scale for values between [-e, e] and a regular log scale for positive values.
Something that might look like that: http://img15.hostingpics.net/pics/12746197ln.png
y = -log(-x) if x < -e
y = x/e if -e <= x <= e
y = log(x) if x > e
Do you think that it might be possible ?
I also created a JSFiddle to sum it up.
Thanks,
Here is one solution I found: JSFiddle
It might be a weird way, but it's working I think, if you have any pieces of advice on improvement, don't hesitate. I think I made mistakes, especially on log base and ticks.
Here is the function I created, based on d3.js itself.
(function() {
scalepnlog = {
init: function(){
return d3_scale_pnlog(d3.scale.linear().domain([ 0, 1 ]), [ 1, 10 ]);
}
}
function d3_scaleExtent(domain) {
var start = domain[0], stop = domain[domain.length - 1];
return start < stop ? [ start, stop ] : [ stop, start ];
}
var d3_scale_logFormat = d3.format(".0e"), d3_scale_logNiceNegative = {
floor: function(x) {
return -Math.ceil(-x);
},
ceil: function(x) {
return -Math.floor(-x);
}
};
function sign(x){
return x >= 0 ? 1:-1;
}
function d3_scale_pnlog(linear, domain) {
function pnlog(x) {
return (x >= Math.E || x <= -Math.E) ? sign(x)*Math.log(Math.abs(x)) : x/Math.E;
}
function pnpow(x) {
return (x >= 1 || x <= -1 )? sign(x)*Math.pow(Math.E,Math.abs(x)) : Math.E*x;
}
function scale(x) {
return linear(pnlog(x));
}
scale.invert = function(x) {
return pnpow(linear.invert(x));
};
scale.domain = function(x) {
if (!arguments.length) return domain;
linear.domain((domain = x.map(Number)).map(pnlog));
return scale;
};
scale.nice = function() {
var niced = d3_scale_nice(domain.map(pnlog), positive ? Math : d3_scale_logNiceNegative);
linear.domain(niced);
domain = niced.map(pow);
return scale;
};
scale.ticks = function() {
var extent = d3_scaleExtent(domain), ticks = [], u = extent[0], v = extent[1], i = Math.floor(pnlog(u)), j = Math.ceil(pnlog(v)), n = 10 % 1 ? 2 : 10;
if (isFinite(j - i)) {
for (;i < j; i++) for (var k = 1; k < n; k++) ticks.push(pnpow(i) * k);
ticks.push(pnpow(i));
for (i = 0; ticks[i] < u; i++) {}
for (j = ticks.length; ticks[j - 1] > v; j--) {}
ticks = ticks.slice(i, j);
}
return ticks;
};
scale.tickFormat = function(n, format) {
if (!arguments.length) return d3_scale_logFormat;
if (arguments.length < 2) format = d3_scale_logFormat; else if (typeof format !== "function") format = d3.format(format);
var k = Math.max(1, 10 * n / scale.ticks().length);
return function(d) {
var i = d / pnpow(Math.round(pnlog(d)));
if (i * 10 < 10 - .5) i *= 10;
return i <= k ? format(d) : "";
};
};
scale.copy = function() {
return d3_scale_pnlog(linear.copy(), domain);
};
return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp");
}
})();
I don't really know what I'm doing but basically, I created pnlog and pnpow, its reciprocal, and added the different d3 functions needed until it worked.
Here they are :
function pnlog(x) {
return (x >= Math.E || x <= -Math.E) ? sign(x)*Math.log(Math.abs(x)) : x/Math.E;
}
and
function pnpow(x) {
return (x >= 1 || x <= -1 )? sign(x)*Math.pow(Math.E,Math.abs(x)) : Math.E*x;
}

Categories

Resources