AMCharts Maps v4 - Is there a way to have more than two colors in a heatLegend?

北战南征 提交于 2020-06-14 07:13:30

问题


Was thinking that it would be nice to be able to set a range of colors for a heatmap instead of just two (min and max). Like what we do for the gradient list.

Something like...

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")
    ];
  }
}

See example from mockup

If something like this already exists, I would the love to see it.


回答1:


Something like this didn't exist.

Unfortunately there's not a simple way to just use heatRules and HeatLegend and have them use additional colors. But it's not overly complicated to emulate heatRules and if you're only using 1 marker in your HeatLegend (i.e. one long bar as opposed to multiple bars), to override its gradient with a custom one.

I grabbed 2 colors from the image you provided and threw those and black in an array:

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

It's not necessary, but can be useful. Those are the colors of a 3-color gradient. I elected to use 3 colors so we can split the calculations evenly between left/right halves of the gradient, it ought to simplify the demo below. The picture you shared might require an additional color in the left half, you would have to adjust calculations accordingly, but it's just as doable.

To emulate heatRules, we'll provide an adapter for mapPolygons' fill. In there, we'll compare a mapPolygon's value against the min/max of the values, the latter can be found via the series' dataItem.values["value"].low and .high. This will give us a percentage in decimals to grab a color from a range of colors. The utility function to pick a color from a range is am4core.colors.interpolate, its first two arguments are iRGBs (plain object with r, g, b, and a properties/values), and the third is the percentage in decimals. If the percentage is within the first half, we'll have the adapter return a color between the first two in heatColors above, if it's in the second half, we'll return a color from the latter two.

Here's what that code looks like:

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;
});

If you have a 1-marker heatLegend, i.e. just a bar with a gradient going across, you can make your own gradient and assign it in an adapter, too:

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

If you have multiple markers in a heatLegend (as per the top heat legend in your picture), custom coloring would be more like what we did for the heatRules, except instead of an adapter, because we need to know their place and there's no dataItem or index available, we'll iterate through the markers once they're ready and then override their colors there:

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
        )
      );
    }
  });
});

I forked our US heat (choropleth) map demo with the above code and then some to get closer to the look/feel of the image you shared:

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




回答2:


I simplified the demo and made it responsive: 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
  }
];


来源:https://stackoverflow.com/questions/53860518/amcharts-maps-v4-is-there-a-way-to-have-more-than-two-colors-in-a-heatlegend

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!