简体   繁体   中英

Working with mutation in Clojurescript

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) but this doesn't appear to correctly transform the values. It is quite likely that I am accessing or setting the values incorrectly.

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!

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.

I see that you're using aget on data and that data is a ClojureScript data structure. aget is only designed to work on Array-like JavaScript objects.

I'm not familiar with clojurescript; some suggestions though:

Formating the array of arrays into a flat array of objects with coordinates isn't too hard with vanilla javascript:

var grid = [];
board.forEach(function(row, y){ 
  row.forEach(function(d, x){ 
    d.y = y; d.x = x; grid.push(d); }); });

To display the board, you probably want to use something like this:

svg.selectAll("rect")
  .data(grid).enter().append("svg:rect")
    .attr("x", function(d){ return xScale(d.x); })
    .attr("y", function(d){ return yScale(d.y); })
    .attr("height", rectHeight)
    .attr("width",  rectWidth)

Notice that the actual position of the square isn't stored in the object, it is easier to keep track of that with a scale:

xScale = d3.scale.linear()
  .domain(d3.extent(grid.map(function(d){ return d.x; }).
  .range(0, svgWidth);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM