ronantakizawa/codereview-bench huggingface dataset:
Repository: chartjs/Chart.js
File: src/core/core.scale.js (JavaScript)

Code before:
/* eslint-disable */
import Element from './core.element';
import {_alignPixel, _measureText, renderText, clipArea, unclipArea} from '../helpers/helpers.canvas';
import {callback as call, each, finiteOrDefault, isArray, isFinite, isNullOrUndef, isObject, valueOrDefault} from '../helpers/helpers.core';
import {toDegrees, toRadians, _int16Range, _limitValue, HALF_PI} from '../helpers/helpers.math';
import {_alignStartEnd, _toLeftRightCenter} from '../helpers/helpers.extras';
import {createContext, toFont, toPadding, _addGrace} from '../helpers/helpers.options';
import {autoSkip} from './core.scale.autoskip';

const reverseAlign = (align) => align === 'left' ? 'right' : align === 'right' ? 'left' : align;
const offsetFromEdge = (scale, edge, offset) => edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset;

/**
 * @typedef { import("./core.controller").default } Chart
 * @typedef {{value:number | string, label?:string, major?:boolean, $context?:any}} Tick
 */

/**
 * Returns a new array containing numItems from arr
 * @param {any[]} arr
 * @param {number} numItems
 */
function sample(arr, numItems) {
  const result = [];
  const increment = arr.length / numItems;
  const len = arr.length;

Review comment (suggestion):
Why would you disable eslint for this file, you should resolve the error instead 

Code after:
import Element from './core.element';
import {_alignPixel, _measureText, renderText, clipArea, unclipArea} from '../helpers/helpers.canvas';
import {callback as call, each, finiteOrDefault, isArray, isFinite, isNullOrUndef, isObject, valueOrDefault} from '../helpers/helpers.core';
import {toDegrees, toRadians, _int16Range, _limitValue, HALF_PI} from '../helpers/helpers.math';
import {_alignStartEnd, _toLeftRightCenter} from '../helpers/helpers.extras';
import {createContext, toFont, toPadding, _addGrace} from '../helpers/helpers.options';
import {autoSkip} from './core.scale.autoskip';

const reverseAlign = (align) => align === 'left' ? 'right' : align === 'right' ? 'left' : align;
const offsetFromEdge = (scale, edge, offset) => edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset;

/**
 * @typedef { import("./core.controller").default } Chart
 * @typedef {{value:number | string, label?:string, major?:boolean, $context?:any}} Tick
 */

/**
 * Returns a new array containing numItems from arr
 * @param {any[]} arr
 * @param {number} numItems
 */
function sample(arr, numItems) {
  const result = [];
  const increment = arr.length / numItems;
  const len = arr.length;
  let i = 0;
Repository: chartjs/Chart.js
File: src/plugins/plugin.colors.ts (TypeScript)

Code before:
      return true;
    }
  }

  return false;
}

export default {
  id: 'colors',

  defaults: {
    enabled: true,
    forceColorsPlugin: false
  },

  beforeDatasetsUpdate(chart: Chart, _args, options: ColorsPluginOptions) {
    if (!options.enabled) {
      return;
    }

    const {
      options: {elements},
      data: {datasets}
    } = chart.config;

    if ((containsColorsDefinitions(datasets) || elements && containsColorsDefinitions(elements)) && !options.forceColorsPlugin) {
      return;
    }

    const colorizer = getColorizer(chart);

    datasets.forEach(colorizer);
  }
};


Review comment (suggestion):
```suggestion
    if (!options.forceColorsPlugin && (containsColorsDefinitions(datasets) || elements && containsColorsDefinitions(elements))) {
```

to do not run `containsColorsDefinitions` and `containsColorsDefinitions` if the option is `true` 

Code after:
      return true;
    }
  }

  return false;
}

export default {
  id: 'colors',

  defaults: {
    enabled: true,
    forceOverride: false
  } as ColorsPluginOptions,

  beforeLayout(chart: Chart, _args, options: ColorsPluginOptions) {
    if (!options.enabled) {
      return;
    }

    const {
      options: {elements},
      data: {datasets}
    } = chart.config;

    if (!options.forceOverride && (containsColorsDefinitions(datasets) || elements && containsColorsDefinitions(elements))) {
      return;
    }

    const colorizer = getColorizer(chart);

    datasets.forEach(colorizer);
  }
};
Repository: chartjs/Chart.js
File: src/plugins/plugin.colors.ts (TypeScript)

Code before:
import {DoughnutController, PolarAreaController} from '../index.js';
import type {Chart, ChartDataset} from '../types.js';

export interface ColorsPluginOptions {
  enabled?: boolean;
  forceColorsPlugin: boolean;
}

interface ColorsDescriptor {
  backgroundColor?: unknown;
  borderColor?: unknown;
}

const BORDER_COLORS = [
  'rgb(54, 162, 235)', // blue
  'rgb(255, 99, 132)', // red
  'rgb(255, 159, 64)', // orange
  'rgb(255, 205, 86)', // yellow
  'rgb(75, 192, 192)', // green
  'rgb(153, 102, 255)', // purple
  'rgb(201, 203, 207)' // grey
];

// Border colors with 50% transparency
const BACKGROUND_COLORS = /* #__PURE__ */ BORDER_COLORS.map(color => color.replace('rgb(', 'rgba(').replace(')', ', 0.5)'));

function getBorderColor(i: number) {
  return BORDER_COLORS[i % BORDER_COLORS.length];
}

function getBackgroundColor(i: number) {

Review comment (suggestion):
```suggestion
  forceColorsPlugin?: boolean;
```

Code after:
import {DoughnutController, PolarAreaController} from '../index.js';
import type {Chart, ChartDataset} from '../types.js';

export interface ColorsPluginOptions {
  enabled?: boolean;
  forceOverride?: boolean;
}

interface ColorsDescriptor {
  backgroundColor?: unknown;
  borderColor?: unknown;
}

const BORDER_COLORS = [
  'rgb(54, 162, 235)', // blue
  'rgb(255, 99, 132)', // red
  'rgb(255, 159, 64)', // orange
  'rgb(255, 205, 86)', // yellow
  'rgb(75, 192, 192)', // green
  'rgb(153, 102, 255)', // purple
  'rgb(201, 203, 207)' // grey
];

// Border colors with 50% transparency
const BACKGROUND_COLORS = /* #__PURE__ */ BORDER_COLORS.map(color => color.replace('rgb(', 'rgba(').replace(')', ', 0.5)'));

function getBorderColor(i: number) {
  return BORDER_COLORS[i % BORDER_COLORS.length];
}

function getBackgroundColor(i: number) {
Repository: chartjs/Chart.js
File: src/plugins/plugin.colors.ts (TypeScript)

Code before:
}

function containsColorsDefinitions(
  descriptors: ColorsDescriptor[] | Record<string, ColorsDescriptor>
) {
  let k: number | string;

  for (k in descriptors) {
    if (descriptors[k].borderColor || descriptors[k].backgroundColor) {
      return true;
    }
  }

  return false;
}

function containsColorsDefinition(
  descriptor: ColorsDescriptor
) {
  if (descriptor && descriptor.borderColor || descriptor.backgroundColor) {
    return true;
  }

  return false;
}

export default {
  id: 'colors',

  defaults: {
    enabled: true,
    forceOverride: false
  } as ColorsPluginOptions,

  beforeLayout(chart: Chart, _args, options: ColorsPluginOptions) {
    if (!options.enabled) {
      return;
    }

    const {
      options: {elements},
      data: {datasets}
    } = chart.config;

    if (!options.forceOverride && (containsColorsDefinitions(datasets) || containsColorsDefinition(chart.config.options) || (elements && containsColorsDefinitions(elements)))) {
      return;
    }

    const colorizer = getColorizer(chart);

    datasets.forEach(colorizer);

Review comment (suggestion):
```suggestion
function containsColorsDefinition(
  descriptor: ColorsDescriptor
) {
  return descriptor && (descriptor.borderColor || descriptor.backgroundColor)
}

```

Code after:
}

function containsColorsDefinitions(
  descriptors: ColorsDescriptor[] | Record<string, ColorsDescriptor>
) {
  let k: number | string;

  for (k in descriptors) {
    if (descriptors[k].borderColor || descriptors[k].backgroundColor) {
      return true;
    }
  }

  return false;
}

function containsColorsDefinition(
  descriptor: ColorsDescriptor
) {
  return descriptor && (descriptor.borderColor || descriptor.backgroundColor);
}

export default {
  id: 'colors',

  defaults: {
    enabled: true,
    forceOverride: false
  } as ColorsPluginOptions,

  beforeLayout(chart: Chart, _args, options: ColorsPluginOptions) {
    if (!options.enabled) {
      return;
    }

    const {
      data: {datasets},
      options: chartOptions
    } = chart.config;
    const {elements} = chartOptions;

    if (!options.forceOverride && (containsColorsDefinitions(datasets) || containsColorsDefinition(chartOptions) || (elements && containsColorsDefinitions(elements)))) {
      return;
    }

    const colorizer = getColorizer(chart);

    datasets.forEach(colorizer);
  }
};
Repository: chartjs/Chart.js
File: src/plugins/plugin.colors.ts (TypeScript)

Code before:
  descriptor: ColorsDescriptor
) {
  if (descriptor && descriptor.borderColor || descriptor.backgroundColor) {
    return true;
  }

  return false;
}

export default {
  id: 'colors',

  defaults: {
    enabled: true,
    forceOverride: false
  } as ColorsPluginOptions,

  beforeLayout(chart: Chart, _args, options: ColorsPluginOptions) {
    if (!options.enabled) {
      return;
    }

    const {
      options: {elements},
      data: {datasets}
    } = chart.config;

    if (!options.forceOverride && (containsColorsDefinitions(datasets) || containsColorsDefinition(chart.config.options) || (elements && containsColorsDefinitions(elements)))) {
      return;
    }

    const colorizer = getColorizer(chart);

    datasets.forEach(colorizer);
  }
};


Review comment (suggestion):
```suggestion
      data: {datasets},
      options
    } = chart.config;
    const {elements} = options
```

Code after:
  descriptor: ColorsDescriptor
) {
  return descriptor && (descriptor.borderColor || descriptor.backgroundColor);
}

export default {
  id: 'colors',

  defaults: {
    enabled: true,
    forceOverride: false
  } as ColorsPluginOptions,

  beforeLayout(chart: Chart, _args, options: ColorsPluginOptions) {
    if (!options.enabled) {
      return;
    }

    const {
      data: {datasets},
      options: chartOptions
    } = chart.config;
    const {elements} = chartOptions;

    if (!options.forceOverride && (containsColorsDefinitions(datasets) || containsColorsDefinition(chartOptions) || (elements && containsColorsDefinitions(elements)))) {
      return;
    }

    const colorizer = getColorizer(chart);

    datasets.forEach(colorizer);
  }
};
Repository: chartjs/Chart.js
File: src/plugins/plugin.colors.ts (TypeScript)

Code before:
  if (descriptor && descriptor.borderColor || descriptor.backgroundColor) {
    return true;
  }

  return false;
}

export default {
  id: 'colors',

  defaults: {
    enabled: true,
    forceOverride: false
  } as ColorsPluginOptions,

  beforeLayout(chart: Chart, _args, options: ColorsPluginOptions) {
    if (!options.enabled) {
      return;
    }

    const {
      options: {elements},
      data: {datasets}
    } = chart.config;

    if (!options.forceOverride && (containsColorsDefinitions(datasets) || containsColorsDefinition(chart.config.options) || (elements && containsColorsDefinitions(elements)))) {
      return;
    }

    const colorizer = getColorizer(chart);

    datasets.forEach(colorizer);
  }
};


Review comment (suggestion):
```suggestion
    if (!options.forceOverride && (containsColorsDefinitions(datasets) || containsColorsDefinition(options) || (elements && containsColorsDefinitions(elements)))) {
```

Code after:
  return descriptor && (descriptor.borderColor || descriptor.backgroundColor);
}

export default {
  id: 'colors',

  defaults: {
    enabled: true,
    forceOverride: false
  } as ColorsPluginOptions,

  beforeLayout(chart: Chart, _args, options: ColorsPluginOptions) {
    if (!options.enabled) {
      return;
    }

    const {
      data: {datasets},
      options: chartOptions
    } = chart.config;
    const {elements} = chartOptions;

    if (!options.forceOverride && (containsColorsDefinitions(datasets) || containsColorsDefinition(chartOptions) || (elements && containsColorsDefinitions(elements)))) {
      return;
    }

    const colorizer = getColorizer(chart);

    datasets.forEach(colorizer);
  }
};
Repository: chartjs/Chart.js
File: src/helpers/helpers.config.ts (TypeScript)

Code before:
      proxy[prop] = value; // set to proxy
      delete target[prop]; // remove from cache
      return true;
    }
  }) as ContextProxy<T, R>;
}

/**
 * @private
 */
export function _descriptors(
  proxy: ResolverCache,
  defaults: DescriptorDefaults = {scriptable: true, indexable: true}
): Descriptor {
  const {_scriptable = defaults.scriptable, _indexable = defaults.indexable, _allKeys = defaults.allKeys} = proxy;
  return {
    allKeys: _allKeys,
    scriptable: _scriptable,
    indexable: _indexable,
    isScriptable: isFunction(_scriptable) ? _scriptable : () => _scriptable,
    isIndexable: isFunction(_indexable) ? _indexable : () => _indexable
  };
}

const readKey = (prefix: string, name: string) => prefix ? prefix + _capitalize(name) : name;
const needsSubResolver = (prop: string, value: any) => isObject(value) && prop !== 'adapters' &&
  (Object.getPrototypeOf(value) === null || value.constructor === Object);

function _cached(
  target: AnyObject,
  prop: string,
  resolve: () => any
) {
  if (Object.prototype.hasOwnProperty.call(target, prop)) {
    return target[prop];
  }

  const value = resolve();
  // cache the resolved value
  target[prop] = value;
  return value;
}

function _resolveWithContext(
  target: ContextCache,
  prop: string,
  receiver: AnyObject
) {
  const {_proxy, _context, _subProxy, _descriptors: descriptors} = target;
  let value = _proxy[prop]; // resolve from proxy


Review comment (suggestion):
A thought here. Is `unknown` better than `any`? I recall that `any` disables some TS features whereas `unknown` does not

Code after:
      proxy[prop] = value; // set to proxy
      delete target[prop]; // remove from cache
      return true;
    }
  }) as ContextProxy<T, R>;
}

/**
 * @private
 */
export function _descriptors(
  proxy: ResolverCache,
  defaults: DescriptorDefaults = {scriptable: true, indexable: true}
): Descriptor {
  const {_scriptable = defaults.scriptable, _indexable = defaults.indexable, _allKeys = defaults.allKeys} = proxy;
  return {
    allKeys: _allKeys,
    scriptable: _scriptable,
    indexable: _indexable,
    isScriptable: isFunction(_scriptable) ? _scriptable : () => _scriptable,
    isIndexable: isFunction(_indexable) ? _indexable : () => _indexable
  };
}

const readKey = (prefix: string, name: string) => prefix ? prefix + _capitalize(name) : name;
const needsSubResolver = (prop: string, value: unknown) => isObject(value) && prop !== 'adapters' &&
  (Object.getPrototypeOf(value) === null || value.constructor === Object);

function _cached(
  target: AnyObject,
  prop: string,
  resolve: () => unknown
) {
  if (Object.prototype.hasOwnProperty.call(target, prop)) {
    return target[prop];
  }

  const value = resolve();
  // cache the resolved value
  target[prop] = value;
  return value;
}

function _resolveWithContext(
  target: ContextCache,
  prop: string,
  receiver: AnyObject
) {
  const {_proxy, _context, _subProxy, _descriptors: descriptors} = target;
  let value = _proxy[prop]; // resolve from proxy
Repository: chartjs/Chart.js
File: src/core/core.config.js (JavaScript)

Code before:
  }
  return axis;
}

function getDefaultScaleIDFromAxis(axis, indexAxis) {
  return axis === indexAxis ? '_index_' : '_value_';
}

function axisFromId(id) {
  if (id === 'x' || id === 'y' || id === 'r') {
    return id;
  }
}

function axisFromPosition(position) {
  if (position === 'top' || position === 'bottom') {
    return 'x';
  }
  if (position === 'left' || position === 'right') {
    return 'y';
  }
}

export function determineAxis(id, ...scaleOptions) {
  if (axisFromId(id)) {
    return id;
  }
  for (let i = 0, n = scaleOptions.length; i < n; i++) {
    const opts = scaleOptions[i];
    const axis = opts.axis
      || axisFromPosition(opts.position)
      || id.length > 1 && axisFromId(id[0].toLowerCase());
    if (axis) {
      return axis;
    }
  }
  throw new Error(`Cannot determine type of '${id}' axis. Please provide 'axis' or 'position' option.`);
}

function getAxisFromDataset(id, axis, dataset) {
  if (dataset[axis + 'AxisID'] === id) {
    return {axis};
  }
}

function retrieveAxisFromDatasets(id, config) {
  if (config.data && config.data.datasets) {
    const boundDs = config.data.datasets.filter((d) => d.xAxisID === id || d.yAxisID === id);
    if (boundDs.length) {
      return getAxisFromDataset(id, 'x', boundDs[0]) || getAxisFromDataset(id, 'y', boundDs[0]);
    }

Review comment (suggestion):
This might need to be the return value from `axisFromId`. If the axis id is `xAxis` the axis returned should be `x`

Code after:
  }
  return axis;
}

function getDefaultScaleIDFromAxis(axis, indexAxis) {
  return axis === indexAxis ? '_index_' : '_value_';
}

function idMatchesAxis(id) {
  if (id === 'x' || id === 'y' || id === 'r') {
    return id;
  }
}

function axisFromPosition(position) {
  if (position === 'top' || position === 'bottom') {
    return 'x';
  }
  if (position === 'left' || position === 'right') {
    return 'y';
  }
}

export function determineAxis(id, ...scaleOptions) {
  if (idMatchesAxis(id)) {
    return id;
  }
  for (const opts of scaleOptions) {
    const axis = opts.axis
      || axisFromPosition(opts.position)
      || id.length > 1 && idMatchesAxis(id[0].toLowerCase());
    if (axis) {
      return axis;
    }
  }
  throw new Error(`Cannot determine type of '${id}' axis. Please provide 'axis' or 'position' option.`);
}

function getAxisFromDataset(id, axis, dataset) {
  if (dataset[axis + 'AxisID'] === id) {
    return {axis};
  }
}

function retrieveAxisFromDatasets(id, config) {
  if (config.data && config.data.datasets) {
    const boundDs = config.data.datasets.filter((d) => d.xAxisID === id || d.yAxisID === id);
    if (boundDs.length) {
      return getAxisFromDataset(id, 'x', boundDs[0]) || getAxisFromDataset(id, 'y', boundDs[0]);
    }
  }
Repository: chartjs/Chart.js
File: src/types/index.d.ts (TypeScript)

Code before:
  /**
   * Reverse the scale.
   * @default false
   */
  reverse: boolean;
  /**
   * The weight used to sort the axis. Higher weights are further away from the chart area.
   * @default true
   */
  weight: number;
  /**
   * Callback called before the update process starts.
   */
  beforeUpdate(axis: Scale): void;
  /**
   * Callback that runs before dimensions are set.
   */
  beforeSetDimensions(axis: Scale): void;
  /**
   * Callback that runs after dimensions are set.
   */
  afterSetDimensions(axis: Scale): void;
  /**
   * Callback that runs before data limits are determined.
   */
  beforeDataLimits(axis: Scale): void;
  /**
   * Callback that runs after data limits are determined.
   */
  afterDataLimits(axis: Scale): void;
  /**
   * Callback that runs before ticks are created.
   */
  beforeBuildTicks(axis: Scale): void;
  /**
   * Callback that runs after ticks are created. Useful for filtering ticks.
   */
  afterBuildTicks(axis: Scale): void;
  /**
   * Callback that runs before ticks are converted into strings.
   */
  beforeTickToLabelConversion(axis: Scale): void;
  /**
   * Callback that runs after ticks are converted into strings.
   */
  afterTickToLabelConversion(axis: Scale): void;
  /**
   * Callback that runs before tick rotation is determined.
   */
  beforeCalculateLabelRotation(axis: Scale): void;
  /**

Review comment (suggestion):
Thought this does not work with extending the scale since the props are defined here it does not take them from the specifics anymore so they will have to be ommited from every other override if I remember correctly.

Same got done in the `CategoryScaleOptions`

Code after:
  /**
   * Reverse the scale.
   * @default false
   */
  reverse: boolean;
  /**
   * The weight used to sort the axis. Higher weights are further away from the chart area.
   * @default true
   */
  weight: number;
  /**
   * User defined minimum value for the scale, overrides minimum value from data.
   */
  min: unknown;
  /**
   * User defined maximum value for the scale, overrides maximum value from data.
   */
  max: unknown;
  /**
   * Adjustment used when calculating the maximum data value.
   */
  suggestedMin: unknown;
  /**
   * Adjustment used when calculating the minimum data value.
   */
  suggestedMax: unknown;
  /**
   * Callback called before the update process starts.
   */
  beforeUpdate(axis: Scale): void;
  /**
   * Callback that runs before dimensions are set.
   */
  beforeSetDimensions(axis: Scale): void;
  /**
   * Callback that runs after dimensions are set.
   */
  afterSetDimensions(axis: Scale): void;
  /**
   * Callback that runs before data limits are determined.
   */
  beforeDataLimits(axis: Scale): void;
  /**
   * Callback that runs after data limits are determined.
   */
  afterDataLimits(axis: Scale): void;
  /**
   * Callback that runs before ticks are created.
   */
  beforeBuildTicks(axis: Scale): void;
  /**
Repository: chartjs/Chart.js
File: src/core/core.scale.js (JavaScript)

Code before:
    const padding = toPadding(title.padding);
    const align = title.align;
    let offset = font.lineHeight / 2;
    let strokeWidth = title.strokeWidth;
    let strokeColor = title.strokeColor;

    if (position === 'bottom' || position === 'center' || isObject(position)) {
      offset += padding.bottom;
      if (isArray(title.text)) {
        offset += font.lineHeight * (title.text.length - 1);
      }
    } else {
      offset += padding.top;
    }

    const {titleX, titleY, maxWidth, rotation} = titleArgs(this, offset, position, align);

    renderText(ctx, title.text, 0, 0, font, {
      color: title.color,
      maxWidth,
      rotation,
      textAlign: titleAlign(align, position, reverse),
      textBaseline: 'middle',
      translation: [titleX, titleY],
      strokeColor: strokeColor,
      strokeWidth: strokeWidth
    });
  }

  draw(chartArea) {
    if (!this._isVisible()) {
      return;
    }

    this.drawBackground();
    this.drawGrid(chartArea);
    this.drawBorder();
    this.drawTitle();
    this.drawLabels(chartArea);
  }

  /**
	 * @return {object[]}
	 * @private
	 */
  _layers() {
    const opts = this.options;
    const tz = opts.ticks && opts.ticks.z || 0;
    const gz = valueOrDefault(opts.grid && opts.grid.z, -1);
    const bz = valueOrDefault(opts.border && opts.border.z, 0);


Review comment (suggestion):
Since we only use it here it is not necesarry to put it in a separate variable.
```suggestion
      strokeColor: title.strokeColor,
      strokeWidth: title.strokeWidth
```

Code after:
    const padding = toPadding(title.padding);
    const align = title.align;
    let offset = font.lineHeight / 2;

    if (position === 'bottom' || position === 'center' || isObject(position)) {
      offset += padding.bottom;
      if (isArray(title.text)) {
        offset += font.lineHeight * (title.text.length - 1);
      }
    } else {
      offset += padding.top;
    }

    const {titleX, titleY, maxWidth, rotation} = titleArgs(this, offset, position, align);

    renderText(ctx, title.text, 0, 0, font, {
      color: title.color,
      maxWidth,
      rotation,
      textAlign: titleAlign(align, position, reverse),
      textBaseline: 'middle',
      translation: [titleX, titleY],
      strokeColor: title.strokeColor,
      strokeWidth: title.strokeWidth
    });
  }

  draw(chartArea) {
    if (!this._isVisible()) {
      return;
    }

    this.drawBackground();
    this.drawGrid(chartArea);
    this.drawBorder();
    this.drawTitle();
    this.drawLabels(chartArea);
  }

  /**
	 * @return {object[]}
	 * @private
	 */
  _layers() {
    const opts = this.options;
    const tz = opts.ticks && opts.ticks.z || 0;
    const gz = valueOrDefault(opts.grid && opts.grid.z, -1);
    const bz = valueOrDefault(opts.border && opts.border.z, 0);

    if (!this._isVisible() || this.draw !== Scale.prototype.draw) {
      // backward compatibility: draw has been overridden by custom scale