rocinante
rocinante

Reputation: 47

D3.js pseudo 3D bar chart

I started looking into d3 and tried to create a histogram with 3D effect. Correctly calculated the position of all faces, but they are visible only cleanly and only in the browser's inspector. What's wrong with my code? (See method "updateChart")

enter image description here

Above, something interferes with the display. But I can't figure out what exactly

enter image description here

class BarChart extends React.Component {
   //skip code...

  updateChart() {
    this.updateScales();
    const { data, width, height, connectFauxDOM } = this.props;
    const { xAxisLength, yAxisLength } = this.getAxisLength();

    const faux = connectFauxDOM('div', 'chart');
    const svg = d3
      .select(faux)
      .append('svg')
      .attr('viewBox', `0 0 ${width} ${height}`)
      .attr('preserveAspectRatio', 'xMinYMin');

    const xAxis = d3.axisBottom().scale(this.xScale);
    const yAxis = d3.axisLeft().scale(this.yScale);


    svg
      .append('g')
      .attr('class', 'x-axis')
      .attr(
        'transform',
        `translate(${this.margin},${height - this.margin})`,
      )
      .call(xAxis);

    svg
      .append('g')
      .attr('class', 'y-axis')
      .attr(
        'transform', 
        `translate(${this.margin},${this.margin})`,
      )
      .call(yAxis);

    svg
      .selectAll('g.y-axis g.tick')
      .append('line')
      .classed('grid-line', true)
      .attr('x1', 0)
      .attr('y1', 0)
      .attr('x2', xAxisLength)
      .attr('y2', 0);
    const g = svg.append('g').attr('class', 'body').attr(
      'transform', 
      `translate(${this.margin}, 0 )`,
    );
    g.selectAll('svg.bar')
      .data(data)
      .enter()
      .append('svg')
      .attr('class', 'bar');

    const angel = 45;
    const rectWidth = () =>
      Math.floor(xAxisLength / data.length) - this.padding;
    const rectHeight = (value) => yAxisLength - this.yScale(value);
    const rectX = (value) => this.xScale(value);
    const rectY = (value) => this.yScale(value) + this.margin;

    const bars = g
      .selectAll('svg.bar')
      .data(data)
      .attr('x', (d) => rectX(d.label))
      .attr('y', (d) => rectY(d.value));

    bars
      .data(data)
      .append('rect')
      .attr('class', 'forward-bar')
      .attr('height', (d) => rectHeight(d.value))
      .attr('width', () => rectWidth());

    // side
    bars
      .data(data)
      .append('rect')
      .attr('class', 'side-bar')
      .attr('width', rectWidth() / 2)
      .attr('height', (d) => rectHeight(d.value))
      .attr('transform', `translate (${rectWidth()}, 0) skewY(${-angel})`);

    bars
      .data(data)
      .append('rect')
      .attr('class', 'top-bar')
      .attr('width', rectWidth())
      .attr('height', rectWidth() / 2)
      .attr(
        'transform',
        `translate (${rectWidth() / 2},${-rectWidth() / 2}) skewX(${-angel})`,
      );
  }

  render() {
    const { chart, classes } = this.props;
    return <div className={classes.svg}>{chart}</div>;
  }
}

Upvotes: 1

Views: 523

Answers (1)

Michael Rovinsky
Michael Rovinsky

Reputation: 7210

I think skewed <rect>s will not work, <path> is much more suitable for your task.

Here is a simple function add3DBar:

const add3DBar = (parent, xPos, yPos, width, height, depth) => {
  const g = parent.append('g').attr('transform', `translate(${xPos}, ${yPos})`);
  g.append('path').attr('d', `M 0,0 V ${-height} H ${width} V 0 H 0 Z`).style('fill', '#000080');
  g.append('path').attr('d', `M 0,${-height} L ${depth},${-height-depth} H ${depth + width} L ${width},${-height} Z`).style('fill', '#0000FF');
  g.append('path').attr('d', `M ${width},0 L ${width + depth},${-depth}, V ${-height-depth} L ${width} ${-height} Z`).style('fill', '#0000C0');
}

, where

xPos and yPos - coordinates of the bottom-left corner of the bar

width, height, depth - dimensions of the bar

See how it works in the fiddle:

const add3DBar = (parent, xPos, yPos, width, height, depth) => {
  const g = parent.append('g').attr('transform', `translate(${xPos}, ${yPos})`);
  g.append('path').attr('d', `M 0,0 V ${-height} H ${width} V 0 H 0 Z`).style('fill', '#000080');
  g.append('path').attr('d', `M 0,${-height} L ${depth},${-height-depth} H ${depth + width} L ${width},${-height} Z`).style('fill', '#0000FF');
  g.append('path').attr('d', `M ${width},0 L ${width + depth},${-depth}, V ${-height-depth} L ${width} ${-height} Z`).style('fill', '#0000C0');
}

const svg = d3.select('svg');

add3DBar(svg, 30, 150, 30, 100, 10);
add3DBar(svg, 70, 150, 30, 70, 10);
add3DBar(svg, 110, 150, 30, 120, 10);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<svg/>

Upvotes: 1

Related Questions