Episode 11: Charts with Menus

Exercise: Make the menus work with species in

Hi @curran

I was trying to add a ‘click’ event listener to the “option” DOM elements but this does not seem to be working.

What I have done is:

  1. In the menu.js file added a click event to the listener variables on line 6.

  2. Then, on lines 27-29 added a click event right after selecting the option elements.

  1. As an initial test, I am just trying to print out the click event using a console.log. This is done on line 70-71 of index.js

However, in the console window it is not printing out the click event object. Would you have any tips / suggestions on where I might be going wrong here?

My intention here is to expose the text of the clicked option tag to the index.js file so that I can pass it into the scatter plot function.

Thanks!

PS: Here’s by current code setup where I am trying to figure this out:

Oh yes, I’ve done the same thing in the past! It turns out you need to add the event listener to the <select> element rather than the <option> elements. And the event name should be "change". Good luck!

Thanks @curran for your reply. However, using event.target.value returns the “value” of the option element, e.g.: sepal_length.

Is there a way to extract the “text” of the option element as well, e.g.: Sepal Length?

I tried replacing event,target.value with event.target.textContent but this seems to give the following output:

Petal WidthSepal WidthPetal LengthSepal LengthSpecies

which is a concatenation of all the option names rather than the one selected…

After tinkering around a bit more - I think I have finally found a way! This gives me an array of both the text as well as the value of the selected option.

A bit unwieldy but seems to do the job. Now, onto the actual exercise! :sweat_smile:

But I’ll be really keen to know if there is a more cleaner solution. And thanks again for nudging me in the right direction!

Nice solution! That works.

What I would probably do is set up a lookup based on the value, something like this:

  const columns = [
    {
      value: 'petal_width',
      label: 'Petal Width',
      type: 'quantitative',
    },
    {
      value: 'sepal_width',
      label: 'Sepal Width',
      type: 'quantitative',
    },
    {
      value: 'petal_length',
      label: 'Petal Length',
      type: 'quantitative',
    },
    {
      value: 'sepal_length',
      label: 'Sepal Length',
      type: 'quantitative',
    },
    {
      value: 'species',
      label: 'Species',
      type: 'categorical',
    },
  ];
  const columnsByValue = new Map(
    columns.map((column) => [column.value, column])
  );

Then you could look up the label with columnsByValue.get(value).label , or the type with columnsByValue.get(value).type.

See also https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map

1 Like

Finally figured out handling species! Used the first approach to output the label text and variable name from the menu.js file itself.

But will try out @curran’s approach of using a lookup table as table. That seemed a more elegant approach.

Also, tried playing around with mouse-events to add a tooltip. Although this is still work in progress :sweat_smile:

Hi everyone.

Here is my take on the exercise. I have added the ‘scale type’ into select options object and wired things around. I have also experimented with adding a new x-click event that fires when x label is clicked and toggles x-axis color.

my viz is can be found here

Hi Kaustav,

Another (maybe easier) way of doing it is to simply pass the index of selection in the value of each option. Then use that index to retrieve the whole object from the options array in index.js. I have used that approach in my response.

Hi Wail,

Thanks for sharing your code! Just had a look at your implementation of extending the options array object. This definitely looks like a way simpler approach! I think this is also similar to the approach suggested by Curran as well:

Also, really liked your idea of adding options to change the scale as well. Very neat :+1:

Exercise - make the menus stratify species

1 Like

Hi @curran, I’d like to ask you a clarification on the meaning of the term accessor. If I remember well, you used such a term for things like this:

const xValue = (d) => d.petal_length;

or this one, in the reusable chart pattern:

my.width = function (_) {
    return arguments.length ? ((width = +_), my) : width;
};

so, my question is: what is an accessor? Is it a kind of function that returns a specific value? Something close to the getters of the OOP world?

@curran I was not able to make the exercise, I got stuck with a concept about JS -> scope.
To resume what I did here, I refactored the code to get the data as a module.I’m reading the json from the net, and I wan’t to read this information, and append (push) to a array.
But I’m not able to retrieve this array. I think when changing the scope, I loose the information.

Check lines 35-45. How can I do it?

Hi floatingpurr,

If it helps I would like to provide my explanation and leave it for Curran to confirm/correct it.
Both are examples of two different types of accessors. I will explain…

const xValue = (d) => d.petal_length;

The above is an example of what Curran calls ‘value accessor’. It is a function used to retrieve the ‘column’ value of a data array object. As the same value accessor function is used in multiple places of d3 chart it simplifies code to have one location where this function is defined.

my.width = function (_) {
     return arguments.length ? ((width = +_), my) : width;
};

On the other hand, the above code is used in function chaining and is very similar to OOP getters and setters. It works as both a getter and a setter actually.
If you pass in a value (argument), it will set the property (width in the example above) to that value and return the inner object (my in the example above) to allow further chaining.
If no argument has been passed, it acts as a getter and return the value of property (width in the example above).

Hope that helps.

1 Like

Hi @wailassaad, thank you very much for your kind explanation. So, if I understand correctly, an accessor is just a function returning (i.e., accessing to) a value, encapsulating the complexity and the logics for that purpose. It seems very close to a getter. But ok, it’s just a matter of semantics :slight_smile:

1 Like

You are welcome! I think the point you have raised is very interesting! I would love to see Curran’s view on it.

Also, I like your definition. I could not have defined accessors better even if I tried! :slightly_smiling_face:

1 Like

thanx a lot, I’m also curios because the term is widely used also in the D3 doc. E.g.,

If value is specified, sets the value accessor to the specified function or number and returns this pie generator. If value is not specified, returns the current value accessor, which defaults to […]

1 Like

For the benefit of others, here is a small plan of how to achieve what is requested by this exercise.

The whole trick is that we need to change scale type dynamically in scatterPlot.js file. Scales are declared in this section (line 22-28)

    const x = scaleLinear()
      .domain(extent(data, xValue))
      .range([margin.left, width - margin.right]);

    const y = scaleLinear()
      .domain(extent(data, yValue))
      .range([height - margin.bottom, margin.top]);

As you see this defines scales to be only scaleLinear which will not work with ‘species’. The domain will also need to be changed.

The plan goes like this:

1. scatterPlot.js new scale type properties
Create two new properties in scatterPlot.js that we can use to set x and y scales to different types. I will call these properties xType and yType but you can call them whatever you want.

To achieve this you will need to declare two new variable (xType and yType) In scatterPlot.js similar to variables of other properties shown below

  let width;
  let height;
  let data;
  let xValue;

Also, you will need to create new accessors for these variables in scatterPlot.js similar to the accessors shown below

  my.width = function (_) {
    return arguments.length ? ((width = +_), my) : width;
  };

  my.height = function (_) {
    return arguments.length ? ((height = +_), my) : height;
  };

  my.data = function (_) {
    return arguments.length ? ((data = _), my) : data;
  };

  my.xValue = function (_) {
    return arguments.length ? ((xValue = _), my) : xValue;
  };

  my.yValue = function (_) {
    return arguments.length ? ((yValue = _), my) : yValue;
  };

2. scatterPlot.js dynamic scales
Update the section in scatterPlot.js where scales are declared to use different scales based on the new properties. You can achieve this with if statement. Remember that you will need to change the domain as well. For example:

if (xType==='numeric') {
    const x = scaleLinear()
      .domain(extent(data, xValue))
      .range([margin.left, width - margin.right]);

} else{
  //declare new scale type for 'species' case here ...
}

3. Add new entry in index.js options array for 'species’
It is already done in Curran’s chart above. options should look like this

  const options = [
    { value: 'petal_width', text: 'Petal Width' },
    { value: 'sepal_width', text: 'Sepal Width' },
    { value: 'petal_length', text: 'Petal Length' },
    { value: 'sepal_length', text: 'Sepal Length' },
    { value: 'species', text: 'Species' }
  ];

4. Update ‘change’ event handler of xMenu/yMenu in index.js to set axis value and type
Currently on menu change only axis value is set

      .on('change', (column) => {
        svg.call(plot.xValue((d) => d[column]));
      })

You will have to change that to set both the value and type of axes. You can achieve this with if statement. For example

      .on('change', (column) => {
        if (column!=='species'){
          svg.call(plot.xValue((d) => d[column]).xType('numeric');} else {
          // set value and type for 'species' case here ...
          }
      })

Hope this can help somebody :slightly_smiling_face: