]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Cartesian axis text alignment (#7846)
authorEvert Timberg <evert.timberg+github@gmail.com>
Sat, 10 Oct 2020 15:38:55 +0000 (11:38 -0400)
committerGitHub <noreply@github.com>
Sat, 10 Oct 2020 15:38:55 +0000 (11:38 -0400)
* Generate textBaseline per tick label
* Enable configuration of tick alignment
* Add image based tests of text alignment options

docs/docs/axes/styling.md
samples/samples.js
samples/scales/label-text-alignment.html [new file with mode: 0644]
src/core/core.scale.js
test/fixtures/core.scale/label-align-center.js [new file with mode: 0644]
test/fixtures/core.scale/label-align-center.png [new file with mode: 0644]
test/fixtures/core.scale/label-align-end.js [new file with mode: 0644]
test/fixtures/core.scale/label-align-end.png [new file with mode: 0644]
test/fixtures/core.scale/label-align-start.js [new file with mode: 0644]
test/fixtures/core.scale/label-align-start.png [new file with mode: 0644]
types/scales/index.d.ts

index a9c8f1a0b5155c44b0a447a8e86a58e1151f06c9..eed36ab4d9492f83ede4f5cd95febf6e9620a8ff 100644 (file)
@@ -42,6 +42,7 @@ The tick configuration is nested under the scale configuration in the `ticks` ke
 
 | Name | Type | Scriptable | Default | Description
 | ---- | ---- | :-------------------------------: | ------- | -----------
+| `alignment` | `string` | | `'center'` | The tick alignment along the axis. Can be `'start'`, `'center'`, or `'end'`.
 | `callback` | `function` | | | Returns the string representation of the tick value as it should be displayed on the chart. See [callback](../axes/labelling.md#creating-custom-tick-formats).
 | `display` | `boolean` | | `true` | If true, show tick labels.
 | `font` | `Font` | Yes | `defaults.font` | See [Fonts](../general/fonts.md)
index 67d1f44863039d69c1fce50e075fffe8e1f89fc7..57cac6b3adf18b3355a6b04fd632542567797e2b 100644 (file)
                }, {
                        title: 'Filtering Labels',
                        path: 'scales/filtering-labels.html'
+               }, {
+                       title: 'Label Text Alignment',
+                       path: 'scales/label-text-alignment.html'
                }, {
                        title: 'Non numeric Y Axis',
                        path: 'scales/non-numeric-y.html'
diff --git a/samples/scales/label-text-alignment.html b/samples/scales/label-text-alignment.html
new file mode 100644 (file)
index 0000000..4e54269
--- /dev/null
@@ -0,0 +1,163 @@
+<!doctype html>
+<html>
+
+<head>
+       <title>Label Text Alignment</title>
+       <script src="../../dist/chart.min.js"></script>
+       <script src="../utils.js"></script>
+       <style>
+               canvas {
+                       -moz-user-select: none;
+                       -webkit-user-select: none;
+                       -ms-user-select: none;
+               }
+               .chart-container {
+                       width: 500px;
+                       margin-left: 40px;
+                       margin-right: 40px;
+               }
+               .container {
+                       display: flex;
+                       flex-direction: row;
+                       flex-wrap: wrap;
+                       justify-content: center;
+               }
+       </style>
+</head>
+
+<body>
+       <div class="container">
+               <div class="chart-container">
+                       <canvas id="chart-start-start"></canvas>
+               </div>
+               <div class="chart-container">
+                       <canvas id="chart-start-center"></canvas>
+               </div>
+               <div class="chart-container">
+                       <canvas id="chart-start-end"></canvas>
+               </div>
+               <div class="chart-container">
+                       <canvas id="chart-center-start"></canvas>
+               </div>
+               <div class="chart-container">
+                       <canvas id="chart-center-center"></canvas>
+               </div>
+               <div class="chart-container">
+                       <canvas id="chart-center-end"></canvas>
+               </div>
+               <div class="chart-container">
+                       <canvas id="chart-end-start"></canvas>
+               </div>
+               <div class="chart-container">
+                       <canvas id="chart-end-center"></canvas>
+               </div>
+               <div class="chart-container">
+                       <canvas id="chart-end-end"></canvas>
+               </div>
+       </div>
+       <script>
+               var color = Chart.helpers.color;
+               function createConfig(xAlign, yAlign, colorName) {
+                       return {
+                               type: 'line',
+                               data: {
+                                       labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
+                                       datasets: [{
+                                               label: 'My First dataset',
+                                               data: [
+                                                       randomScalingFactor(),
+                                                       randomScalingFactor(),
+                                                       randomScalingFactor(),
+                                                       randomScalingFactor(),
+                                                       randomScalingFactor(),
+                                                       randomScalingFactor(),
+                                                       randomScalingFactor()
+                                               ],
+                                               backgroundColor: color(window.chartColors[colorName]).alpha(0.5).rgbString(),
+                                               borderColor: window.chartColors[colorName],
+                                               borderWidth: 1
+                                       }]
+                               },
+                               options: {
+                                       responsive: true,
+                                       legend: {
+                                               display: false,
+                                       },
+                                       scales: {
+                                               x: {
+                                                       display: true,
+                                                       ticks: {
+                                                               alignment: xAlign,
+                                                       }
+                                               },
+                                               y: {
+                                                       display: true,
+                                                       ticks: {
+                                                               alignment: yAlign
+                                                       }
+                                               }
+                                       },
+                                       title: {
+                                               display: true,
+                                               text: 'X Tick Alignment: ' + xAlign + ', Y Tick Alignment ' + yAlign
+                                       }
+                               }
+                       };
+               }
+
+               window.onload = function() {
+                       [{
+                               id: 'chart-start-start',
+                               xAlign: 'start',
+                               yAlign: 'start',
+                               color: 'red'
+                       }, {
+                               id: 'chart-start-center',
+                               xAlign: 'start',
+                               yAlign: 'center',
+                               color: 'orange'
+                       }, {
+                               id: 'chart-start-end',
+                               xAlign: 'start',
+                               yAlign: 'end',
+                               color: 'yellow'
+                       }, {
+                               id: 'chart-center-start',
+                               xAlign: 'center',
+                               yAlign: 'start',
+                               color: 'green'
+                       }, {
+                               id: 'chart-center-center',
+                               xAlign: 'center',
+                               yAlign: 'center',
+                               color: 'blue'
+                       }, {
+                               id: 'chart-center-end',
+                               xAlign: 'center',
+                               yAlign: 'end',
+                               color: 'purple'
+                       }, {
+                               id: 'chart-end-start',
+                               xAlign: 'end',
+                               yAlign: 'start',
+                               color: 'grey'
+                       }, {
+                               id: 'chart-end-center',
+                               xAlign: 'end',
+                               yAlign: 'center',
+                               color: 'red'
+                       }, {
+                               id: 'chart-end-end',
+                               xAlign: 'end',
+                               yAlign: 'end',
+                               color: 'orange'
+                       }].forEach(function(details) {
+                               var ctx = document.getElementById(details.id).getContext('2d');
+                               var config = createConfig(details.xAlign, details.yAlign, details.color);
+                               new Chart(ctx, config);
+                       });
+               };
+       </script>
+</body>
+
+</html>
index 2a39bd9370b69b949c796fdb406fdfac9fb35094..69a69be7614636084c63c38eeb8d47c9de898625 100644 (file)
@@ -61,7 +61,8 @@ defaults.set('scale', {
                // We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
                callback: Ticks.formatters.values,
                minor: {},
-               major: {}
+               major: {},
+               alignment: 'center',
        }
 });
 
@@ -761,6 +762,12 @@ export default class Scale extends Element {
                                        paddingRight = labelsBelowTicks ?
                                                sinRotation * (lastLabelSize.height - lastLabelSize.offset) :
                                                cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset;
+                               } else if (tickOpts.alignment === 'start') {
+                                       paddingLeft = 0;
+                                       paddingRight = lastLabelSize.width;
+                               } else if (tickOpts.alignment === 'end') {
+                                       paddingLeft = firstLabelSize.width;
+                                       paddingRight = 0;
                                } else {
                                        paddingLeft = firstLabelSize.width / 2;
                                        paddingRight = lastLabelSize.width / 2;
@@ -780,8 +787,19 @@ export default class Scale extends Element {
 
                                minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth);
 
-                               me.paddingTop = lastLabelSize.height / 2;
-                               me.paddingBottom = firstLabelSize.height / 2;
+                               let paddingTop = lastLabelSize.height / 2;
+                               let paddingBottom = firstLabelSize.height / 2;
+
+                               if (tickOpts.alignment === 'start') {
+                                       paddingTop = 0;
+                                       paddingBottom = firstLabelSize.height;
+                               } else if (tickOpts.alignment === 'end') {
+                                       paddingTop = lastLabelSize.height;
+                                       paddingBottom = 0;
+                               }
+
+                               me.paddingTop = paddingTop;
+                               me.paddingBottom = paddingBottom;
                        }
                }
 
@@ -1240,13 +1258,14 @@ export default class Scale extends Element {
                const rotation = -toRadians(me.labelRotation);
                const items = [];
                let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
+               let textBaseline = 'middle';
 
                if (position === 'top') {
                        y = me.bottom - tl - tickPadding;
-                       textAlign = !rotation ? 'center' : 'left';
+                       textAlign = me._getXAxisLabelAlignment();
                } else if (position === 'bottom') {
                        y = me.top + tl + tickPadding;
-                       textAlign = !rotation ? 'center' : 'right';
+                       textAlign = me._getXAxisLabelAlignment();
                } else if (position === 'left') {
                        x = me.right - (isMirrored ? 0 : tl) - tickPadding;
                        textAlign = isMirrored ? 'left' : 'right';
@@ -1261,7 +1280,7 @@ export default class Scale extends Element {
                                const value = position[positionAxisID];
                                y = me.chart.scales[positionAxisID].getPixelForValue(value) + tl + tickPadding;
                        }
-                       textAlign = !rotation ? 'center' : 'right';
+                       textAlign = me._getXAxisLabelAlignment();
                } else if (axis === 'y') {
                        if (position === 'center') {
                                x = ((chartArea.left + chartArea.right) / 2) - tl - tickPadding;
@@ -1273,6 +1292,14 @@ export default class Scale extends Element {
                        textAlign = 'right';
                }
 
+               if (axis === 'y') {
+                       if (optionTicks.alignment === 'start') {
+                               textBaseline = 'top';
+                       } else if (optionTicks.alignment === 'end') {
+                               textBaseline = 'bottom';
+                       }
+               }
+
                for (i = 0, ilen = ticks.length; i < ilen; ++i) {
                        tick = ticks[i];
                        label = tick.label;
@@ -1303,13 +1330,34 @@ export default class Scale extends Element {
                                label,
                                font,
                                textOffset,
-                               textAlign
+                               textAlign,
+                               textBaseline,
                        });
                }
 
                return items;
        }
 
+       _getXAxisLabelAlignment() {
+               const me = this;
+               const {position, ticks} = me.options;
+               const rotation = -toRadians(me.labelRotation);
+
+               if (rotation) {
+                       return position === 'top' ? 'left' : 'right';
+               }
+
+               let align = 'center';
+
+               if (ticks.alignment === 'start') {
+                       align = 'left';
+               } else if (ticks.alignment === 'end') {
+                       align = 'right';
+               }
+
+               return align;
+       }
+
        /**
         * @protected
         */
@@ -1409,8 +1457,8 @@ export default class Scale extends Element {
                        ctx.rotate(item.rotation);
                        ctx.font = tickFont.string;
                        ctx.fillStyle = tickFont.color;
-                       ctx.textBaseline = 'middle';
                        ctx.textAlign = item.textAlign;
+                       ctx.textBaseline = item.textBaseline;
 
                        if (useStroke) {
                                ctx.strokeStyle = tickFont.strokeStyle;
diff --git a/test/fixtures/core.scale/label-align-center.js b/test/fixtures/core.scale/label-align-center.js
new file mode 100644 (file)
index 0000000..1e9907b
--- /dev/null
@@ -0,0 +1,34 @@
+module.exports = {
+       config: {
+               type: 'line',
+               data: {
+                       datasets: [{
+                               data: [1, 2, 3],
+                       }],
+                       labels: ['Label1', 'Label2', 'Label3']
+               },
+               options: {
+                       legend: false,
+                       title: false,
+                       scales: {
+                               x: {
+                                       ticks: {
+                                               alignment: 'center',
+                                       },
+                               },
+                               y: {
+                                       ticks: {
+                                               alignment: 'center',
+                                       }
+                               }
+                       }
+               }
+       },
+       options: {
+               spriteText: true,
+               canvas: {
+                       height: 256,
+                       width: 512
+               }
+       }
+};
diff --git a/test/fixtures/core.scale/label-align-center.png b/test/fixtures/core.scale/label-align-center.png
new file mode 100644 (file)
index 0000000..576f025
Binary files /dev/null and b/test/fixtures/core.scale/label-align-center.png differ
diff --git a/test/fixtures/core.scale/label-align-end.js b/test/fixtures/core.scale/label-align-end.js
new file mode 100644 (file)
index 0000000..19a0e8a
--- /dev/null
@@ -0,0 +1,34 @@
+module.exports = {
+       config: {
+               type: 'line',
+               data: {
+                       datasets: [{
+                               data: [1, 2, 3],
+                       }],
+                       labels: ['Label1', 'Label2', 'Label3']
+               },
+               options: {
+                       legend: false,
+                       title: false,
+                       scales: {
+                               x: {
+                                       ticks: {
+                                               alignment: 'end',
+                                       },
+                               },
+                               y: {
+                                       ticks: {
+                                               alignment: 'end',
+                                       }
+                               }
+                       }
+               }
+       },
+       options: {
+               spriteText: true,
+               canvas: {
+                       height: 256,
+                       width: 512
+               }
+       }
+};
diff --git a/test/fixtures/core.scale/label-align-end.png b/test/fixtures/core.scale/label-align-end.png
new file mode 100644 (file)
index 0000000..13d72d2
Binary files /dev/null and b/test/fixtures/core.scale/label-align-end.png differ
diff --git a/test/fixtures/core.scale/label-align-start.js b/test/fixtures/core.scale/label-align-start.js
new file mode 100644 (file)
index 0000000..b5284cd
--- /dev/null
@@ -0,0 +1,34 @@
+module.exports = {
+       config: {
+               type: 'line',
+               data: {
+                       datasets: [{
+                               data: [1, 2, 3],
+                       }],
+                       labels: ['Label1', 'Label2', 'Label3']
+               },
+               options: {
+                       legend: false,
+                       title: false,
+                       scales: {
+                               x: {
+                                       ticks: {
+                                               alignment: 'start',
+                                       },
+                               },
+                               y: {
+                                       ticks: {
+                                               alignment: 'start',
+                                       }
+                               }
+                       }
+               }
+       },
+       options: {
+               spriteText: true,
+               canvas: {
+                       height: 256,
+                       width: 512
+               }
+       }
+};
diff --git a/test/fixtures/core.scale/label-align-start.png b/test/fixtures/core.scale/label-align-start.png
new file mode 100644 (file)
index 0000000..ad7c280
Binary files /dev/null and b/test/fixtures/core.scale/label-align-start.png differ
index 68211674392bfd52f13ed815743f6db6c5389ad0..fe19273c63a62472584d3893c11cd41ab9fe3724 100644 (file)
@@ -128,6 +128,11 @@ export interface ICartesianScaleOptions extends ICoreScaleOptions {
      * @default ticks.length
      */
     sampleSize: number;
+    /**
+     * The label alignment
+     * @default 'center'
+     */
+    alignment: 'start' | 'center' | 'end';
     /**
      *         If true, automatically calculates how many labels can be shown and hides labels accordingly. Labels will be rotated up to maxRotation before skipping any. Turn autoSkip off to show all labels no matter what.
      * @default true