D3.js: Unveiling the Extraordinary Tales

I'm currently working on a project that requires me to develop a unique legend featuring two text values. While I have successfully created a legend and other components, I am facing difficulties in achieving the desired design.

Specifically, the current output fails to handle long texts gracefully. Additionally, I need the legend to display the labels ("A", "B", etc.) in blue while keeping the rest of the text in black.

<!DOCTYPE html>
<meta charset="utf-8">

<body>      
<script src="https://d3js.org/d3.v6.js"></script>
<script>
var data = [{"legend_value":"A","value":8,"label":"test1"},
            {"legend_value":"B","value":15,"label":"this is a veryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy long text"},
            {"legend_value":"C","value":20,"label":"test3"},
            {"legend_value":"D","value":10,"label":"test4"},
            {"legend_value":"E","value":25,"label":"test5"},
            {"legend_value":"F","value":1000,"label":"test6"},
];
for (var i=0;i <data.length;i++){
data[i]["legend_value"]=String.fromCharCode(65+i)
}
console.log(data)
// set the dimensions and margins of the graph
var margin = {top: 20, right: 500, bottom: 20, left: 40},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

// define the ranges
var y = d3.scaleBand()
          .range([height, 0])
          .padding(0.2);

var x = d3.scaleLinear()
          .range([0, width-50]);
          
// append the svg object to the body of the page
// append a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("preserveAspectRatio", "xMinYMin meet")
  .attr("viewBox", "0 0 300 300")
    .attr("transform", 
          "translate(" + margin.left + "," + margin.top + ")");

  // format the data
  data.forEach(function(d) {
    d.value = +d.value;
  });

  // Scale the range of the data in the domains
  x.domain([0, d3.max(data, function(d){ return d.value; })])
  y.domain(data.map(function(d) { return d.legend_value; }));
  //y.domain([0, d3.max(data, function(d) { return d.value; })]);


var bar_height=Math.min(Math.ceil((height/data.length)-data.length*1),45);
  
  // append the rectangles for the bar chart
  var bars = svg.selectAll(".bar")
      .data(data)
    .enter()
    
    bars.append("path")
      .attr("class", "bar")
      //.attr("x", function(d) { return x(d.value); })
      .attr("width", function(d) {return x(d.value); } )
      .attr("y", function(d) { return y(d.legend_value); })
      .attr("height", y.bandwidth())
      .attr("fill", "#056DFF")
      .attr("d",  function (d) {
                    console.log("y.bandwidth()",y.bandwidth())
                    return rightRoundedRect(0,Math.floor(y(d.legend_value)) , x(d.value), y.bandwidth(), 5);
            });
    
function rightRoundedRect(x, y, width, height, radius) {
  return "M" + x + "," + y
       + "h" + (width - radius)
       + "a" + radius + "," + radius + " 0 0 1 " + radius + "," + radius
       + "v" + (height - 2 * radius)
       + "a" + radius + "," + radius + " 0 0 1 " + -radius + "," + radius
       + "h" + (radius - width)
       + "z";
}

bars.append("text")
            .attr("class", "legend_value")
            //y position of the legend_value is halfway down the bar
            .attr("y",  function (d) { 
             return Math.floor(y(d.legend_value)+y.bandwidth()/data.length) + Math.ceil(bar_height/2);
             })
            //x position is 3 pixels to the right of the bar
            .attr("x", d => x(d.value)+20)
            .style("font-size", function (d) {
                
                return 10;
            })
            .style("font-family","sans-serif")
            .style('fill', '#DCDCDC')
            
            .text(function (d) {
                return d.value;
            })
            //.style("text-anchor", "middle");



  // add the x Axis
  svg.append("g")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(x).tickSize(0));

  // add the y Axis
  svg.append("g")
      .call(d3.axisLeft(y).tickSize(0));





// add gray legend rect
svg.append("rect")
  .attr("class", "legend_text")
.attr("x", function(d, i){ 
    return  450 ;
})
.attr("y", function(d, i){ return 0 })
  .attr("rx", "5px")
  .attr("width", margin.left + margin.right-100)
  .attr("height", height)
  .attr("stroke", "darkgray")
  .attr("fill", "#F8F8F8");
  
var legend_text = svg.selectAll("legend_text")
  .data(data)
  .enter();

legend_text.append("text")
  .attr("class", "legend_text")
.attr("x", function(d, i){ 
    return  470 ;
})
.attr("y", function(d, i){ return 20 + i*20 })
  .attr("dy", "0.32em")
  .style("font-weight", "bold")
  .text(function(d) {
    return d["legend_value"] + "  " + d["label"] ;
  });

</script>
</body>

Current Output: https://i.stack.imgur.com/STSHp.png

Expected Output: https://i.stack.imgur.com/5bPP4.png

Answer №1

If it is not absolutely necessary, I suggest making the legend a standard HTML element for simplicity. A possible approach would be to create a container with two elements: one wrapping the SVG and another containing the legend.

I've implemented this idea in a codepen example that you can find here.

In case you are unable to implement the above solution due to certain limitations, an alternative option would be to modify and adapt the code from this resource to fulfill your requirements, such as incorporating text-wrapping within an SVG's <text> element.

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

HTML content that is produced through JSONP retrieval

Recently, I've been experimenting with the jQuery library and have found it to be quite useful. Lately, I've been delving into AJAX requests to fetch various information like weather updates, current downloads, and more, which has been going smoo ...

Automated logout feature will be enabled if no user interaction is detected, prompting a notification dialog box

Here is my working script that I found on this site. After a period of idle time, an alert message will pop up and direct the user to a specific page. However, instead of just the alert message, I would like to implement a dialog box where the user can ch ...

Next.js Refresh Screen

Is there a way to refresh my client's web page from the server at a specific time? I need the client's page to be refreshed at 12pm, and although I'm using a scheduler, it doesn't seem to be automatically refreshing the web page on the ...

The Jquery ajax get method is malfunctioning

I've been trying out this code but it doesn't seem to be working. Apologies for the basic question, but I'm curious to understand why it's not functioning as expected. $(document).ready(function(){ $("button").click(function(){ ...

Is it possible to pass a parameter to a PHP controller using JavaScript without relying on jQuery or AJAX?

Is it possible to achieve the task at hand? That's the main question here. My goal is to extract data from a popup window and then, upon closing it, send this extracted content to a PHP controller for further processing. I'm facing conflicts wi ...

Converting HTML elements into Vue.js Components

In my Vue.js application, I am utilizing a d3.js plugin to generate a intricate visualization. I am interested in implementing a customized vue directive to the components that were incorporated by the d3 plugin. It seems that the $compile feature, which ...

Exploring Best Practices for Coding in node.js

Method 1: Constructor and Prototype Objects function Database(url) { this.url = url; } Database.prototype.info = function (callback) { http.get(this.url + '/info', callback); }; Method 2: Closures Approach function Database(url) { ...

Vue.js error: Reaching maximum call stack size due to failed data passing from parent to child component

I'm having trouble passing data from a parent component to a child component. I tried using props and returning data, but with no success. The parent component is a panel component that contains the data, while the child component is a panelBody. Thi ...

AngularJS uses double curly braces, also known as Mustache notation, to display

I'm currently working on a project where I need to display an unordered list populated from a JSON endpoint. Although I am able to fetch the dictionary correctly from the endpoint, I seem to be facing issues with mustache notation as it's renderi ...

Show basic HTML elements on a single page of an Angular Material web application

I am working on an Angular (6) web app with a customized theme using Angular Material. Currently, I am trying to incorporate a popup dialog that includes basic HTML elements like checkboxes, buttons, and inputs. Even though I have successfully displayed ...

Unable to designate the drop-down option as the default selection

Can anyone help me with setting a default drop-down value using JavaScript? I have been struggling to achieve this. You can find my code on jsFiddle <div ng-controller="csrClrt"> <div ng:repeat="(key, item) in items track by $index"> ...

show a notification once the maximum number of checkboxes has been selected

I came across this code snippet from a previous question and I'm interested in making some modifications to it so that a message can be displayed after the limit is reached. Would adding a slideToggle to the .checkboxmsg within the function be the mos ...

Once the page is refreshed, the checkbox should remain in its current state and

I have a challenge with disabling all checkboxes on my page using Angular-Js and JQuery. After clicking on a checkbox, I want to disable all checkboxes but preserve their state after reloading the page. Here is an example of the code snippet: $('# ...

"Error: The function $(...).autocomplete is not recognized" in conjunction with Oracle Apex

Currently experiencing some frustration trying to solve the issue at hand. The Uncaught TypeError: $(...).autocomplete is not a function error keeps popping up and I have exhausted all resources on Stack Overflow in an attempt to fix it. This problem is oc ...

The JSON response from Rails containing multiple lines is not being parsed accurately

I am currently working on a Rails application with a json response using show.js.erb. { "opening": "<%= @frame.opening %>", "closing": "<%= @frame.closing %>"} An issue I encountered is that when @frame.opening contains multiple lines, jQuer ...

Using React to create multiple modals and dynamically passing props

Each item in my list (ProjectActivityList) has an Edit button which, when clicked, should open a modal for editing that specific item. The modal requires an ID to identify the item being edited. var ProjectActivities = React.createClass({ onEditItem: ...

The submission of the Jquery form is not successful

I am struggling with a form on my page. I want to disable the submit button and display a custom message when submitted, then use jQuery to actually submit the form. <form> <input type="text" name="run"/> <input type=&quo ...

Stubborn boolean logic that refuses to work together

Seeking guidance on resolving a persistent issue with my website that has been causing me headaches for the past few weeks. This website was created as my capstone project during a recent graduate program, where I unfortunately did not achieve all the desi ...

Change from using fs.writeFileSync to fs.writeFile

I have a question about changing fs.writeFileSync to fs.writeFile const users = { "(user id)": { "balance": 28, "lastClaim": 1612012406047, "lastWork": 1612013463181, "workersCount": 1, ...

Refreshing the page causes the Angular/Ionic Singleton instance to be destroyed

I have a TypeScript singleton class that is responsible for storing the login credentials of a user. When I set these credentials on the login page and navigate to the next page using Angular Router.navigate (without passing any parameters), everything wor ...