toofarsideways - 4 months ago 21

JSON Question

I'm learning how Clojurescript works by trying to draw some grids from a JSON structure using d3.js. I'm using strokes to access d3.

The JSON looks like this:

`[[{"players":{"0":{"rep":0},"1":{"rep":0}}},{"players":{"0":{"rep":0},"1":{"rep":0}}},`

{"players":{"0":{"rep":0},"1":{"rep":0}}},{"players":{"0":{"rep":0},"1":{"rep":0}}}],

[{"players":{"0":{"rep":0},"1":{"rep":0}}},{"players":{"0":{"rep":0},"1":{"rep":0}}},

{"players":{"0":{"rep":0},"1":{"rep":0}}},{"players":{"0":{"rep":0},"1":{"rep":0}}}],

[{"players":{"0":{"rep":0},"1":{"rep":0}}},{"players":{"0":{"rep":0},"1":{"rep":0}}},

{"players":{"0":{"rep":0},"1":{"rep":0}}},{"players":{"0":{"rep":0},"1":{"rep":0}}}],

[{"players":{"0":{"rep":0},"1":{"rep":0}}},{"players":{"0":{"rep":0},"1":{"rep":0}}},

{"players":{"0":{"rep":0},"1":{"rep":0}}},{"players":{"0":{"rep":0},"1":{"rep":0}}}]]

It represents a 4 by 4 grid. I'm trying to add values to the cells such as height, width, x and y coordinates so that then it's just a simple case of passing the data to d3 to be drawn.

For example it would look something like this:

`[[{"width":32,"height":32,"x":0,"y":0,"value":{"players":{"0":{"rep":0},"1":{"rep":0}}}},`

{"width":32,"height":32,"x":32,"y":0,"value":{"players":{"0":{"rep":0},"1":{"rep":0}}}},...

Usually I'd map over the structure with a transformation function to convert the cells from their current value into a new form, however this approach just doesn't appear to work. I've tried

`map-indexed: (map-indexed #(doto %2 (aset "width" %1)) row)`

The current iteration of the code looks like this:

`(defn board->grid [grid-width grid-height board square]`

(let [x-length (count board)

y-length (count (first board))

same (min (/ grid-width x-length) (/ grid-height y-length))

grid-item-width (if square same (/ grid-width x-length))

grid-item-height (if square same (/ grid-height y-length))

start-x (/ grid-item-width 2)

start-y (/ grid-item-height 2)

values (array)

grid (array)

data (js->clj board :keywordize-keys true)]

(doseq [x (range x-length)

y (range y-length)]

(let [current-cell (aget data y x)]

(.log js/console (apply str (aset (aget data y x) "a" "b")))

(.push grid (aget data y x))))

(.text ($ :#status) grid)))

Any help would be appreciated! Or better yet, suggestions of better approaches, I can't help but feel I'm going about this slightly wrong!

Answer

The basic rule of thumb for working with mutability in clojure and clojurescript is "don't". JS arrays and objects don't implement most of the protocols that functions rely on to do their work. For example, plain js arrays are not seqable! Do most of your work with immutable data structures and convert to mutable equivalents only when you need to interface with other libraries.

There are some additional functions which are array-specific: `into-array`

, `to-array`

, `aget`

, `aset`

, `amap`

, `areduce`

and `alength`

. (See this cheat sheet.)

There are also many closure library functions you may find helpful in `goog.array`

, `goog.object`

, or `goog.structs`

if you want to stick with mutable data structures. (Remember, clojurescript includes the google closure library!)

You can also use this form everywhere:

```
(defn amap2d [arr f]
(doseq [x (range (alength arr))
y (range (alength (aget A x)))
:let [cell (aget A y x)]]
(f x y cell)))
```

But I think you'll be happier doing `js->clj`

, working on the data, and then `clj->js`

right before you pass it to d3.