Data-binding between X and Y axes in several grouped bar charts using d3.js

Check out this Fiddle Example

I've been exploring different approaches for creating multiple grouped bar charts on a single page. I came across two examples (Example 1) and (Example 2), but I'm facing an issue with setting the X and Y domains correctly. Here's a snippet of the JSON data I'm working with:

var data = [
{"name":"AA","sales_price":20,"retail_price":25},
{"name":"BB","sales_price":30,"retail_price":45},
{"name":"CC","sales_price":10,"retail_price":55},
{"name":"DD","sales_price":10,"retail_price":25},
{"name":"EE","sales_price":13,"retail_price":20},
{"name":"GG","sales_price":13,"retail_price":15},
];

While I've managed to display the bar values accurately in each chart, I'm struggling with binding the individual row’s sales_price and retail_price values to the axes instead of the entire dataset. The block of code causing trouble is as follows:

 data.forEach(function(d) {
    d.compare = field_name.map(function(name) {       
       return {name: name, value: +d[name]}; 
    });
 });

 x0.domain(data.map(function(d) { console.log(d); return d.name; }));
 x1.domain(field_name).rangeRoundBands([0, x0.rangeBand()]);
 y.domain([0, d3.max(data, function(d) { 
       return d3.max(d.compare, function(d) {         
       return d.value; }); 
 })]);

My aim is to have the domains reflect each row's values for each grouped bar chart. How can I achieve this?

Here is the complete code:

function multi_bars(el){
 var margin = {top: 45, right:20, bottom: 20, left: 50},
     width = 350 - margin.left - margin.right,
     height = 250 - margin.top - margin.bottom;
 var x0 = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1);
 var x1 = d3.scale.ordinal();
 var color = d3.scale.ordinal()
    .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
 var y = d3.scale.linear()
    .range([height, 0]);
 var xAxis = d3.svg.axis()
    .scale(x0)
    .orient("bottom");
 var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");
 var field_name = ['retail_price','sales_price'];
 data.forEach(function(d) {
    d.compare = field_name.map(function(name) {       
       return {name: name, value: +d[name]}; 
    });
 });

 x0.domain(data.map(function(d) { console.log(d); return d.name; }));
 x1.domain(field_name).rangeRoundBands([0, x0.rangeBand()]);
 y.domain([0, d3.max(data, function(d) { 
       return d3.max(d.compare, function(d) {         
       return d.value; }); 
 })]);

 var svg = d3.select(el).selectAll("svg")
    .data(data)
    .enter().append("svg:svg")
     .attr("width", width + margin.left + margin.right)
     .attr("height", height + margin.top + margin.bottom)
    .append("g")
     .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

 svg.append("g")
     .attr("class", "x axis")
     .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

 svg.append("g")
     .attr("class", "y axis")
    .call(yAxis)
    .append("text")
     .attr("transform", "rotate(-90)")
     .attr("y", 6)
     .attr("dy", ".71em")
     .style("text-anchor", "end")
     .text("Price");


  // Accessing nested data: https://groups.google.com/forum/#!topic/d3-js/kummm9mS4EA
  // data(function(d) {return d.values;}) 
  // this will dereference the values for nested data for each group
  svg.selectAll(".bar")
    .data(function(d) {return d.compare;})
    .enter()
    .append("rect")
     .attr("class", "bar")
     .attr("x", function(d) { return x1(d.name); })
     .attr("width", x1.rangeBand())
     .attr("y", function(d) { return y(d.value); })
     .attr("height", function(d) { return height - y(d.value); })
     .attr("fill", color)

  var legend = svg.selectAll(".legend")
      .data(field_name.slice().reverse())
    .enter().append("g")
      .attr("class", "legend")
      .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

  legend.append("rect")
      .attr("x", width - 18)
      .attr("width", 18)
      .attr("height", 18)
      .style("fill", color);

  legend.append("text")
      .attr("x", width - 24)
      .attr("y", 9)
      .attr("dy", ".35em")
      .style("text-anchor", "end")
      .text(function(d) { return d; });

 function type(d) {
  d.percent = +d.percent;
  return d;
 }

}

multi_bars(".container");

Answer №1

Your setup of x0, x1, and y has been executed correctly.

However, when manipulating the DOM is where you encounter issues with your data references. To address this, I have made two adjustments. Firstly, I modified your initial block of code to create only one SVG element instead of multiple:

 var svg = d3.select(el).selectAll("svg")
.data(data)
.enter().append("svg:svg")
 .attr("width", width + margin.left + margin.right)
 .attr("height", height + margin.top + margin.bottom)
.append("g")
 .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

Subsequently, I followed the example provided in http://bl.ocks.org/mbostock/3887051 and adjusted your code accordingly. The outcome can be viewed here: http://jsfiddle.net/ee2todev/g61f93gx/

If you wish to have individual charts for each group as demonstrated in your original fiddle, simply translate each bar using the x0 scale. Two modifications need to be implemented:

a) You must include the group name in d.compare to access it from the corresponding data in the bar selection

 data.forEach(function(d) {
d.compare = field_name.map(function(name) {       
   return {group: d.name, name: name, value: +d[name]}; 
});

});

b) Adjust each group translation in the bar selection accordingly:

  svg.selectAll(".bar")
.data(function(d) {return d.compare;})
.enter()
.append("rect")
 .attr("class", "bar")
 .attr("transform", function(d) { return "translate(" + x0(d.group) + ",0)"; })
 .attr("x", function(d) { console.log("x: "+d.value); return x1(d.name); })
 .attr("width", x1.rangeBand())
 .attr("y", function(d) { return y(d.value); })
 .attr("height", function(d) { return height - y(d.value); })
 .attr("fill", color);

The complete fiddle can be accessed here: http://jsfiddle.net/ee2todev/en8sr5m4/

Additionally, I would like to note two things: 1) I have slightly modified your code. It is advisable to use meaningful and intuitive variable/object names to reduce errors. Renaming the d.compare properties will enhance clarity. For instance, {groupName: d.name, priceType: name, value: +d[name]}. This adjustment is crucial to avoid confusion between variables. 2) This scenario showcases a select of selections concept. Refer to The first selectAll selection (the svg variable) contains an Array[6] with objects. The subsequent selection iterates over each element of the svg data, containing an Array[2] with price type and value. Here, the group name has been included.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Transforming pure JavaScript code into jQuery code

Currently, I am attempting to add JSON data from the last.fm API into my project. Throughout this process, I have been using the alert() function at various points to confirm that the API data is being correctly parsed. However, it appears that the getEl ...

What is the correct timing for implementing a JavaScript delay to activate CSS styling?

Let's say I have some CSS like #joe { transition: opacity 500ms; opacity: 0; } Next, I include JavaScript that triggers the addition of a div and then animates the opacity to = 1 when you click the page. document.body.onclick = function () { var j ...

Prevent the AngularJS bar-footer from covering up the page content

I am currently working on developing an app using Ionic and AngularJS. I am facing a challenge with the bar-footer overlapping the content at the bottom of the page. I want the content to be fully visible before the footer appears, without any overlap. Can ...

Vue-Firebase: A guide to linking multiple Firebase services in a single app

I am facing an issue with connecting to two firebases within the same project. In my project, I have two javascript files that connect to each firebase separately, which seems fine. Here is how I import them: import db from '../FireBase' i ...

Ways to initiate the showModalDialog function within the C# script execution in ASP.NET 2.0, all without the use of Ajax

I have a scenario where I need to show a modal dialog in the middle of C# code execution and then continue the code based on a condition. Here is an example of what the code might look like: protected void Button_Click(object sender, EventArgs e) { //so ...

Try using the slice method to cut a portion of a JSON object

I am trying to chop up a JSON array, but keep encountering the error message below: Object # has no method 'slice' This is my current code snippet: $scope.getPagedDataAsync = function (pageSize, page, searchText) { setTimeout(function ( ...

Return an unspecified value when using Array.map function

How can I prevent this map from returning undefined? var onCompareSelectedClick = function () { var talentProfileInfoForAppliedResources = appliedResourcesEntries.map(function(res) { console.log(res); if(res.c ...

Direct user to an external webpage with Vue 3

I created a navigation bar with links to external social media platforms. After clicking on the GitHub link, I encountered some errors and here are the URLs for reference: https://i.sstatic.net/KCh3C.png https://i.sstatic.net/OXQOK.png <template> ...

Sending information from a webpage to an application in Express

Seeking guidance on a problem where I'm trying to send a string to the server from a jade view. The value is returning on the frontend, but when attempting to store it in an object, I get [object Object]. I suspect the issue lies around the parameter ...

Transforming CSV files into JSON format using pyspark while applying filters and including extra columns

Suppose we have a CSV data file with the following headers: "CorrelationID", "Message", "EventTimeStamp", "Flag", "RandomColumns" 12345, "Hello", "2019-06-09 04:25:15", "True", ...

Iterate through every object in a JSON request using either jQuery or JavaScript

Admittedly, I'm fairly new to json/jQuery and JavaScript, so please bear with me if I make any obvious mistakes. Despite looking at similar questions, none of the solutions seemed to work for me. My goal is to retrieve the "image.full" property for e ...

Using AngularJS to iterate through JSON data

I received JSON data from my Facebook account and saved it in a file called facebook.json. Next step is to retrieve and process the data from the JSON file in my controller. app.controller('Ctrl', function($scope, $http) { $http({metho ...

The `introJs()` API encounters issues when connected to an element within a jQuery modal dialog box

I am attempting to showcase an Intro.js tour on a specific element located within a <table>. This particular <table> is nested inside a dialog box created using jQuery UI. The rows of the table are dynamically inserted using JavaScript: while ...

Deliver asynchronous requests using Web Components (Model-View-Controller)

I am currently working on developing an application using pure javascript and Web Components. My goal is to incorporate the MVC Pattern, but I have encountered a challenge with asynchronous calls from the model. Specifically, I am creating a meal-list com ...

Tips for automatically scrolling mat-select option to the top when mat-select is closed

Is there a way to automatically scroll the mat-select options to the top after closing it? I am faced with a situation where I have numerous options in my mat-select dropdown. If I select an option from the bottom and then close the mat-select, is there a ...

Analyzing the AJAX response data against the content stored in the div

I need to compare and analyze data in order to determine if the div requires reloading. // <![CDATA[ $(function () { function reload (elem, interval) { var $elem = $(elem); var $original = $elem.html(); $.ajax({ ...

Attempting to dynamically change the text of a button in JavaScript without any user interaction

I have created a button function that displays a word's definition when clicked. However, I am now attempting to modify it so that the definitions are shown automatically every few seconds using "SetInterval" without requiring a click. I am unsure of ...

Ensure that the input field only accepts numerical values

Can anyone help me with an issue I'm facing in my plunker? I have an input text field that I want to accept only numbers. Despite trying normal AngularJS form validation, the event is not firing up. Has anyone encountered a similar problem or can prov ...

Steps for automatically retrying a failed expect statement in Jest

In my React application, I am utilizing Jest for performing unit testing. One aspect of my application involves a Material UI date picker with next and previous buttons. The selected date is displayed in the browser URL. Each time the user clicks on the ne ...

Utilizing the functions setInterval and clearInterval to dynamically update a counter within a React application

I'm currently developing an app featuring a 'bathtub' component with two buttons. The tub can hold up to 5 levels of water. One button fills the tub one level every 2 seconds until it reaches level 5, while the other drains the tub back to l ...