[Chartjs]-How can I create a horizontal scrolling Chart.js line chart with a locked y axis?

25👍

Chart.js 2.7.2: https://jsfiddle.net/EmmaLouise/eb1aqpx8/3/

This approach handles different DPR settings and will scale the axis to match the scaling that Chart.js applies to its charts. It also calls .clearRect() on the original Y axis that Chart.js draws, clearing the pixels in the defined area which means that there is no duplication of axes or overlaps.

CSS:

.chartWrapper {
 position: relative;
}

.chartWrapper > canvas {
  position: absolute;
  left: 0;
  top: 0;
  pointer-events: none;
}

.chartAreaWrapper {
  width: 600px;
  overflow-x: scroll;
}

HTML

<div class="chartWrapper">
    <div class="chartAreaWrapper">
        <div class="chartAreaWrapper2">
          <canvas id="chart-Test" height="300" width="1200"></canvas>
        </div>
     </div>
     <canvas id="axis-Test" height="300" width="0"></canvas>
</div>

JS:

    $(function () {
    var rectangleSet = false;

    var canvasTest = $('#chart-Test');
    var chartTest = new Chart(canvasTest, {
        type: 'bar',
        data: chartData,
        maintainAspectRatio: false,
        responsive: true,
        options: {
            tooltips: {
                titleFontSize: 0,
                titleMarginBottom: 0,
                bodyFontSize: 12
            },
            legend: {
                display: false
            },
            scales: {
                xAxes: [{
                    ticks: {
                        fontSize: 12,
                        display: false
                    }
                }],
                yAxes: [{
                    ticks: {
                        fontSize: 12,
                        beginAtZero: true
                    }
                }]
            },
            animation: {
                onComplete: function () {
                    if (!rectangleSet) {
                        var scale = window.devicePixelRatio;                       

                        var sourceCanvas = chartTest.chart.canvas;
                        var copyWidth = chartTest.scales['y-axis-0'].width - 10;
                        var copyHeight = chartTest.scales['y-axis-0'].height + chartTest.scales['y-axis-0'].top + 10;

                        var targetCtx = document.getElementById("axis-Test").getContext("2d");

                        targetCtx.scale(scale, scale);
                        targetCtx.canvas.width = copyWidth * scale;
                        targetCtx.canvas.height = copyHeight * scale;

                        targetCtx.canvas.style.width = `${copyWidth}px`;
                        targetCtx.canvas.style.height = `${copyHeight}px`;
                        targetCtx.drawImage(sourceCanvas, 0, 0, copyWidth * scale, copyHeight * scale, 0, 0, copyWidth * scale, copyHeight * scale);

                        var sourceCtx = sourceCanvas.getContext('2d');

                        // Normalize coordinate system to use css pixels.

                        sourceCtx.clearRect(0, 0, copyWidth * scale, copyHeight * scale);
                        rectangleSet = true;
                    }
                },
                onProgress: function () {
                    if (rectangleSet === true) {
                        var copyWidth = chartTest.scales['y-axis-0'].width;
                        var copyHeight = chartTest.scales['y-axis-0'].height + chartTest.scales['y-axis-0'].top + 10;

                        var sourceCtx = chartTest.chart.canvas.getContext('2d');
                        sourceCtx.clearRect(0, 0, copyWidth, copyHeight);
                    }
                }
            }
        }
    });

37👍

Scrollable Chart

You’re pretty much on the right track. If you add another wrapper and the y axis you are done.


Preview

enter image description here


CSS

.chartWrapper {
    position: relative;
}

.chartWrapper > canvas {
    position: absolute;
    left: 0;
    top: 0;
    pointer-events:none;
}

.chartAreaWrapper {
    width: 600px;
    overflow-x: scroll;
}

HTML

<div class="chartWrapper">
    <div class="chartAreaWrapper">
        <canvas id="myChart" height="300" width="1200"></canvas>
    </div>
    <canvas id="myChartAxis" height="300" width="0"></canvas>
</div>

Script

...

new Chart(ctx).Line(data, {
    onAnimationComplete: function () {
        var sourceCanvas = this.chart.ctx.canvas;
        // the -5 is so that we don't copy the edges of the line
        var copyWidth = this.scale.xScalePaddingLeft - 5;
        // the +5 is so that the bottommost y axis label is not clipped off
        // we could factor this in using measureText if we wanted to be generic
        var copyHeight = this.scale.endPoint + 5;
        var targetCtx = document.getElementById("myChartAxis").getContext("2d");
        targetCtx.canvas.width = copyWidth;
        targetCtx.drawImage(sourceCanvas, 0, 0, copyWidth, copyHeight, 0, 0, copyWidth, copyHeight);
    }
});

Fiddle – http://jsfiddle.net/mbhavfwm/

24👍

with the latest version (2.4.0) this worked for me:

HTML

<div style="width: 100%; overflow-x: auto; overflow-y: hidden">
  <div style="width: 3000px; height: 300px">
    <canvas id="chart1" height="300" width="0"></canvas>
  </div>
</div>

You can also calculate the width dynamically based on the length of data. For example in VueJS you can do it as follow (considering 30px for each entry):

VueJS

<div style="width: 100%; overflow-x: auto;">
  <div :style="{width: (data.length * 30) + 'px', height: '300px'}">
    <canvas id="chart1" height="300" width="0"></canvas>
  </div>
</div>

5👍

Here is the Complete working example with 5000 data points and add as much data as you want.
the scrollbar code is taken from the internet so you can modify it as you want.
this Example also contains a scrollbar with multiple series. (code is commented)

Chart with Scrollbar

    <!DOCTYPE html>
<html lang="en">
<head> </head>
<body>
    <div style="width: 500pt;">
    <canvas id="myChart" style="display: block;width: 1333px;height: 369px;"></canvas>
    <section class="range-slider">
        <span class="rangeValues"></span>
        <input value="1" min="1" max="" type="range" />
        <input value="10" min="1" max="" type="range" />
    </section>
    </div>
</body>
<style>
    section.range-slider {
    position: relative;
    width: 700px;
    height: 300px;
    float: left;
    text-align: center;
    }
    section.range-slider input[type="range"] {
    pointer-events: none;
    position: absolute;
    -webkit-appearance: none;
    -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
    border: none;
    border-radius: 14px;
    background: #f1efef;
    box-shadow: inset 0 1px 0 0 #cdc6c6, inset 0 -1px 0 0 #d9d4d4;
    -webkit-box-shadow: inset 0 1px 0 0 #cdc6c6, inset 0 -1px 0 0 #d9d4d4;
    overflow: hidden;
    left: 0;
    top: 50px;
    width: 700px;
    outline: none;
    height: 20px;
    margin: 0;
    padding: 0;
    }
    section.range-slider input[type="range"]::-webkit-slider-thumb {
    pointer-events: all;
    position: relative;
    z-index: 1;
    outline: 0;
    -webkit-appearance: none;
    width: 20px;
    height: 20px;
    border: none;
    border-radius: 14px;
    background-image: -webkit-gradient(
        linear,
        left top,
        left bottom,
        color-stop(0%, #dad8da),
        color-stop(100%, #413f41)
    );
    /* android <= 2.2 */
    background-image: -webkit-linear-gradient(top, #dad8da 0, #413f41 100%);
    /* older mobile safari and android > 2.2 */
    background-image: linear-gradient(to bottom, #dad8da 0, #413f41 100%);
    /* W3C */
    }
    section.range-slider input[type="range"]::-moz-range-thumb {
    pointer-events: all;
    position: relative;
    z-index: 10;
    -moz-appearance: none;
    width: 20px;
    height: 20px;
    border: none;
    border-radius: 14px;
    background-image: linear-gradient(to bottom, #dad8da 0, #413f41 100%);
    /* W3C */
    }
    section.range-slider input[type="range"]::-ms-thumb {
    pointer-events: all;
    position: relative;
    z-index: 10;
    -ms-appearance: none;
    width: 20px;
    height: 20px;
    border-radius: 14px;
    border: 0;
    background-image: linear-gradient(to bottom, #dad8da 0, #413f41 100%);
    /* W3C */
    }
    section.range-slider input[type="range"]::-moz-range-track {
    position: relative;
    z-index: -1;
    background-color: black;
    border: 0;
    }
    section.range-slider input[type="range"]:last-of-type::-moz-range-track {
    -moz-appearance: none;
    background: none transparent;
    border: 0;
    }
    section.range-slider input[type="range"]::-moz-focus-outer {
    border: 0;
    }
</style>
</html>

<script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
<script>



var ctx = document.getElementById("myChart").getContext("2d");
    // create dummt data

    var labels = [];
    var values = [];

    for(var i=0;i<5000;i++){
        labels.push("Label"+i)
        values.push(Math.random()*30)
    }  
var chart = new Chart(ctx, {
    // The type of chart we want to create
    type: "bar",
    // The data for our dataset
    data: {
    labels: labels,
    datasets: [
        {
        label: "My First dataset",
        backgroundColor: "rgb(255, 99, 132)",
        borderColor: "rgb(255, 99, 132)",
        data: values,
        },
    ],
    },
    // Configuration options go here
    options: {},
});


function getVals() {
    // Get slider values
    var parent = this.parentNode;
    var slides = parent.getElementsByTagName("input");
    var min = parseFloat(slides[0].value);
    var max = parseFloat(slides[1].value);
    // Neither slider will clip the other, so make sure we determine which is larger
    if (min > max) {
    var tmp = max;
    max = min;
    min = tmp;}

        var label = [];
        var value = [];

        label = JSON.parse(JSON.stringify(labels)).slice(min, max);

        //var datasets = Data.datasets;
        // IF YOU HAVE MULTIPLE SERIESES

        // ChartObj.data.labels = label;
        // for (var i = 0; i < datasets.length; i++) {
        //     values = datasets[i].data.slice(min, max);
        //     ChartObj.data.datasets[i].data = values;
        // }
        // ChartObj.update();
        value = JSON.parse(JSON.stringify(values)).slice(min, max);
        chart.data.labels = label;
        chart.data.datasets[0].data = value;
        chart.update();

    var displayElement = parent.getElementsByClassName("rangeValues")[0];
    displayElement.innerHTML = "Min : " + min + " Max : " + max;
}

// Initialize Sliders
var sliderSections = document.getElementsByClassName("range-slider");
for (var x = 0; x < sliderSections.length; x++) {
    var sliders = sliderSections[x].getElementsByTagName("input");
    for (var y = 0; y < sliders.length; y++) {
    if (sliders[y].type === "range") {
        sliders[y].oninput = getVals;
        sliders[y].max=JSON.parse(JSON.stringify(labels)).length;
        // Manually trigger event first time to display values
        sliders[y].oninput();
    }
    }
}




</script>

3👍

All these answers are rather muddy and complex.. Heres what I went with for my project. I just call fitChart() whenever the dataset changes or the container size changes.

HTML:

<div class="chartWrapper">
    <div class="chartContainer">
        <canvas id="chart"></canvas>
    </div>
</div>

CSS: (Set the width and height to whatever you want)

div.chartWrapper {
    position: relative;
    overflow: auto;
    width: 100%;
}

div.chartContainer {
    position: relative;
    height: 300px;
}

JS:

var xAxisLabelMinWidth = 15; // Replace this with whatever value you like
var myChart = new Chart(document.getElementById('chart').getContext('2d'), {
    type: 'line',
    data: {},
    options: {
        responsive: true,
        maintainAspectRatio: false
    }
});

function fitChart(){
    var chartCanvas = document.getElementById('chart');
    var maxWidth = chartCanvas.parentElement.parentElement.clientWidth;
    var width = Math.max(mayChart.data.labels.length * xAxisLabelMinWidth, maxWidth);

    chartCanvas.parentElement.style.width = width +'px';
}

1👍

Chart.js 2.x:

CSS:

    .graph {
        padding: 10px;
        position: relative;
        overflow-x: scroll;
        width: 100%;
        .graph-container {
            height: 500px;
            width: 100%;
            min-width: 100%
        }
    }

HTML : Ignore the angular ngFor

  <div *ngFor="let graph of graphs; let i = index" class="graph">
        <div class="graph-container">
            <canvas #graphCanvas></canvas>
        </div>
  </div>

JS

Set these options:

        options: {
            responsive: true,
            maintainAspectRatio: false
        }

Set the width of the “graph-container”

I’m setting the width based on the number of elements I have X a zoom that the user chooses

graphCanvasElement.nativeElement.parentElement
    .setAttribute('style', `width: ${chartData.data.labels.length*graph.zoom}px;min-width: 100%`);

graph.chartObj = new Chart(graphCanvasElement.nativeElement.getContext('2d'), chartData);

1👍

To add to Emma Louise’s solution above (I don’t have the reputation to comment), I found that if I filled the rectangle with a color before drawing the image, the overlay canvas is opaque. Then you don’t need to worry about clearing the rectangle, either onProgress or onComplete.

    targetCtx.fillStyle = 'white'
    targetCtx.fillRect(0, 0, copyWidth * scale, copyHeight * scale);

Also, maybe it’s because my devicePixelRatio is one, but these two lines caused the text in my chart to be all fuzzy. It looks fine when I remove them.

    targetCtx.canvas.style.width = `${copyWidth}px`;
    targetCtx.canvas.style.height = `${copyHeight}px`;

0👍

<ion-content >
<div scrollX="true" scrollY="true" style="overflow:scroll; position:fixed; display:inline; top:0; right:0; bottom:0; left:0; white-space: nowrap;">
<canvas baseChart
[data]="barChartData"
[labels]="barChartLabels"
[options]="barChartOptions"
[colors]="lineChartColors"
[legend]="barChartLegend"
[chartType]="barChartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)">
</canvas>
</div>
<button (click)="randomize()">Update</button>
</ion-content>

It works for me as I set canvas width 700 px and scroll works smoothly.

Leave a comment