Generate SVG components without displaying them

Is there a way to generate a custom SVG graphic through a function without the need to attach it to any element? Can I simply create an empty selection and return that instead? Here is my current implementation:

function makeGraphic(svgParent) {
    return svgParent.append('circle').attrs({...});
}

This is what I am aiming for:

function makeGraphic() {
    return d3.makeCircle?.attrs({...});
}

svgParent.append(makeGraphic());

Answer №1

To improve the shape generation process, you can create a temporary svg node within a function without actually rendering the svg itself. This allows you to manipulate the svg as a regular node and generate various shapes.

It's worth noting that the .append() method in D3.js accepts either a tag name or a function. Below, I provide the function instead of its result:

var svg = d3.select("body")
  .append("svg")
  .attr("width", 400)
  .attr("height",200)
  .attr("transform", "translate(200,100)");
  
var makeCircle = function() {
  // create a temporary svg
  let svg = document.createElementNS(d3.namespaces.svg, "svg")
  // create a circle
  let circle = d3.select(svg).append("circle")
    .attr("r", 20)
    .attr("fill", "steelblue");
    
  // return a circle
  return circle.node(); 

}

svg.append(makeCircle);
<script src="https://d3js.org/d3.v5.min.js"></script>

This approach enables you to develop a more complex shape generator for creating shapes that can be used with methods like d3.append():

let svg = d3.select("body").append("svg")
    .attr("width", 500)
    .attr("height", 200);
    

var shapes = [{shape: "circle",y: 40, fill: "darkblue"},{shape:"square", y: 35},{y:40}]

svg.selectAll()
  .data(shapes)
  .enter()
  .append(shapemaker);    

function shapemaker(options = {}) {
 let svg = document.createElementNS(d3.namespaces.svg, "svg")
 var shape;
 if (options.shape == "circle") {
   shape = d3.select(svg).append("circle")
       .attr("cx", options.x ? options.x : 50)
       .attr("cy", options.y ? options.y : 50) 
       .attr("r", options.r ? options.r : 10)
       .attr("fill", options.fill ? options.fill : "steelblue" )
    }
    else if (options.shape == "square") {
      shape = d3.select(svg).append("rect")
       .attr("x", options.x ? options.x : 100)
       .attr("y", options.y ? options.y : 50) 
       .attr("width", options.width ? options.size : 10)
       .attr("height", options.width ? options.size : 10)
       .attr("fill", options.fill ? options.fill : "orange" )
    }
    else {
      let x = options.x ? options.x : 150, y = options.y ? options.y : 50;
      shape = d3.select(svg).append("path")
        .attr("d", d3.symbol().type(d3.symbolStar).size(options.size ? options.size : 100))
        .attr("transform","translate("+[x,y]+")")
        .attr("fill", options.fill ? options.fill : "crimson")

    }
 
  return shape.node();
   
}
<script src="https://d3js.org/d3.v5.min.js"></script>

Answer №2

One approach that fits perfectly in this scenario is utilizing a detached element. Another option could be using a DocumentFragment, but within D3 code, a detached element is considered more idiomatic.

To create a detached whatever SVG element, you can easily do the following:

var detached = d3.create('svg:whatever');

As per the documentation,

Upon providing the specified element name, it returns a single-element selection containing a detached element with the given name within the current document.

This procedure mirrors Andrew's response to some extent, because d3.create internally utilizes document.createElement. Nevertheless, make sure to note that it uses document.createElement, not document.createElementNS, hence the namespace is imperative here:

var detached = d3.create('svg:whatever');
//this signifies the namespace----ˆ

Subsequently, you can structure it as desired. For example:

detached.selectAll(null)
    .data(d3.range(5))
    .enter()
    .append("circle")
    //etc...

Following this, obtain the node of the detached element and insert it wherever needed using append:

svg.append(function(){ return detached.node();});

Here's an illustration; the function makeGraphic generates a detached element and provides its node. Afterwards, we simply append the returned value to the SVG:

var svg = d3.select("body")
  .append("svg")
  .attr("width", 500)
  .attr("height", 300);

function makeGraphic() {
  var detached = d3.create('svg:g');
  detached.selectAll(null)
    .data(d3.range(10))
    .enter()
    .append("circle")
    .attr("cx", function() {
      return Math.random() * 500
    })
    .attr("cy", function() {
      return Math.random() * 300
    })
    .attr("r", function() {
      return Math.random() * 50
    })
    .attr("fill", function(_, i) {
      return d3.schemeCategory10[i]
    });
  return detached.node();
};

svg.append(makeGraphic)
<script src="https://d3js.org/d3.v5.min.js"></script>

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

How to send the value of a JavaScript loop variable to PHP using AJAX

How can I send variables in a loop to a PHP file using AJAX? var lat; var lng; var array = [22.399602, 114.041176, 22.344043, 114.0168, 22.327529, 114.087181]; console.log(array); for (var i = 0; i < 6; i += 2) { lat = array[i]; console.log("l ...

How to pass only the clicked element to the onClick function in React.js

I have several elements with the same className, and I want to add the className active to an element (with the className history-node) when it is clicked, in addition to its current className. However, I am facing an issue where the child elements of tha ...

Basic Timer with Visual Background

Struggling to find the right CSS/script for a straightforward countdown timer, Here are the requirements: Countdown in days only, no need for hours, minutes, and seconds Ability to use a custom image as background I've scoured online searches but n ...

What steps should I follow to ensure that the message "Read It" is logged to the console before "Ex It"?

app.get('/', async (req, res) => { await fs.readFile('./views/website/index.html', 'utf8', (err, d) => { data = d console.log("Successfully read the file") // console.log(data) ...

Prevent a div from being displaced by the transform property once it reaches the window's border

Is it possible to prevent the viewer I created using CSS transform from moving when its borders reach the window borders? Should I consider using a different method instead? If you'd like to see the code, you can check it out here. var x=0, y=0 ...

Sending PDF file to client's request using PDFKIT and Strapi (Koa) via HTTP response

My goal is to send a PDF file as a response to a GET request on my Strapi endpoint. The current Strapi controller, which uses Koa, is structured like this: const PDFDocument = require("pdfkit"); module.exports = { async printOne(ctx) { const doc = ...

Having issues with ToggleClass and RemoveClass functionalities not functioning properly

As a novice in Jquery and CSS/scss, I've managed to create dynamic bootstrap cards for recording players. Each card consists of a music-container with control-play and vinyl elements. I aim to have multiple bootstrap cards generated based on the data ...

When using jQuery AJAX, the script is returning blank values

Facing a frustrating issue here. I'm sending an AJAX request to a PHP file, but when I check Chrome Network Tools, it doesn't return any JSON data. However, when I try posting the same data using POSTMAN in Chrome, it returns correctly. Also, if ...

Incorporate the Vue JS response into the table component

I am attempting to append my response from Vue into a table but I am unable to do so and I don't know why. I can retrieve all the data from my database, I can see it in my web browser console, but my table remains empty. Below is my current code: Vu ...

What is the reason behind div elements shifting when hovering over a particular element?

Currently, I have floated all my div elements (icons) to the left and margin-lefted them to create space in between. I've displayed them inline as well. However, when I hover over one element (icon), the rest of the elements move. Can you please help ...

Determine the name of the Java exception class using JavaScript

Here is the code I am using to call a Java web API: m$.ajaxq({ url: contextPath + "/updateElapsedTime", type: "POST", data: params, contentType: "application/json; charset=utf-8", dataType: 'text', async: optionalRunAsync, success: ...

"Utilizing AJAX to set an array as a global variable

Struggling with storing data values from an AJAX response XML into a global array, and then attempting to call a function that removes specific elements from it. The issue lies in the fact that the array is not declared as global. Here's the current c ...

Guide to retrieving data from a URL and storing it in a string variable with JavaScript

Attempting to retrieve the JSON data from a specific URL and store it in a variable. The code snippet below successfully loads the JSON into a div element: $("#siteloader").html('<object data="MYURL">'); However, the goal is to extract t ...

Utilizing Angular for making API requests using double quotes

I am experiencing an issue with my service where the double quotation marks in my API URL are not displayed as they should be. Instead of displaying ".." around my values, it prints out like %22%27 when the API is called. How can I ensure that my ...

Unlocking the Power of Global Props in AlpineJS: A Step-by-Step Guide

I'm currently exploring AlpineJS after having some experience with React and basic knowledge of Vue. One thing that has puzzled me about AlpineJS is how to update a state value from within a function, similar to how it's done in React. Let' ...

Troubleshooting VueJS Promise.all Problem

I need help implementing promise-based logic for asynchronous data fetching in VueJS. Previously, I had the following logic: if (influencer.suggested?.length && url.length) { const [ interactions, suggested_ids ] = await Promise.all([ $axios.$ ...

How can I pass my cookie token in a Next.js server-side component request?

My Next.js version is 14.1.0 and I am currently using the App router. async function Page() { const dataPromise: Promise<any> = getData(); const data = await dataPromise; console.log('data: ', data); return ( .... ); } The ge ...

AJAX request failed to elicit a response

Recently, I've been facing an issue with my AJAX call to the API. Previously, it was functioning correctly and returning a response JSON. However, now I am unable to retrieve any JSON object. When using Mozilla, no error is shown but the response JSON ...

This TypeScript error occurs when trying to assign a value of type 'null' to a parameter that expects a type of 'Error | PromiseLike<Error | undefined> | undefined'

Currently, I am making use of the Mobx Persist Store plugin which allows me to store MobX Store data locally. Although the documentation does not provide a TypeScript version, I made modifications to 2 lines of code (one in the readStore function and anot ...

What is the process for creating custom event bindings in AngularJS?

There is a custom event called core-transitionend (specifically triggered by Polymer), and I am able to specify an event handler using document.addEventListener(). However, what would be the most recommended approach for achieving this in AngularJS? Alter ...