BigFatDog / parcoords-es

ES6 module of Syntagmatic's Parallel Coordinates
MIT License
62 stars 32 forks source link

add ability to brush null data #66

Open rvberloo opened 5 years ago

rvberloo commented 5 years ago

I like the concept of displaying null data outside of the main chart, but I also would like to be able to include items that have a null value in the selection when brushing on that axis. Either by a switch (include_nulldata_in_brush) per viewer, or per axis or by using a multi brush per axis and extending the brushable area to the axis regions where the null values are plotted.

Why: we use the parallel coordinate viewer for selection but when a measurement was not (yet) made it is uncertain. So the items could be within the brush range, we dont know. To be safe we would like to include these items

rvberloo commented 5 years ago

I am new to ES6 programming but got it running in VS code finally.. and now get a bit more insight in the code to make the selection of null data that is plotted outside of the axis working, should I adapt the brush extent ? and why are there a different extent function for 1D and 1D-multi brushes?

BigFatDog commented 5 years ago

and why are there a different extent function for 1D and 1D-multi brushes?

The implementations of 1D and 1D-multi differ a lot. Therefore they are in separated modules. I have already forgotten the implementation details of 1D-multi, it used to be based on (d3-multiple-brushes)[https://github.com/ludwigschubert/d3-brush-multiple] when this project is written in d3 v3. My current approach is based on the concept of this block

Should I adapt the brush extent ?

I guess this is a general question. I don't have an answer for now, but I may assist if you have a more detailed question regards with some specific lines of code.

rvberloo commented 5 years ago

Hi XING yun, thanks for your reply concrete code question: I am comparing the files BrushExtent,js for both the 1d brush and the 1d-multi brush. (Also because the 'brush with arguments' demo file fails for the 1d-multi brush, and I would like to extract these brush details also for multi brush) These file look pretty similar in global structure but not 100% identical

eg

in 1d version :

acc[cur] = {
          extent: brush.extent(),
          selection: {
            raw,
            scaled,
          },
        };

in 1d-multi

acc[cur] = axisBrushes.reduce((d, p, i) => {
          const range = brushSelection(
            document.getElementById('brush-' + pos + '-' + i)
          );
          if (range !== null) {
            d = d.push(range);
          }

          return d;
        }, []);

This actually gives me a d is not an array error the second time the d.push is hit (so the second brush on the same axis), but it is also lacking the raw and scaled return objects I think?

will continue my studies to try and understand what happens here, but if you can shed some light I would be grateful

gordonwoodhull commented 5 years ago

Array.push returns the size of the array, not the array, so that line is suspicious.

BigFatDog commented 5 years ago

@rvberloo You're welcome. raw and selection were for 1d brushes only. I just made some changes to 1d-multi brush so that it has the same behaviors as 1d brush does

       acc[cur] = axisBrushes.reduce((d, p, i) => {
          const raw = brushSelection(
            document.getElementById('brush-' + pos + '-' + i)
          );

          if (raw) {
            const yScale = config.dimensions[cur].yscale;
            const scaled = invertByScale(raw, yScale);

            d.push({
              extent: p.brush.extent(),
              selection: {
                raw,
                scaled,
              },
            });
          }
          return d;
        }, []);

Above changes have been tested but not released yet. I feel there would be more modifications along with our discussion.

BigFatDog commented 5 years ago

@gordonwoodhull Yes it is. Code has been changed.

rvberloo commented 5 years ago

Thanks! this helps a lot. for our purpose we would like to report the brush selection to outside consumers and possibly store it to set the brushes also (create a stored parcoviewer session basically) I am busy adding a function that will report the brush ranges (raw and scaled) regardless of the 1d or 1d multi brush settings. I will contribute this code once it is done off course

My original starting question was a different one though: how to allow data being selected by brushing when the values are missing. The idea behind this is that missing data is unknown, so it might be in the range of the brush, just to be sure lets keep it in the selection. Ideally this could be a dimension property 'includeMissinginSelection' that can be set per axis .. ;-)

BigFatDog commented 5 years ago

You're welcome. I understand we're discussing two issues here:

Do you need me to release a new version containing my modifications right now, or later with your PR instead?

rvberloo commented 5 years ago

Hi, I managed to get the brush ranges exported with a user function (which I added to the brush-with-arguments demo). I am still too inexperienced in node javascript programming to transform it into a proper separate file/ES6 module I am afraid. It does serve my need though: consistent reporting of brush ranges for all axes with brushes incl multibrushed ranges. I will share the changes I made, and leave it up to you to release the modifications and adopt parts of my code if you want. You are right the brush selections for null values are a different topic, though related through the brush extents I think.

code I modified in 'brush-with-arguments' demo file added this function to script area:

const brushResults = (extents) => {
  var ranges =[];
  Object.keys(extents).forEach(key => {
    let value = extents[key];
    if (Array.isArray(value)) {
      if (value.length > 0) {
        value.forEach(axisbrush => {
          ranges.push({
          axis: key,
          selection: axisbrush.selection,  
          });
        });
      }
    } else {
      ranges.push({
        axis: key,
        selection: value.selection,  
      });
    }
  });
  return ranges;
}

changed the .onbrushend handler

.on("brushend", function(brushed, args){
              if(args !== undefined  && Object.keys(this.brushExtents()).length > 0) {
                var brush_selection = args.selection;
                var ext = this.brushExtents();
                var ranges = brushResults(ext);
                if(ranges !== undefined) {
                  var brushrangeshtml='<span style="font-weight:bold;">brush end</span><table border=1>';
                    for(const key in ranges) {
                    let value = ranges[key];
                    let axis = value.axis;
                    brushrangeshtml += '<tr><td>';
                    brushrangeshtml += axis + '</td>';
                    brushrangeshtml += '<td>Raw: ' + value.selection.raw + '</td>';
                    brushrangeshtml += '<td>Scaled: ' + value.selection.scaled + '</td></tr>';
                  };
                  brushrangeshtml += '</table>';
                  d3.select("#brush-results").html(brushrangeshtml);
                }
              } else {
                d3.select("#brush-results").html('cleared brush')
              }
            })

I also needed to include the invertByScale reference to the 1D-brush-multi brushextents.js

BigFatDog commented 5 years ago

v2.2.9 has been released to fix brushExtent

rvberloo commented 5 years ago

Thanks!. This helps a lot in our goal to support parcoviewer sessions, where we store the data, and the active brush selections to be re-used later :-).

on the issue of the null data brushing, I think I can get this to work globally by modifying selected.js to also return true when the data === undefined (only for numeric data). I would prefer this to be a per-axis setting though, but for now it will do.

BigFatDog commented 5 years ago

You're welcome! I'll continue to investigate into null data brushing. I'll let you know my progress.

berci-i commented 3 years ago

Hello!

Is there any option to rebrush over an axe with values of 0? I am asking because I can see the raw values of the brushes, but the scaled values are, of course [0]. But we should use the brushes values to generate new values (from backend) and we could store this, but we would also like to keep this values on refresh and/or state change. So is there any option to update the visual effect on refresh for example (so programmatically redraw the brushes for an axe with only one value)? I tried to search for an option but I can't seem to find any. I would apreciate a lot a direction on this problem

Thanks a lot!