AMCharts Maps v4 - есть ли способ использовать более двух цветов в HeatLegend?

Думал, что было бы неплохо иметь возможность установить диапазон цветов для тепловой карты вместо двух (мин и макс). Как то, что мы делаем для списка градиентов.

Что-то вроде...

function am4themes_myHeatmap(target) {
  if (target instanceof am4core.ColorSet) {
    target.list = [
      am4core.color("#F7E3D4"),
      am4core.color("#FFC480"),
      am4core.color("#DC60BF"),
      am4core.color("#A43B7D"),
      am4core.color("#5B0A25")
    ];
  }
}

Смотрите пример из макета

Если что-то подобное уже существует, я бы с радостью это увидел.

1 ответ

Нечто подобное не существовало.

К сожалению, нет простого способа использовать heatRules а также HeatLegend и пусть они используют дополнительные цвета. Но подражать не слишком сложно heatRules и если вы используете только 1 marker в вашем HeatLegend (т. е. один длинный столбец в отличие от нескольких столбцов), чтобы переопределить его градиент на собственный.

Я взял 2 цвета из предоставленного вами изображения и бросил их и черный в массив:

var heatColors = [
  am4core.color("rgb(248, 227, 211)"),
  am4core.color("rgb(237, 137, 166)"),
  am4core.color("rgb(0,0,0)")
];

Это не обязательно, но может быть полезно. Это цвета трехцветного градиента. Я решил использовать 3 цвета, чтобы мы могли разделить вычисления равномерно между левой / правой половинами градиента, это должно упростить демонстрацию ниже. Для картинки, которой вы поделились, может потребоваться дополнительный цвет в левой половине, вам придется соответствующим образом скорректировать вычисления, но это так же выполнимо.

Подражать heatRules мы предоставим адаптер для mapPolygons ' fill, Там мы сравним mapPolygon"s value против мин / макс значений, последние могут быть найдены через серию dataItem.values["value"].low а также .high, Это даст нам процент в десятичных числах, чтобы получить цвет из диапазона цветов. Функция полезности для выбора цвета из диапазона am4core.colors.interpolate его первые два аргумента iRGB s (простой объект с r, g, b, а также a свойства / значения), а третий процент в десятичных числах. Если процент находится в пределах первой половины, у нас будет адаптер, возвращающий цвет между первыми двумя heatColors выше, если это во второй половине, мы вернем цвет из последних двух.

Вот как выглядит этот код:

polygonSeries.mapPolygons.template.adapter.add("fill", function(
  fill,
  mapPolygon
) {
  var workingValue = mapPolygon.dataItem.values["value"].workingValue;
  var minValue = polygonSeries.dataItem.values["value"].low;
  var maxValue = polygonSeries.dataItem.values["value"].high;
  var percent = (workingValue - minValue) / (maxValue - minValue);
  if (am4core.type.isNumber(percent)) {
    if (percent > 0.5) {
      return new am4core.Color(
        am4core.colors.interpolate(
          heatColors[1].rgb,
          heatColors[2].rgb,
          (percent - 0.5) * 2
        )
      );
    } else {
      return new am4core.Color(
        am4core.colors.interpolate(
          heatColors[0].rgb,
          heatColors[1].rgb,
          percent * 2
        )
      );
    }
  }
  return fill;
});

Если у вас есть 1 маркер heatLegend То есть, просто полоса с градиентом, вы можете создать свой собственный градиент и назначить его в адаптере:

var gradient = new am4core.LinearGradient();
heatColors.forEach(function(color) {
  gradient.addColor(color);
});
heatLegend.markers.template.adapter.add("fill", function() {
  return gradient;
});

Если у вас есть несколько маркеров в heatLegend (согласно верхней легенде на вашей картинке), пользовательские цвета будут больше похожи на то, что мы сделали для heatRules кроме как вместо адаптера, потому что нам нужно знать их место и нет dataItem или же index доступно, мы будем перебирать markers как только они будут готовы, а затем переопределить их цвета там:

var heatLegendTop = chart.createChild(am4maps.HeatLegend);
heatLegendTop.series = polygonSeries;
heatLegendTop.minColor = heatColors[0];
heatLegendTop.maxColor = heatColors[2];
heatLegendTop.marginBottom = 10;
heatLegendTop.markerCount = 10;
heatLegendTop.events.on("inited", function() {
  heatLegendTop.markers.each(function(marker, markerIndex) {

    // Gradient colors!
    if (markerIndex < heatLegendTop.markerCount / 2) {
      marker.fill = new am4core.Color(
        am4core.colors.interpolate(
          heatColors[0].rgb,
          heatColors[1].rgb,
          (markerIndex / heatLegendTop.markerCount) * 2
        )
      );
    } else {
      marker.fill = new am4core.Color(
        am4core.colors.interpolate(
          heatColors[1].rgb,
          heatColors[2].rgb,
          ((markerIndex - heatLegendTop.markerCount / 2) /
            heatLegendTop.markerCount) *
            2
        )
      );
    }
  });
});

Я разобрал нашу демонстрацию карты тепла США (choropleth) с приведенным выше кодом, а затем еще немного, чтобы приблизиться к внешнему виду изображения, которым вы поделились:

https://codepen.io/team/amcharts/pen/7fd84c880922a6fc50f80330d778654a

Я упростил демонстрацию и сделал ее адаптивной: https://codepen.io/team/amcharts/pen/eYJZVEV

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

// Create map instance
var chart = am4core.create("chartdiv", am4maps.MapChart);

// Set map definition
chart.geodata = am4geodata_usaAlbersLow;

// Set projection
chart.projection = new am4maps.projections.Miller();

// Create map polygon series
var polygonSeries = chart.series.push(new am4maps.MapPolygonSeries());

// Make map load polygon data (state shapes and names) from GeoJSON
polygonSeries.useGeodata = true;

// //Set min/max fill color for each area
// polygonSeries.heatRules.push({
//   property: "fill",
//   target: polygonSeries.mapPolygons.template,
//   min: chart.colors.getIndex(1).brighten(1),
//   max: chart.colors.getIndex(1).brighten(-0.3)
// });

// make room for heatLegends
chart.chartContainer.paddingBottom = 50;

// Base colors for custom "heatRules" gradient
var heatColors = [
  am4core.color("rgb(248, 227, 211)"),
  am4core.color("rgb(237, 137, 166)"),
  am4core.color("rgb(0,0,0)")
];

// Let hover state colors be relative to the "heatRule" color
var hoverState = polygonSeries.mapPolygons.template.states.create("hover");
hoverState.adapter.add("fill", function(fill) {
  return fill.lighten(-0.1);
});

// Emulate heatRule but with 2 color ranges instead of 1
polygonSeries.mapPolygons.template.adapter.add("fill", function(
  fill,
  mapPolygon
) {
  var workingValue = mapPolygon.dataItem.values["value"].workingValue;
  var minValue = polygonSeries.dataItem.values["value"].low;
  var maxValue = polygonSeries.dataItem.values["value"].high;
  var percent = (workingValue - minValue) / (maxValue - minValue);
  // This may run before workingValue is even a thing. Let's only do our thing
  // if workingValue and ergo percent are a thing.
  if (am4core.type.isNumber(percent)) {
    if (percent > 0.5) {
      return new am4core.Color(
        am4core.colors.interpolate(
          heatColors[1].rgb,
          heatColors[2].rgb,
          (percent - 0.5) * 2
        )
      );
    } else {
      return new am4core.Color(
        am4core.colors.interpolate(
          heatColors[0].rgb,
          heatColors[1].rgb,
          percent * 2
        )
      );
    }
  }
  return fill;
});

// Set up heat legends
var heatLegendTop = chart.createChild(am4maps.HeatLegend);
heatLegendTop.series = polygonSeries;
heatLegendTop.align = "center";
heatLegendTop.width = am4core.percent(38);
heatLegendTop.minValue = 0;
heatLegendTop.maxValue = 40000000;
heatLegendTop.minColor = heatColors[0];
heatLegendTop.maxColor = heatColors[2];
heatLegendTop.marginBottom = 10;
heatLegendTop.markerCount = 10;
heatLegendTop.markerContainer.minHeight = 10;
heatLegendTop.markers.template.minHeight = 10;
var markerWidth = 20;
heatLegendTop.events.on("inited", function() {
  heatLegendTop.markers.each(function(marker, markerIndex) {
    // Override default heatLegend functionality
    marker.width = markerWidth;

    // Distribute the space, this needs to be repeated e.g. on window resize,
    // orientation change, etc.
    if (markerIndex < heatLegendTop.markers.length - 1) {
      marker.marginRight =
        (heatLegendTop.markerContainer.pixelWidth -
          heatLegendTop.markerCount * markerWidth) /
        (heatLegendTop.markerCount - 1);
    }

    // Gradient colors!
    if (markerIndex < heatLegendTop.markerCount / 2) {
      marker.fill = new am4core.Color(
        am4core.colors.interpolate(
          heatColors[0].rgb,
          heatColors[1].rgb,
          (markerIndex / heatLegendTop.markerCount) * 2
        )
      );
    } else {
      marker.fill = new am4core.Color(
        am4core.colors.interpolate(
          heatColors[1].rgb,
          heatColors[2].rgb,
          ((markerIndex - heatLegendTop.markerCount / 2) /
            heatLegendTop.markerCount) *
            2
        )
      );
    }
  });
});

// Blank out internal heat legend value axis labels
heatLegendTop.valueAxis.renderer.labels.template.disabled = true;

var heatLegend = chart.createChild(am4maps.HeatLegend);
heatLegend.series = polygonSeries;
heatLegend.align = "center";
heatLegend.width = am4core.percent(38);
heatLegend.minValue = 0;
heatLegend.maxValue = 40000000;
heatLegend.markerContainer.minHeight = 10;
heatLegend.markers.template.minHeight = 10;

// Set up custom heat map legend labels using axis ranges
var minRange = heatLegend.valueAxis.axisRanges.create();
minRange.value = heatLegend.minValue;
minRange.label.inside = true;
minRange.label.horizontalCenter = "right";
minRange.label.dy = 5;
minRange.label.dx = -3;
minRange.label.text = "Less";
var maxRange = heatLegend.valueAxis.axisRanges.create();
maxRange.value = heatLegend.maxValue;
maxRange.label.inside = true;
maxRange.label.horizontalCenter = "left";
maxRange.label.dy = 5;
maxRange.label.dx = 3;
maxRange.label.text = "More";

// Blank out internal heat legend value axis labels
heatLegend.valueAxis.renderer.labels.template.adapter.add("text", function(
  labelText
) {
  return "";
});

// Allow the heatLegend to function in general
heatLegend.minColor = heatColors[0];
heatLegend.maxColor = heatColors[2];

// Override heatLegend gradient
var gradient = new am4core.LinearGradient();
heatColors.forEach(function(color) {
  gradient.addColor(color);
});
heatLegend.markers.template.adapter.add("fill", function() {
  return gradient;
});

// Configure series tooltip
var polygonTemplate = polygonSeries.mapPolygons.template;
polygonTemplate.tooltipText = "{name}: {value}";

// // Create hover state and set alternative fill color
// var hs = polygonTemplate.states.create("hover");
// hs.properties.fill = am4core.color("#3c5bdc");

// Set heatmap values for each state
polygonSeries.data = [
  {
    id: "US-AL",
    value: 4447100
  },
  {
    id: "US-AK",
    value: 626932
  },
  {
    id: "US-AZ",
    value: 5130632
  },
  {
    id: "US-AR",
    value: 2673400
  },
  {
    id: "US-CA",
    value: 33871648
  },
  {
    id: "US-CO",
    value: 4301261
  },
  {
    id: "US-CT",
    value: 3405565
  },
  {
    id: "US-DE",
    value: 783600
  },
  {
    id: "US-FL",
    value: 15982378
  },
  {
    id: "US-GA",
    value: 8186453
  },
  {
    id: "US-HI",
    value: 1211537
  },
  {
    id: "US-ID",
    value: 1293953
  },
  {
    id: "US-IL",
    value: 12419293
  },
  {
    id: "US-IN",
    value: 6080485
  },
  {
    id: "US-IA",
    value: 2926324
  },
  {
    id: "US-KS",
    value: 2688418
  },
  {
    id: "US-KY",
    value: 4041769
  },
  {
    id: "US-LA",
    value: 4468976
  },
  {
    id: "US-ME",
    value: 1274923
  },
  {
    id: "US-MD",
    value: 5296486
  },
  {
    id: "US-MA",
    value: 6349097
  },
  {
    id: "US-MI",
    value: 9938444
  },
  {
    id: "US-MN",
    value: 4919479
  },
  {
    id: "US-MS",
    value: 2844658
  },
  {
    id: "US-MO",
    value: 5595211
  },
  {
    id: "US-MT",
    value: 902195
  },
  {
    id: "US-NE",
    value: 1711263
  },
  {
    id: "US-NV",
    value: 1998257
  },
  {
    id: "US-NH",
    value: 1235786
  },
  {
    id: "US-NJ",
    value: 8414350
  },
  {
    id: "US-NM",
    value: 1819046
  },
  {
    id: "US-NY",
    value: 18976457
  },
  {
    id: "US-NC",
    value: 8049313
  },
  {
    id: "US-ND",
    value: 642200
  },
  {
    id: "US-OH",
    value: 11353140
  },
  {
    id: "US-OK",
    value: 3450654
  },
  {
    id: "US-OR",
    value: 3421399
  },
  {
    id: "US-PA",
    value: 12281054
  },
  {
    id: "US-RI",
    value: 1048319
  },
  {
    id: "US-SC",
    value: 4012012
  },
  {
    id: "US-SD",
    value: 754844
  },
  {
    id: "US-TN",
    value: 5689283
  },
  {
    id: "US-TX",
    value: 20851820
  },
  {
    id: "US-UT",
    value: 2233169
  },
  {
    id: "US-VT",
    value: 608827
  },
  {
    id: "US-VA",
    value: 7078515
  },
  {
    id: "US-WA",
    value: 5894121
  },
  {
    id: "US-WV",
    value: 1808344
  },
  {
    id: "US-WI",
    value: 5363675
  },
  {
    id: "US-WY",
    value: 493782
  }
];
Другие вопросы по тегам