D3.js (или просто D3) — это JavaScript-библиотека для обработки и визуализации данных с невероятно огромными возможностями. Я, когда впервые узнал про нее, наверное, потратил не менее двух часов, просто просматривая примеры визуализации данных, созданных на D3. И конечно, когда мне самому понадобилось строить графики для небольшого внутреннего сайта на нашем предприятии, первым делом вспомнил про D3 и с мыслью, что “сейчас я всех удивлю крутейшей визуализацией”, взялся изучать исходники примеров…
… и понял, что сам абсолютно ничего не понимаю! Странная логика работы библиотеки, в примерах целая куча строк кода, чтобы создать простейший график — это был конечно же удар, главным образом по самолюбию. Ладно, утер сопли — понял, что с наскоку D3 не взять и для понимания этой библиотеки надо начинать с самых ее основ. Потому решил пойти другим путем — взять за основу для своих графиков одну из библиотек — надстроек на D3. Как выяснилось, библиотек таких довольно много — значит не один я такой, непонимающий (говорило мое поднимающееся из пепла самолюбие).
Попробовав несколько библиотек, остановился на dimple как на более или менее подходящей для моих нужд, отстроил с ее помощью все свои графики, но неудовлетворенность осталась. Некоторые вещи работали не так, как хотелось бы, другой функционал без глубокого копания в dimple не удалось реализовать, и он был отложен. Да и вообще, если нужно глубоко копать, то лучше это делать напрямую с D3, а не с дополнительной настройкой, богатый функционал которой в моем случае используется не более, чем на пять-десять процентов, а вот нужных мне настроек наоборот не хватало. И поэтому случилось то, что случилось — D3.js.
Попытка номер два
Первым делом перечитал все, что есть по D3 на Хабре. И в комментарии под одной из статей увидел ссылку на книгу Interactive Data Visualization for the Web. Открыл, глянул, начал читать — и моментально втянулся! Книга написана на простом и понятном английском, плюс автор сам по себе прекрасный и интересный рассказчик, хорошо раскрывающий тему D3 с азов.
По результатам чтения этой книги (а так же параллельно кучи другой документации по теме) я и написал для себя небольшую (правильнее наверное сказать — микроскопическую) библиотеку по построению простых и минималистичных линейных графиков. И в свою очередь на примере этой библиотеки хочу показать, что строить графики с помощью D3.js совсем не сложно.
Итак (мое самое любимое слово), приступим.
Первым делом давайте решим, какие данные мы хотим отстроить в виде графика. Я решил не вымучивать из себя набор условных данных, а взял реальные, с которыми сталкиваюсь каждый день, упростив и обезличив их для лучшего понимания.
Представьте себе какой нибудь завод по добыче и переработке, допустим, железной руды на каком нибудь условном месторождении (“свечной заводик бери”, — напоминают мне крылатую фразу классиков литературы из-за плеча, но данные уже подготовлены, — потому свечной заводик отложен до следующего раза).
Итак, добываем руду и выпускаем железо. Есть план добычи, составленный технологами, учитывающий геологические особенности месторождения, производственные мощности, и тд. и тп. План (в нашем случае) разбит по месяцам, и видно, сколько в тот или иной месяц необходимо добыть руды и выплавить железа, чтобы выполнить этот план. Есть также факт — ежемесячные фактические данные по выполнению плана. Давайте все это и отобразим в виде графика.
Вышеназванные данные сервер нам будет предоставлять в виде следующего tsv файла:
Category Date Metal month Mined %
fact 25.10.2010 2234 0.88
fact 25.11.2010 4167 2.55
...
plan 25.09.2010 1510 1
plan 25.10.2010 2790 2
plan 25.11.2010 3820 4
...
Где в столбце Category находятся плановые или фактические значения, Date — это данные за каждый месяц (у нас датируются 25-м числом), Metal month — сколько металла за месяц, которое мы запланировали (или получили) и столбец Mined % — какой процент металла добыт на текущий момент.
С данными думаю все понятно, теперь приступаем к программе. Весь код, как то вызовы библиотек, css стили и тому подобное, я показывать не буду, чтобы не загромождать статью и сосредоточусь на главном, а описанный здесь пример вы сможете скачать гитхаба по ссылке в конце статьи.
Первым делом с помощью функции d3.tsv загрузим данные:
d3.tsv("sample.tsv", function(error, data) {
if (error) throw error;
//здесь будет дальнейший код построения графика
}
Загрузка данных в D3 очень проста. Если вам нужно загрузить данные в другом формате, например в csv, просто меняйте меняете вызов с d3.tsv на d3.сsv. Ваши данные в формате JSON? Меняете вызов на d3.json. Я пробовал все три формата и остановился на tsv, как на наиболее удобным для меня. Вы же можете использовать любой, какой понравится, или вообще генерировать данные непосредственно в программе.
На приведенном рисунке можно увидеть, как выглядят загруженные данные в нашей программе.
Если присмотреться внимательно к рисунку, то видно, что данные у нас загружены в виде строк, потому следующий этап работы программы — это приведение дат к типу данных date, а цифровых значений — к типу numeric. Без этих приведений D3 не сможет правильно обрабатывать даты, а к цифровым значениям будет применять избирательный подход, т.е. какие-то цифры будут браться, а другие — просто игнорироваться. Для этих приведений вызовем следующую функцию:
preParceDann("Date","%d.%m.%Y",["Metal month","Mined %"],data);
В параметрах этой функции мы передаем название столбца, в котором записаны даты, формат даты в соответствии с правилами записи дат в D3; затем идет массив с названиями столбцов, для которых нужно сделать преобразование цифровых значений. И последний параметр — это данные, которые мы загрузили ранее. Сама функция преобразования совсем небольшая и потому, чтобы снова к ней не возвращаться, приведу ее сразу:
function preParceDann(dateColumn,dateFormat,usedNumColumns,data){
var parse = d3.time.format(dateFormat).parse;
data.forEach(function(d) {
d[dateColumn] = parse(d[dateColumn]);
for (var i = 0, len = usedNumColumns.length; i < len; i += 1) {
d[usedNumColumns[i]] = +d[usedNumColumns[i]];
}
});
};
Здесь мы инициализируем функцию прасинга дат и затем для каждой строки данных конвертируем даты, а для заданных столбцов переводим строки в цифры.
После выполнения данной функции наши данные представляются уже в таком виде:
Сразу отвечаю на возможный вопрос — Зачем в этой функции усложнение с указанием списка столбцов, в которых нужно форматировать цифровые данные? — и ответ этот прост: в реальной таблице может быть (и есть) гораздо большее количество столбцов и не все из них могут быть цифровые. Да и строить непременно по всем столбцам графики мы не будем, так зачем же лишние манипуляции по преобразованию данных?
Прежде чем перейти к следующему действию, вспомним наш файл данных — в нем последовательно записаны сначала фактические, а затем проектные данные. Если мы сейчас отстроим данные, как они есть, то получим полную кашу. Потому что и факт, и план отрисуются в виде одной диаграммы. Поэтому проводим еще одну манипуляцию с данными при помощи функции D3 с любопытным названием nest (гнездо):
var dataGroup = d3.nest()
.key(function(d) { return d.Category; })
.entries(data);
В результате работы этой функции получаем следующий набор данных:
где мы видим что наш массив данных уже разбит на два подмассива: один факт, другой план.
Все, с подготовкой данных мы закончили — теперь переходим к заданию параметров для построения графика:
var param = {
parentSelector: "#chart1",
width: 600,
height: 300,
title: "Iron mine work",
xColumn: "Date",
xColumnDate: true,
yLeftAxisName: "Tonnes",
yRightAxisName: "%",
categories: [
{name: "plan", width: "1px"},
{name: "fact", width: "2px"}
],
series: [
{yColumn: "Metal month", color: "#ff6600", yAxis: "left"},
{yColumn: "Mined %", color: "#0080ff", yAxis: "right"}
]
};
Здесь все просто:
Параметр | Значение |
---|---|
parentSelector | id элемента нашей странички, в котором будет отстроен график |
width: 600 | ширина |
height: 300 | высота |
title: "Iron mine work" | заголовок |
xColumn: "Date" | название столбца, из которого будут браться координаты для оси Х |
xColumnDate: true | если true, то ось x — это даты (к сожалению, данный функционал еще недоделан, т.е. по оси x мы можем строить только даты) |
yLeftAxisName: "Tonnes" | название левой оси y |
yRightAxisName: "%" | названия правой оси y |
categories: | долго думал, как же назвать то. что вылетает из “гнезда” D3 и ничего лучше категорий не придумал. Для каждой категории задается наименование — как она прописана в наших данных и ширина построения |
series: | обственно, сами диаграммы, задаем, из какого столбца берем значения для оси y, цвет, и к какой оси диаграмма будет относится, левой или правой |
Все исходные данные мы задали, теперь наконец вызываем построение графика и наслаждаемся результатом:
d3sChart(param,data,dataGroup);
Что же видим мы на этом графике? А видим мы, что планы были через чур оптимистичны и чтобы иметь достоверный прогноз нужно делать неизбежную корректировку. Также необходимо присмотреться и к производству, уж больно рваный фактический график… Ладно, ладно — это мы уже лезем туда, куда нас, программистов, никто не звал, поэтому возвращаемся к нашим баранам — как же этот график строится?
Снова повторю вызов функции построения графика:
d3sChart(param,data,dataGroup);
Глядя на нее возникает резонный вопрос, который вы возможно хотите мне задать — Зачем в функцию передаются два массива данных, data и dataGroup? Отвечаю: исходный массив данных нужен для того, чтобы правильно задать диапазон данных для осей. Подозреваю, звучит это не очень понятно — но постараюсь вскоре этот момент объяснить.
Первое что мы выполняем в функции построения — это проверяем, есть ли вообще в наличии объект, в котором мы будем строить график. И если этого самого объекта нет — сильно ругаемся:
function d3sChart (param,data,dataGroup){
// check availability the object, where is displayed chart
var selectedObj = null;
if (param.parentSelector === null || param.parentSelector === undefined) { parentSelector = "body"; };
selectedObj = d3.select(param.parentSelector);
if (selectedObj.empty()) {
throw "The '" + param.parentSelector + "' selector did not match any elements. Please prefix with '#' to select by id or '.' to select by class";
};
Следующие наши действия: инициализируем различные отступы, размеры и создаем шкалы.
var margin = {top: 30, right: 40, bottom: 30, left: 50},
width = param.width - margin.left - margin.right,
height = param.height - margin.top - margin.bottom;
// set the scale for the transfer of real values
var xScale = d3.time.scale().range([0, width]);
var yScaleLeft = d3.scale.linear().range([height, 0]);
var yScaleRight = d3.scale.linear().range([height, 0]);
Не забываем, что библиотека наша только-только вылупилась и настраивать кое — какие вещи (например отступы) извне еще не приучена, ввиду искуственно мной ускоренного инкубационного процесса. Поэтому еще раз прошу понять и простить.
Шутки-шутками, но вернемся обратно к коду выше — с отступами и размерами думаю все и так понятно, шкалы же нужны нам для пересчета исходных значений координат в координаты области построения. Видно, что шкала х инициализируется как шкала времени, а левые и правые шкалы по оси y инициализируются как линейные. Вообще в D3 много различных шкал, но рассмотрение их, а так же многого, многого другого уже сильно выходит за рамки этой статьи.
Продолжаем, шкалы мы создали, теперь нужно их настроить. И вот здесь то как раз и пригодится тот исходный набор данных. Если совсем по-простому — предыдущими действиями мы задали диапазон шкал в координатах графика, следующими же командами мы связываем этот диапазон с диапазонами данных:
xScale.domain([d3.min(data, function(d) { return d[param.xColumn]; }), d3.max(data, function(d) { return d[param.xColumn]; })]);
yScaleLeft.domain([0,d3.max(data, function(d) { return d[param.series[0].yColumn]; })]);
yScaleRight.domain([0,d3.max(data, function(d) { return d[param.series[1].yColumn]; })]);
Для шкалы X мы задаем минимальным значением минимальную дату в наших данных, максимальным — максимальную. Для осей Y за минимум берем 0, максимум же также узнаем из данных. Вот для этого и были нужны не разбитые данные — чтобы узнать минимальные и максимальные значения.
Следующее действие — настраиваем оси. Тут начинается небольшая путаница. В D3 есть шкалы (scales) и оси (axis). Шкалы отвечают за преобразование исходных координат в координаты области построения, оси же предназначены для отображения на графиках тех палочек и черточек, которые мы видим на графике и которые по русски называются “шкалы координат, отложенные по осям X и Y”. Поэтому, в дальнейшем, если я пишу шкала, имейте ввиду что речь идет об axis, т.е. об отрисовке шкалы на графике.
Итак, напоминаю — у нас две шкалы для оси Y и одна шкала для оси X, с которой пришлось изрядно повозиться. Дело в том, что меня совершенно не устраивало, как D3 по умолчанию выводит шкалу дат. Но все мои попытки настроить подписи дат так, как мне это нужно, разбивались, как волны, о скалы мощности и монументальности этой библиотеки. Потому пришлось пойти на подлог и обман: я создал две шкалы по оси X. На одной шкале у меня выводятся только годы, на другой месяцы. Для месяцев добавлена небольшая проверка, которая исключает первый месяц из вывода. И ведь всего пару предложений назад я обвинял эту библиотеку в монументальности, а тут такой замечательный пример гибкости.
var xAxis = d3.svg.axis().scale(xScale).orient("bottom")
.ticks(d3.time.year,1).tickFormat(d3.time.format("%Y"))
.tickSize(10);
var monthNameFormat = d3.time.format("%m");
var xAxis2 = d3.svg.axis().scale(xScale).orient("bottom")
.ticks(d3.time.month,2).tickFormat(function(d) { var a = monthNameFormat(d); if (a == "01") {a = ""}; return a;})
.tickSize(2);
var yAxisLeft = d3.svg.axis().scale(yScaleLeft).orient("left");
var yAxisRight = d3.svg.axis().scale(yScaleRight).orient("right");
Продолжаем рассматривать код. Все подготовительные работы мы провели и теперь приступаем непосредственно к формированию изображения. Следующие 4 строчки кода последовательно создают область svg, рисуют оконтуривающую рамку, создают с заданным смещением группу объектов svg, в которой будет строится наш график. И последнее действие — выводится заголовок.
var svg = selectedObj.append("svg")
.attr({width: param.width, height: param.height});
// outer border
svg.append("rect").attr({width: param.width, height: param.height})
.style({"fill": "none", "stroke": "#ccc"});
// create group in svg for generate graph
var g = svg.append("g").attr({transform: "translate(" + margin.left + "," + margin.top + ")"});
// add title
g.append("text").attr("x", margin.left) .attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle").style("font-size", "14px")
.text(param.title);
Следующий большой кусок кода подписывает единицы измерения наших 3-х осей. Думаю здесь все понятно и подробно рассматривать не нужно:
g.append("g").attr("class", "x axis").attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("x", width-20).attr("dx", ".71em")
.attr("y", -4).style("text-anchor", "end")
.text(param.xColumn);
g.append("g").attr("class", "x axis2").attr("transform", "translate(0," + height + ")")
.call(xAxis2);
g.append("g").attr("class", "y axis")
.call(yAxisLeft)
.append("text").attr("transform", "rotate(-90)")
.attr("y", 6).attr("dy", ".71em").style("text-anchor", "end")
.text(param.yLeftAxisName);
g.append("g").attr("class", "y axis").attr("transform", "translate(" + width + " ,0)")
.call(yAxisRight)
.append("text").attr("transform", "rotate(-90)")
.attr("y", -14).attr("dy", ".71em").style("text-anchor", "end")
.text(param.yRightAxisName);
Ну и, наконец, ядро функции построения графика — отрисовка самих диаграмм:
dataGroup.forEach(function(d, i) {
for (var i = 0, len = param.categories.length; i < len; i += 1) {
if (param.categories[i].name == d.key){
for (var j = 0, len1 = param.series.length; j < len1; j += 1) {
if (param.series[j].yAxis == "left"){
// init line for left axis
var line = d3.svg.line()
.x(function(d) { return xScale(d[param.xColumn]); })
.y(function(d) { return yScaleLeft(d[param.series[j].yColumn] ); });
};
if (param.series[j].yAxis == "right"){
// init line for right axis
var line = d3.svg.line()
.x(function(d) { return xScale(d[param.xColumn]); })
.y(function(d) { return yScaleRight(d[param.series[j].yColumn] ); });
};
// draw line
g.append("path").datum(d.values)
.style({"fill": "none", "stroke": param.series[j].color, "stroke-width": param.categories[i].width})
.attr("d", line);
};
};
};
});
“Три вложенных друг в друга цикла!” — в ярости воскликните вы. И будете совершенно правы в своем негодовании — сам не люблю делать такие вложенные конструкции, но иногда приходится. В третьей вложенности цикла мы инициализируем наши линии диаграмм, где в зависимости от series указываем, к правой или левой шкале будет относится эта линия. После этого, во второй вложенности мы уже выводим линию на график, задавая ее толщину из свойств категорий. Т.е. фактически у нас на построении задействованы всего две строчки кода, все остальное лишь обвязка, необходимая для обработки различного количества диаграмм на графике.
Ну и последние действие с нашим графиком — это вывод легенды. С легендой я каюсь — тут уже торопился и сделал ее на тяп-ляп, код этот будет в скором времени переписан и показываю я его лишь для того чтобы еще раз продемонстрировать, что в D3 все довольно таки просто. А еще — вот хороший пример того, как делать не нужно:
var legend = svg.append("g").attr("class", "legend").attr("height", 40).attr("width", 200)
.attr("transform", "translate(180,20)");
legend.selectAll('rect').data(param.series).enter()
.append("rect").attr("y", 0 - (margin.top / 2)).attr("x", function(d, i){ return i * 90;})
.attr("width", 10).attr("height", 10)
.style("fill", function(d) {return d.color; });
legend.selectAll('text').data(param.series).enter()
.append("text").attr("y", 0 - (margin.top / 2)+10).attr("x", function(d, i){ return i * 90 + 11;})
.text(function(d) { return d.yColumn; });
// add legend for categories
var legend1 = svg.append("g").attr("class", "legend").attr("height", 40).attr("width", 200)
.attr("transform", "translate(350,20)");
legend1.selectAll('line').data(param.categories).enter()
.append("line").attr("y1", 0 - (margin.top / 2)+5).attr("x1", function(d, i){ return i * 60;})
.attr("y2", 0 - (margin.top / 2)+5).attr("x2", function(d, i){ return i * 60+15;})
.style("stroke", "black").style("stroke-width", function(d) { return d.width; });
legend1.selectAll('text').data(param.categories).enter()
.append("text").attr("y", 0 - (margin.top / 2)+10).attr("x", function(d, i){ return i * 60 + 17;})
.text(function(d) { return d.name; });
Вот и все. Спасибо за внимание! Надеюсь, что не разочаровал вас своей статьей.
Код и исходный пример данных можно скачать с Гитхаба.
В заключение хочу лишь добавить, что именно подобную статью или туториал я искал, когда сам пытался разобраться с библиотекой D3. Искал статью, где бы на примерах, раздельно и последовательно, было бы показано: как загрузить и подготовить данные, как создать и настроить область построения, и как эти данные отобразить. К сожалению, ничего подобного я тогда не встретил, а в примерах по D3 от автора так все перемешано, что не понимая логики работы и не имея начальных знаний по этой библиотеке, очень трудно разобраться, где заканчиваются манипуляция с данными, а где начинается манипуляции с представлением этих данных, и наоборот.
23.06.2016 upd. Обновил программу на Гитхабе: доработал шкалу времени, сделал что по оси X можно вместо дат пускать числовой ряд, исправил некоторые ошибки и плюс теперь можно строить графики без категорий.
Комментарии (21)
dom1n1k
22.06.2016 13:25+2А по-моему, код D3 очень простой и понятный. Они даже сознательно жертвует лаконичностью и красотой в пользу наглядности. И это одна из причин успеха этой библиотеки — не нужно заморачиваться (во всяком случае сразу) какими-то хитрыми концепциями и паттернами. Просто посмотрел несколько примеров и погнал. В этом она схожа с jQuery.
Kopleman
22.06.2016 13:26Подскажите, а есть ли в D3 возможность N-цати Y осей( я вижу в примере 2 оси, но смущает жесткая привязка yAxisLeft, yAxisRight) и встроенный тип шкалы для времени?
Petrichuk
22.06.2016 13:27Никаких ограничений по осям нет, yAxisLeft и yAxisRight я ведь сам создавал. Про шкалу времени не понял вопрос.
Devgru
22.06.2016 14:28Оси нужно создавать «руками», но вполне можно делать это в цикле.
Тип шкалы для времени — d3.time.scale() https://github.com/d3/d3/wiki/Time-Scales
comerc
22.06.2016 15:02Совсем недавно нужно было выбрать движок для отображения графиков. Остановился на Chart.js и очень пожалел. Вторая версия сырая, пришлось откатываться на первую. Смотрел в сторону D3, но испугался. В фаворитах Google Charts.
kovalevsky
22.06.2016 15:09+1Тоже боялся d3, пока не вынудили попробовать, в итоге все чарты теперь на d3 на проекте строим. Там действительно, как писали выше, достаточно пару примеров посмотреть, а дальше самому всё ясно.
Disminder
23.06.2016 13:23Посмотрите на Dygraphs. В проекте стояла задача отображения огромного количества данных на графиках, а так же реал-тайм графики. Эта библиотека справилась на отлично.
pxz
22.06.2016 15:39+1Для простых графиков лучше выбрать нечто вроде amcharts или highcharts. D3 для более серьёзных вещей, ИМХО.
И разобраться было проще, чем с D3.js. Но это сугубо субъективное мнение.Petrichuk
22.06.2016 15:47Платные я принципиально не рассматривал, потому про эти два даже на глаза не попались.
KoGor
22.06.2016 17:02На основе D3.js существует достаточно библиотек, реализующих базовые графики. Мне, например, нравится C3.js. А сам D3.js как раз для сложных или кастомных графиков.
Кстати, вместо добавления второй осиабсциссX и остальных хаков можно воспользоваться встроенным функционалом d3.time.format.multi(formats).Petrichuk
22.06.2016 18:00C3 рассматривал тоже, но остановился на dimple, сделал на нем что нужно и после вернулся к D3, потому что нужна как раз именно кастомность, графики то у меня не сложные.
А как работает d3.time.format.multi(formats) я так и не понял — по единственному примеру ничего не ясно, потому не стал углубятся. Тем более, что получившееся при использовании двух шкал даже превысило мои ожидания. Т.е. именно такую шкалу я всегда хотел получить но нигде не мог этого добиться. Осталось совсем немного довести до ума то что связано с масштабированием и можно навсегда забыть про эти даты :)
Mingun
23.06.2016 22:27+1Так же возможно кому-то будет полезен сделанный мной перевод документации по API к этой замечательной библиотеке.
Petrichuk
24.06.2016 04:08Спасибо!
Можно узнать, ваш перевод как-то связан с тем что на официальной странице? И второй вопрос — планируете ли вы делать перевод документации по 4-й версии?Mingun
24.06.2016 18:52Можно узнать, ваш перевод как-то связан с тем что на официальной странице?
Вообще-то нет.
Однажды мне потребовалось решение по выводу графиков, достаточно гибкое в плане настраиваимости и либеральное в плане лицензии. После довольно долгих поисков я вышел на d3. Мне понравился подход к организации кода в этой библиотеке, так же я был впечатлён её возможностями и красивыми демками. Но поскольку для неподготовленного человека она всё же немножко рвёт шаблон, нужно было посидеть, по изучать как там всё устроено. Далее последовательно пошло изучение документации, обнаружение огрызка попытки перевода на русский язык в виде файлаAPI-Reference-(русскоязычная-версия)
и решение перевести документацию самостоятельно, дабы, в том числе, поглубже влезть в кухню внутреннего устройства библиотеки, да и читать русскую версию как-то более приятно.
И второй вопрос — планируете ли вы делать перевод документации по 4-й версии?
После реализации своего проекта, с которого всё пошло-поехало, мне длительное время визуализация никак не требовалась, поэтому я, признаться, несколько подзабил на актуализацию перевода. Нужно будет как-нибудь засесть и привести в соответствие.
gibson_dev
А почему бы не JSON использовать чтобы не считать стобцы и прочее?
Petrichuk
JSON же избыточен, данные одни и те же а разница в размере файла в несколько раз. Про считать столбцы не понял.
gibson_dev
XML избыточен а JSON наименьшее зло с удобной структурой, а так это экономия на спичках.
Про считать — изменится у вас порядок колонок или еще несколько добавится и придется искать все вызовы
и им подобные (кто знает что вы в таком же стиле пытаетесь использовать) и изменять каждый вызов
В случае со струтурированными данными такого гемороя не будет, достаточно будет или изменить обработку измененных полей если это понадобится.
Petrichuk
В наверное не поняли смысл этой функции, она всего лишь приводит строчные значение полученные после загрузки данных в date и numeric. При загрузке в формате JSON нужно проделывать те же действия, т.к. тип данных в этом формате тоже не передается и так же будут загружены одни строчные значения.
btoe
Почему обязательно строчные? Передавайте number.
Petrichuk
После выполнения функции
в data данные загрузятся в виде строк — я специально заострил на этом внимание в статье. Потому и их нужно переконвертировать.
btoe
Простите, не сразу понял, что изначально данные хранятся в .tsv