Пример линейной диаграммы взаимодействия шкалы Protovis
Я следую примеру взаимодействия с масштабами @ http://mbostock.github.com/protovis/docs/invert.html где я пытаюсь нарисовать 2-рядную линейную диаграмму.
Мой файл JSON выглядит следующим образом:
var psSeriesData =
[{"Dates":["1-10","2-10","3-10","4-10","5-10","6-10","7-10","8-10"],"ScoresForA":
[78.78,79.79,78.78,78.78,78.78,79.79,79.79,76.92],"ScoresForB":
[78.78,79.79,78.78,78.78,78.78,79.79,79.79,76.92]}]
Я намереваюсь построить ось X, используя Dates и 2-х линейную диаграмму, используя ScoresForA и ScoresForB соответственно, но я не совсем понимаю, как это сделать после долгих изменений.
Мой код выглядит следующим образом:
var data = pv.range(2).map(function(i) {
return pv.range(0, 10, .1).map(function(x) {
return {x: psSeriesData.Dates, y: psSeriesData.ScoresForA,ScoresForB };
});
});
/* Chart dimensions and scales. */
var w = 400,
h = 200,
x = pv.Scale.linear(0, 9.9).range(0, w),
y = pv.Scale.linear(0, 10).range(0, h),
i = -1;
/* The root panel. */
var vis = new pv.Panel()
.width(w)
.height(h)
.bottom(20)
.left(20)
.right(10)
.top(5);
/* Y-ticks. */
vis.add(pv.Rule)
.data(pv.range(100))
.visible(function() !(this.index % 2))
.bottom(function(d) Math.round(y(d)) - .5)
.strokeStyle(function(d) d ? "#eee" : "#000")
.anchor("left").add(pv.Label)
.text(function(d) (d * 10).toFixed(0) );
/* X-ticks. */
vis.add(pv.Rule)
.data(x.ticks())
.visible(function(d) d > 0)
.left(function(d) Math.round(x(d)) - .5)
.strokeStyle(function(d) d ? "#eee" : "#000")
.anchor("bottom").add(pv.Label)
.text(function(d) d.toFixed());
/* A panel for each data series. */
var panel = vis.add(pv.Panel)
.data(data);
/* The line. */
var line = panel.add(pv.Line)
.data(function(d) d)
.left(function(d) x(d.x))
.bottom(function(d) y(d.y))
.lineWidth(3);
/* The mouseover dots and label. */
line.add(pv.Dot)
.visible(function() i >= 0)
.data(function(d) [d[i]])
.fillStyle(function() line.strokeStyle())
.strokeStyle("#000")
.size(20)
.lineWidth(1)
.add(pv.Dot)
.left(10)
.bottom(function() this.parent.index * 12 + 10)
.anchor("right").add(pv.Label)
.text(function(d) (d.y * 10).toFixed(5));
/* An invisible bar to capture events (without flickering). */
vis.add(pv.Bar)
.fillStyle("rgba(0,0,0,.001)")
.event("mouseout", function() {
i = -1;
return vis;
})
.event("mousemove", function() {
var mx = x.invert(vis.mouse().x);
i = pv.search(data[0].map(function(d) d.x), mx);
i = i < 0 ? (-i - 2) : i;
return vis;
});
vis.render();
Что я делаю неправильно?
После того, как входные данные были даны nrabinowitz:
var psSeriesData = {
"Dates": ["1/10","2/10","3/10","4/10","5/10","6/10","7/10","8/10"],
"ScoresForA": [78.78,79.79,78.78,78.78,78.78,79.79,79.79,76.92],
"ScoresForB": [78.78,79.79,78.78,78.78,78.78,79.79,79.79,76.92]
};
// start by iterating over the two keys for your time series data
var data = ["ScoresForA","ScoresForB"].map(function(seriesKey) {
// use pv.range to walk through the indexes of the
// date array (basically the same as a for loop)
return pv.range(0, psSeriesData.Dates.length)
// map these indexes to an array of objects
.map(function(dateIndex) {
// now return an object with the date index
// and series value for that index
return {
x: dateIndex,
y: psSeriesData[seriesKey][dateIndex]
}
});
});
/* Chart dimensions and scales. */
var w = 400,
h = 200,
x = pv.Scale.linear(0, 9.9).range(0, w),
y = pv.Scale.linear(0, 10).range(0, h),
i = -1;
/* The root panel. */
var vis = new pv.Panel()
.width(w)
.height(h)
.bottom(20)
.left(20)
.right(10)
.top(5);
/* Y-ticks. */
vis.add(pv.Rule)
.data(pv.range(100))
.visible(function() !(this.index % 2))
.bottom(function(d) Math.round(y(d)) - .5)
.strokeStyle(function(d) d ? "#eee" : "#000")
.anchor("left").add(pv.Label)
.text(function(d) (d * 10).toFixed(0) );
/* X-ticks. */
vis.add(pv.Rule)
//.data(function(d) [d[i].Dates])
.data(pv.range(0, psSeriesData.Dates.length).map(function(a) (psSeriesData[a].Dates)))
.visible(function(d) d > 0)
.left(function(d) Math.round(x(d)) - .5)
.strokeStyle(function(d) d ? "#eee" : "#000")
.anchor("bottom").add(pv.Label)
.text(function(d) (d).toFixed());
/* A panel for each data series. */
var panel = vis.add(pv.Panel)
.data(data);
/* The line. */
var line = panel.add(pv.Line)
.data(function(d) d)
.left(function(d) x(d.x))
.bottom(function(d) y(d.y))
.lineWidth(3);
/* The mouseover dots and label. */
line.add(pv.Dot)
.visible(function() i >= 0)
.data(function(d) [d[i]])
.fillStyle(function() line.strokeStyle())
.strokeStyle("#000")
.size(20)
.lineWidth(1)
.add(pv.Dot)
.left(10)
.bottom(function() this.parent.index * 12 + 10)
.anchor("right").add(pv.Label)
.text(function(d) (d.y ).toFixed(5));
/* An invisible bar to capture events (without flickering). */
vis.add(pv.Bar)
.fillStyle("rgba(0,0,0,.001)")
.event("mouseout", function() {
i = -1;
return vis;
})
.event("mousemove", function() {
var mx = x.invert(vis.mouse().x);
i = pv.search(data[0].map(function(d) d.x), mx);
i = i < 0 ? (-i - 2) : i;
return vis;
});
vis.render();
Даты по-прежнему не отображаются в виде оси X, хотя я использовал функцию карты и ссылки на массив. Кажется, есть проблема с чтением свойства "Даты". Любые советы
Ошибка: TypeError: Невозможно прочитать свойство 'Dates' из неопределенного
1 ответ
Первое, что нужно сделать при работе с такой визуализацией (особенно при следовании примерам Protovis), - убедиться, что ваши данные находятся в нужном вам формате. Я не рассмотрел весь ваш код здесь, но у вас есть некоторые явные проблемы с данными сразу:
Почему ваши начальные данные в массиве? Есть ли какая-либо причина для включения в нее прямых прямых скобок (т. Е. Внешних скобок в
psSeriesData = [{ ... }]
)? Нет никаких причин для этого, что я могу видеть в коде, как вы его представили, и это только запутает вещи (например,psSeriesData.Dates
не определено - вам нужно сослатьсяpsSeriesData[0].Dates
).Я не совсем понимаю, что вы делаете в исходном коде настройки данных, но я почти уверен, что он не дает вам то, что вы хотите - это похоже на слепую вырезку и вставку из примера, даже хотя это не относится. Пример использует
pv.range
для создания поддельных данных - вам это не нужно, у вас есть реальные данные, и вы можете пройти через это.
Лучший способ начать здесь - понять, как должны выглядеть данные. В примере данные создаются следующим образом:
data = pv.range(3).map(function(i) {
return pv.range(0, 10, .1).map(function(x) {
return {x: x, y: i + Math.sin(x) + Math.random() * .5 + 2};
});
});
Запустите это в консоли, и вы увидите, что полученные данные выглядят так:
[
[
{
x: 0.1,
y: 2.34
},
// ...
],
// ...
]
Внешний массив содержит различные временные ряды; каждый временной ряд представляет собой массив объектов, таких как {x:0.1, y:2.34}
, Если ваши данные не выглядят так, они не будут работать с примером кода.
Ваши исходные данные должны хорошо работать для этого, но вам нужно будет привести их в правильный формат. Одной из проблем здесь является список дат - это строки, а не числа, и вы не сможете использовать их в качестве данных, если не конвертируете их в Date
объекты (это реальная боль, избегайте ее, если это возможно) или сопоставьте их с числами - последнее здесь легко, потому что они в регулярной серии. (Если бы у вас были неравномерно распределенные даты, все это было бы более сложным, но давайте пока что об этом забудем.) Вы можете просто использовать индекс дат в качестве x
значения, а затем использовать ваши две серии в качестве y
ценности.
Собрав все это вместе, вы можете отформатировать ваши данные следующим образом:
// note - no enclosing array
var psSeriesData = {
"Dates": ["1-10","2-10","3-10","4-10","5-10","6-10","7-10", "8-10"],
"ScoresForA": [78.78,79.79,78.78,78.78,78.78,79.79,79.79,76.92],
"ScoresForB": [78.78,79.79,78.78,78.78,78.78,79.79,79.79,76.92]
};
// start by iterating over the two keys for your time series data
var data = ["ScoresForA","ScoresForB"].map(function(seriesKey) {
// use pv.range to walk through the indexes of the
// date array (basically the same as a for loop)
return pv.range(0, psSeriesData.Dates.length)
// map these indexes to an array of objects
.map(function(dateIndex) {
// now return an object with the date index
// and series value for that index
return {
x: dateIndex,
y: psSeriesData[seriesKey][dateIndex]
}
});
});
Есть много других способов сделать это, но главное в том, что у вас получится такой массив: [[{x:0, y:79.79}, ...], ...]
, Я не смотрел на остальную часть вашего кода, но теперь, когда ваши данные в правильном формате, вы сможете заменить поддельные данные в примере реальными данными в вашем коде, и все это будет работать как ожидаемый (хотя вам нужно изменить любые предположения в примере об ожидаемых максимальных и минимальных значениях для x
а также y
).