Difference between revisions of "TAChart Tutorial: ListChartSource, Logarithmic Axis, Fitting/ru"

From Lazarus wiki
Jump to navigationJump to search
 
(54 intermediate revisions by the same user not shown)
Line 4: Line 4:
 
[[File:TAChart_LogAx_Tutorial15.png]]
 
[[File:TAChart_LogAx_Tutorial15.png]]
  
Вы знаете [[wikipedia:Moore's_law|Закон Мура]]? Он перед вами...
+
Вы знаете [https://ru.wikipedia.org/wiki/Закон_Мура Закон Мура]? Он перед вами...
  
 
Возможно, читая это, вы сидите перед своим десктопом или держите на коленях ноутбук, или смартфон в руках. В последние десятилетия произошёл огромный прогресс микроэлектроники и он всё продолжается. Это происходит благодаря минитюаризации электронных устройств, позволяющей упаковывать всё больше функций на один кремниевый чип. И закон Мура именно об этом: количество транзисторов на чипе удваивается примерно каждые два года.
 
Возможно, читая это, вы сидите перед своим десктопом или держите на коленях ноутбук, или смартфон в руках. В последние десятилетия произошёл огромный прогресс микроэлектроники и он всё продолжается. Это происходит благодаря минитюаризации электронных устройств, позволяющей упаковывать всё больше функций на один кремниевый чип. И закон Мура именно об этом: количество транзисторов на чипе удваивается примерно каждые два года.
Line 17: Line 17:
 
* как ''' создать серию точек с метками сверху каждой точки'''
 
* как ''' создать серию точек с метками сверху каждой точки'''
 
* как '''создать логарифмическую ось'''
 
* как '''создать логарифмическую ось'''
* как '''выровнять данные'''.
+
* как '''аппроксимировать данные'''.
  
Вы должны иметь базовые знания о том, как работать с TAChart; иначе вы должны взглянуть на [[TAChart Tutorial: Getting started/ru|Руководство по началу работы]] TAChart. Конечно, вы должны быть знакомы с [[Lazarus]] и [[Object Pascal]]. И не надо слишком бояться математики, вам понадобятся логарифмы и экспоненциальная функция.
+
Вам нужно иметь базовые знания о том, как работать с TAChart; иначе необходимо просмотреть [[TAChart Tutorial: Getting started/ru|Руководство по началу работы]] TAChart. Конечно, вы должны быть знакомы с [[Lazarus]] и [[Object Pascal]]. И не надо слишком бояться математики, вам понадобятся логарифмы и экспоненциальная функция.
  
 
== Подготовка ==
 
== Подготовка ==
Line 25: Line 25:
 
=== Настройка диаграммы ===
 
=== Настройка диаграммы ===
 
* Создадим новый проект.
 
* Создадим новый проект.
* Поскольку мы получим несколько меток по длинной оси, увеличим размер формы примерно до 540 x 320 пикселей.
+
* Поскольку получается несколько меток по длинной оси, увеличим размер формы примерно до 540 x 320 пикселей.
* Добавим компонент <code>TChart</code>, выровняем его <code>alClient</code>, установим <code>BackColor</code> в <code>clWhite</code> и <code>Grid.Color</code> каждой оси в <code>clSilver</code>.
+
* Добавим компонент <code>TChart</code>, выровняем его в режиме <code>alClient</code>, установим <code>BackColor</code> в <code>clWhite</code> и <code>Grid.Color</code> каждой оси в <code>clSilver</code>.
* Добавим имя оси x ("Year of market introduction — Год выхода на рынок") и оси y ("Number of transistors — Количество транзисторов").  
+
* Добавим имя оси '''X''' («Year of market introduction» — Год выхода на рынок) и оси '''Y''' («Number of transistors» — Количество транзисторов).  
* Используем текст "Progress in Microelectronics — Прогресс в микроэлектронике" как заголовок диаграммы.  
+
* Используем текст «Progress in Microelectronics» (Прогресс в микроэлектронике) как заголовок диаграммы.  
 
* Зададим стиль шрифта <code>fsBold</code>.  
 
* Зададим стиль шрифта <code>fsBold</code>.  
 
* Дадим ссылку на наши данные в нижнем колонтитуле. Используем для этого свойство диаграммы [[TAChart_documentation#Title_and_footer|<code>Foot</code>]]. Заметьте, что редактор свойства <code>Foot.Text</code> (а также <code>Title.Text</code>) позволяет вводить многострочные заголовки.
 
* Дадим ссылку на наши данные в нижнем колонтитуле. Используем для этого свойство диаграммы [[TAChart_documentation#Title_and_footer|<code>Foot</code>]]. Заметьте, что редактор свойства <code>Foot.Text</code> (а также <code>Title.Text</code>) позволяет вводить многострочные заголовки.
Line 34: Line 34:
  
 
=== Создание серии точек ===
 
=== Создание серии точек ===
Мы хотим нарисовать каждую запись TDataRecord как единую точку данных — в других программах для построения диаграмм это называется «серией точек». Редактор серии TAChart не имеет такого типа серии потому что  это можно сделать с помощью [[TAChart_documentation#Line_series|<code>TLineSeries</code>]]. Нам нужно только установить его свойство <code>ShowPoints</code> в <code>true</code> и отключить соединительные линии (<code>LineType</code> = <code>ltNone</code>).Эти символы определены свойством <code>Pointer</code>.  
+
Мы хотим нарисовать каждую запись TDataRecord как единую точку данных — в других программах для построения диаграмм это называется «серией точек». Редактор серии TAChart не имеет такого типа серии потому что  это можно сделать с помощью [[TAChart_documentation/ru#Line-диаграмма|<code>TLineSeries</code>]]. Нам нужно только установить его свойство <code>ShowPoints</code> в <code>true</code> и отключить соединительные линии (<code>LineType</code> = <code>ltNone</code>).Эти символы определены свойством <code>Pointer</code>.  
  
 
Итак, добавим LineSeries на форму и установим свойства для создания серии точек. Дополнительно установим <code>Pointer.Brush.Color</code> в <code>clRed</code>, и <code>Pointer.Style</code> в <code>psCircle</code>, чтобы нарисовать красный круг в каждой точке.
 
Итак, добавим LineSeries на форму и установим свойства для создания серии точек. Дополнительно установим <code>Pointer.Brush.Color</code> в <code>clRed</code>, и <code>Pointer.Style</code> в <code>psCircle</code>, чтобы нарисовать красный круг в каждой точке.
Line 40: Line 40:
 
== Ввод данных ==
 
== Ввод данных ==
 
=== Список источников данных (ListChartSource) ===
 
=== Список источников данных (ListChartSource) ===
Есть много способов для ввода данных в диаграмму. Мы будем использовать [[TAChart_documentation/ru#Источники|ListChartSource]]. Обычно данные загружаются в источник динамически во время выполнения программы, например, после чтения файла. Однако в нашем проекте мы хотим ввести их во время разработки программы с посредством редактора точек с данными в ListChartSource. Главное преимущество в том, что мы немедленно увидим результат наших действий, не компилируя проект. Однако у этого подхода также есть и свои недостатки.
+
Есть много способов для ввода данных в диаграмму. Мы будем использовать [[TAChart_documentation/ru#Источники|ListChartSource]]. Обычно данные загружаются в источник динамически во время выполнения программы, например, после чтения файла. Однако в нашем проекте мы хотим ввести их во время разработки программы с помощью редактора точек ListChartSource. Главное преимущество этого в том, что мы немедленно увидим результат наших действий, не компилируя проект. Однако у этого подхода также есть и свои недостатки.
  
 
ListChartSource хранит данные для рисования в списке — отсюда и его название. Элементы списка это так называемые <code>TChartDataItem</code> объекты, содержащие для каждой точки следующее:
 
ListChartSource хранит данные для рисования в списке — отсюда и его название. Элементы списка это так называемые <code>TChartDataItem</code> объекты, содержащие для каждой точки следующее:
Line 56: Line 56:
 
[[File:TAChart_LogAx_Tutorial16.png]]
 
[[File:TAChart_LogAx_Tutorial16.png]]
  
При нажатии на кнопку с многоточием рядом со свойством <code>DataPoints</code> открывается редактор данных источника диаграммы. Это таблица, в которую вы можете вводить данные. Введите данные с предыдущего изображения. Они взяты с веб-сайта, упомянутого во введении. На этом сайте указано время выхода на рынок по годам и месяцам — для простоты я пропустил месяц и округлил до ближайшего календарного года. Введите года в столбец '''X'''. Столбец '''Y''' получит количество транзисторов на чип. Название каждого микропроцессора войдёт в столбец '''Text'''. Поскольку мы не хотим присваивать каждой точке отдельный цвет, мы оставляем столбец '''Color''' в покое, но, конечно, можно поэкспериментировать с этой функцией.
+
При нажатии на кнопку с многоточием рядом со свойством <code>DataPoints</code> открывается редактор данных источника диаграммы. Это таблица, в которую мы можем вводить данные. Введём данные с предыдущего изображения. Они взяты с веб-сайта, упомянутого во введении. На этом сайте указано время выхода на рынок по годам и месяцам — для простоты я пропустил месяц и округлил до ближайшего календарного года. Введите год'''а''' в столбец '''X'''. Столбец '''Y''' получит количество транзисторов на чип. Название каждого микропроцессора войдёт в столбец '''Text'''. Поскольку мы не хотим присваивать каждой точке отдельный цвет, оставляем столбец '''Color''' в покое, но, конечно, можно поэкспериментировать и с этой функцией.
  
 
После закрытия редактора данных, серия автоматически обновляется и отображает текущий набор данных. Вау!
 
После закрытия редактора данных, серия автоматически обновляется и отображает текущий набор данных. Вау!
  
=== Displaying datapoint marks ===
+
=== Отображение меток точек ===
Why don't we see the processor names that we entered in the grid? [[TAChart_documentation#Marks|Data point marks]] are turned off by default. Select the series and go to <code>Marks</code>. Look at the subproperty <code>Style</code>, it is <code>smsNone</code> which means "off". Open the dropdown list. You will see a variety of options how the data points can be labeled. Play with these settings to learn what they mean. Here we want to use <code>smsLabel</code> which displays the <code>Text</code> field of the <code>TChartDataItem</code>. In order to assign '''Marks.Style''' through source code, <code>uses ... TAChartUtils</code> shall be added.
+
Почему мы не видим имена процессоров, введённых в таблицу? Потому, что [[TAChart_documentation/ru#Метки|метки точек]] по умолчанию отключены. Выберем серию и перейдём к <code>Marks</code>. Посмотрим на свойство <code>Style</code>, оно содержит  <code>smsNone</code>, что значит «выключено». Откроем раскрывающийся список. Увидим много вариантов маркировки точек. Поиграемся с этими установками чтобы выучить, что они значат. Здесь мы хотим использовать <code>smsLabel</code>, отображающий поле <code>Text</code> свойства <code>TChartDataItem</code>. Чтобы назначить '''Marks.Style''' через исходный код, нужно добавить <code>uses ... TAChartUtils</code>.
  
There is also a connecting line between the label and the datapoint. Its color is white by default, that's why we cannot see it on the white background. The property name for this line is <code>LinkPen</code>. You may want to set the <code>LinkPen.Color</code> to <code>clGray</code>.
+
Между метками и точками существуют также соединительные линии . У них по умолчанию белый цвет, поэтому на белом фоне мы его не видим. Имя свойства для этой линии <code>LinkPen</code>. Можно установить  <code>LinkPen.Color</code> в <code>clGray</code>.
  
The position of the marks is OK here, but you should know that the series has a property [[TAChart_documentation#Mark_positions_and_attachment|<code>MarkPositions</code>]] to control positioning to some degree.
+
Позиция метки нас устраивает, но надо знать, что серия имеет свойство [[TAChart_documentation#Mark_positions_and_attachment|<code>MarkPositions</code>]], чтобы контролировать её положение.
  
 
[[File:TAChart_LogAx_Tutorial3.png]]
 
[[File:TAChart_LogAx_Tutorial3.png]]
  
Maybe we compile the project at this time.
+
В этот момент можно скомпилировать проект.
  
There is nothing new - we saw everything already at design-time. This is a great advantage of working with the datapoints editor of a ListChartSource.
+
Ничего нового —  мы видели всё уже в режиме разработки. Это большое преимущество работы с редактором точек в ListChartSource.
  
There are two things which can be improved at this stage:
+
Есть две вещи, которые можно улучшить на этом этапе:
* The point labels are cut off at the edges of the chart. We can fix this by increasing the chart's <code>Margin.Left</code> and <code>Margin.Right</code> to 24. [[TAChart_documentation#Margins|<code>Margin</code>]] defines the space surrounding the inner plot area to keep it free from data points. There is also a property [[TAChart_documentation#Margins|<code>MarginExternal</code>]] which surrounds the outer border of the chart and can be used to change the distance to neighboring controls.
+
* Метки точек обрезаются по краям диаграммы. Можно исправить это увеличением <code>Margin.Left</code> и <code>Margin.Right</code> диаграммы до 24. [[TAChart_documentation/ru#Поля|<code>Margin</code>]] определяет пространство, окружающее внутреннюю область графика, чтобы оно не содержало точек. Также есть свойство [[TAChart_documentation/ru#Поля|<code>MarginExternal</code>]], которое определяет окружение внешней границы диаграммы и может использоваться для изменения расстояния до соседних элементов управления.  
* Since almost all data points are crowded at the bottom of the chart the plot is not very meaningful. This is why we need a '''logarithmic axis'''.
+
* Поскольку почти все точки сосредоточены в нижней части диаграммы, график не очень удобен. Именно поэтому нам нужна '''логарифмическая ось'''.
  
== Setting up a logarithmic axis ==
+
== Настройка логарифмической оси ==
Plotting data on a logarithmic axis means that the logarithms of the data values are plotted, not the values directly. The y values in our diagram, for example, range between 2300 and 731 million. When we calculate the ([[wikipedia:Common_logarithm|decadic]]/log<sub>10</sub>) logarithms, the range is only between about 3.3 and 7.1 - in such a diagram the data can be distinguished much easier.
+
Отображение данных на логарифмической оси означает, что на график наносятся логарифмы значений данных, а не сами значения. Например, значения '''y''' на нашей диаграмме находятся в диапазоне от 2300 до 731 миллиона. Когда мы вычисляем [https://ru.wikipedia.org/wiki/Десятичный_логарифм десятичные] (log<sub>10</sub>) логарифмы, диапазон составляет примерно от 3,3 до 7,1 — на такой диаграмме данные различить намного проще.
  
=== TChartTransformations and LogarithmicAxisTransform ===
+
 
 +
=== TChartTransformations и LogarithmicAxisTransform ===
 
[[File:TAChart_LogAx_Tutorial4.png|left]]
 
[[File:TAChart_LogAx_Tutorial4.png|left]]
Calculating the logarithm can be done by TAChart automatically. The main advantage is that the original data units are drawn on the axis while the logarithms are used for plotting.  
+
Вычисление логарифма может быть выполнено TAChart автоматически. Основное преимущество этого заключается в том, что исходные единицы данных отображаются на оси, а логарифмы используются для построения графика.
  
In fact, there is an entire group of components for axis transformations: [[TAChart_documentation#Axis_transformations|<code>TChartAxisTransformations</code>]]. An axis transformation is a function which maps "real world" data in units as displayed on the axis (''"axis coordinates"'') to internal units that are common to all series in the same chart (''"graph coordinates"''). The axis coordinate of the transistor count of the 4004 processor, for example, is 2300, the graph coordinate is the logarithm of that number, i.e. log<sub>10</sub>(2300) = 3.36.
+
Фактически есть целая группа компонентов для трансформаций осей: [[TAChart_documentation/ru#Преобразования_осей|<code>TChartAxisTransformations</code>]]. Преобразование оси это функция, которая сопоставляет данные «реального мира» в единицах, указанных на оси (''«осевые координаты»'') во внутренние единицы, общие для всех серий в той же диаграмме (''«координаты графика»''). Осевая координата количества транзисторов процессора 4004, например, 2300, координата на графике — логарифм этого числа, т.е. log<sub>10</sub>(2300) = 3.36.
  
TAChart provides a variety of transforms. In addition to the logarithm transform, there is a [[TAChart_documentation#Linear_and_logarithmic_transformation|linear transform]] which allows to multiply the data by a factor and to add an offset. The [[TAChart_documentation#Auto-scaling_transformation|auto-scaling transform]] is useful when several independently scaled series have to be drawn on the same axis. The [[TAChart_documentation#User-defined_source|user-defined transform]] allows to apply any arbitrary transformation.
+
TAChart предлагает много преобразований. В добавок к логарифмическому преобразованию есть [[TAChart_documentation#Linear_and_logarithmic_transformation|линейное преобразование]], которое позволяет умножать данные на коэффициент и добавлять смещение. [[TAChart_documentation#Auto-scaling_transformation|Автомасштабирующее преобразование]] полезно когда несколько серий надо нарисовать на одной оси. [[TAChart_documentation#User-defined_source|Пользовательское преобразование]] позволяет применить любое произвольное преобразование.
  
 
[[File:TAChart_LogAx_Tutorial5.png]]
 
[[File:TAChart_LogAx_Tutorial5.png]]
  
Add a <code>TAChartTransformations</code> component to the form, double-click on it (or right-click on it in the object tree and select "Edit axis transformations"), click on "Add" and select "Logarithmic". This will create a <code>ChartAxisTransformations1LogarithmAxisTransform1</code> component - what a name! Anyway, there's a high chance that you will not have to type it...  
+
Добавим на форму компонент <code>TAChartTransformations</code>, дважды кликнем по нему (или кликнем правой клавишей на нём в дереве объектов и выберем «Edit axis transformations»), кликнем на «Add» и выберем «Logarithmic». Создастся компонент  <code>ChartAxisTransformations1LogarithmAxisTransform1</code> — вот ведь имечко! Хорошо хоть, что скорее всего вам не придётся вводить его руками.  
  
In the object inspector, you will see only a few properties - the most important one is <code>Base</code>. This is the base of the logarithm to be calculated. Change it to 10, since we want to calculate the [[wikipedia:Common_logarithm|decadic logarithms]] - this case comprises 99% of all logarithmic charts.
+
В инспекторе объектов мы увидим несколько свойств —  самое важное —  <code>Base</code>. Это основание вычисляемого логарифма. Сменим его на 10, т.к. мы хотим считать [[wikipedia:Common_logarithm|десятичные логарифмы]] — это подходит к  99% всех логарифмических диаграмм.
  
A side-note: it is very important that you '''save the project at this time'''. Why? You will see in a minute...
+
Примечание: очень важно '''в этом месте сохранить проект'''. Почему? Увидите через минуту.
  
Now we must identify the axis which is to be transformed. For this purpose each axis has a property <code>Transformations</code>. In our case, the huge numbers are plotted on the y axis. So, go to the left axis and set its property <code>Transformations</code> to <code>ChartAxisTransformations1</code>.  
+
Теперь мы должны определить ось, которая должна быть преобразована. Для этого каждая ось имеет свойство <code>Transformations</code>. В нашем случае огромные числа отложены по оси '''Y'''. Итак, переходим к левой оси и устанавливаем ее свойство <code>Transformations</code> в <code>ChartAxisTransformations1</code>.  
  
 
[[File:TAChart_LogAx_Tutorial17.png]]
 
[[File:TAChart_LogAx_Tutorial17.png]]
  
Oh - what's that? An error message pops up reporting a range check error. Hopefully you had saved the project. If you click on Cancel Lazarus will shut down, everything will be lost. If you click on OK suddenly the chart is gone, it reappears when you click somewhere, but the faulty axis is hidden ...
+
Чёрт, что это? Выскакивает сообщение об ошибке проверки диапазона. Надеюсь, вы сохранили проект. Если вы нажмете «Отмена», Lazarus выключится, все будет потеряно. Если вы нажмете «ОК», диаграмма внезапно исчезнет, ​​​​она снова появится, когда вы кликните где-нибудь, но неисправная ось будет скрыта.
  
Are you getting desperate?
+
Вы в отчаянии?
  
This is the main disadvantage of working at design-time. If something goes wrong, there is no debugger, no indication of what caused the trouble. Since components are compiled into Lazarus you would have to debug the IDE. Sounds complicated...
+
Это основной недостаток работы в фазе конструирования. Если что-то пойдет не так, то нет отладчика, нет индикации того, что вызвало проблему. Поскольку компоненты компилируются в Lazarus, вам придется отлаживать IDE. Звучит сложно.
  
Let's sit down and think what we did. We assigned the logarithmic transform to the y axis. The transform does not yet have any connection to the data, therefore the data are still in "real" units, their maximum is 731 million. But the transform "thinks" that the data are already in graph units (logarithms). When it calculates the axis labels in axis units it takes hundreds of millions to the power of 10! That's what causes the range check error. However, the situation is not always so dramatic; the least thing that could happen is that the data are not transformed, but the axis is.
+
Давайте сядем и подумаем, что мы сделали. Мы присвоили логарифмическое преобразование оси '''Y'''. Преобразование пока не имеет никакой связи с данными, поэтому данные пока в «реальных» единицах, их максимум 731 миллион. Но преобразование «думает», что данные уже представлены в графических единицах (логарифмах). Когда оно вычисляет метки осей в осевых единицах, это занимает сотни миллионов в степени 10! Вот что вызывает ошибку проверки диапазона. Однако ситуация не всегда столь драматична; самое меньшее, что может случиться, это то, что данные не трансформируются, а ось трансформируется.
  
What can we do against that? Each series has properties <code>AxisIndexX</code> and <code>AxisIndexY</code>. The transform can use this information to calculate the logarithms of the correct coordinates before the axis labels are updated. This solves our problem.  
+
Что мы можем с этим сделать? Каждая серия имеет свойства <code>AxisIndexX</code> и <code>AxisIndexY</code>. Преобразование может использовать эту информацию чтобы вычислить логарифмы правильных координат перед обновлением меток осей. Это решает нашу проблему.
  
So, click on Cancel to shutdown Lazarus. Restart and reload the project. In the saved state, the transformation is not yet connected to the axis.  
+
Итак, нажмём «Отмена», чтобы выключить Lazarus. Перезапустим и перезагрузим проект. В сохраненном состоянии трансформация еще не связана с осью.
  
Now have a look at the object tree above the object inspector and you will see that the left axis has index 0. So set the series' <code>AxisIndexY</code> to this value. Although not necessary it may be a good idea to disarm also the x axis by assigning its index 1 to the <code>AxisIndexX</code> of the series - who can guarantee that we won't transform the x axis in the future?
+
Теперь взглянем на дерево объектов над инспектором объектов, и увидим, что левая ось имеет индекс 0. Поэтому установим <code>AxisIndexY</code> серии в это значение. Хотя это и не обязательно, может быть хорошей идеей обезвредить также ось '''X''', назначив индекс 1 свойству <code>AxisIndexX</code> серии — кто может гарантировать, что мы не будем трансформировать ось x в будущем?
  
After this is done you can set the <code>LeftAxis.Transformation</code> to <code>ChartTransformations1</code> without the range check error.
+
После этого мы можем установить для <code>LeftAxis.Transformation</code> значение <code>LeftAxis.Transformation</code> без ошибки проверки диапазона.
  
With the logarithm transform activated the data points spread nicely across the y axis range now. But what's wrong with the y axis labels? And the years on the x axis are too close and partly overlap.
+
С активированным логарифмическим преобразованием точки теперь хорошо распределяются по диапазону оси '''Y'''. Но что не так с метками оси '''Y'''? А годы на оси абсцисс слишком близки и частично перекрываются.
  
 
[[File:TAChart_LogAx_Tutorial6.png]]
 
[[File:TAChart_LogAx_Tutorial6.png]]
  
Finding axis labels is a non-trivial task, in particular when transformations are active that heavily distort the axis intervals. Unfortunately, logarithmic axes belong to that group. Basically, there are two ways to control label positioning, an '''automatic''' and a '''manual''' way.
+
Поиск меток осей — нетривиальная задача, особенно когда активны преобразования, сильно искажающие интервалы осей. К сожалению, логарифмические оси принадлежат к этой группе. По сути, есть два способа управления позиционированием метки: '''автоматический''' и '''ручной'''.
  
=== Automatic finding of axis labels ===
+
=== Автоматическая расстановка осевых меток ===
For automatic label positioning, each axis has a property [[TAChart_documentation#Axis_intervals|<code>Intervals</code>]] which gives access to several, partly mutually excluding parameters - please see [[TAChart_documentation#Axis_intervals|TAChart documentation]] for an explanation. In case of the logarithmic axis the issue is usually caused by the fact that the option <code>aipGraphCoordinates</code> is not set. This option, if set, enforces calculation of the tick intervals for the transformed data ("graph coordinates"), not the "real world" data ("axis coordinates"). So, set <code>aipGraphCoordinates</code> in the <code>LeftAxis.Intervals.Options</code>. The labels jump to more evenly distributed locations.
+
Для автоматической расстановки меток ось имеет свойство [[TAChart_documentation/ru#Интервалы_осей|<code>Intervals</code>]], которое дает доступ к нескольким, частично взаимоисключающим параметрам — см. пояснение в [[TAChart_documentation/ru#Интервалы_осей|документации TAChart]]. В случае логарифмической оси проблема обычно возникает из-за того, что не установлена опция <code>aipGraphCoordinates</code>. Этот параметр, если он установлен, принудительно вычисляет интервалы делений для преобразованных данных («координаты графика»), а не для данных «реального мира» («координаты оси»). Итак, установим <code>aipGraphCoordinates</code> в <code>LeftAxis.Intervals.Options</code>. Метки разместятся более равномерно.
  
 
[[File:TAChart_LogAx_Tutorial7.png]]
 
[[File:TAChart_LogAx_Tutorial7.png]]
  
Depending on the size of your form you may get quite nice, or not so good labels. If you resize the form you will see some "crooked" labels jump in.
+
В зависимости от размера формы могут получиться очень хорошие или не очень хорошие метки. Изменив размер формы, увидим несколько «кривых» меток.
  
You can improve the quality of label presentation in the following way:
+
Улучшить внешний вид метки можно следующим образом:
* Increase the <code>Intervals.Tolerance</code>. This allows for varying spacing between tick marks.
+
* Увеличить <code>Intervals.Tolerance</code>. Это позволяет изменять расстояние между делениями.
* Adjust the range, in pixels, in which the label distance can vary. This is defined by the properties <code>Intervals.MaxLength</code> and <code>Intervals.MinLength</code>. The optimum value depends on the size of the chart and on the range of the data. In our example project, good labels are obtained by setting these properties to 100 and 50, respectively. Usually <code>Intervals.MaxLength</code> provides better results.
+
* Отрегулировать диапазон в пикселях, в котором может варьироваться расстояние между метками. Он определяется свойствами <code>Intervals.MaxLength</code> и <code>Intervals.MinLength</code>. Оптимальное значение зависит от размера диаграммы и диапазона данных. В нашем учебном проекте хорошие метки получаются при установке этих свойств в 100 и 50 соответственно. Обычно <code>Intervals.MaxLength</code> обеспечивает лучшие результаты.
  
In the same way, the overlapping year labels of the x axis can be addressed. Just increase the <code>BottomAxis.Intervals.MaxLength</code> to 70.
+
Таким же образом можно поступить с перекрывающимися метками года оси '''X'''. Просто увеличим <code>BottomAxis.Intervals.MaxLength</code> до 70.
  
What is left now is the "1" that appears at the y axis between "10000000" and "1E009". This is due to a bug in some FPC versions. If you have that as well, simply change the property <code>LeftAxis.Marks.Format</code>. This string is passed to the <code>Format</code> function to convert the numbers to strings. The format specifier "%0.0n" for example avoids that conversion error and, additionally, adds nice thousand separators to the labels which makes them much more readable.  
+
Теперь осталась «1», которая появляется на оси '''Y''' между «10000000» и «1E009». Это связано с ошибкой в некоторых версиях FPC. Если у вас это тоже есть, просто измените свойство <code>LeftAxis.Marks.Format</code>. Эта строка передается функции <code>Format</code> для преобразования чисел в строки. Спецификатор формата «%0.0n», например, позволяет избежать этой ошибки преобразования и, кроме того, добавляет к меткам разделители тысяч, что делает их намного более читабельными.
  
 
[[File:TAChart_LogAx_Tutorial8.png]]
 
[[File:TAChart_LogAx_Tutorial8.png]]
  
=== Manual finding of axis labels ===
+
=== Расстановка осевых меток вручную ===
This is the best we can do with automatic label positioning. It is not perfect because when we increase the height of the window, or zoom in, the half-decade values may appear, or the label interval may be two decades as in above figure.
+
Это лучшее, что мы смогли сделать с автоматическим позиционированием меток. Это не идеально, потому что, когда мы увеличиваем высоту окна или увеличиваем масштаб, могут появиться значения полудекады, или интервал метки может составлять две декады, как на рисунке выше.
  
If you are not happy with that you have to use '''manual axis label selection'''. For this purpose, each axis has a property <code>Source</code> which can be linked to a ListChartSource containing only the allowed axes labels. So when this chart source contains only full-decade labels there is no risk of half-decade labels or omitting every other label. On the other hand, when you zoom into the chart you may come to a point where no labels are visible any more.  
+
Если нас это не устраивает, мы должны использовать '''ручной выбор осевых меток'''. Для этого у каждой оси есть свойство <code>Source</code>, которое можно связать с ListChartSource, содержащим только разрешенные метки осей. Поэтому, когда этот источник диаграммы содержит только метки полных декад, нет риска меток полудекад или пропуска всех остальных меток. С другой стороны, когда мы увеличиваем диаграмму, мы можем наткнуться на точку, у которой метки больше не видны.
  
Add a second ListChartSource to the form. You can use the DataPoints editor again to enter the full-decade numbers. This has the advantage that you can complete most of this project without writing a single line of code!
+
Добавим в форму второй источник ListChartSource. Можно снова использовать редактор DataPoints, чтобы ввести числа полных декад. Это имеет то преимущество, что мы можем выполнить большую часть этого проекта, не написав ни одной строки кода!
  
But you could also populate the listsource easily in the <code>FormCreate</code> event:
+
Но можно также легко заполнить источник списка и в событии <code>FormCreate</code>:
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
Line 164: Line 165:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
This procedure adds powers of 10 in a wide enough range to the ListChartSource by means of its method <code>Add</code>.  
+
Эта процедура добавляет степени 10 в достаточно широком диапазоне в ListChartSource с помощью метода <code>Add</code>.
  
Connect <code>ListChartSource1</code> to <code>LeftAxis.Marks.Source</code> to activate the manual labels of the ListChartSource. You should also remove all flags from the <code>Options</code> property. Otherwise automatic tick finding will still be active to some degree. If you did not use the DataPoints editor you must compile to see the effect.  
+
Подключим <code>ListChartSource1</code> к <code>LeftAxis.Marks.Source</code>, чтобы активировать ручные метки ListChartSource. Нам нужно также  удалить все флаги из свойства <code>Options</code>. В противном случае автоматический поиск делений будет в какой-то степени активен. Если мы не использовали редактор DataPoints, мы должны скомпилировать проект, чтобы увидеть эффект.
  
 
[[File:TAChart_LogAx_Tutorial9.png]]
 
[[File:TAChart_LogAx_Tutorial9.png]]
  
=== Minor tick marks ===
+
=== Мелкие деления ===
Very often minor tick marks are placed between the major tick marks. TAChart allows to add several sets of minor ticks to each axis. We only need one here. Go to <code>LeftAxis</code> and click on the ellipsis button next to the property <code>Minors</code>. This opens the editor for <code>Chart1.AxisList[0].Minors</code>. Click on "Add" and on the "M" in the list below. Now you can adjust the parameters in the object inspector to get "good" minor ticks. If the major ticks on a logarithmic axis are at full decades then the minor ticks usually are at 2, 3, 4,..., 8, 9, and, of course, powers of 10. This can be achieved easily by turning off all <code>Intervals.Options</code> except for <code>aipUseCount</code> and setting <code>Intervals.Count = 9</code>. Of course, this makes sense only when the major labels are fixed at full decades like in the manual approach above.
+
Очень часто между крупными делениями располагаются мелкие деления. TAChart позволяет добавить несколько наборов мелких делений на каждую ось. Нам здесь нужен только один. Ищем свойство <code>LeftAxis</code> и кликаем кнопку с многоточием рядом со свойством <code>Minors</code>. Открывается редактор для <code>Chart1.AxisList[0].Minors</code>. Нажимаем «Add» и «М» в списке ниже. Теперь можем настроить параметры в инспекторе объектов, чтобы получить «хорошие» мелкие деления. Если большие деления на логарифмической оси соответствуют полным декадам, то мелкие деления обычно соответствуют 2, 3, 4,..., 8, 9 и, конечно же, десятым степеням. Этого можно легко добиться, отключив все <code>Intervals.Options</code>, кроме <code>aipUseCount</code>, и установив <code>Intervals.Count</code> = 9. Конечно, это имеет смысл только в том случае, если большие метки показывают полные декады, как в описанном выше ручном подходе.
  
Usually the plot gets too crowded by the minor grid which appears now, you should set the minor's <code>Grid.Visible</code> to <code>false</code>.
+
Обычно график становится слишком переполненным мелкой сеткой, как сейчас, и нам надо установить для мелкой сетки <code>Grid.Visible</code> значение <code>false</code>.
  
 
[[File:TAChart_LogAx_Tutorial10.png]]
 
[[File:TAChart_LogAx_Tutorial10.png]]
  
== Fitting ==
+
== Аппроксимация ==
Now let's look for a relation between the data, i.e. we want to find a mathematical formula which describes the dependence of transistor count on market introduction year. This is called "fitting": we select a formula with parameters and adjust the parameters such that the deviation to the data is as small as possible.
+
Теперь поищем связь между данными, т. е. мы хотим найти математическую формулу, описывающую зависимость количества транзисторов от года выхода на рынок. Это называется «аппроксимация»: мы выбираем формулу с параметрами и подбираем эти параметры так, чтобы отклонение от данных было как можно меньше.
  
TAChart does not contain a full-fledged fitting engine. It uses the fitting routines from the FPC numerical library [[numlib]]. Therefore, TAChart cannot address all variants of fitting, but it covers the most important case, fitting of a [[wikipedia:Polynomial|polynomial]] by means of the [[wikipedia:Least squares|linear least squares technique]]. This is about the level available to Excel users when they add a "trend line" to their chart.
+
TAChart не содержит полноценного механизма аппроксимации. Он использует процедуры аппроксимации из библиотеки  FPC [[numlib]]. Поэтому TAChart не может охватить все варианты аппроксимации, но охватывает самый важный случай —  приближение с помощью [[wikipedia:Polynomial|полиномов]], используя [[wikipedia:Least squares|метод наименьших квадратов]]. Это уровень, доступный пользователям Excel, когда они добавляют «линию тренда» на свой график.
  
 
=== TFitSeries ===
 
=== TFitSeries ===
  
TAChart provides a specialized [[TAChart_documentation#Fit_series|<code>TFitSeries</code>]] for fitting. This series has a property <code>FitEquation</code> which defines the formula that is used:
+
Для аппроксимации TAChart предоставляет специалиальный объект типа [[TAChart_documentation#Fit_series|<code>TFitSeries</code>]]. Такая серия имеет свойство <code>FitEquation</code>, которое определяет используемую формулу:
  
* <code>fePolynomial</code>: <i>y</i> = <i>b</i><sub>0</sub> + <i>b</i><sub>1</sub><i>x</i> + <i>b</i><sub>2</sub><i>x</i><sup>2</sup> + &hellip; + <i>b<sub>n</sub>x</i><i><sup>n</sup></i>. Specify the number of fitting parameters <i>a<sub>i</sub></i> by the property <code>ParamCount</code> = <i>n</i> + 1.
+
* <code>fePolynomial</code>: <i>y</i> = <i>b</i><sub>0</sub> + <i>b</i><sub>1</sub><i>x</i> + <i>b</i><sub>2</sub><i>x</i><sup>2</sup> + &hellip; + <i>b<sub>n</sub>x</i><i><sup>n</sup></i>. Указывает число аппроксимирующих параметров <i>a<sub>i</sub></i> в свойстве <code>ParamCount</code> = <i>n</i> + 1.
* <code>feLinear</code>: <i>y</i> = <i>a</i> + <i>bx</i> -- this is a special case of the general polynomial with <i>n</i> = 1 and fitting parameters <i>a</i> and <i>b</i>. It is made available as a separate item because straight lines define the most important fitting conditions.
+
* <code>feLinear</code>: <i>y</i> = <i>a</i> + <i>bx</i> — это частный случай полинома с <i>n</i> = 1 и параметрами аппроксимации <i>a</i> and <i>b</i>. Он доступен как отдельный элемент, поскольку прямые линии определяют наиболее важные условия аппроксимации.
* <code>feExp</code>: <i>y</i> = <i>a</i> * <i>e<sup>bx</sup></i> -- This equation can also be reduced to the polynomial case although this is not straightforward to see. But take the (natural) logarithm of this equation, and you get to ln(<i>y</i>) = <i>a</i> + <i>bx</i>. Now when we fit ln(<i>y</i>) instead of <i>y</i> we have the linear case again.
+
* <code>feExp</code>: <i>y</i> = <i>a</i> * <i>e<sup>bx</sup></i> — Эта функция также может быть к полиномиальной, хотя это не так просто заметить. Но взяв натуральный логарифм этой функции, получим ln(<i>y</i>) = <i>a</i> + <i>bx</i>. Теперь, когда мы аппроксимируем ln(y) вместо y, мы снова имеем линейный случай.
* <code>fePower</code>: <i>y</i> = <i>a</i> * <i>x<sup>b</sup></i>. Again, this can be reduced to a linear equation by a logarithmic transformation.
+
* <code>fePower</code>: <i>y</i> = <i>a</i> * <i>x<sup>b</sup></i>. Снова это может быть сведено к линейной функции  путём логарифмических преобразований обеих осей.
  
 
[[File:TAChart_LogAx_Tutorial11.png]]
 
[[File:TAChart_LogAx_Tutorial11.png]]
  
Enough of theory. Let's add a FitSeries to the chart: double-click on the chart, and in the series editor click on "Add" and select the entry "Least squares fit series" from the dropdown list.
+
Довольно теории. Давайте добавим FitSeries на диаграмму: дважды кликнем по диаграмме и в редакторе серий нажмём «Add» и выберем  «Least squares fit series»(аппроксимация методом наименьших квадратов) из выпадающего меню.
  
At first, we need to tell the fit series where it finds its data. For this purpose, we connect the series' <code>Source</code> with <code>ListChartSource1</code> as we had done with the line series. You see: the same chart source can be used for several series.
+
Во-первых, нам нужно указать серии, где она найдёт свои данные. Для этого мы свяжем свойство серии <code>Source</code> с <code>ListChartSource1</code>, как мы уже делали с линейной серией. Заметим, что один и тот же источник данных может использоваться несколькими сериями.
  
You hopefully remember the disaster above with the AxisIndex. So, set the <code>AxisIndexY</code> to the index of the left axis as we did with the line series.  
+
Надеюсь, вы помните описанную выше катастрофу с AxisIndex. Так что установим в <code>AxisIndexY</code> индекс левой оси, как мы делали в линейной серии.  
  
Which one of the four <code>FitEquation</code> possibilities do we select? Well, the data look like lying on a straight line. So let's select <code>feLinear</code>.
+
Какую из четырёх возможностей <code>FitEquation</code> выбрать? Ну, данные выглядят как лежащие на прямой. Так что выберем <code>feLinear</code>.
  
 
[[File:TAChart_LogAx_Tutorial12.png]]
 
[[File:TAChart_LogAx_Tutorial12.png]]
  
Oops... We see the black fitted curve, but it does not "fit" at all. And we wanted a straight line, but we get a twisted curve. How can this be?
+
Блин! Мы видим черную аппроксимирующую кривую, но она совсем не аппроксимирует. Мы хотели прямую линию, а получили изогнутую кривую. Как такое может быть?
  
The reason is the logarithmic transform that we applied to the y data. Therefore, our plot shows the logarithms, but the fit takes the "raw" data. We are effectively fitting the straight line to the data in the screenshot in the section [[#Displaying_datapoint_marks|Displaying datapoint marks]] where the log transform had not yet been introduced - it is clear that the line would not "fit" there. And when the fitted function is drawn the log transform distorts the straight line to the twisted curve that we see.
+
Причина в логарифмическом преобразовании, которое мы применили к данным оси '''Y'''. Поэтому на нашем графике показаны логарифмы, но для аппроксимации используются «сырые» данные. Мы эффективно аппроксимируем прямой данные на скриншоте в разделе [[#Displaying_datapoint_marks|Отображение меток точек]], где логарифмическое преобразование ещё не было введено — понятно, что прямая линия там не подойдёт. И когда аппроксимирующая функция нарисована, логарифмическое преобразование искажает прямую линию в искривленную кривую, которую мы и видим.
  
On the other hand, if the '''log data''' follow a straight line our fitting law is not linear, but exponential. Let's set <code>FitEquation</code> to <code>feExp</code> and try again.
+
С другой стороны, если '''логарифмизированные данные''' находятся на прямой линии, то наша аппроксимирующая функция не линейна, а экспоненциальна. Установим <code>FitEquation</code> в <code>feExp</code> и попробуем снова.
  
 
[[File:TAChart_LogAx_Tutorial13.png]]
 
[[File:TAChart_LogAx_Tutorial13.png]]
  
Ah - much better!
+
Вот, намного лучше!
  
Now we know that the exponential law, <i>y</i> = <i>a</i> * <i>x<sup>b</sup></i>, is a good description of our data. But how do we get the fitting parameters <i>a</i> and <i>b</i>?
+
Теперь мы знаем, что экспоненциальный закон <i>y</i> = <i>a</i> * <i>x<sup>b</sup></i> — хорошее приближение наших данных. Но как нам получить параметры <i>a</i> и <i>b</i>?
  
=== Fit results ===
+
=== Результаты аппроксимации ===
The fit series has a public array property <code>Param</code> which contains the fitting parameters. <code>a</code> is in <code>Params[0]</code>, and <code>b</code> is in <code>Params[1]</code>. Of course, these values are correct only when a valid fit has been performed. How do we know that? Well, the fit series provides an event <code>OnFitComplete</code> that is generated when the fit complete successfully. That's where we can evaluate the obtained fit parameters. As an example, let's display the fit results in a message along with the fit equation:
+
Аппроксимирующая серия имеет свойство типа массив <code>Param</code>, содержащий параметры аппроксимации. <code>a</code> это <code>Params[0]</code>, и <code>b</code> лежит в <code>Params[1]</code>. Конечно, эти значения имеют смысл только тогда, когда была произведена удачная аппроксимация. Как мы это узнаем? Ну, аппроксимирующая серия генерирует событие <code>OnFitComplete</code>, когда аппроксимация удачно завершается. Вот где мы можем узнать полученные параметры аппроксимации. Например, отобразим результаты аппроксимации в сообщении вместе с аппроксимирующей функцией:
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
Line 229: Line 230:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
And that's what we get:
+
И вот что мы получим:
  
 
[[File:TAChart_LogAx_Tutorial14.png]]
 
[[File:TAChart_LogAx_Tutorial14.png]]
  
Now we want to calculate the time T until the number of transistors on a chip is doubled. As an exercise try to show that
+
Теперь мы хотим вычислить время T за которое число транзисторов на пластине удваивается. В качестве упражнения попробуйте показать, что
  
 
  T = ln(2) / b
 
  T = ln(2) / b
  
It would be nice to show the doubling time as an additional line of the chart title. For this, we modify the <code>OnFitComplete</code> event handler as follows:
+
Было бы неплохо указать время удвоения в виде дополнительной строки в заголовке графика. Для этого модифицируем обработчик события <code>OnFitComplete</code> следующим образом:
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
Line 251: Line 252:
 
[[File:TAChart_LogAx_Tutorial15.png]]
 
[[File:TAChart_LogAx_Tutorial15.png]]
  
Wow! This is Moore's law: "The number of transistors per chip doubles every two years"...
+
Вау! Это закон Мура: «Количество транзисторов на пластине удваивается каждые два года».
  
== Source code ==
+
== Исходный код ==
=== Project file ===
+
=== Файл проекта ===
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
 
program project1;
 
program project1;

Latest revision as of 13:01, 1 August 2023

English (en) suomi (fi) русский (ru)

Введение

TAChart LogAx Tutorial15.png

Вы знаете Закон Мура? Он перед вами...

Возможно, читая это, вы сидите перед своим десктопом или держите на коленях ноутбук, или смартфон в руках. В последние десятилетия произошёл огромный прогресс микроэлектроники и он всё продолжается. Это происходит благодаря минитюаризации электронных устройств, позволяющей упаковывать всё больше функций на один кремниевый чип. И закон Мура именно об этом: количество транзисторов на чипе удваивается примерно каждые два года.

В этом руководстве мы возьмем опубликованные данные крупного производителя микропроцессоров и воспользуемся TAChart для построения графика количества транзисторов в зависимости от года выпуска продуктов на рынок. Этими данными мы попытаемся подтвердить закон Мура.

Вы можете найти эти данные на сайте www.intel.com/pressroom/kits/quickreffam.htm, который содержит список микропроцессоров, дату их выхода на рынок и количество транзисторов на чип.

При изучении этого примера вы научитесь:

  • как вводить данные в фазе разработки формы (designtime)
  • как создать серию точек с метками сверху каждой точки
  • как создать логарифмическую ось
  • как аппроксимировать данные.

Вам нужно иметь базовые знания о том, как работать с TAChart; иначе необходимо просмотреть Руководство по началу работы TAChart. Конечно, вы должны быть знакомы с Lazarus и Object Pascal. И не надо слишком бояться математики, вам понадобятся логарифмы и экспоненциальная функция.

Подготовка

Настройка диаграммы

  • Создадим новый проект.
  • Поскольку получается несколько меток по длинной оси, увеличим размер формы примерно до 540 x 320 пикселей.
  • Добавим компонент TChart, выровняем его в режиме alClient, установим BackColor в clWhite и Grid.Color каждой оси в clSilver.
  • Добавим имя оси X («Year of market introduction» — Год выхода на рынок) и оси Y («Number of transistors» — Количество транзисторов).
  • Используем текст «Progress in Microelectronics» (Прогресс в микроэлектронике) как заголовок диаграммы.
  • Зададим стиль шрифта fsBold.
  • Дадим ссылку на наши данные в нижнем колонтитуле. Используем для этого свойство диаграммы Foot. Заметьте, что редактор свойства Foot.Text (а также Title.Text) позволяет вводить многострочные заголовки.

TAChart LogAx Tutorial1.png

Создание серии точек

Мы хотим нарисовать каждую запись TDataRecord как единую точку данных — в других программах для построения диаграмм это называется «серией точек». Редактор серии TAChart не имеет такого типа серии потому что это можно сделать с помощью TLineSeries. Нам нужно только установить его свойство ShowPoints в true и отключить соединительные линии (LineType = ltNone).Эти символы определены свойством Pointer.

Итак, добавим LineSeries на форму и установим свойства для создания серии точек. Дополнительно установим Pointer.Brush.Color в clRed, и Pointer.Style в psCircle, чтобы нарисовать красный круг в каждой точке.

Ввод данных

Список источников данных (ListChartSource)

Есть много способов для ввода данных в диаграмму. Мы будем использовать ListChartSource. Обычно данные загружаются в источник динамически во время выполнения программы, например, после чтения файла. Однако в нашем проекте мы хотим ввести их во время разработки программы с помощью редактора точек ListChartSource. Главное преимущество этого в том, что мы немедленно увидим результат наших действий, не компилируя проект. Однако у этого подхода также есть и свои недостатки.

ListChartSource хранит данные для рисования в списке — отсюда и его название. Элементы списка это так называемые TChartDataItem объекты, содержащие для каждой точки следующее:

  • координаты x и y,
  • Text для метки точки,
  • Color, который перекрывает SeriesColor,
  • а также YList, содержащий дополнительные значения y, которые нужны в некоторых специальных типах серий.

ListChartSource — это тот же самый тип источника данных, который используется внутри многих классов серий. Однако наличие его в качестве отдельного компонента имеет большое преимущество, заключающееся в том, что тот же источник диаграммы можно снова использовать для других серий — эта функция понадобится нам позже при применении FitSeries.

TAChart LogAx Tutorial2.png

Начнем с добавления TListChartSource в нашу форму. Это вторая иконка в палитре компонентов диаграммы. Привяжем к этой серии наш источник, указав в её свойстве Source новый ListChartSource1.

Редактор данных в ListChartSource

TAChart LogAx Tutorial16.png

При нажатии на кнопку с многоточием рядом со свойством DataPoints открывается редактор данных источника диаграммы. Это таблица, в которую мы можем вводить данные. Введём данные с предыдущего изображения. Они взяты с веб-сайта, упомянутого во введении. На этом сайте указано время выхода на рынок по годам и месяцам — для простоты я пропустил месяц и округлил до ближайшего календарного года. Введите года в столбец X. Столбец Y получит количество транзисторов на чип. Название каждого микропроцессора войдёт в столбец Text. Поскольку мы не хотим присваивать каждой точке отдельный цвет, оставляем столбец Color в покое, но, конечно, можно поэкспериментировать и с этой функцией.

После закрытия редактора данных, серия автоматически обновляется и отображает текущий набор данных. Вау!

Отображение меток точек

Почему мы не видим имена процессоров, введённых в таблицу? Потому, что метки точек по умолчанию отключены. Выберем серию и перейдём к Marks. Посмотрим на свойство Style, оно содержит smsNone, что значит «выключено». Откроем раскрывающийся список. Увидим много вариантов маркировки точек. Поиграемся с этими установками чтобы выучить, что они значат. Здесь мы хотим использовать smsLabel, отображающий поле Text свойства TChartDataItem. Чтобы назначить Marks.Style через исходный код, нужно добавить uses ... TAChartUtils.

Между метками и точками существуют также соединительные линии . У них по умолчанию белый цвет, поэтому на белом фоне мы его не видим. Имя свойства для этой линии LinkPen. Можно установить LinkPen.Color в clGray.

Позиция метки нас устраивает, но надо знать, что серия имеет свойство MarkPositions, чтобы контролировать её положение.

TAChart LogAx Tutorial3.png

В этот момент можно скомпилировать проект.

Ничего нового — мы видели всё уже в режиме разработки. Это большое преимущество работы с редактором точек в ListChartSource.

Есть две вещи, которые можно улучшить на этом этапе:

  • Метки точек обрезаются по краям диаграммы. Можно исправить это увеличением Margin.Left и Margin.Right диаграммы до 24. Margin определяет пространство, окружающее внутреннюю область графика, чтобы оно не содержало точек. Также есть свойство MarginExternal, которое определяет окружение внешней границы диаграммы и может использоваться для изменения расстояния до соседних элементов управления.
  • Поскольку почти все точки сосредоточены в нижней части диаграммы, график не очень удобен. Именно поэтому нам нужна логарифмическая ось.

Настройка логарифмической оси

Отображение данных на логарифмической оси означает, что на график наносятся логарифмы значений данных, а не сами значения. Например, значения y на нашей диаграмме находятся в диапазоне от 2300 до 731 миллиона. Когда мы вычисляем десятичные (log10) логарифмы, диапазон составляет примерно от 3,3 до 7,1 — на такой диаграмме данные различить намного проще.


TChartTransformations и LogarithmicAxisTransform

TAChart LogAx Tutorial4.png

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

Фактически есть целая группа компонентов для трансформаций осей: TChartAxisTransformations. Преобразование оси это функция, которая сопоставляет данные «реального мира» в единицах, указанных на оси («осевые координаты») во внутренние единицы, общие для всех серий в той же диаграмме («координаты графика»). Осевая координата количества транзисторов процессора 4004, например, 2300, координата на графике — логарифм этого числа, т.е. log10(2300) = 3.36.

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

TAChart LogAx Tutorial5.png

Добавим на форму компонент TAChartTransformations, дважды кликнем по нему (или кликнем правой клавишей на нём в дереве объектов и выберем «Edit axis transformations»), кликнем на «Add» и выберем «Logarithmic». Создастся компонент ChartAxisTransformations1LogarithmAxisTransform1 — вот ведь имечко! Хорошо хоть, что скорее всего вам не придётся вводить его руками.

В инспекторе объектов мы увидим несколько свойств — самое важное — Base. Это основание вычисляемого логарифма. Сменим его на 10, т.к. мы хотим считать десятичные логарифмы — это подходит к 99% всех логарифмических диаграмм.

Примечание: очень важно в этом месте сохранить проект. Почему? Увидите через минуту.

Теперь мы должны определить ось, которая должна быть преобразована. Для этого каждая ось имеет свойство Transformations. В нашем случае огромные числа отложены по оси Y. Итак, переходим к левой оси и устанавливаем ее свойство Transformations в ChartAxisTransformations1.

TAChart LogAx Tutorial17.png

Чёрт, что это? Выскакивает сообщение об ошибке проверки диапазона. Надеюсь, вы сохранили проект. Если вы нажмете «Отмена», Lazarus выключится, все будет потеряно. Если вы нажмете «ОК», диаграмма внезапно исчезнет, ​​​​она снова появится, когда вы кликните где-нибудь, но неисправная ось будет скрыта.

Вы в отчаянии?

Это основной недостаток работы в фазе конструирования. Если что-то пойдет не так, то нет отладчика, нет индикации того, что вызвало проблему. Поскольку компоненты компилируются в Lazarus, вам придется отлаживать IDE. Звучит сложно.

Давайте сядем и подумаем, что мы сделали. Мы присвоили логарифмическое преобразование оси Y. Преобразование пока не имеет никакой связи с данными, поэтому данные пока в «реальных» единицах, их максимум 731 миллион. Но преобразование «думает», что данные уже представлены в графических единицах (логарифмах). Когда оно вычисляет метки осей в осевых единицах, это занимает сотни миллионов в степени 10! Вот что вызывает ошибку проверки диапазона. Однако ситуация не всегда столь драматична; самое меньшее, что может случиться, это то, что данные не трансформируются, а ось трансформируется.

Что мы можем с этим сделать? Каждая серия имеет свойства AxisIndexX и AxisIndexY. Преобразование может использовать эту информацию чтобы вычислить логарифмы правильных координат перед обновлением меток осей. Это решает нашу проблему.

Итак, нажмём «Отмена», чтобы выключить Lazarus. Перезапустим и перезагрузим проект. В сохраненном состоянии трансформация еще не связана с осью.

Теперь взглянем на дерево объектов над инспектором объектов, и увидим, что левая ось имеет индекс 0. Поэтому установим AxisIndexY серии в это значение. Хотя это и не обязательно, может быть хорошей идеей обезвредить также ось X, назначив индекс 1 свойству AxisIndexX серии — кто может гарантировать, что мы не будем трансформировать ось x в будущем?

После этого мы можем установить для LeftAxis.Transformation значение LeftAxis.Transformation без ошибки проверки диапазона.

С активированным логарифмическим преобразованием точки теперь хорошо распределяются по диапазону оси Y. Но что не так с метками оси Y? А годы на оси абсцисс слишком близки и частично перекрываются.

TAChart LogAx Tutorial6.png

Поиск меток осей — нетривиальная задача, особенно когда активны преобразования, сильно искажающие интервалы осей. К сожалению, логарифмические оси принадлежат к этой группе. По сути, есть два способа управления позиционированием метки: автоматический и ручной.

Автоматическая расстановка осевых меток

Для автоматической расстановки меток ось имеет свойство Intervals, которое дает доступ к нескольким, частично взаимоисключающим параметрам — см. пояснение в документации TAChart. В случае логарифмической оси проблема обычно возникает из-за того, что не установлена опция aipGraphCoordinates. Этот параметр, если он установлен, принудительно вычисляет интервалы делений для преобразованных данных («координаты графика»), а не для данных «реального мира» («координаты оси»). Итак, установим aipGraphCoordinates в LeftAxis.Intervals.Options. Метки разместятся более равномерно.

TAChart LogAx Tutorial7.png

В зависимости от размера формы могут получиться очень хорошие или не очень хорошие метки. Изменив размер формы, увидим несколько «кривых» меток.

Улучшить внешний вид метки можно следующим образом:

  • Увеличить Intervals.Tolerance. Это позволяет изменять расстояние между делениями.
  • Отрегулировать диапазон в пикселях, в котором может варьироваться расстояние между метками. Он определяется свойствами Intervals.MaxLength и Intervals.MinLength. Оптимальное значение зависит от размера диаграммы и диапазона данных. В нашем учебном проекте хорошие метки получаются при установке этих свойств в 100 и 50 соответственно. Обычно Intervals.MaxLength обеспечивает лучшие результаты.

Таким же образом можно поступить с перекрывающимися метками года оси X. Просто увеличим BottomAxis.Intervals.MaxLength до 70.

Теперь осталась «1», которая появляется на оси Y между «10000000» и «1E009». Это связано с ошибкой в некоторых версиях FPC. Если у вас это тоже есть, просто измените свойство LeftAxis.Marks.Format. Эта строка передается функции Format для преобразования чисел в строки. Спецификатор формата «%0.0n», например, позволяет избежать этой ошибки преобразования и, кроме того, добавляет к меткам разделители тысяч, что делает их намного более читабельными.

TAChart LogAx Tutorial8.png

Расстановка осевых меток вручную

Это лучшее, что мы смогли сделать с автоматическим позиционированием меток. Это не идеально, потому что, когда мы увеличиваем высоту окна или увеличиваем масштаб, могут появиться значения полудекады, или интервал метки может составлять две декады, как на рисунке выше.

Если нас это не устраивает, мы должны использовать ручной выбор осевых меток. Для этого у каждой оси есть свойство Source, которое можно связать с ListChartSource, содержащим только разрешенные метки осей. Поэтому, когда этот источник диаграммы содержит только метки полных декад, нет риска меток полудекад или пропуска всех остальных меток. С другой стороны, когда мы увеличиваем диаграмму, мы можем наткнуться на точку, у которой метки больше не видны.

Добавим в форму второй источник ListChartSource. Можно снова использовать редактор DataPoints, чтобы ввести числа полных декад. Это имеет то преимущество, что мы можем выполнить большую часть этого проекта, не написав ни одной строки кода!

Но можно также легко заполнить источник списка и в событии FormCreate:

procedure TForm1.FormCreate(Sender: TObject);
const
  MIN = 0;
  MAX = 12;
var
  i: Integer;
  value: double;
begin
  for i:=MIN to MAX do begin
    value := Power(10, i);
    ListChartSource2.Add(value, value);
  end;
end;

Эта процедура добавляет степени 10 в достаточно широком диапазоне в ListChartSource с помощью метода Add.

Подключим ListChartSource1 к LeftAxis.Marks.Source, чтобы активировать ручные метки ListChartSource. Нам нужно также удалить все флаги из свойства Options. В противном случае автоматический поиск делений будет в какой-то степени активен. Если мы не использовали редактор DataPoints, мы должны скомпилировать проект, чтобы увидеть эффект.

TAChart LogAx Tutorial9.png

Мелкие деления

Очень часто между крупными делениями располагаются мелкие деления. TAChart позволяет добавить несколько наборов мелких делений на каждую ось. Нам здесь нужен только один. Ищем свойство LeftAxis и кликаем кнопку с многоточием рядом со свойством Minors. Открывается редактор для Chart1.AxisList[0].Minors. Нажимаем «Add» и «М» в списке ниже. Теперь можем настроить параметры в инспекторе объектов, чтобы получить «хорошие» мелкие деления. Если большие деления на логарифмической оси соответствуют полным декадам, то мелкие деления обычно соответствуют 2, 3, 4,..., 8, 9 и, конечно же, десятым степеням. Этого можно легко добиться, отключив все Intervals.Options, кроме aipUseCount, и установив Intervals.Count = 9. Конечно, это имеет смысл только в том случае, если большие метки показывают полные декады, как в описанном выше ручном подходе.

Обычно график становится слишком переполненным мелкой сеткой, как сейчас, и нам надо установить для мелкой сетки Grid.Visible значение false.

TAChart LogAx Tutorial10.png

Аппроксимация

Теперь поищем связь между данными, т. е. мы хотим найти математическую формулу, описывающую зависимость количества транзисторов от года выхода на рынок. Это называется «аппроксимация»: мы выбираем формулу с параметрами и подбираем эти параметры так, чтобы отклонение от данных было как можно меньше.

TAChart не содержит полноценного механизма аппроксимации. Он использует процедуры аппроксимации из библиотеки FPC numlib. Поэтому TAChart не может охватить все варианты аппроксимации, но охватывает самый важный случай — приближение с помощью полиномов, используя метод наименьших квадратов. Это уровень, доступный пользователям Excel, когда они добавляют «линию тренда» на свой график.

TFitSeries

Для аппроксимации TAChart предоставляет специалиальный объект типа TFitSeries. Такая серия имеет свойство FitEquation, которое определяет используемую формулу:

  • fePolynomial: y = b0 + b1x + b2x2 + … + bnxn. Указывает число аппроксимирующих параметров ai в свойстве ParamCount = n + 1.
  • feLinear: y = a + bx — это частный случай полинома с n = 1 и параметрами аппроксимации a and b. Он доступен как отдельный элемент, поскольку прямые линии определяют наиболее важные условия аппроксимации.
  • feExp: y = a * ebx — Эта функция также может быть к полиномиальной, хотя это не так просто заметить. Но взяв натуральный логарифм этой функции, получим ln(y) = a + bx. Теперь, когда мы аппроксимируем ln(y) вместо y, мы снова имеем линейный случай.
  • fePower: y = a * xb. Снова это может быть сведено к линейной функции путём логарифмических преобразований обеих осей.

TAChart LogAx Tutorial11.png

Довольно теории. Давайте добавим FitSeries на диаграмму: дважды кликнем по диаграмме и в редакторе серий нажмём «Add» и выберем «Least squares fit series»(аппроксимация методом наименьших квадратов) из выпадающего меню.

Во-первых, нам нужно указать серии, где она найдёт свои данные. Для этого мы свяжем свойство серии Source с ListChartSource1, как мы уже делали с линейной серией. Заметим, что один и тот же источник данных может использоваться несколькими сериями.

Надеюсь, вы помните описанную выше катастрофу с AxisIndex. Так что установим в AxisIndexY индекс левой оси, как мы делали в линейной серии.

Какую из четырёх возможностей FitEquation выбрать? Ну, данные выглядят как лежащие на прямой. Так что выберем feLinear.

TAChart LogAx Tutorial12.png

Блин! Мы видим черную аппроксимирующую кривую, но она совсем не аппроксимирует. Мы хотели прямую линию, а получили изогнутую кривую. Как такое может быть?

Причина в логарифмическом преобразовании, которое мы применили к данным оси Y. Поэтому на нашем графике показаны логарифмы, но для аппроксимации используются «сырые» данные. Мы эффективно аппроксимируем прямой данные на скриншоте в разделе Отображение меток точек, где логарифмическое преобразование ещё не было введено — понятно, что прямая линия там не подойдёт. И когда аппроксимирующая функция нарисована, логарифмическое преобразование искажает прямую линию в искривленную кривую, которую мы и видим.

С другой стороны, если логарифмизированные данные находятся на прямой линии, то наша аппроксимирующая функция не линейна, а экспоненциальна. Установим FitEquation в feExp и попробуем снова.

TAChart LogAx Tutorial13.png

Вот, намного лучше!

Теперь мы знаем, что экспоненциальный закон y = a * xb — хорошее приближение наших данных. Но как нам получить параметры a и b?

Результаты аппроксимации

Аппроксимирующая серия имеет свойство типа массив Param, содержащий параметры аппроксимации. a это Params[0], и b лежит в Params[1]. Конечно, эти значения имеют смысл только тогда, когда была произведена удачная аппроксимация. Как мы это узнаем? Ну, аппроксимирующая серия генерирует событие OnFitComplete, когда аппроксимация удачно завершается. Вот где мы можем узнать полученные параметры аппроксимации. Например, отобразим результаты аппроксимации в сообщении вместе с аппроксимирующей функцией:

procedure TForm1.Chart1FitSeries1FitComplete(Sender: TObject);
begin
  with Chart1FitSeries1 do
    ShowMessage(Format(
      'Fit result: a = %g, b = %g', [
      Param[0], Param[1]
    ]));
end;

И вот что мы получим:

TAChart LogAx Tutorial14.png

Теперь мы хотим вычислить время T за которое число транзисторов на пластине удваивается. В качестве упражнения попробуйте показать, что

T = ln(2) / b

Было бы неплохо указать время удвоения в виде дополнительной строки в заголовке графика. Для этого модифицируем обработчик события OnFitComplete следующим образом:

procedure TForm1.Chart1FitSeries1FitComplete(Sender: TObject);
begin
  Chart1.Title.Text.Add(Format(
    'The number of transistors doubles every %.0f years',
    [ln(2) / Chart1FitSeries1.Param[1]]
  ));
end;

TAChart LogAx Tutorial15.png

Вау! Это закон Мура: «Количество транзисторов на пластине удваивается каждые два года».

Исходный код

Файл проекта

program project1;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Interfaces, // this includes the LCL widgetset
  Forms, Unit1, tachartlazaruspkg
  { you can add units after this };

{$R *.res}

begin
  //RequireDerivedFormResource := True;
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

Unit1.pas

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, TAGraph, TASeries, TASources, Forms, Controls,
  Graphics, Dialogs, TACustomSource, TATransformations, TAFuncSeries;

type

  { TForm1 }

  TForm1 = class(TForm)
    Chart1: TChart;
    Chart1FitSeries1: TFitSeries;
    Chart1LineSeries1: TLineSeries;
    ChartAxisTransformations1: TChartAxisTransformations;
    ChartAxisTransformations1LogarithmAxisTransform1: TLogarithmAxisTransform;
    ListChartSource1: TListChartSource;
    ListChartSource2: TListChartSource;
    procedure Chart1FitSeries1FitComplete(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

uses
  math;

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
const
  MIN = 0;
  MAX = 12;
var
  i: Integer;
  value: double;
begin
  for i:=MIN to MAX do begin
    value := IntPower(10, i);
    ListChartSource2.Add(value, value);
  end;
  Chart1FitSeries1.ExecFit;
end;

procedure TForm1.Chart1FitSeries1FitComplete(Sender: TObject);
begin
  {
  with Chart1FitSeries1 do
    ShowMessage(Format(
      'Fit result: a = %g, b = %g', [
      Param[0], Param[1]
    ]));
  }
  Chart1.Title.Text.Add(Format(
    'The number of transistors doubles every %.0f years',
    [ln(2) / Chart1FitSeries1.Param[1]]
  ));
end;

end.

Unit1.lfm

object Form1: TForm1
  Left = 244
  Height = 356
  Top = 193
  Width = 552
  Caption = 'Form1'
  ClientHeight = 356
  ClientWidth = 552
  OnCreate = FormCreate
  LCLVersion = '1.1'
  object Chart1: TChart
    Left = 0
    Height = 356
    Top = 0
    Width = 552
    AxisList = <    
      item
        Grid.Color = clSilver
        Marks.Format = '%0:.0n'
        Marks.Source = ListChartSource2
        Marks.Style = smsCustom
        Minors = <        
          item
            Grid.Visible = False
            Intervals.Count = 9
            Intervals.MinLength = 5
            Intervals.Options = [aipUseCount]
          end>
        Title.LabelFont.Orientation = 900
        Title.LabelFont.Style = [fsBold]
        Title.Visible = True
        Title.Caption = 'Number of transistors'
        Transformations = ChartAxisTransformations1
      end    
      item
        Grid.Color = clSilver
        Intervals.MaxLength = 60
        Alignment = calBottom
        Minors = <>
        Title.LabelFont.Style = [fsBold]
        Title.Visible = True
        Title.Caption = 'Year of market introduction'
      end>
    BackColor = clWhite
    Foot.Alignment = taLeftJustify
    Foot.Brush.Color = clBtnFace
    Foot.Font.Color = clBlue
    Foot.Text.Strings = (
      'Source:'
      'http://www.intel.com/pressroom/kits/quickreffam.htm'
    )
    Foot.Visible = True
    Margins.Left = 24
    Margins.Right = 24
    Title.Brush.Color = clBtnFace
    Title.Font.Color = clBlue
    Title.Font.Style = [fsBold]
    Title.Text.Strings = (
      'Progress in Microelectronics'
    )
    Title.Visible = True
    Align = alClient
    ParentColor = False
    object Chart1LineSeries1: TLineSeries
      Marks.Format = '%2:s'
      Marks.LinkPen.Color = clGray
      Marks.Style = smsLabel
      AxisIndexY = 0
      LineType = ltNone
      Pointer.Brush.Color = clRed
      Pointer.Style = psCircle
      ShowPoints = True
      Source = ListChartSource1
    end
    object Chart1FitSeries1: TFitSeries
      AxisIndexX = 1
      AxisIndexY = 0
      FitEquation = feExp
      OnFitComplete = Chart1FitSeries1FitComplete
      ParamCount = 2
      Source = ListChartSource1
    end
  end
  object ListChartSource1: TListChartSource
    DataPoints.Strings = (
      '1972|2300|?|4004'
      '1974|6000|?|8080'
      '1978|29000|?|8086'
      '1982|134000|?|80286'
      '1986|275000|?|80386'
      '1989|1200000|?|80486'
      '1993|3100000|?|Pentium'
      '1997|7500000|?|Pentium II'
      '2001|42000000|?|Xeon'
      '2006|152000000|?|Core Duo'
      '2009|731000000|?|Core i7'
    )
    left = 240
    top = 40
  end
  object ChartAxisTransformations1: TChartAxisTransformations
    left = 243
    top = 96
    object ChartAxisTransformations1LogarithmAxisTransform1: TLogarithmAxisTransform
      Base = 10
    end
  end
  object ListChartSource2: TListChartSource
    left = 243
    top = 176
  end
end