Я нашел одно решение для этого. следуйте ниже:
// I use the scrollbarX to create a horizontal scrollbar
chart.scrollbarX = new am4core.Scrollbar();
// here I set the scroolbar bottom the chart
chart.scrollbarX.parent = chart.bottomAxesContainer;
//here I chose not to show the startGrip (or button that expand the series from chart)
chart.scrollbarX.startGrip.hide();
chart.scrollbarX.endGrip.hide();
// here I set the start and end scroolbarX series that I would like show in chart initially
chart.scrollbarX.start = 0;
chart.scrollbarX.end = 0.25;
// here I chose not to show the zoomOutButton that appear above from chart
chart.zoomOutButton = new am4core.ZoomOutButton();
chart.zoomOutButton.hide();
Итак, мой полный метод построения диаграммы таков:
private buildChart(dataChart) {
/* Chart code */
// Themes begin
am4core.useTheme(am4themes_animated);
// Create chart instance
const chart = am4core.create('chartdiv', am4charts.XYChart);
for (const data of dataChart) {
chart.data.push(data);
}
// Create axes
const categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = 'model';
categoryAxis.renderer.grid.template.location = 0;
const valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.renderer.inside = true;
valueAxis.renderer.labels.template.disabled = true;
valueAxis.min = 0;
// Create series
function createSeries(field, name) {
// Set up series
const series = chart.series.push(new am4charts.ColumnSeries());
series.name = name;
series.dataFields.valueY = field;
series.dataFields.categoryX = 'model';
series.sequencedInterpolation = true;
// Make it stacked
series.stacked = true;
// Configure columns
series.columns.template.width = am4core.percent(60);
series.columns.template.tooltipText = '[bold]{name}[/]\n[font-size:15px]{categoryX}: {valueY}';
// Add label
const labelBullet = series.bullets.push(new am4charts.LabelBullet());
labelBullet.label.text = '{valueY}';
labelBullet.locationY = 0.5;
return series;
}
createSeries('DISCONNECTED', 'DISCONNECTED');
createSeries('AVAILABLE', 'AVAILABLE');
// Legend
chart.legend = new am4charts.Legend();
chart.scrollbarX = new am4core.Scrollbar();
chart.scrollbarX.parent = chart.bottomAxesContainer;
chart.scrollbarX.startGrip.hide();
chart.scrollbarX.endGrip.hide();
chart.scrollbarX.start = 0;
chart.scrollbarX.end = 0.25;
chart.zoomOutButton = new am4core.ZoomOutButton();
chart.zoomOutButton.hide();
}
Следуйте распечатке ниже, показывая, как это получилось
Способ думать об этом состоит в том, чтобы "думать как компилятор".
Предполагают, что Вы пишете компилятор. И Вы видите код как это.
// file: A.h
class A {
B _b;
};
// file: B.h
class B {
A _a;
};
// file main.cc
#include "A.h"
#include "B.h"
int main(...) {
A a;
}
, Когда Вы компилируете файл .cc (помнят, что .cc а не .h является единицей компиляции), необходимо выделить место для объекта A
. Так, хорошо, сколько пространства тогда? Достаточно сохранить B
! Каков размер B
тогда? Достаточно сохранить A
! Ой.
Очевидно циклическая ссылка, которую необходимо повредить.
можно повредить его, позволив компилятору вместо этого зарезервировать столько пространства, сколько это знает о первичном - указатели и ссылки, например, будут всегда составлять 32 или 64 бита (в зависимости от архитектуры) и поэтому если бы Вы заменили (любой один) указателем или ссылкой, то вещи были бы большими. Скажем, мы заменяем в [1 111]:
// file: A.h
class A {
// both these are fine, so are various const versions of the same.
B& _b_ref;
B* _b_ptr;
};
Теперь вещи лучше. Несколько. main()
все еще говорит:
// file: main.cc
#include "A.h" // <-- Houston, we have a problem
#include
, для всех степеней и целей (если Вы вынимаете препроцессор) просто копирует файл в .cc. Таким образом, действительно .cc похож:
// file: partially_pre_processed_main.cc
class A {
B& _b_ref;
B* _b_ptr;
};
#include "B.h"
int main (...) {
A a;
}
Вы видите, почему компилятор не может иметь дело с этим - он понятия не имеет, что B
- он даже не видел символ прежде.
Так позволяют нам сказать компилятор приблизительно [1 115]. Это известно как предописание и обсуждено далее в [1 121] этот ответ .
// main.cc
class B;
#include "A.h"
#include "B.h"
int main (...) {
A a;
}
Это работы . Это не большое . Но в этой точке у Вас должно быть понимание проблемы циклической ссылки и что мы сделали для "зафиксированного" его, хотя фиксация плоха.
причина, эта фиксация плоха, состоит в том, потому что следующий человек к [1 116] должен будет объявить B
, прежде чем они смогут использовать ее и получат ужасное #include
ошибка. Поэтому давайте переместим объявление в [1 142] сам A.h.
// file: A.h
class B;
class A {
B* _b; // or any of the other variants.
};
И в [1 143] B.h, в этой точке, Вы можете всего #include "A.h"
непосредственно.
// file: B.h
#include "A.h"
class B {
// note that this is cool because the compiler knows by this time
// how much space A will need.
A _a;
}
HTH.
Можно избежать ошибок компиляции, если Вы удаляете определения метода из заголовочных файлов и позволяете классам содержать только объявления метода и переменные объявления/определения. Определения метода должны быть помещены в .cpp файл (точно так же, как в инструкции по лучшей практике говорится).
вниз сторона следующего решения (предполагающий размещение методов в заголовочный файл для встраивания их), что методы больше не встраиваются компилятором, и пытающийся использовать встроенное ключевое слово производит ошибки компоновщика.
//A.h
#ifndef A_H
#define A_H
class B;
class A
{
int _val;
B* _b;
public:
A(int val);
void SetB(B *b);
void Print();
};
#endif
//B.h
#ifndef B_H
#define B_H
class A;
class B
{
double _val;
A* _a;
public:
B(double val);
void SetA(A *a);
void Print();
};
#endif
//A.cpp
#include "A.h"
#include "B.h"
#include <iostream>
using namespace std;
A::A(int val)
:_val(val)
{
}
void A::SetB(B *b)
{
_b = b;
cout<<"Inside SetB()"<<endl;
_b->Print();
}
void A::Print()
{
cout<<"Type:A val="<<_val<<endl;
}
//B.cpp
#include "B.h"
#include "A.h"
#include <iostream>
using namespace std;
B::B(double val)
:_val(val)
{
}
void B::SetA(A *a)
{
_a = a;
cout<<"Inside SetA()"<<endl;
_a->Print();
}
void B::Print()
{
cout<<"Type:B val="<<_val<<endl;
}
//main.cpp
#include "A.h"
#include "B.h"
int main(int argc, char* argv[])
{
A a(10);
B b(3.14);
a.Print();
a.SetB(&b);
b.Print();
b.SetA(&a);
return 0;
}
Вещи помнить:
class A
будет иметь объект class B
как участник или наоборот. Чтение FAQ:
Я когда-то решил этот вид проблемы путем перемещения всего , встраивает после того, как определение класса и помещение #include
для других классов незадолго до эти встраивает в заголовочном файле. Таким образом, каждый удостоверяется, что все definitions+inlines установлены предшествующие, встраивание анализируется.
Выполнение как это позволяет все еще иметь набор, встраивает в обоих (или несколько) заголовочные файлы. Но необходимо иметь , включают защиту .
Как это
// File: A.h
#ifndef __A_H__
#define __A_H__
class B;
class A
{
int _val;
B *_b;
public:
A(int val);
void SetB(B *b);
void Print();
};
// Including class B for inline usage here
#include "B.h"
inline A::A(int val) : _val(val)
{
}
inline void A::SetB(B *b)
{
_b = b;
_b->Print();
}
inline void A::Print()
{
cout<<"Type:A val="<<_val<<endl;
}
#endif /* __A_H__ */
... и выполнение того же в B.h