Адаптер заполнения Amcharts, используемый в легенде, переопределяется при переключении

#javascript #typescript #amcharts #amcharts4

Вопрос:

Я работаю над проектом с учетом дизайна, который имеет индивидуальный внешний вид легенды. Используя React и TypeScript, мне нужно создать диаграмму, которая выглядит примерно так: Требуется дизайн

TLDR: Попробуйте переключить легенду на этой кодовой панели. Вопросы, перечисленные ниже.

Что я пытался:

 chart.legend = new Legend();
chart.legend.position = "top";
chart.legend.contentAlign = "left";
chart.legend.margin(0, 0, 30, 0);
// Disabled default markers that resemble color.
chart.legend.useDefaultMarker = true;
chart.legend.labels.template.marginLeft = 26;
chart.legend.labels.template.paddingTop = 3;
 

Затем я начал изменять саму легенду

 // Create checkbox outline
const marker = chart.legend.markers.template;
const markerColumn = marker.children.getIndex(0) as Sprite;
markerColumn.defaultState.properties.fillOpacity = 0;
markerColumn.defaultState.properties.strokeWidth = 1;
markerColumn.defaultState.properties.stroke = color("#3896CB");
markerColumn.defaultState.properties.strokeOpacity = 1;
 

Затем я добавил как флажок, так и спрайт круга, следуя этому руководству:

 const checkbox = marker.createChild(Image);
checkbox.width = 20;
checkbox.height = 20;
checkbox.verticalCenter = "top";
checkbox.horizontalCenter = "left";
checkbox.href = "https://cdn.onlinewebfonts.com/svg/img_207414.png";

const checkboxActiveState = checkbox.states.create("active");
checkboxActiveState.properties.opacity = 0;

const circle = marker.createChild(Circle);
circle.strokeWidth = 0;
circle.width = 8;
circle.height = 8;
circle.dx = 36;
circle.dy = 12;
 

Затем я наткнулся на проблему: я не нашел способа выбрать ТОЧНЫЕ дочерние элементы контейнера, чтобы я мог заполнить их любым цветом, который я хочу. В большинстве примеров используются шаблоны, которые изменяют каждого отдельного ребенка.
Я обнаружил, что адаптеры могут мне помочь. И написал этот код:

 const colorValues: string[] = ["#8BC4D7", "#009299", "#1ABDB5", "#3896CB"];

function createSeries(chart: XYChart, data: ChartData[], field: string, name: string, setColor: string) {
  // ... Creating line series
  series.dummyData = {
     fill: setColor,
  };
  return series;
}

function colorAdapter(fill, target) {
  if (!target.dataItem) {
    return fill;
  }
  const settings = target.dataItem.dataContext.dummyData;
  return settings.fill;
}

// Finally, adding color via dummyData:
// Correctly sets colors on first render, BUT on legend toggle forgets them
circle?.adapter.add("fill", (fill, target) => {
  if (!target.dataItem) {
    return fill;
  }
  const settings = target.dataItem.dataContext.dummyData;
  return settings.fill;
});
 

Проблема:
Попробуйте переключить легенду! Те, у которых есть фиктивные данные, остаются серыми после их повторного включения.

Вот что происходит, когда я пытаюсь включать и выключать легенду (посмотрите на первые два элемента легенды).:

Вы можете поиграть с аналогичным случаем в этом коде.

ОБНОВЛЕНИЕ: Вот мой кодовый ключ с моим делом.

Вопросы:

  1. (Основной вопрос) Как решить эту проблему с помощью адаптеров? Ожидаемый результат: после включения они должны вернуть свой цвет заливки.
  2. (Просто для расширения знаний) Если возможно, как решить эту проблему без использования адаптеров? Как с помощью жесткого кода выбрать дочерний элемент любого контейнера, который я хочу?
  3. (Альтернативное решение?) Что, если я хочу, чтобы эти круги оставались цветными даже после переключения? Как я могу это сделать?
  4. (Необязательно, это, вероятно, где-то в документах) Я не совсем понял, как заменить спрайт изображением без внешней интернет-ссылки. Может ли .href работать с svg через относительные пути?

Комментарии:

1. Не уверен, правильно ли я понимаю, но если вы хотите сохранить цвет, просто измените его на function colorAdapter(fill, target){return fill} .

2. @Lain, к сожалению, это не так. Если вы не включите эту проверку, вы получите следующую ошибку: Не удается прочитать свойства undefined (чтение «DataContext»). Попробуйте сделать это в последней версии кода, которую я предоставил. Согласно документам, это необходимо для сохранения природы.

3. @Lain Вот кодовый код, в котором я попробовал ваш ответ. К сожалению, это работает не так, как требуется.

4. Я добавил свой кодовый набор со всей имеющейся у меня кодовой базой. Я надеюсь, что сейчас вам будет удобнее работать над моим вопросом.

5. Обновлен кодовый набор с решением @Lain. Слава им.

Ответ №1:

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

 /**
 * ---------------------------------------
 * This demo was created using amCharts 4.
 * 
 * For more information visit:
 * https://www.amcharts.com/
 * 
 * Documentation is available at:
 * https://www.amcharts.com/docs/v4/
 * ---------------------------------------
 */

// Themes begin
am4core.useTheme(am4themes_animated);
// Themes end

// Create chart instance
var chart = am4core.create("chartdiv", am4charts.XYChart);

// Add data
chart.data = [ {
  "year": "2003",
  "europe": 2.5,
  "namerica": 2.5,
  "asia": 2.1,
  "lamerica": 1.2,
  "meast": 0.2,
  "africa": 0.1
}, {
  "year": "2004",
  "europe": 2.6,
  "namerica": 2.7,
  "asia": 2.2,
  "lamerica": 1.3,
  "meast": 0.3,
  "africa": 0.1
}, {
  "year": "2005",
  "europe": 2.8,
  "namerica": 2.9,
  "asia": 2.4,
  "lamerica": 1.4,
  "meast": 0.3,
  "africa": 0.1
} ];

// Create axes
var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "year";
categoryAxis.title.text = "Local country offices";
categoryAxis.renderer.grid.template.location = 0;
categoryAxis.renderer.minGridDistance = 20;
categoryAxis.renderer.cellStartLocation = 0.1;
categoryAxis.renderer.cellEndLocation = 0.9;

var  valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.min = 0;
valueAxis.title.text = "Expenditure (M)";

// Create series
function createSeries(field, name, stacked) {
  var series = chart.series.push(new am4charts.ColumnSeries());
  series.dataFields.valueY = field;
  series.dataFields.categoryX = "year";
  series.name = name;
  series.columns.template.tooltipText = "{name}: [bold]{valueY}[/]";
  series.stacked = stacked;
  series.columns.template.width = am4core.percent(95);
  // Let's try red, for example
  series.dummyData = {
    fill: "red",
  };
}

createSeries("europe", "Europe", false);
createSeries("namerica", "North America", true);
createSeries("asia", "Asia", false);
createSeries("lamerica", "Latin America", true);
createSeries("meast", "Middle East", true);
createSeries("africa", "Africa", true);

// Add legend
chart.legend = new am4charts.Legend();
chart.legend.labels.template.marginLeft = 26;
chart.legend.useDefaultMarker = true;

const marker = chart.legend.markers.template;
const markerColumn = marker.children.getIndex(0);

const markerColumnActiveState = markerColumn.states.getKey("active");
markerColumn.defaultState.properties.fillOpacity = 0;
markerColumn.defaultState.properties.strokeWidth = 1;
markerColumn.defaultState.properties.stroke = am4core.color("#000");
markerColumn.defaultState.properties.strokeOpacity = 1;

// Add custom image instead
const checkbox = marker.createChild(am4core.Image);
checkbox.width = 20;
checkbox.height = 20;
checkbox.verticalCenter = "top";
checkbox.horizontalCenter = "left";

checkbox.href = "https://cdn.onlinewebfonts.com/svg/img_207414.png";
checkbox.dx = 1;
checkbox.dy = 1;

const checkboxActiveState = checkbox.states.create("active");
checkboxActiveState.properties.opacity = 0;

const circle = marker.createChild(am4core.Circle);
circle.width = 8;
circle.height = 8;
circle.verticalCenter = "top";
circle.horizontalCenter = "left";
circle.dx = 36;
circle.dy = 8;

//REM: https://www.amcharts.com/docs/v4/concepts/states/
const circleActiveState = circle.states.create("active");
circleActiveState.adapter.add("fill", (fill, target) => {
  return "#000000"
});

const circleDefaultState = circle.states.create("default");
circleDefaultState.adapter.add("fill", (fill, target) => {
    const settings = target?.sprite?.dataItem?.dataContext?.dummyData;
    return settings?.fill || fill;
}); 
 <script src="https://www.amcharts.com/lib/4/core.js"></script>
<script src="https://www.amcharts.com/lib/4/charts.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/animated.js"></script>

<div id = 'chartdiv'></div> 

Комментарии:

1. Попытаюсь применить его к моему новейшему кодовому коду (упомянутому в TLDR и перед списком вопросов). Было бы неплохо, если бы вы все еще могли помочь мне с этим и ответить на другие вопросы. Держу вас в курсе событий!

2. Да, это сработало. Без плавного перехода цвета, но это так. Если вы не возражаете — помогите мне разобраться в других упомянутых темах в списке вопросов. С таким же успехом вы можете просто кидать мне ссылки на документы. Ty.

3. Я действительно не понимаю, что вы подразумеваете под выбором любого дочернего элемента контейнера, который я хочу? . Если вы хотите выбрать sprites те, которые есть target.sprite . Я рекомендую задать отдельный вопрос для каждого из вас с небольшим примером использования. Вопрос номер три должен быть решен и моим ответом. Для четвертого вопроса: обычно для его оформления требуется встроенный svg, если только для использования тега объекта.