2

So I made an animated square pie chart that uses some basic CSS to display itself in a large grid format. However, how can I change each individual block (the larger squares) to be arranged in a state map grid?

I have the below code in javascript that I think will allow me to reorganize them via CSS, and I know I need to "translate the coordinates to CSS fixed positioning in pixels" but I am not sure how to connect the two.

<script id="field_details">
var field_details = {
  "ME" : { "state": "ME", "row": 0, "col": 10 },
  "WI" : { "state": "WI", "row": 1, "col": 5 },
  "VT" : { "state": "VT", "row": 1, "col": 9 },
  "NH" : { "state": "NH", "row": 1, "col": 10 },
  "WA" : { "state": "WA", "row": 2, "col": 0 },
  "ID" : { "state": "ID", "row": 2, "col": 1 },
  "MT" : { "state": "MT", "row": 2, "col": 2 },
  "ND" : { "state": "ND", "row": 2, "col": 3 },
...

The issue I see is that "field_details" is already being called on in the code below. It takes takes in the dataset, creates a small 100unit grid out of the state data, and displays it onto the canvas.

d3.csv("data/test3.csv", type, function(error, data) {
if (error) throw error;

var valfields = d3.keys(field_details);

// Make data accessible by grp key
data.forEach(function(o) {
    grp_vals["grp" + o.agegrp] = o;
});


//
// Setup grid.
//
var cells = [];
d3.select("#grid").text().split("\n").forEach(function(line, i) {
  //replace every alphanumeric character with an empty string
  var re = /\w+/g, m;
  while (m = re.exec(line)) cells.push({
    name: m[0],
    selected: 1,
    x: m.index / 3,
    y: i
  });
});


//
// Make a square pie for each field.
//
valfields.forEach(function(v,i) {
    var grid_width = d3.max(cells, function(d) { return d.x; }) + 1,
        grid_height = d3.max(cells, function(d) { return d.y; }) + 1,
        cell_size = width / grid_width,
        holder_width = width + margin.left + margin.right;


    var div = d3.select("#charts").append("div")
        .attr("id", "holder"+v)
        .attr("class", "chartholder")
        div.append("h3").html(field_details[v].desc );

All the code is located in the link, including CSS.

Thanks for any help/guidance!

UPDATE 2

HTML

 </style>
 </head>
<div id="main-wrapper">
<h1 class="centered">Test</h1>
<p class="desc centered">Test</p>

<div id="update">
<div id="update">
        <div class="clr"></div>
    </div>
    <div id="agegrp" class="buttons">
        <h3>Test</h3>
        <div a href="#" data-toggle="tooltip" data-placement="bottom" 
         data-trigger="hover focus" title ="Test"
          class="button current test" data-val="1" id = "HE" name = "HE" 
          value = "HE">Test</div>

        <div a href="#" data-toggle="tooltip" data-placement="bottom" 
         data-trigger="hover focus" title ="Test"
          class="button 2 test" data-val="2" id = "GM" name = "GM" value = 
           "GM">Test</div>

            <div a href="#" data-toggle="tooltip" data-placement="bottom" 
              data-trigger="hover focus" title = "Test"
               class="button 3 test" data-val="3" id = "RC" name = "RC" 
                 value = "RC">Test</div>

        <div a href="#" data-toggle="tooltip" data-placement="bottom" 
           data-trigger="hover focus" title = "Test"
          class="button 4 test" data-val="4" style="margin-right:0" id = 
            "CAPS" name = "CAPS" value ="CAPS">Test</div>

        <div class="clr"></div>

<div id="map" style="position:absolute" class="block"></div>

    </div>


    <div id="racesimp" class="buttons">
    </div>
</div><!-- @end #update -->

  <div class="clr"></div>
  <div class="genericholder">

      <div  id="charts" style="position:relative"></div>
      <div id="grid2"></div>
  </div>
<div id="container">
</div>

Javascript

var valfields = d3.keys(field_details);
// Make data accessible by grp key
data.forEach(function(o) {
    grp_vals["grp" + o.agegrp] = o;
});

//
// Setup grid.
//
var cells = [];
d3.select("#grid").text().split("\n").forEach(function(line, i) {
  //replace every alphanumeric character with an empty string
  var re = /\w+/g, m;
  while (m = re.exec(line)) cells.push({
    name: m[0],
    selected: 1,
    x: m.index / 3,
    y: i
  });
});


//
// Make a square pie for each field.
//
valfields.forEach(function(v,i) {
    var grid_width = d3.max(cells, function(d) { return d.x; }) + 1,
        grid_height = d3.max(cells, function(d) { return d.y; }) + 1,
        cell_size = width / grid_width,
        holder_width = width + margin.left + margin.right;

    var div = d3.select("#charts").append("div")
        .attr("id", "holder"+v)
        .attr("class", "chartholder")
        div.style("top",(data.row * 35) + "px");
        div.style("left",(data.col * 35) + "px");
        div.append("h3").html(field_details[v].desc );

CSS

#charts {
  position: relative;
  margin-top: 20px;
  width: 100%;
}
#map {
  position: relative;
  width: 100%;
  height: 100%
}
  .div.css {
  position: absolute;
  width: 100%;
  height: 100%
}
  .newdiv {
  position: absolute;
  width: 100%;
  height: 100%
}
  .block {
  position: absolute;
  width: 100%;
  height: 100%
}
Marcus Campbell
  • 2,746
  • 4
  • 22
  • 36
Deeba Yavrom
  • 81
  • 1
  • 11

1 Answers1

1

Looks like you've already done the hard part of translating the geographical map layout into rows and columns; so this is as simple as multiplying the 'row' and 'col' values by the width / height of the blocks, and using the results as the pixel position.

I'm going to skip over the parts of your code that aren't relevant to the question, and draw the "blocks" as single elements based on your 'field_details' object:

var field_details = {
ME: {row: 0, col: 10}, WI: {row: 1, col: 5}, VT: {row: 1, col: 9}, NH: {row: 1, col: 10}, WA: {row: 2, col: 0}, ID: {row: 2, col: 1}, MT: {row: 2, col: 2}, ND: {row: 2, col: 3}, MN: {row: 2, col: 4}, IL: {row: 2, col: 5}, MI: {row: 2, col: 6}, NY: {row: 2, col: 8}, MA: {row: 2, col: 9}, OR: {row: 3, col: 0}, NV: {row: 3, col: 1}, WY: {row: 3, col: 2}, SD: {row: 3, col: 3}, IA: {row: 3, col: 4}, IN: {row: 3, col: 5}, OH: {row: 3, col: 6}, PA: {row: 3, col: 7}, NJ: {row: 3, col: 8}, CT: {row: 3, col: 9}, RI: {row: 3, col: 10}, CA: {row: 4, col: 0}, UT: {row: 4, col: 1}, CO: {row: 4, col: 2}, NE: {row: 4, col: 3}, MO: {row: 4, col: 4}, KY: {row: 4, col: 5}, WV: {row: 4, col: 6}, VA: {row: 4, col: 7}, MD: {row: 4, col: 8}, DE: {row: 4, col: 9}, AZ: {row: 5, col: 1}, NM: {row: 5, col: 2}, KS: {row: 5, col: 3}, AR: {row: 5, col: 4}, TN: {row: 5, col: 5}, NC: {row: 5, col: 6}, SC: {row: 5, col: 7}, OK: {row: 6, col: 3}, LA: {row: 6, col: 4}, MS: {row: 6, col: 5}, AL: {row: 6, col: 6}, GA: {row: 6, col: 7}, HI: {row: 7, col: 0}, AK: {row: 7, col: 1}, TX: {row: 7, col: 3}, FL: {row: 7, col: 8} 
};

for (state in field_details) {
  var data = field_details[state];

  // create the element (yours probably already exist, so just access them by ID instead)
  var newElement = $('<div class="block">'+state+'<div>');
  $('#map').append(newElement);

  // set the position (row times width, col times height plus a bit of padding).  I hardcoded the sizes here out of laziness.
  newElement.css("top",(data.row * 35) + "px"); 
  newElement.css("left",(data.col * 35) + "px");
}
#map {
  position: relative;
  width: 100%;
  height: 100%
}

.block {
  font-size: 10px;
  width: 30px;
  height: 30px;
  border: 1px solid #000;
  position: absolute;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="map">
</div>

(Since this uses absolute positioning it doesn't need to be on a strict grid; that's just a consequence of your row/col data. You could improve the layout by adjusting the positioning of some states a bit -- for example doubling all the row/col values in the data and multiplying by half the width/height would let you nudge FL and TX by half a step left or right, or the northeastern states a half-step south...)

Update

Tying this in to your existing code:

You're already looping through field_details and creating DOM elements, just as in my sample above. So you're most of the way there; just add the positioning while you're at it:

valfields.forEach(function(v,i) {
    // ...
    var div = d3.select("#charts").append("div")
    // ...

    // New code: position the elements:
    div.style("top",(/* math */) + "px"); 
    div.style("left",(/* math */) + "px");          
    // (make sure those divs get a `position:absolute`, and #charts or some other containing element has `position:relative`
    // ...
}

Final update

These exact lines, replacing your existing div.style lines, work for me in your code:

div.style("position", "absolute"); // or set this in CSS on .holder
div.style("top", field_details[v].row * 95 + "px");
div.style("left", field_details[v].col * 95 + "px");
Daniel Beck
  • 20,653
  • 5
  • 38
  • 53
  • wow, this is so helpful, thank you! So the issue I am having is connecting your "field_details" code to MY "field_details" code. The issue being that it is already being called on. I updated the original posting to reflect that... – Deeba Yavrom Mar 05 '18 at 18:59
  • @DeebaYavrom spelled it out a bit further with your new code. – Daniel Beck Mar 05 '18 at 20:56
  • thanks Daniel. So I know this must be an increidibly easy question, but how does one connect the "div.css" to the actual CSS stylesheet as there is no class associated with "div.css". I updated the original post to show you what I have. Otherwise I seem to have inputted your code in the right area, or at least I hope! – Deeba Yavrom Mar 06 '18 at 02:09
  • The easiest way to do that would be to put a classname on those divs. Alternatively you could set the `position` rule on them through javascript, just like the `top` and `left` rules. – Daniel Beck Mar 06 '18 at 13:35
  • Would you be able to guide me to where to look to learn this? I added a some new CSS, updated the HTML with a new div with
    but I keep on getting "Uncaught ReferenceError: div.css is not defined" no matter what i do
    – Deeba Yavrom Mar 06 '18 at 14:32
  • Hm. That's a bit confusing: `.css()` is a jQuery function; that error would come up if you tried to call it on a plain DOM object but it certainly looks like `div` is a jQuery object in your code.... Just to confirm, you're calling `.css` on the div variable generated within the `valFields.forEach` loop, correct? – Daniel Beck Mar 06 '18 at 15:22
  • Yup, i belive so at least. I updated my original post with exactly what I have, including HTML (with updated divs), JS and CSS I am in fact getting the error "(index):335 Uncaught TypeError: div.css is not a function". Updated the bl.ocks.org link here to show it all too... https://bl.ocks.org/dyavrom/ad1be318c541dbe38e0c47f2de63df82 – Deeba Yavrom Mar 06 '18 at 15:49
  • ::thwacks forehead:: I'm a fool. I failed to notice that div is a `d3` object, not jQuery -- the syntax is similar, but it looks like d3 uses `style()` not `css()`. Very sorry! I updated the answer, give that a try. – Daniel Beck Mar 06 '18 at 16:34
  • oh no need to be sorry at all, i really appreciate the help! it seems you were right, and the div.style addition fixed all the errors. However now i get the same project i had before, nothing has changed. I followed your instructions exactly 1. I updated the divs to have either relative or absolute positioning 2. I added the div.style code based on your instructions 3. I added come CSS elements just in case Thankfully the original viz is showing, however it seems like each large "block" is still unable to connect to the state grid rows and columns. – Deeba Yavrom Mar 06 '18 at 18:41
  • Looking at your code, you're trying to read values from `data.row` and `data.col` -- but the row/col information isn't in `data`, it's in `field_values`. – Daniel Beck Mar 06 '18 at 19:07
  • yes of course, how did i miss that! i changed it to "field_details.row" and field_details.col" but sadly still the same issue. no errors but there is nothing changing. so odd to me, as intuitively all of the code makes sense – Deeba Yavrom Mar 06 '18 at 19:14
  • I updated the answer with the exact code you need. (not `field_details.row`, `field_details[v].row`) – Daniel Beck Mar 06 '18 at 19:15
  • yes! thank you so much Daniel, i am extremely grateful. if i can pay you back in anyway, please let me know. – Deeba Yavrom Mar 06 '18 at 19:18
  • No trouble at all! – Daniel Beck Mar 06 '18 at 19:23
  • seriously, i feel i really got lucky with having you to guide me through this, i am very very thankful to have such an amazing resource. if i can buy you a cup of coffee or anything really, please let me know. its the least i can do for the time you took out for me – Deeba Yavrom Mar 06 '18 at 19:31
  • Thanks for the kind words! I'll admit this was more effort than I usually put into followup on SO answers; you had the good fortune to have an interesting problem :) I see you work at the EPA -- keep on protecting the environment for me and we'll call it even. (And hang in there, you've probably had a pretty rough year) – Daniel Beck Mar 06 '18 at 19:53