osechet
osechet

Reputation: 466

Chart content overlaps axes with d3.js

I'm using d3.js to create a graphical grid with 2 axis. Data is displayed using cells whose color will change based on the data value. An simple example is visible here. The chart is created as an svg image. I have a problem with the clipping of the data area : when zooming and panning, the data overlaps the axes. You can see this in the provided example, by clicking on the chart content and dragging up or left.

Does someone know how I can make the data to disappear under the axes?

Here is the typescript code I used to create the chart:

let cellSize = 49;
let borderSize = 5;
let margin = { top: 40, right: 40, bottom: 40, left: 40 };

function createChart() {
    // Create test data
    let count = 500;
    let perLine = 50;
    let markers = [];
    for (let i = 0; i < count; i++) {
        let id = i + 1;
        let line = Math.floor(i / perLine) + 1;
        let point = i % perLine + 1;
        markers.push({id: id, line: line, point: point});
    }
    //---

    let lineMin = d3.min(markers, (item) => item.line);
    let lineMax = d3.max(markers, (item) => item.line);
    let pointMin = d3.min(markers, (item) => item.point);
    let pointMax = d3.max(markers, (item) => item.point);

    let chart = document.querySelector('.chart');

    let viewWidth = chart.clientWidth - 2;
    let viewHeight = 400;
    let chartWidth = cellSize * (pointMax - pointMin + 1);
    let chartHeight = cellSize * (lineMax - lineMin + 1);

    let point = d3.scale.linear()
          .domain([pointMin, pointMax])
          .range([cellSize / 2, chartWidth - cellSize / 2]);

    let line = d3.scale.linear()
      .domain([lineMin, lineMax])
      .range([cellSize / 2, chartHeight - cellSize / 2]);

    let pointAxis = d3.svg.axis()
      .scale(point)
      .tickSize(0)
      .tickValues(d3.range(pointMin, pointMax + 1))
      .orient('top');

    let lineAxis = d3.svg.axis()
      .scale(line)
      .tickSize(0)
      .tickValues(d3.range(lineMin, lineMax + 1))
      .orient('left');

    let zoom = d3.behavior.zoom()
      .x(point)
      .y(line)
      .scaleExtent([0.1, 1])
      .on('zoom', () => {
        let width = Math.min(viewWidth - margin.left - margin.right, chartWidth);
        let height = Math.min(viewHeight - margin.top - margin.bottom, chartHeight);
        let t = zoom.translate();
        let tx = Math.min(t[0], 0);
        tx = Math.max(tx, width - chartWidth);
        let ty = Math.min(t[1], 0);
        ty = Math.max(ty, height - chartHeight);
        zoom.translate([tx, ty]);
        let scale = zoom.scale();

        svg.select('.x.axis').call(pointAxis).selectAll('text')
          .attr('x', -3)
          .attr('y', 5)
          .attr('transform', 'rotate(90)')
          .style('text-anchor', 'end');
        svg.select('.y.axis').call(lineAxis);

        grid.attr('transform', `translate(${[tx, ty]})scale(${scale})`);
      });

    let svg = d3.select('.chart').append('svg')
      .attr('id', 'svg')
      .attr('width', viewWidth)
      .attr('height', viewHeight)
      .append('g')
      .attr('transform', `translate(${margin.left}, ${margin.top})`)
      .call(zoom);

    let grid = svg.append('g')
        .attr('id', 'grid');

    // Add the grid background
    grid.append('rect')
      .attr('class', 'grid')
      .attr('x', 0)
      .attr('y', 0)
      .attr('width', chartWidth)
      .attr('height', chartHeight);

    // Create the cells
    let cell = grid.selectAll('.cell')
      .data(markers).enter()
      .append('g')
      .attr('id', (marker) => `cell${marker.line}-${marker.point}`)
      .attr('class', 'cell')
      .attr('transform', (marker) => `translate(${point(marker.point) - cellSize / 2}, ${line(marker.line) - cellSize / 2})`);

    cell.append('rect')
      .attr('class', 'border')
      .attr('x', 0)
      .attr('y', 0)
      .attr('width', cellSize)
      .attr('height', cellSize);

    cell.append('rect')
      .attr('class', (marker) => `fill valid`)
      .attr('x', borderSize)
      .attr('y', borderSize)
      .attr('width', cellSize - 2 * borderSize)
      .attr('height', cellSize - 2 * borderSize);

    cell.append('rect')
      .attr('class', 'selection')
      .attr('x', 0)
      .attr('y', 0)
      .attr('width', cellSize)
      .attr('height', cellSize);

    // Add the axes
    svg.append('g')
      .attr('class', 'x axis')
      .call(pointAxis)
      .selectAll('text')
      .attr('x', -3)
      .attr('y', 5)
      .attr('transform', 'rotate(90)')
      .style('text-anchor', 'end');

    svg.append('g')
      .attr('class', 'y axis')
      .call(lineAxis);
}

Upvotes: 0

Views: 1387

Answers (1)

ksav
ksav

Reputation: 20821

If you don't need to actually use clipPath, you could just add a simple bg for x axis like this before you add the axes.

  // Add bg for x axis
  svg.append('rect')
    .attr('height', 30)
    .attr('y', -25)
    .attr('fill', 'white')
    .attr('width', chartWidth);

Plunkr: https://plnkr.co/edit/tVZb7YEaDTMZaFX4Nod3?p=preview

Upvotes: 1

Related Questions