Подтвердить что ты не робот

Вычисление длины сегмента в логарифмической шкале

Я хочу рассчитать длину строки для ряда событий.

Я делаю это со следующим кодом.

var maxLineLength = 20;
var lineLen = function(x, max) {
    return maxLineLength * (x / max);
}
var events = [0.1, 1, 5, 20, 50];
var max = Math.max.apply(null, events);
events.map(function (x) {
    console.log(lineLen(x, max));
});
4b9b3361

Ответ 1

Вы можете взять log(x+1) вместо log(x), который не слишком сильно меняет значение, а отношения поддерживаются для меньших чисел.

var maxLineLength = 20;
var lineLen = (x, max) => maxLineLength * Math.log(x+1)/Math.log(max+1);
var events = [ 0.1, 1, 5, 20, 50];
var visualizer = function(events){
    var max = Math.max.apply(null, events);
    return events.reduce((y, x) => {
         y.push(lineLen(x, max));
         return y;
    }, []);
};

console.log(visualizer(events));

Ответ 2

Вы можете использовать выражение типа Math.pow(x, 0.35) вместо Math.log(x). Он сохраняет все значения положительными и дает поведение, которое вы хотите для небольших коэффициентов. Вы можете поэкспериментировать с разными показателями экспоненты в диапазоне (0,1), чтобы найти тот, который соответствует вашим потребностям.

var maxLineLength = 20;
var exponent = 0.35;
var lineLen = function(x, max) {
   return maxLineLength * Math.pow(x/max, exponent);
}
var events = [0, 0.01, 0.1, 1, 5, 20, 50];
var max = Math.max.apply(null, events);
events.map(function (x) {
    console.log(lineLen(x, max));
});

Ответ 3

Вы можете уменьшить maxLineLength и добавить один в конце вычисления.

При значениях, меньших единицы, вы можете использовать коэффициент, который нормализует все значения относительно первого значения. Начальное значение всегда одно или с точки зрения логарифмического представления, ноль.

var maxLineLength = 20,
    lineLen = function(max) {
        return function (x) {
            return (maxLineLength - 1) * Math.log(x) / Math.log(max) + 1;
        };
    },
    events = [0.1, 1, 5, 20, 50],
    normalized = events.map((v, _, a) => v / a[0]),
    max = Math.max.apply(null, normalized),
    result = normalized.map(lineLen(max));

console.log(result);
console.log(normalized);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Ответ 4

Более короткий, но не слишком короткий; длиннее, но не слишком долго.

На самом деле я встретил ту же проблему много лет назад и отказался от этого (может быть?). Я только что прочитал ваш вопрос здесь и сейчас, и я думаю, что я только что нашел решение: shifting.

const log = (base, value) => (Math.log(value) / Math.log(base));

const weights = [0, 0.1, 1, 5, 20, 50, 100];
const base = Math.E; // Setting

const shifted = weights.map(x => x + base);
const logged = shifted.map(x => log(base, x));
const unshifted = logged.map(x => x - 1);

const total = unshifted.reduce((a, b) => a + b, 0);
const ratio = unshifted.map(x => x / total);
const percents = ratio.map(x => x * 100);

console.log(percents);
// [
//   0,
//   0.35723375538333857,
//   3.097582209424984,
//   10.3192042142806,
//   20.994247877004888,
//   29.318026542735115,
//   35.91370540117108
// ]

Визуализация

Чем меньше логарифмическая база, тем больше они корректируются; и наоборот. На самом деле я не знаю причины. XD

<!DOCTYPE html>
<html>
	<head>
		<meta name="author" content="K.">

		<title>Shorter but not too short; longer but not too long.</title>

		<style>
			canvas
			{
				background-color: whitesmoke;
			}
		</style>
	</head>

	<body>
		<canvas id="canvas" height="5"></canvas>

		<label>Weights: <input id="weights" type="text" value="[0, 0.1, 100, 1, 5, 20, 2.718, 50]">.</label>
		<label>Base: <input id="base" type="number" value="2.718281828459045">.</label>
		<button id="draw" type="button">Draw</button>

		<script>
			const input = new Proxy({}, {
				get(_, thing)
				{
					return eval(document.getElementById(thing).value);
				}
			});
			const color = ["tomato", "black"];
			const canvas_element = document.getElementById("canvas");
			const canvas_context = canvas_element.getContext("2d");
			canvas_element.width = document.body.clientWidth;

			document.getElementById("draw").addEventListener("click", _ => {
				const log = (base, value) => (Math.log(value) / Math.log(base));

				const weights = input.weights;
				const base = input.base;

				const orig_total = weights.reduce((a, b) => a + b, 0);
				const orig_percents = weights.map(x => x / orig_total * 100);

				const adjusted = weights.map(x => x + base);
				const logged = adjusted.map(x => log(base, x));
				const rebased = logged.map(x => x - 1);

				const total = rebased.reduce((a, b) => a + b, 0);
				const ratio = rebased.map(x => x / total);
				const percents = ratio.map(x => x * 100);
				const result = percents.map((percent, index) => `${weights[index]} | ${orig_percents[index]}% --> ${percent}% (${color[index & 1]})`);
				console.info(result);

				let position = 0;
				ratio.forEach((rate, index) => {
					canvas_context.beginPath();
					canvas_context.moveTo(position, 0);
					position += (canvas_element.width * rate);
					canvas_context.lineTo(position, 0);
					canvas_context.lineWidth = 10;
					canvas_context.strokeStyle = color[index & 1];
					canvas_context.stroke();
				});
			});
		</script>
	</body>
</html>

Ответ 5

Вы масштабируете эти числа. Ваш начальный набор - это домен, в котором вы попадаете - это диапазон. Форма этого преобразования, по-видимому, будет либо вести журнал, либо следовать за мощностью.

Оказывается, это очень распространенная проблема во многих областях, особенно визуализация данных. Вот почему D3.js - инструмент визуализации данных - все, что вам нужно, чтобы сделать это легко.

 const x = d3.scale.log().range([0, events]);

Это право на работу. И если вам нужно сделать несколько графиков, вы все настроены!

Ответ 6

Пока ваши события равны > 0, вы можете избежать смещения путем масштабирования событий, чтобы минимальное значение было выше 1. Скаляр может быть рассчитан на основе минимальной длины строки в дополнение к максимальной длине строки, которую вы уже имеете.

// generates an array of line lengths between minLineLength and maxLineLength
// assumes events contains only values > 0 and 0 < minLineLength < maxLineLength
function generateLineLengths(events, minLineLength, maxLineLength) {
  var min = Math.min.apply(null, events);
  var max = Math.max.apply(null, events);

  //calculate scalar that sets line length for the minimum value in events to minLineLength
  var mmr = minLineLength / maxLineLength;
  var scalar = Math.pow(Math.pow(max, mmr) / min, 1 / (1 - mmr));

  function lineLength(x) {
    return maxLineLength * (Math.log(x * scalar) / Math.log(max * scalar));
  }

  return events.map(lineLength)
}

var events = [0.1, 1, 5, 20, 50];

console.log('Between 1 and 20')
generateLineLengths(events, 1, 20).forEach(function(x) {
  console.log(x)
})
// 1
// 8.039722549519123
// 12.960277450480875
// 17.19861274759562
// 20

console.log('Between 5 and 25')
generateLineLengths(events, 5, 25).forEach(function(x) {
  console.log(x)
})
// 5
// 12.410234262651711
// 17.58976573734829
// 22.05117131325855
// 25