This question has its ups and downs

Unreadable, 2199 2145 2134 2104 2087 2084 bytes

Supports both k/j as well as / syntax.

In good Unreadable tradition, here is the program formatted in proportional font, to obfuscate the distinction between apostrophes and double-quotes:

'""""""'""""""""'"""'""'""'""'""'"""'"""""'""""""'""'""'""'""'"""'""'""""""'""""""'""""""""'"""'""'""'"""""""'""""""""'"""'""""""""""'""""'""""'""""'""""'""""'""""""'""'""'""'""'""'"""'""'"""'""""""'""'""'""'""'"""'"""""""""'"""""'""""""'""'""'""'""'"""'""""""""'"""""""'""'""'""'""'"""'""""'""""""'""'""'""'""'""'"""'"""""""""'"""""""'""'""'""'""'""'"""'""""""""'"""""""'""'""'""'""'""'"""'""'""'"""'""""""'"""'"""""""""'"""""""'"""'""'"""""""'"""'""""""""'""""""""'""""""""'""""""""'""""""""'"""'"""""""""'""'"""""""'"""'"""""""""'""'""'""'"""""""'"""'"""'""'""'"""'""'"""'"""""""""'"""""""'""'""'""'""'""'"""'"""'""'""'"""'""""""'"""'"""""""""'"""""""'"""'"""""""""'""'""'"""""""'"""'"""""""""'""'""'""'""'"""""""'"""'"""'""'""'"""'""'"""'"""""""""'"""""""'""'""'""'""'""'"""'""'""'"""'"""'"""""'""""""'""'""'""'""'"""'""""""""'"""""""'""'""'""'""'"""'""""'""""'"""""""""'"""""""'""'""'""'"""'""""""'""'""'""'"""'""'"""""""'""'""'""'"""'"""'""""""'""'""'"""'""'"""""""'""'""'"""'""""""'""'"""'""""""""'"""""""'""'"""'"""""'""""""'"""'""""""""'"""""""'"""'""""'""""'""""""'""'""'""'"""'""""""""'"""""""'""'""'""'"""'"""""""""'"""""""'""'""'"""'""""""'""'""'"""'""""""""'"""""""'""'""'"""'"""'""""""'""'"""'""'"""""""'""'"""'""""""'""'"""""""'""""""""'"""'""'"""""""'""'"""'"""""'""""""""'""""""'""'""'""'"""'""'"""""""'""'""'""'"""'""""'""""""'""'"""'""""""""'"""""""'""'"""'""""""'""'""'"""'""'"""""""'""'""'"""'"""""'""""""""'""""""'""'"""'""'"""""""'""'"""'""""'""""""'"""'""'"""""""'""""""""'"""'"""""'""""""""'""""""""'""""""'"""'""""""""'""""""""'"""""""'"""'""""""'"""""""'"""'""'"""""""'"""""""'"""'"""""'""'""'""""""'""'""'"""'""""""""'"""""""'""'""'"""'""""'""""'""""'""""""'""'"""'""'""'""'""'""'""""""'"""'""'""'""'""'"""'"""""'""""""""'""""""""'""""""""'""""""""'""""""""'""""""'""""""""'"""'""""""""'""""""""'"""""""'""""""""'"""'"'"""""""""'""""""'""'""""""'"""'""'""'"""""""'"""'""""""""'"""""""'""'"""""""'"""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'"""""""'""'"""'"""""""'"""""""'"""'""""""'""""""""'"""'""'""'"""""""'"""'"'"""""""'""'"""

This was an amazing challenge. Thank you for posting!

Explanation

To get a feel for what Unreadable can and can’t do, imagine Brainfuck with an infinite tape in both directions, but instead of a memory pointer moving one cell at a time, you can access any memory cell by dereferencing a pointer. This comes in quite handy in this solution, although other arithmetic operations — including modulo — have to be done by hand.

Here is the program as pseudocode with director’s commentary:

// Initialize memory pointer. Why 5 will be explained at the very end!
ptr = 5

// FIRST PASS:
// Read all characters from stdin, store them in memory, and also keep track of the
// current line number at each character.

// We need the +1 here so that EOF, which is -1, ends the loop. We increment ptr by 2
// because we use two memory cells for each input character: one contains the actual
// character (which we store here); the other will contain the line number at which the
// character occurs (updated at the end of this loop body).
while ch = (*(ptr += 2) = read) + 1:

    // At this point, ch will be one more than the actual value.
    // However, the most code-economical way for the following loop is to
    // decrement inside the while condition. This way we get one fewer
    // iteration than the value of ch. Thus, the +1 comes in handy.

    // We are now going to calculate modulo 4 and 5. Why? Because
    // the mod 4 and 5 values of the desired input characters are:
    //
    //  ch  %5  %4
    //  ^   1
    //  v   2
    //  k   3
    //  j   4
    //  ▲   0   2
    //  ▼   0   0
    //
    // As you can see, %5 allows us to differentiate all of them except ▲/▼,
    // so we use %4 to differentiate between those two.

    mod4 = 0      // read Update 2 to find out why mod5 = 0 is missing
    while --ch:
        mod5 = mod5 ? mod5 + 1 : -4
        mod4 = mod4 ? mod4 + 1 : -3

    // At the end of this loop, the value of mod5 is ch % 5, except that it
    // uses negative numbers: -4 instead of 1, -3 instead of 2, etc. up to 0.
    // Similarly, mod4 is ch % 4 with negative numbers.

    // How many lines do we need to go up or down?
    // We deliberately store a value 1 higher here, which serves two purposes.
    // One, as already stated, while loops are shorter in code if the decrement
    // happens inside the while condition. Secondly, the number 1 ('""") is
    // much shorter than 0 ('""""""""'""").
    up = (mod5 ? mod5+1 ? mod5+3 ? 1 : 3 : 2 : mod4 ? 3 : 1)
    dn = (mod5 ? mod5+2 ? mod5+4 ? 1 : 3 : 2 : mod4 ? 1 : 3)

    // As an aside, here’s the reason I made the modulos negative. The -1 instruction
    // is much longer than the +1 instruction. In the above while loop, we only have
    // two negative numbers (-3 and -4). If they were positive, then the conditions in
    // the above ternaries, such as mod5+3, would have to be mod5-3 etc. instead. There
    // are many more of those, so the code would be longer.

    // Update the line numbers. The variables updated here are:
    // curLine = current line number (initially 0)
    // minLine = smallest linenum so far, relative to curLine (always non-positive)
    // maxLine = highest linenum so far, relative to curLine (always non-negative)
    // This way, we will know the vertical extent of our foray at the end.

    while --up:
        curLine--
        minLine ? minLine++ : no-op
        maxLine++

    while --dn:
        curLine++
        minLine--
        maxLine ? maxLine-- : no-op

    // Store the current line number in memory, but +1 (for a later while loop)
    *(ptr + 1) = curLine + 1

// At the end of this, minLine and maxLine are still relative to curLine.
// The real minimum line number is curLine + minLine.
// The real maximum line number is curLine + maxLine.
// The total number of lines to output is maxLine - minLine.

// Calculate the number of lines (into maxLine) and the real minimum
// line number (into curLine) in a single loop. Note that maxLine is
// now off by 1 because it started at 0 and thus the very line in which
// everything began was never counted.
while (++minLine) - 1:
  curLine--
  maxLine++

// Make all the row numbers in memory positive by adding curLine to all of them.
while (++curLine) - 1:
  ptr2 = ptr + 1
  while (ptr2 -= 2) - 2:    // Why -2? Read until end!
    *ptr2++

// Finally, output line by line. At each line, we go through the memory, output the
// characters whose the line number is 0, and decrement that line number. This way,
// characters “come into view” in each line by passing across the line number 0.
while (--maxLine) + 2:    // +2 because maxLine is off by 1
  ptr3 = 5
  while (ptr -= 2) - 5:
    print (*((ptr3 += 2) + 1) = *(ptr3 + 1) - 1) ? 32 : *ptr3   // 32 = space
  ptr = ptr3 + 2
  print 10  // newline

So much for the program logic. Now we need to translate this to Unreadable and use a few more interesting golfing tricks.

Variables are always dereferenced numerically in Unreadable (e.g. a = 1 becomes something like *(1) = 1). Some numerical literals are longer than others; the shortest is 1, followed by 2, etc. To show just how much longer negative numbers are, here are the numbers from -1 to 7:

-1  '""""""""'""""""""'"""  22
 0  '""""""""'"""           13
 1  '"""                     4
 2  '""'"""                  7
 3  '""'""'"""              10
 4  '""'""'""'"""           13
 5  '""'""'""'""'"""        16
 6  '""'""'""'""'""'"""     19
 7  '""'""'""'""'""'""'"""  22

Clearly, we want to allocate variable #1 to the one that occurs most frequently in the code. In the first while loop, this is definitely mod5, which comes up 10 times. But we don’t need mod5 anymore after the first while loop, so we can re-allocate the same memory location to other variables we use later on. These are ptr2 and ptr3. Now the variable is referenced 21 times in total. (If you’re trying to count the number of occurrences yourself, remember to count something like a++ twice, once for getting the value and once for setting it.)

There’s only one other variable that we can re-use; after we calculated the modulo values, ch is no longer needed. up and dn come up the same number of times, so either is fine. Let’s merge ch with up.

This leaves a total of 8 unique variables. We could allocate variables 0 to 7 and then start the memory block (containing the characters and line numbers) at 8. But! Since 7 is the same length in code as −1, we could as well use variables −1 to 6 and start the memory block at 7. This way, every reference to the start position of the memory block is slightly shorter in code! This leaves us with the following assignments:

-1    dn
 0                      ← ptr or minLine?
 1    mod5, ptr2, ptr3
 2    curLine
 3    maxLine
 4                      ← ptr or minLine?
 5    ch, up
 6    mod4
 7... [data block]

Now this explains the initialization at the very top: it’s 5 because it’s 7 (the beginning of the memory block) minus 2 (the obligatory increment in the first while condition). The same goes for the other two occurrences of 5 in the last loop.

Note that, since 0 and 4 are the same length in code, ptr and minLine could be allocated either way around. ... Or could they?

What about the mysterious 2 in the second-last while loop? Shouldn’t this be a 6? We only want to decrement the numbers in the data block, right? Once we reach 6, we’re outside the data block and we should stop! It would be a buffer overflow bug error failure security vulnerability!

Well, think about what happens if we don’t stop. We decrement variables 6 and 4. Variable 6 is mod4. That’s only used in the first while loop and no longer needed here, so no harm done. What about variable 4? What do you think, should variable 4 be ptr or should it be minLine? That’s right, minLine is no longer used at this point either! Thus, variable #4 is minLine and we can safely decrement it and do no damage!

UPDATE 1! Golfed from 2199 to 2145 bytes by realizing that dn can also be merged with mod5, even though mod5 is still used in the calculation of the value for dn! New variable assignment is now:

 0    ptr
 1    mod5, dn, ptr2, ptr3
 2    curLine
 3    maxLine
 4    minLine
 5    ch, up
 6    mod4
 7... [data block]

UPDATE 2! Golfed from 2145 to 2134 bytes by realizing that, since mod5 is now in the same variable as dn, which is counted to 0 in a while loop, mod5 no longer needs to be explicitly initialized to 0.

UPDATE 3! Golfed from 2134 to 2104 bytes by realizing two things. First, although the “negative modulo” idea was worth it for mod5, the same reasoning does not apply to mod4 because we never test against mod4+2 etc. Therefore, changing mod4 ? mod4+1 : -3 to mod4 ? mod4-1 : 3 takes us to 2110 bytes. Second, since mod4 is always 0 or 2, we can initialize mod4 to 2 instead of 0 and reverse the two ternaries (mod4 ? 3 : 1 instead of mod4 ? 1 : 3).

UPDATE 4! Golfed from 2104 to 2087 bytes by realizing that the while loop that calculates the modulo values always runs at least once, and in such a case, Unreadable allows you to re-use the last statement’s value in another expression. Thus, instead of while --ch: [...]; up = (mod5 ? mod5+1 ? [...] we now have up = ((while --ch: [...]) ? mod5+1 ? [...] (and inside that while loop, we calculate mod4 first, so that mod5 is the last statement).

UPDATE 5! Golfed from 2087 to 2084 bytes by realizing that instead of writing out the constants 32 and 10 (space and newline), I can store the number 10 in the (now unused) variable #2 (let’s call it ten). Instead of ptr3 = 5 we write ten = (ptr3 = 5) + 5, then 32 becomes ten+22 and print 10 becomes print ten.


Pyth, 27 bytes

jCm.<.[*5lzd\ =+Ztx"v ^k"dz

Try it online: Demonstration or Test Suite

I use k and j instead of and . There are lots of leading and trailing empty lines. You have to search quite a bit to find the image. Here's a 34 byte version, that removes all leading and trailing empty lines.

j.sCm.<.[*5lzd\ =+Ztx"v ^k"dz]*lzd

Try it online: Demonstration or Test Suite

Explanation:

jCm.<.[*5lzd\ =+Ztx"v ^k"dz  implicit: Z = 0
  m                       z  map each char d from input string z to:
                  x"v ^k"d     find d in the string "v ^k", -1 if not found
                 t             -1, that gives -2 for j, -1 for v, 1 for ^ and 2 for k
              =+Z              add this number to Z
     .[*5lzd\                  append spaces on the left and on the right of d, 
                               creating a 5*len(input_string) long string
   .<           Z              rotate this string to the left by Z chars
jC                           transpose and print on lines

CJam, 37 bytes

r_,2*:L3*S*f{\_iImd8-g\8>)*L+:L\t}zN*

This prints empty lines before and after the desired output, which has been allowed by the OP.

Try it online in the CJam interpreter.

How it works

r_     e# Read a token from STDIN and push a copy.
,2*:L  e# Compute its length, double it and save it in L.
3*S*   e# Push a string of 6L spaces.
f{     e# For each character C in the input, push C and the string of spaces; then
  \    e#   Swap C with the string of spaces.
  _i   e#   Push a copy of C and cast it to integer.
  Imd  e#   Push quotient and remainder of its division by 18.
  8-g  e#   Push the sign((C%18) - 8). Gives -1 for ^ and ▲, 1 for v and ▼.
  \    e#   Swap the result with the quotient.
  8>)  e#   Push ((C/18) > 1) + 1. Gives 2 for ▲ and ▼, 1 for ^ and v.
  *    e#   Multiply both results. This pushes the correct step value.
  L+:L e#   Add the product to L, updating L.
  \t   e#   Replace the space at index L with C.
}      e# We've built the columns of the output.
z      e# Zip; transpose rows with columns.
N*     e# Join the rows, separating by linefeeds.