Search code examples
javascriptd3.jssvgcanvasrendering

D3 / Canvas: Axis not displayed


i have the following issue: I want to create a simple chart using the d3 library and the canvas rendering.

var width = 800;
var height = 600;

var data = [10, 15, 20, 25, 30];

var chart = d3.select("#chart")
            .append("canvas")
            .attr("width", width)
            .attr("height", height);

var scale = d3.scaleLinear()
              .domain([d3.min(data), d3.max(data)])
              .range([0, width - 100]);

var x_axis = d3.axisBottom()
               .scale(scale);

chart.append("group")
   .call(x_axis);

I don't see anything in my browser - but, if I inspect the element, the Axis is 'compiled' in the code and I can hover over every tick in the code while the position in the DOM is marked. But, nothing to see.

There is already a chart written in d3 / canvas, but, the Axis are accessed to the canvas directly, like

const canvas = document.getElementById(this.id)
const context = canvas.getContext('2d')
// ..a lot of code here..
context.beginPath()
moveTo(0, height + tickSize)
lineTo(0, height - 0.5)
// ...

The goal is, to use d3 for that.

Is this possible only with svg?

Thanks in advance!


Solution

  • You would need to draw the axis manually on the <canvas> element. Here's an example, with help from D3 and Canvas in 3 steps and D3 + Canvas demo:

    // Grab a reference to the canvas element
    let canvas = document.querySelector('canvas');
    // And to its context
    let ctx = canvas.getContext('2d');
    
    // Define our margins and plot dimensions
    let margins = {
      top: 20,
      bottom: 40,
      left: 30,
      right: 20
    };
    let width = canvas.width - margins.left - margins.right;
    let height = canvas.height - margins.top - margins.bottom;
    
    // Center the chart
    ctx.translate(margins.left, margins.top);
    
    // Your data
    let data = [10, 15, 20, 25, 30];
    
    // Create the x-scale
    let x = d3.scaleBand()
      .domain(data)
      .rangeRound([0, width])
      .padding(0.1);
    
    // Create the y-scale
    let y = d3.scaleLinear()
      .domain([0, d3.max(data)])
      .rangeRound([height, 0]);
    
    // Define y-ticks
    var yTickCount = 10;
    var yTicks = y.ticks(yTickCount);
    
    // Draw the x-axis
    ctx.beginPath();
    x.domain().forEach(d => {
      ctx.moveTo(x(d) + x.bandwidth() / 2, height);
      ctx.lineTo(x(d) + x.bandwidth() / 2, height + 6);
    });
    ctx.strokeStyle = '#000';
    ctx.stroke();
    
    // And apply the x-axis labels
    ctx.textAlign = "center";
    ctx.textBaseline = "top";
    x.domain().forEach((d, i) => {
      ctx.fillText(i, x(d) + x.bandwidth() / 2, height + 6);
    });
    ctx.textAlign = "center";
    ctx.textBaseline = "bottom";
    x.domain().forEach((d, i) => {
      ctx.fillText(d, x(d) + x.bandwidth() / 2, y(d));
    });
    ctx.strokeStyle = "#000";
    ctx.stroke();
    
    // Now draw the y-axis
    yTicks.forEach(d => {
      ctx.moveTo(0, y(d) + 0.5);
      ctx.lineTo(-6, y(d) + 0.5);
    });
    ctx.strokeStyle = "#000";
    ctx.stroke();
    
    // And add the y-axis ticks and labels
    ctx.textAlign = "right";
    ctx.textBaseline = "middle";
    yTicks.forEach(d => {
      ctx.fillText(d, -9, y(d));
    });
    ctx.beginPath();
    ctx.lineTo(0.5, 0.5);
    ctx.lineTo(0.5, height + 0.5);
    ctx.strokeStyle = "#000";
    ctx.stroke();
    
    ctx.save();
    ctx.rotate(-Math.PI / 2);
    ctx.textAlign = "right";
    ctx.textBasline = "top";
    ctx.restore();
    
    // Finally, draw the bars using your data
    ctx.fillStyle = 'blue';
    data.forEach((d, i) => {
      ctx.fillRect(x(d), y(d), x.bandwidth(), height - y(d));
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <canvas id="canvas" height=300 width=600></canvas>

    If you don't have to use D3, you might want to look into a different charting library that's designed especially for the canvas element, like CanvasJS.