javascripthtmlcsswaffle-chart

Translating coordinates to CSS fixed positioning in pixels


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%
}

Solution

  • 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");