162

Is it possible using Chart.js to display data values?

I want to print the graph.

Thanks for any advice..

FuSsA
  • 4,223
  • 7
  • 39
  • 60
  • my graph is working perfectly ..i just want to display data values in my LineBar chart.. then i can print my graphs.. because now i can display data values just by mouse event – FuSsA Jul 25 '15 at 21:41

13 Answers13

177

There is an official plugin for Chart.js 2.7.0+ to do this: Datalabels

Otherwise, you can loop through the points / bars onAnimationComplete and display the values


Preview

Enter image description here


HTML

<canvas id="myChart1" height="300" width="500"></canvas>
<canvas id="myChart2" height="300" width="500"></canvas>

Script

var chartData = {
    labels: ["January", "February", "March", "April", "May", "June"],
    datasets: [
        {
            fillColor: "#79D1CF",
            strokeColor: "#79D1CF",
            data: [60, 80, 81, 56, 55, 40]
        }
    ]
};

var ctx = document.getElementById("myChart1").getContext("2d");
var myLine = new Chart(ctx).Line(chartData, {
    showTooltips: false,
    onAnimationComplete: function () {

        var ctx = this.chart.ctx;
        ctx.font = this.scale.font;
        ctx.fillStyle = this.scale.textColor
        ctx.textAlign = "center";
        ctx.textBaseline = "bottom";

        this.datasets.forEach(function (dataset) {
            dataset.points.forEach(function (points) {
                ctx.fillText(points.value, points.x, points.y - 10);
            });
        })
    }
});

var ctx = document.getElementById("myChart2").getContext("2d");
var myBar = new Chart(ctx).Bar(chartData, {
    showTooltips: false,
    onAnimationComplete: function () {

        var ctx = this.chart.ctx;
        ctx.font = this.scale.font;
        ctx.fillStyle = this.scale.textColor
        ctx.textAlign = "center";
        ctx.textBaseline = "bottom";

        this.datasets.forEach(function (dataset) {
            dataset.bars.forEach(function (bar) {
                ctx.fillText(bar.value, bar.x, bar.y - 5);
            });
        })
    }
});

Fiddle - http://jsfiddle.net/uh9vw0ao/

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
potatopeelings
  • 40,709
  • 7
  • 95
  • 119
  • What if i had two line chart in the same Chart ? – FuSsA Jul 26 '15 at 12:08
  • 1
    With the above it would still show the values, but you might see an overlap if the points are too close to each other. But you can always put in logic to change the value position. Like for instance - http://jsfiddle.net/hh719qex/ if you have a color printer. If you have only 2 series you could also do stuff like setting the textBaseline different IF the 2 points are 2 close to each other. – potatopeelings Jul 26 '15 at 12:21
  • Can you add styles to this data above the points? Maybe use the styles from the generic chartjs tooltips? – Tom Rudge Feb 17 '16 at 14:03
  • @TomRudge - sure, just change the part where you set the ctx properties to use whichever font/color you wish. Cheers! – potatopeelings Feb 17 '16 at 21:01
  • @potatopeelings Thanks, can we add a bubble to the data like on the tooltips styling? or are we limited to just font color and type? – Tom Rudge Feb 18 '16 at 11:16
  • @TomRudge - sure you could use `Chart.helpers.drawRoundedRectangle`, but I think it would be simpler then to just show the tooltips themselves (for all elements). I think there is already an answer that does this (search for tooltips.. with [chart.js]). – potatopeelings Feb 18 '16 at 21:04
  • @potatopeelings is it possible to have showTooltips: true ? Current if i set it to true it removes the labels after I hover over a bar. – Yasin Yaqoobi Apr 22 '16 at 14:10
  • @YasinYaqoobi - yep, but then you need to move your tooltip code to an extension. See http://jsfiddle.net/4d5ojt0t/ – potatopeelings Apr 24 '16 at 04:02
  • Thanks @potatopeelings :D – Mayllon Baumer Jun 21 '16 at 17:43
  • 6
    this was for the v1.0.2 how to do this for v2.1.3?The object structure is different in the later version. – techie_28 Aug 24 '16 at 06:16
  • 2
    @potatopeelings I have used the `onComplete` callback here but it also fires when the mouseover is on the bar's of the chart causing the number to blink due to redraw.Would that be same if `onAnimationComplete`? I am unable to use this callback with my existing chart code (see `var chartBar` at the bottom http://pastebin.com/tT7yTkGh) – techie_28 Aug 24 '16 at 07:19
  • It is throwing error saying `Cannot read property 'ctx' of undefined` – Black Mamba May 24 '17 at 09:28
  • @potatopeelings What if I have too may values for example 360 different values when I show the graph is looks like a line instead of curve. Also I need to have a lot of values in X. – nik Jun 02 '17 at 16:04
  • Giving error as (1)Uncaught TypeError: Cannot read property 'getContext' of null (2)Uncaught TypeError: (intermediate value).Bar is not a function – Anand Maurya May 11 '18 at 08:04
  • 1
    I need both tool tip and data on top of the bar in my graph. When tool tip is showing, the data on top of the bar is turned to white colour which is visible in my transparent tool tip. How can I fix this? – LJP Nov 19 '18 at 08:03
94

This works for Chart.js 2.3, including for both line/bar types.

Important: Even if you don't need the animation, don't change the duration option to 0. Otherwise, you will get chartInstance.controller is undefined error.

var chartData = {
    labels: ["January", "February", "March", "April", "May", "June"],
        datasets: [
            {
                fillColor: "#79D1CF",
                strokeColor: "#79D1CF",
                data: [60, 80, 81, 56, 55, 40]
            }
        ]
    };

var opt = {
    events: false,
    tooltips: {
        enabled: false
    },
    hover: {
        animationDuration: 0
    },
    animation: {
        duration: 1,
        onComplete: function () {
            var chartInstance = this.chart,
                ctx = chartInstance.ctx;
            ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
            ctx.textAlign = 'center';
            ctx.textBaseline = 'bottom';

            this.data.datasets.forEach(function (dataset, i) {
                var meta = chartInstance.controller.getDatasetMeta(i);
                meta.data.forEach(function (bar, index) {
                    var data = dataset.data[index];
                    ctx.fillText(data, bar._model.x, bar._model.y - 5);
                });
            });
        }
    }
};
var ctx = document.getElementById("Chart1"),
    myLineChart = new Chart(ctx, {
       type: 'bar',
       data: chartData,
       options: opt
    });
<canvas id="myChart1" height="300" width="500"></canvas>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ross
  • 2,123
  • 1
  • 18
  • 18
  • 1
    I get "Uncaught TypeError: Cannot read property '0' of undefined" when trying this on bar graphs. Do bars use something other than "dataset.metaData[i]._model"? – Chris Ensell Jun 02 '16 at 19:08
  • It's actually var model = dataset._meta[i].data[0]._model; – Flanamacca Jun 08 '16 at 02:29
  • That's also not the full truth. I still get exceptions. Will try to figure it out. – val Jun 19 '16 at 09:34
  • var model = dataset._meta[0].data[i]._model; actually. – brian Jul 02 '16 at 20:03
  • 2
    Looks like it is not as simple as _meta[0]. In fact the _meta object isn't an array, it has a single element with a number as the key. I can't figure out the rationale behind what the number will be so I just take the first element by looking in the keys list, like this : var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model; – IrishDubGuy Jul 10 '16 at 12:32
  • If your labels are getting cut off by the scale boundaries: See @Hung Tran solution below, or calculate and set y-axis Max property, so chartjs doesn't need to auto calculate chart area, like so: yAxes: [{ gridLines: { display: false }, ticks: { // add extra padding (5%) on y-axis by calculating max data point + 5% // this way chartjs doesn't need to auto calculate chart area and so the labels are not cut off max: Math.max.apply(null, [Your Data Array]) * 1.05 } }] – Ross Dec 19 '16 at 19:53
  • I am using pie chart and would like to show lable in the center or outside of bar chart. Can you please also share the code for pie chart as well? Also the signature of fillText does not have parameters. I am using 2.7.0 version. – Ravi Khambhati Oct 03 '17 at 20:38
  • I noticed that all the labels are centered and overlapped on each other. – Ravi Khambhati Oct 03 '17 at 20:48
  • This answer needs updated. The code example given is broken. – Amir Asyraf Dec 04 '20 at 08:34
46

If you are using the plugin chartjs-plugin-datalabels then the following code options object will help.

Make sure you import import ChartDataLabels from 'chartjs-plugin-datalabels'; in your TypeScript file or add reference to <script src="chartjs-plugin-datalabels.js"></script> in your javascript file and register the plugin using ChartJS.register(ChartDataLabels).

options: {
    maintainAspectRatio: false,
    responsive: true,
    scales: {
        yAxes: [{
            ticks: {
                beginAtZero: true,
            }
        }]
    },
    plugins: {
        datalabels: {
            anchor: 'end',
            align: 'top',
            formatter: Math.round,
            font: {
                weight: 'bold'
            }
        }
    }
}
Dave Cole
  • 2,446
  • 2
  • 20
  • 26
Karthik
  • 1,447
  • 1
  • 18
  • 26
36

This animation option works for 2.1.3 on a bar chart.

Slightly modified Ross answer:

animation: {
    duration: 0,
    onComplete: function () {
        // render the value of the chart above the bar
        var ctx = this.chart.ctx;
        ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, 'normal', Chart.defaults.global.defaultFontFamily);
        ctx.fillStyle = this.chart.config.options.defaultFontColor;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'bottom';
        this.data.datasets.forEach(function (dataset) {
            for (var i = 0; i < dataset.data.length; i++) {
                var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
                ctx.fillText(dataset.data[i], model.x, model.y - 5);
            }
        });
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Aaron Hudon
  • 5,280
  • 4
  • 53
  • 60
  • 4
    I also had to set `hover: {animationDuration: 0}` to stop the labels animating away on hover – iamyojimbo Sep 30 '16 at 14:25
  • 1
    Also, note that if you want to hide labels when you hide something from the legend, you need to add the following before the forEach loop: `.filter(dataset => !dataset._meta[0].hidden)` – iamyojimbo Sep 30 '16 at 16:01
  • 1
    This method causes the values to clip if there's not enough room above the bars. – Janne Annala Oct 06 '16 at 07:48
  • 2
    Can't believe it's that ugly ^^ nice job – La masse Nov 01 '16 at 20:51
  • Looks good, but blinks when tooltips are re-drawn. Also does not process collisions on combined charts and cumulative data labels for stacked bars – that will of course require more coding. – dma_k Feb 13 '17 at 19:35
  • I did this and its working but all the labels are centered to the pie chart and it is not displayed similar to tooltip. In tooltip we see "lable: value" but with this approach we only see values. – Ravi Khambhati Oct 03 '17 at 21:00
26

Based on Ross's answer for Chart.js 2.0 and up, I had to include a little tweak to guard against the case when the bar's heights comes too chose to the scale boundary.

Example

The animation attribute of the bar chart's option:

animation: {
            duration: 500,
            easing: "easeOutQuart",
            onComplete: function () {
                var ctx = this.chart.ctx;
                ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontFamily, 'normal', Chart.defaults.global.defaultFontFamily);
                ctx.textAlign = 'center';
                ctx.textBaseline = 'bottom';

                this.data.datasets.forEach(function (dataset) {
                    for (var i = 0; i < dataset.data.length; i++) {
                        var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model,
                            scale_max = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._yScale.maxHeight;
                        ctx.fillStyle = '#444';
                        var y_pos = model.y - 5;
                        // Make sure data value does not get overflown and hidden
                        // when the bar's value is too close to max value of scale
                        // Note: The y value is reverse, it counts from top down
                        if ((scale_max - model.y) / scale_max >= 0.93)
                            y_pos = model.y + 20;
                        ctx.fillText(dataset.data[i], model.x, y_pos);
                    }
                });
            }
        }
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Hung Tran
  • 1,570
  • 1
  • 13
  • 13
  • The text blinks on mouseover of the chart.. I tried calling `ctx.save()` at the end but it did not help – techie_28 Aug 24 '16 at 06:41
  • the `onComplete` call back is executed when I hovered on the bar's which causes the redraw & text appears to blink.Would this be the same with `onAnimationComplete` as well?I am unable to use the `onAnimationComplete` call back with my existing code (see `var chartBar` at the bottom of http://pastebin.com/tT7yTkGh – techie_28 Aug 24 '16 at 07:17
  • Is it possible to do the same in pie chart – Ravi Khambhati Oct 03 '17 at 21:00
  • @RaviKhambhati Yes, you can check it out in this answer: https://stackoverflow.com/a/38797604/6402571 – Hung Tran Nov 19 '17 at 07:19
22

I think the nicest option to do this in Chart.js v2.x is by using a plugin, so you don't have a large block of code in the options. In addition, it prevents the data from disappearing when hovering over a bar.

I.e., simply use this code, which registers a plugin that adds the text after the chart is drawn.

Chart.pluginService.register({
    afterDraw: function(chartInstance) {
        var ctx = chartInstance.chart.ctx;

        // render the value of the chart above the bar
        ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, 'normal', Chart.defaults.global.defaultFontFamily);
        ctx.textAlign = 'center';
        ctx.textBaseline = 'bottom';

        chartInstance.data.datasets.forEach(function (dataset) {
            for (var i = 0; i < dataset.data.length; i++) {
                var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
                ctx.fillText(dataset.data[i], model.x, model.y - 2);
            }
        });
  }
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
David
  • 763
  • 1
  • 7
  • 14
  • Chart.plugins.register({ – hasentopf Aug 26 '16 at 12:00
  • I had to use `Chart.defaults.global.tooltips.enabled = false;`. The issue with that solution is that the `afterDraw` function is called hundreds of times, probably generating CPU overhead. Still, it's the only answer for now without blinks. If you need a better rendering for `horizontalBar` charts, use `ctx.textAlign = 'left'; ctx.textBaseline = 'middle';` and `ctx.fillText(dataset.data[i], model.x+5, model.y);`. – KrisWebDev Sep 17 '16 at 16:06
  • a far more cleaner and modular solution. – hadaytullah Jan 21 '17 at 14:43
9

Following this good answer, I'd use these options for a bar chart:

var chartOptions = {
    animation: false,
    responsive : true,
    tooltipTemplate: "<%= value %>",
    tooltipFillColor: "rgba(0,0,0,0)",
    tooltipFontColor: "#444",
    tooltipEvents: [],
    tooltipCaretSize: 0,
    onAnimationComplete: function()
    {
        this.showTooltip(this.datasets[0].bars, true);
    }
};

window.myBar = new Chart(ctx1).Bar(chartData, chartOptions);

Bar Chart

This still uses the tooltip system and his advantages (automatic positionning, templating, ...) but hiding the decorations (background color, caret, ...)

Community
  • 1
  • 1
Pierre de LESPINAY
  • 44,700
  • 57
  • 210
  • 307
  • Implementation may change depending on chart type. For a line chart, for example, it's `this.datasets[0].points` instead of `.bars`. I do prefer this solution because it uses the tooltip system. – Telmo Marques Mar 13 '16 at 21:16
  • 1
    This solution for an earlier version.How to achieve this for the latest version i.e 2.1.3. I have used the potatopeelings & Huang Trang answers in the animations `onComplete` callback but it is triggered even when hovered over the bars of the chart which causes the text to redraw & give an unwanted blink effect.I also tried `afterDraw` & it is all same.Strangely it doesnt happen in fiddle provided by potatopeelings.Any Ideas plz? – techie_28 Aug 26 '16 at 08:37
9

I'd recommend using this plugin: datalabels

Labels can be added to your charts simply by importing the plugin into the JavaScript file, for example:

import 'chartjs-plugin-datalabels'

And can be fine-tuned using this documentation: https://chartjs-plugin-datalabels.netlify.com/options.html

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
DavidX
  • 1,281
  • 11
  • 16
9

Adapted the @Ross answer to work with 3.7.0 version of the Chartjs

animation: {
        duration: 0,
        onComplete: function() {
            ctx = this.ctx;
            ctx.font = Chart.helpers.fontString(Chart.defaults.font.size, Chart.defaults.font.style, Chart.defaults.font.family);
            ctx.textAlign = 'center';
            ctx.textBaseline = 'bottom';
            chartinst = this;
            this.data.datasets.forEach(function(dataset, i) {
                if(chartinst.isDatasetVisible(i)){
                    var meta = chartinst.getDatasetMeta(i);
                    meta.data.forEach(function(bar, index) {
                        var data = dataset.data[index];
                        ctx.fillText(data, bar.x, bar.y - 5);
                    });
                }
            });
        }
    }

In this case, animation can be 0 To have a nicer looking, you can disable the hover and the tooltip if you want a more "static" visualization

Also, the isDataSetVisible works to get rid of the numbers that stay shown when you hide the dataset in case of multiple datasets

jefissu
  • 244
  • 3
  • 7
7

From Chart.js samples (file Chart.js-2.4.0/samples/data_labelling.html):

        // Define a plugin to provide data labels

        Chart.plugins.register({
            afterDatasetsDraw: function(chartInstance, easing) {
                // To only draw at the end of animation, check for easing === 1
                var ctx = chartInstance.chart.ctx;

                chartInstance.data.datasets.forEach(function (dataset, i) {
                    var meta = chartInstance.getDatasetMeta(i);
                    if (!meta.hidden) {
                        meta.data.forEach(function(element, index) {
                            // Draw the text in black, with the specified font
                            ctx.fillStyle = 'rgb(0, 0, 0)';

                            var fontSize = 16;
                            var fontStyle = 'normal';
                            var fontFamily = 'Helvetica Neue';
                            ctx.font = Chart.helpers.fontString(fontSize, fontStyle, fontFamily);

                            // Just naively convert to string for now
                            var dataString = dataset.data[index].toString();

                            // Make sure alignment settings are correct
                            ctx.textAlign = 'center';
                            ctx.textBaseline = 'middle';

                            var padding = 5;
                            var position = element.tooltipPosition();
                            ctx.fillText(dataString, position.x, position.y - (fontSize / 2) - padding);
                        });
                    }
                });
            }
        });
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Radek Dest
  • 99
  • 1
  • 5
  • I added a line to this for alignment on a Horizontal Bar Chart by doing if(chart.config.type == "horizontalBar") then defining a custom vertical padding of 10 to center align it. – Jeff Beagley Jul 21 '17 at 16:50
  • Even with the reference and code comments, an explanation would be in order. E.g., what is the idea/gist? What are the properties of this solution? In what way does it solve the problem in the question? Pros/cons? Etc. From [the Help Center](https://stackoverflow.com/help/promotion): *"...always explain why the solution you're presenting is appropriate and how it works"*. Please respond by [editing (changing) your answer](https://stackoverflow.com/posts/42391757/edit), not here in comments (***without*** "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen Jan 14 '22 at 17:26
  • OK, the OP seems to have left the building, so this is unlikely to ever happen. Perhaps somebody else can chime in and reverse engineer this and provide the information? – Peter Mortensen Jan 14 '22 at 17:27
7

From my experience, once you include the chartjs-plugin-datalabels plugin (make sure to place the <script> tag after the chart.js tag on your page), your charts begin to display values.

If you then choose you can customize it to fit your needs. The customization is clearly documented here but basically, the format is like this hypothetical example:

var myBarChart = new Chart(ctx, {
    type: 'bar',
    data: yourDataObject,
    options: {
        // other options
        plugins: {
            datalabels: {
                anchor :'end',
                align :'top',
                // and if you need to format how the value is displayed...
                formatter: function(value, context) {
                    return GetValueFormatted(value);
                }
            }
        }
    }
});
Karthik
  • 1,447
  • 1
  • 18
  • 26
Soma Mbadiwe
  • 1,594
  • 16
  • 15
6

I edited Aaron Hudon's answer a little, but only for bar charts. My version adds:

  • Fade in animation for the values.
  • Prevent clipping by positioning the value inside the bar if the bar is too high.
  • No blinking.

Example

Downside: When hovering over a bar that has a value inside it, the value might look a little jagged. I have not found a solution do disable hover effects. It might also need tweaking depending on your own settings.

Configuration:

bar: {
  tooltips: {
    enabled: false
  },
  hover: {
    animationDuration: 0
  },
  animation: {
    onComplete: function() {
      this.chart.controller.draw();
      drawValue(this, 1);
    },
    onProgress: function(state) {
      var animation = state.animationObject;
      drawValue(this, animation.currentStep / animation.numSteps);
    }
  }
}

Helpers:

// Font color for values inside the bar
var insideFontColor = '255,255,255';
// Font color for values above the bar
var outsideFontColor = '0,0,0';
// How close to the top edge bar can be before the value is put inside it
var topThreshold = 20;

var modifyCtx = function(ctx) {
  ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, 'normal', Chart.defaults.global.defaultFontFamily);
  ctx.textAlign = 'center';
  ctx.textBaseline = 'bottom';
  return ctx;
};

var fadeIn = function(ctx, obj, x, y, black, step) {
  var ctx = modifyCtx(ctx);
  var alpha = 0;
  ctx.fillStyle = black ? 'rgba(' + outsideFontColor + ',' + step + ')' : 'rgba(' + insideFontColor + ',' + step + ')';
  ctx.fillText(obj, x, y);
};

var drawValue = function(context, step) {
  var ctx = context.chart.ctx;

  context.data.datasets.forEach(function (dataset) {
    for (var i = 0; i < dataset.data.length; i++) {
      var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
      var textY = (model.y > topThreshold) ? model.y - 3 : model.y + 20;
      fadeIn(ctx, dataset.data[i], model.x, textY, model.y > topThreshold, step);
    }
  });
};
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Janne Annala
  • 25,928
  • 8
  • 31
  • 41
  • Janne, thank you for the solution, it looks great. But I have a situation where my values are actually kinda big (5 decimal numbers), and when the screen size is small or I have many charts, the numbers are on top of each other. Is there any way to make this work with responsivity? Like, make the numbers rotate when there is no space, just like the labels below the bar? – Phiter Oct 27 '16 at 10:47
  • @PhiterFernandes I think it should be possible to rotate by checking current screen size in the modifyCtx function and using ctx.rotate. However I'm not sure how to compare the value size to the available space to determine when to rotate. – Janne Annala Oct 27 '16 at 11:01
  • The modifyCtx function only works once, and not on resize, right? I'll take a look at the chart.js source code and see where they do the rotation of the labels in the x axis. If I see something I'll try to do something and tell you in here, ok? =) – Phiter Oct 27 '16 at 11:04
  • I have found something on the version 2.3.0, line 7075. [`calculateTickRotation`](https://github.com/chartjs/Chart.js/blob/master/src/core/core.scale.js#L182), it's inside the Scale function. I'll analyze it. – Phiter Oct 27 '16 at 11:06
0

To prevent your numbers from being cut off if they're too close to the top of the canvas:

yAxes: [{
    ticks: {
        stepSize: Math.round((1.05*(Math.max.apply(Math, myListOfyValues)) / 10)/5)*5,
        suggestedMax: 1.05*(Math.max.apply(Math, myListOfyValues)),
        beginAtZero: true,
        precision: 0
    }
}]
  • 10 = the number of ticks

  • 5 = rounds tick values to the nearest 5 - all y values will be incremented evenly

  • 1.05 = increases the maximum y axis tick value so the numbers don't get cut off

Something similar will work for xAxes too.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Collin
  • 394
  • 5
  • 14