iwonz
iwonz

Reputation: 421

How to make fixed width of column without ticks.min/max in chart.js?

I need to be able to set fixed column width. For example, if i specified 50 pixels and canvas width is 500 pixels, Chart.JS render 10 columns and automatically setup ticks.min/max to 0 and 9.

I know about barThickness and barPercentage in bar-charts. But i have line-chart. I know about maxTicksLimit but it's work only for 'linear' scale type (i have 'category' scale type). I found a solution. I use onResize callback and make some tricks (pick canvas width and divide it by column needed width then setup ticks.min/max). It's works but works bad because the result doesn't near expected width. And more problem if you have 500px canvas and 5 data items and you need 50px width of column. In this case Chart.JS will stretch columns by canvas width.

Clear example without my tricks.

const ITEMS_COUNT = 50;
const MIN = -5000;
const MAX = 8000;

const defaultDatasetOptions = {
  pointRadius: 0,
  pointHoverRadius: 0,
  pointBorderWidth: 0,
  pointHoverBorderWidth: 0,
  pointHitRadius: 10,
  borderWidth: 0
};

const data = {
  labels: _.range(1, ITEMS_COUNT),
  datasets: [
    {
      ...defaultDatasetOptions,
      label: 'Series 1',
      data: Array.from(Array(ITEMS_COUNT), () => _.random(MIN, MAX)),
      backgroundColor: 'rgba(255, 0, 0, .3)'
    },
    {
      ...defaultDatasetOptions,
      label: 'Series 2',
      data: Array.from(Array(ITEMS_COUNT), () => _.random(MIN, MAX)),
      backgroundColor: 'rgba(0, 255, 0, .3)'
    },
    {
      ...defaultDatasetOptions,
      label: 'Series 3',
      data: Array.from(Array(ITEMS_COUNT), () => _.random(MIN, MAX)),
      backgroundColor: 'rgba(0, 0, 255, .3)'
    }
  ]
};

const options = {
  legend: {
    display: false
  },
  scales: {
    xAxes: [
      {
        type: 'category',
        gridLines: {
          offsetGridLines: true,
          color: 'orange',
          tickMarkLength: 0,
          drawBorder: false
        },
        ticks: {
          display: false,
          maxRotation: 0,
          beginAtZero: true,
          autoSkip: false,
          stepSize: 1
        },
        offset: true
      }
    ],
    yAxes: [{ display: false }]
  },
  tooltips: {
    mode: 'index',
    intersect: false
  },
  hover: {
    mode: 'index',
    intersect: false
  }
};

new Chart(document.querySelector('canvas'), {
  type: 'line',
  data,
  options
});
canvas { border: 1px solid black; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js"></script>

<canvas></canvas>

Example with my solution.

const ITEMS_COUNT = 50;
const MIN = -5000;
const MAX = 8000;

const COLUMN_WIDTH = 50;

const defaultDatasetOptions = {
  pointRadius: 0,
  pointHoverRadius: 0,
  pointBorderWidth: 0,
  pointHoverBorderWidth: 0,
  pointHitRadius: 10,
  borderWidth: 0
};

const data = {
  labels: _.range(1, ITEMS_COUNT + 1),
  datasets: [
    {
      ...defaultDatasetOptions,
      label: 'Series 1',
      data: Array.from(Array(ITEMS_COUNT), () => _.random(MIN, MAX)),
      backgroundColor: 'rgba(255, 0, 0, .3)'
    },
    {
      ...defaultDatasetOptions,
      label: 'Series 2',
      data: Array.from(Array(ITEMS_COUNT), () => _.random(MIN, MAX)),
      backgroundColor: 'rgba(0, 255, 0, .3)'
    },
    {
      ...defaultDatasetOptions,
      label: 'Series 3',
      data: Array.from(Array(ITEMS_COUNT), () => _.random(MIN, MAX)),
      backgroundColor: 'rgba(0, 0, 255, .3)'
    }
  ]
};

const options = {
  onResize: setupTicksMinMax,
  legend: {
    display: false
  },
  scales: {
    xAxes: [
      {
        type: 'category',
        gridLines: {
          offsetGridLines: true,
          color: 'orange',
          tickMarkLength: 0,
          drawBorder: false
        },
        ticks: {
          display: false,
          maxRotation: 0,
          beginAtZero: true,
          autoSkip: false,
          stepSize: 1
        },
        offset: true
      }
    ],
    yAxes: [{ display: false }]
  },
  tooltips: {
    mode: 'index',
    intersect: false
  },
  hover: {
    mode: 'index',
    intersect: false
  }
};

const setupTicksMinMax = (chartInstance) => {
  const xScale = chartInstance.scales['x-axis-0'];

  xScale.options.ticks.min = chartInstance.data.labels[0];
  xScale.options.ticks.max = chartInstance.data.labels[Math.ceil(chartInstance.canvas.width / COLUMN_WIDTH)];

  chartInstance.update(0);
};

const chartInstance = new Chart(document.querySelector('canvas'), {
  type: 'line',
  data,
  options
});

setupTicksMinMax(chartInstance);
canvas { border: 1px solid black; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js"></script>

<canvas></canvas>

Upvotes: 4

Views: 1755

Answers (1)

timclutton
timclutton

Reputation: 13014

If I've understood your question correctly then the below snippet achieves the result you want.

From two input parameters (width and count of points) it adjusts the right padding of the chart to keep the points at the specified width, regardless of whether there are too many or too few points to fill the width of the canvas.

For demonstration purposes you can change the input values and press the "Update" button to see the result.

let chart = new Chart(document.getElementById("chart"), {
  type: "line",
  data: {
    datasets: [{
      data: []
    }]
  },
  options: {
    maintainAspectRatio: false,
    legend: {
      display: false
    },
    scales: {
      xAxes: [{
        type: 'category',
        gridLines: {
          offsetGridLines: true,
          color: 'orange',
          tickMarkLength: 0,
          drawBorder: false
        },
        ticks: {
          display: false,
          maxRotation: 0,
          beginAtZero: true,
          autoSkip: false,
          stepSize: 1
        },
        offset: true
      }],
      yAxes: [{
        display: false
      }]
    }
  },
  plugins: [{
    resize: function(chart) {
      update(chart);
    }
  }]
});

function update(chart) {
  let pointWidth = parseInt(document.getElementById("pw").value),
    pointCount = parseInt(document.getElementById("pc").value),
    width = chart.width - chart.chartArea.left,
    points = Math.floor(width / pointWidth);

  if (points > pointCount) {
    points = pointCount;
  }

  chart.config.data.labels = _.range(1, points + 1);
  chart.config.data.datasets[0].data = Array.from(Array(points), () => _.random(-5000, 8000));
  chart.options.layout.padding.right = width - (pointWidth * points);

  chart.update();
}

document.getElementById("update").addEventListener("click", function() {
  update(chart);
});
canvas {
  background-color: #f5f5f5
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js"></script>
<p>
  Width: <input id="pw" type="text" value="50">px Count: <input id="pc" type="text" value="5"><button id="update">Update</button>
</p>
<canvas id="chart"></canvas>

Upvotes: 2

Related Questions