slamdata / purescript-jtable

A Purescript table renderer capable of displaying multidimensional, heterogeneous JSON data
14 stars 11 forks source link

Table headers should render shallow labels unambiguously #3

Closed brainrake closed 9 years ago

brainrake commented 9 years ago

According to the readme, column headers for labels with depth less than the maximum depth in the hierarchy should be pushed down to lower rows.

For example, according to the specification, this json:

{
    "id": 1,
    "foo": [{
        "bar": 2,
        "baz": 3
    }]
}

should be rendered as:

|-----|-----------|
|     |    foo    |
|-----|-----------|
| id  | bar | baz |
|-----|-----|-----|
| 1   | 2   | 3   |
|-----|-----|-----|

Note how id is in the second row of headers, as if it were on the same depth as bar and baz.

Instead, I propose to render the example as:

|-----|-----------|
| id  |    foo    |
|     |-----------|
|     | bar | baz |
|-----|-----|-----|
| 1   | 2   | 3   |
|-----|-----|-----|

So the table header should maintain a clearer mapping to the json structure, namely:

These changes also resolve the ambiguity where "id" might as well be the only sub-label of a label which is the empty string (allowed in json) like {"": {"id": 1}, ...}.

Please let me know if this makes sense, or if I missed something and there is a good reason to keep the headers as they are.

jdegoes commented 9 years ago

There were two reasons I spec'd the headers as written (which is not to say it's the right decision):

  1. If we assume that deeper headers will be styled differently than shallower headers, then having the id cell header spanning 2 header rows will produce a visual contrast between the entire id cell header and everything to the right of it. In all examples I have seen, they solve this problem by using a uniform style for all header rows. Which gets the job done but doesn't add as much visual clarity as using different styles for differing header levels.
  2. I have operated under the assumption that in any nested data, the further up you go in the label stack, the less relevant the information. In your example, the most relevant labels are 'bar' and 'baz', not 'foo', because 'foo' is generic and 'bar' and 'baz' are very specific (almost subcategories of "foo"). If this assumption is correct, and deeper labels have more meaning than higher labels, then theoretically, your id label has the same information content as bar and baz (which all have more content than foo), which means all should be co-located.

What do you think?

jdegoes commented 9 years ago

I was able to find this example which renders nesting consistent with how it's spec'd out:

Nested Table Headers

Notice the Q1 over on the right hand side, which is placed next to the data rather than at the top.

jdegoes commented 9 years ago

By the way, I think the argument for making it "visually unambiguous" is definitely compelling, although on the net, I'm not sure which path will lead to something that's clearer, cleaner and more aesthetically pleasing for the common case.

brainrake commented 9 years ago

An example war might not be the most useful since this is a new design. However, in the image included above, it looks like Q1 might have a level above it (perhaps "grades") out of view - there is no right border in the top header row. In that case, it is not a relevant example. Regarding your notes:

  1. I think styling is an orthogonal issue. It's not a problem to style a cell that might span multiple rows in a good looking way.
  2. While it is true that id, bar, and baz have the same level of specificity by themselves (they don't have sub-labels), I would argue that this has not much to do with how relevant the data is, or how much information content it has. However, the relevancy and information content of the table header itself would definitely be higher if it conformed to the json structure.

Spanning multiple rows is a required feature in the data segment, so I see no reason why it should be avoided in the header. Empty cells in the data segment mean missing or null values. In the original spec, empty cells in the header are meaningless, inserted just to align the label to the bottom. Or they are labels with an empty string, it is ambiguous. The proposal addresses these irregularities.

In my view, aligning the text in a label spanning multiple rows to the top or middle is fine, to the bottom is counterintuitive (but still a matter of styling), but inserting empty cells (apparently only for alignment) doesn't make sense.

jdegoes commented 9 years ago

Yes, indeed, the example probably does have a level above it, but you can see from this fragment of the table that it looks quite nice and the information content is clear.

  1. I may be wrong here, but I believe the only way it can be done is by uniformly styling the header cells. I haven't seen any examples where this is not true, because the visual disharmony would be too noisy.
  2. If you see a number such as 123.1231, is it more relevant to know it is a latitude or to know its (parent) category is a geo_code? I would argue the former. If you view nested data as a hierarchy of categories, terminating in the exact type of data, then id simply has no parent categories because its type is fully specified, whereas something like lat is organized into the category of geo_code.

One principle of user interface design is consistency: for example, the user should always be able to look in the same place for the same information, which is why in OS X, the application bar is always located in on the top of the screen (that, and because it makes it easier to position the mouse without overshooting).

In the case of a table with nested columns, if the most relevant piece of information is the terminal label, then ideally, users should be able to look in the same place to see that across all columns, instead of visually having to scan up and down continuously as they read from left to right (depending on the nesting of the column).

I've pretty much talked myself into the original spec here, but I think the library itself could (and probably should!) support both of these rendering modes via options (seems fairly trivial). Are you working on the project or just interested in using it after it's completed?

brainrake commented 9 years ago

The example image is irrelevant then since it would be rendered the same with both algos. By the principle of consistency, labels that are on the same level in json should be on the same level in the table header, and empty cells or their absence should mean the same thing in the table header and the table body. I'm possibly interested in both.

jdegoes commented 9 years ago

I think the example image is very helpful to see what it looks like (visually) when you split shallower header cells and bottom align all leaf labels. I could have created a mock to show this, but it's easier just to link to an existing image, and I think the example looks very clean and is easy to understand.

I think we agree but have different viewpoints. You have a top-down view, where the JSON hierarchy is interpreted literally: fields at the same level have the same semantic weight and should be displayed together visually. I have a bottom-up view, where fields at the terminal level have the same semantic weight and should be displayed together visually.

In any case, I don't see why the library can't support both pretty easily (and actually others, since these two are not the only possible wasy to render nested columns -- the two key structural options seem to be, Are the header cells split vertically or not? (Y/N), and if so, Where do you put the label? (Top / Bottom)).

I know of two people working on this right now (Gleb & Isaac).

brainrake commented 9 years ago

Ok, so an option, let's call it "ambiguate", to insert empty cells, but only in the header. Got it.

brainrake commented 9 years ago

What about this version? It is unambiguous and reflects the json structure, and looks almost like the spec.

|-----|-----------|
|     |    foo    |
|     |-----------|
| id  | bar | baz |
|-----|-----|-----|
| 1   | 2   | 3   |
|-----|-----|-----|

(Yes, it is actually the same markup.)

Fresheyeball commented 9 years ago

Can I see the markup you are proposing for this? (the actual html). For styling and performance reasons, the table should be semantic standard html.

brainrake commented 9 years ago
<table>
  <thead>
    <tr>
      <th rowspan="2">id</th>
      <th colspan="2">foo</th>
    </tr>
    <tr>
      <th>bar</th>
      <th>baz</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1</td>
      <td>2</td>
      <td>3</td>
    </tr>
  </tbody>
</table>
brainrake commented 9 years ago

In case you were wondering, rowspan = if null children then (max_depth - current_depth) else 1

brainrake commented 9 years ago

Here, have some more ASCIISS, or rather lack thereof. HTML ths are vertically centered by default, at least on Chrome and FF.

|-----|-----------|
|     |    foo    |
| id  |-----------|
|     | bar | baz |
|-----|-----|-----|
| 1   | 2   | 3   |
|-----|-----|-----|
jdegoes commented 9 years ago

As I've already said before several times, without the vertical splits, it's not possible to use different styles on different depths of the header without creating visual discontinuity.

In particular, the rendering I want will highlight all the bottom rows (id, bar, baz), probably in some blue background color, and higher levels will appear in various shades of gray, thus giving the most visual weight and direct the user's eye to the terminal labels, which will appear at the bottom of the header and have uniform height and size for easy left-to-right scanning.

A simple shift in top/middle/bottom vertical align could be done with styling. So if you want to add an option to this library to accommodate the above visual styles, then it should be a "split cells" or "not split cells" type of option, since the rest can be done with alignment. Albeit, if you split the cells, you have two natural choices of where to put the label: top and bottom. So there's really 3 options, something like:

data VerticalSplit = SplitTop | SplitBottom | NoSplit

I don't need this functionality but would accept a PR for it (or, equivalently, accept a solution which had this functionality).