How do I change the text/style in the balloon on the Treemap chart from amchart 4 ?
In this example below, how do I remove the "Ford" from the balloon?
My sincere apologies for the super late answer, the question showed up recently related to another, not sure how this slipped through the cracks.
This is a good question for at least 2 reasons:
At the time we did not have a guide on modifying tooltip background color.
TreeMap charts are not like others, we're not working directly with an actual TreeMapSeries.
If you're just looking to edit the tooltip's text, we can do this via the TreeMapSeries template, specifically on its column template, i.e. columns.template.tooltipText, e.g. using the original demo as a base:
// The default `tooltipText` for all columns, e.g.
// `chart.series.getIndex(0).columns.template.tooltip`, is
// `"{parentName} {name}: {value}"`
//
// Let's keep {parentName} on a separate line in `tooltipText`, and play with
// font size, colors, and style. (Note we cannot nest formatting brackets.)
//
// More on string and visual formatting:
// https://www.amcharts.com/docs/v4/concepts/formatters/formatting-strings/
level1SeriesTemplate.columns.template.tooltipText =
"[bold font-size: 22px; #fff]{parentName}[/]\n[font-size: 20px]{name}:[/] [font-size: 20px #fff]{value}[/]";
But if you're looking to do more than that, e.g. modify the background, you'll need to work on the actual columns themselves, since actual tooltip objects are not available on column templates. Here's a way of going about this for TreeMapSeries and their columns as soon as they're ready:
// Looking over this chart type, i.e. TreeMap, we find it has a
// seriesContainer property:
// https://www.amcharts.com/docs/v4/reference/treemap/#seriesContainer_property
//
// and all containers have a "childadded" event:
// https://www.amcharts.com/docs/v4/reference/container/#childadded_event
//
// which works just as expected.
//
// More on events here:
// https://www.amcharts.com/docs/v4/concepts/event-listeners/
chart.seriesContainer.events.on("childadded", function(event) {
// The chart will load with the initial series at first,
// we're not interested in that.
if (chart.series.length === 1) return;
// Once we click a car company / TreeMap column, a new series will be generated
// and added to the seriesContainer. Here, event.target will be the seriesContainer,
// and event.newValue will always be the new TreeMapSeries.
var series = event.newValue;
// level-/depth-specific code, if you wanted
// if (series.level === 1) {
// }
// The series exists, but is not ready/populated yet. In general, datavalidated
// is a good event to check against for initial load/readiness of a series.
series.events.once("datavalidated", function() {
series.columns.each(function(column) {
// In order to customize tooltip colors, we need to set getFillFromObject to false,
// otherwise as it sounds, it'll grab color data from the parent object.
// https://www.amcharts.com/docs/v4/reference/tooltip/#getFillFromObject_property
// https://www.amcharts.com/docs/v4/reference/tooltip/#getStrokeFromObject_property
column.tooltip.getFillFromObject = false;
// column.tooltip.getStrokeFromObject = false; // not needed, since it defaults to false
column.tooltip.background.stroke = am4core.color("#fff");
column.tooltip.background.strokeWidth = 3;
column.tooltip.background.strokeOpacity = 0.3;
// background of tooltip, let's make it darker than the column's bg.
column.tooltip.background.fill = am4core.color(
am4core.colors.brighten(column.fill.rgb, -0.3)
);
// let's also make it slightly transparent.
column.tooltip.background.fillOpacity = 0.8;
// tooltip text color, we can also set this via string visual formatting,
// see `tooltipText` assignment further below.
// Every Tooltip has a Label:
// https://www.amcharts.com/docs/v4/reference/tooltip/#label_property
// https://www.amcharts.com/docs/v4/reference/label/
//
// Let's make the default color brighter than the column bg color.
column.tooltip.label.fill = am4core.color(
am4core.colors.brighten(column.fill.rgb, 0.7)
);
// alignment of text within tooltip (cannot use string visual formatting for this).
// Let's center the text, mainly the title/parent company.
column.tooltip.label.textAlign = "middle";
});
});
});
I've prepared a demo here:
https://codepen.io/team/amcharts/pen/07c3ca3e33b4ad955246893d19df3a6c/
Hope this covers the things you may have been interested in modifying.
Related
I'm mapping a series of points with amChart; after loading the data from an external JSON source, the map re-centers instead of staying at the point I'd set earlier with chart.homeGeoPoint.
I believe I need to use an event listener and set the homeGeoPoint after the map renders the points... but I'm at a bit of a loss; the only events I've found are from dataSource.events, and those appear to be related to fetching/parsing the JSON, as opposed to rendering the map.
// Create map instance
var chart = am4core.create("chartdiv", am4maps.MapChart);
// Set map definition
chart.geodata = am4geodata_region_world_northAmericaLow;
// Set projection
chart.projection = new am4maps.projections.Miller();
// Initial Position / Zoom
chart.homeZoomLevel = 2.6;
chart.homeGeoPoint = {
latitude: 39,
longitude: -96.2456
};
// Series for World map
var worldSeries = chart.series.push(new am4maps.MapPolygonSeries());
worldSeries.useGeodata = true;
// Markers
// Create image series
var imageSeries = chart.series.push(new am4maps.MapImageSeries());
// Create a circle image in image series template so it gets replicated to all new images
var imageSeriesTemplate = imageSeries.mapImages.template;
var circle = imageSeriesTemplate.createChild(am4core.Circle);
circle.radius = 5;
circle.fill = am4core.color("#116ad6");
circle.stroke = am4core.color("#FFFFFF");
circle.strokeWidth = 2;
circle.nonScaling = true;
circle.tooltipText = "{title}";
// Set property fields
imageSeriesTemplate.propertyFields.latitude = "latitude";
imageSeriesTemplate.propertyFields.longitude = "longitude";
imageSeriesTemplate.propertyFields.url = "url";
// Load data
imageSeries.dataSource.url = "/foo/map-points.php";
imageSeries.dataSource.parser = new am4core.JSONParser();
imageSeries.dataSource.parser.options.emptyAs = 0;
// Center after render
imageSeries.dataSource.events.on("done", function(ev) {
// This doesn't work - perhaps it is firing too early?
chart.homeGeoPoint = {
latitude: 39,
longitude: -96.2456
};
});
By request, here is a foo.json file for expirmenting with.
[{"title":"ISP","url":"\/airport\/kisp\/","latitude":40.7952,"longitude":-73.1002},{"title":"AEX","url":"\/airport\/kaex\/","latitude":31.3274,"longitude":-92.5486}]
What do I need to do to make sure the map stays centered on my desired location after the JSON data are loaded and rendered?
I've created an issue on GitHub in regards to why the map re-positions on the MapImageSeries' dataSource load and how to better work with that. (If you've a GitHub account, please subscribe to the issue.)
In the meantime, presuming the first time your dataSource gets its data that the user hasn't moved the map and we want to maintain homeGeoPoint as the current center, we can chain events to achieve that.
When the dataSource is "done" with its data, that doesn't necessarily imply anything has been done on the actual map level. The data still needs to propagate to the MapImageSeries, that still needs to create MapImages per data item, have the data validated/parsed there, and for whatever reason the map position shifts around. So the first time that happens (using events.once instead of events.on), we then listen for the MapImageSeries' "datavalidated" event also only one time (because "datavalidated" will have run before this, e.g. as soon as you create the MapImageSeries, if no data is supplied or it's taking some time, it will still run the event and the "inited" event, i.e. I guess you can say the series itself will successfully render nothing).
And to center the map we use chart.goHome(0);, this method will zoom to your homeGeoPoint and homeZoomLevel, the 0 is for how long the animation duration should run, i.e. just do the work, don't animate.
So all that together will look something like this:
// Center after render
imageSeries.dataSource.events.once("done", function(ev) {
imageSeries.events.once("datavalidated", function() {
chart.goHome(0);
});
});
Here's a demo:
https://codepen.io/team/amcharts/pen/239bfdc8689c65468df32d71b29759b8
Even though the map does re-position once the MapImageSeries loads, then it re-centers with the above code, I haven't actually seen the map shift at all anymore. So it looks to me the above code is doing the job of maintaining the homeGeoPoint. Let me know if that is still the case once implemented in your application.
My goal is to be able to click on a country and fill that polygon background of that country with a specific color.
As soon as I select another country the previous background should revert to the standard color.
How to reproduce the bug: Click on a country on the codepen and then click on another country. Now hover over the first one and it will be still "active".
It does work when I only click on the country, however after hovering over the country it will use the specific color instead of the standard color (just as if it was selected).
Here is the codepen:
https://codepen.io/ms92o/pen/KBXqLO
And here are the specific code snippets which I have created:
I think the problem arises from the state which the country gets after the hover effect is over. I thought that the default state will get used which I have provided a standard fill color.
var selectedPolygon;
polygonTemplate.events.on("hit", function(ev) {
let polygon = ev.target;
if(selectedPolygon) {
selectedPolygon.defaultState.properties.fill = am4core.color("#74B266");
selectedPolygon.setState('default');
}
polygon.setState('active');
selectedPolygon = polygon;
})
/* Create hover state and set alternative fill color */
var hs = polygonTemplate.states.create("hover");
hs.properties.fill = am4core.color("#367B25");
/* Create active state and set fill different fill color */
var as = polygonTemplate.states.create("active");
as.properties.fill = am4core.color("#FF0000");
So my question is: what state does the polygon have after the hover effect?
Generally, on hovering out, the sprite returns back to default state.
In this case, the behaviors were unexpected because that custom state, "active", is actually a reserved name state. It's used when selecting polygons in other charts like you're doing here. Instead of returning to default, it returns to active. To avoid messes between hover, default, and active states, a new state "hoverActive" was introduced in beta.45.
If we change the state name/key to something else, we'll find the behavior is more expected, so refactoring "active" out of your code with some arbitrary key back in we get:
var customActiveState = "selectedCountry";
var selectedPolygon;
polygonTemplate.events.on("hit", function(ev) {
/* NEW: Create an active state and set fill color to red */
let polygon = ev.target;
if(selectedPolygon) {
selectedPolygon.defaultState.properties.fill = am4core.color("#74B266");
selectedPolygon.setState('default');
}
polygon.setState(customActiveState);
selectedPolygon = polygon;
})
/* Create hover state and set alternative fill color */
var hs = polygonTemplate.states.create("hover");
hs.properties.fill = am4core.color("#367B25");
/* Create active state and set fill different fill color */
var as = polygonTemplate.states.create(customActiveState);
as.properties.fill = am4core.color("#FF0000");
Example: https://codepen.io/team/amcharts/pen/477e8a80e09b4bf87128cd72cc082741
But that's not very useful as we lose the red color as soon as we hover out.
So there're some routes we can take, simpler method is how to make use of the reserved active state and get it to work for you, or just manage the defaultState almost just as you've been doing.
Method 1 - Making use of the togglable and isActive properties of Sprite (which MapPolygons extend).
Once a sprite is togglable, clicking it toggles the active state automatically, so we leave that logic out of the hit event, and just ensure a previously toggled country is detoggled for sure:
var selectedPolygon;
polygonTemplate.events.on("hit", function(ev) {
/* NEW: Create an active state and set fill color to red */
let polygon = ev.target;
if(selectedPolygon && selectedPolygon !== polygon) {
selectedPolygon.isActive = false;
}
selectedPolygon = polygon;
})
/* Create hover state and set alternative fill color */
var hs = polygonTemplate.states.create("hover");
hs.properties.fill = am4core.color("#367B25");
/* Create active state and set fill different fill color */
var as = polygonTemplate.states.create("active");
as.properties.fill = am4core.color("#FF0000");
Example: https://codepen.io/notacouch/pen/259c803601ff6f286a294d1477f022ba
Method 2 - Omit the whole custom active state, take the code you already have, and override both the newly-selected polygon's current and its default state's fills (we have to override the current fill, because if you just override the defaultState's fill, you have to wait til you hover out to see it):
var selectedPolygon;
polygonTemplate.events.on("hit", function(ev) {
/* NEW: Create an active state and set fill color to red */
let polygon = ev.target;
if(selectedPolygon) {
selectedPolygon.defaultState.properties.fill = am4core.color("#74B266");
selectedPolygon.setState('default');
}
polygon.defaultState.properties.fill = polygon.fill = am4core.color("#FF0000");
selectedPolygon = polygon;
})
/* Create hover state and set alternative fill color */
var hs = polygonTemplate.states.create("hover");
hs.properties.fill = am4core.color("#367B25");
Example: https://codepen.io/team/amcharts/pen/67121e27c3c1d50d669bca0ed040f746
I have created a Scene with ILNumerics that consists of 3 PlotCubes and a Colorbar.
Screenshot of the ILPanel
I wanted to add a method that exports the scene as an image in two ways, the first being the screenshot you see above.
The second export is supposed to only show the centric cube.
I attempted to follow the guidelines of ILNumerics for scene management.
I wrote the following code:
public void ExportAsImage(int resolutionWidth, int resolutionHeight, string path, bool includeSubCubes)
{
using (ILScope.Enter())
{
ILGDIDriver backgroundDriver = new ILGDIDriver(resolutionWidth, resolutionHeight, ilPanel1.Scene);
if (includeSubCubes)
{
// code for standard export here
}
else
{
// setting left and top cube and color bar invisible and
// adjusting main cube size is affecting the ilPanel.Scene
backgroundDriver.Scene.First<ILColorbar>().Visible = false;
GetElementByTag<ILPlotCube>(backgroundDriver.Scene, _leftCubeTag).Visible = false;
GetElementByTag<ILPlotCube>(backgroundDriver.Scene, _topCubeTag).Visible = false;
GetElementByTag<ILPlotCube>(backgroundDriver.Scene, _mainCubeTag).ScreenRect = new RectangleF(0, 0, 1, 1);
GetElementByTag<ILPlotCube>(backgroundDriver.Scene, _mainCubeTag).DataScreenRect = new RectangleF.Empty;
backgroundDriver.Scene.Configure();
backgroundDriver.Render();
// save image
backgroundDriver.BackBuffer.Bitmap.Save(path,System.Drawing.Imaging.ImageFormat.Png);
// revert changes done to cubes and color bar
backgroundDriver.Scene.First<ILColorbar>().Visible = true;
GetElementByTag<ILPlotCube>(backgroundDriver.Scene, _leftCubeTag).Visible = true;
GetElementByTag<ILPlotCube>(backgroundDriver.Scene, _topCubeTag).Visible = true;
AdjustCubeSizes();
}
}
}
Note: "GetElementByTag" is an own implementation to retrieve objects in the ILNumerics Scene.
I first expected that the new driver basically creates a copy of the Scene I can work on, but like the code shows I have to revert all changed after the export or the displayed ilPanel only shows the scene the way I exported it.
Is it possible at all to do export to image without affecting the real Scene? Am I just missing some details?
Regards,
Florian S.
Florian, it does make a copy. But you need to add the interesting part to a new scene. The magic is happening in the Add() method:
var scene4render = new ILScene();
scene4render.Add(oldScene.First<ILPlotCube>(mytag));
// ... configure scene4render here, it will be detached from the original scene
// with the exception of shared buffers.
// ... proceed with rendering
In order to also include + render interactive state changes to the original plot cube (let's say rotations by the users mouse) you'd use something like that:
scene4render.Add(panel.SceneSyncRoot.First<ILPlotCube>(mytag));
Also, I wonder what GetElementByTag does better than ILGroup.First<T>(tag, predicate) or ILGroup.Find<T>(...)?
See also: http://ilnumerics.net/scene-management.html
I have been looking at a way to save off my client side edited grid data automatically when the user changes to another row (just like in access, sql management studio etc). It really seems to be a bit of a challenge to do.
One scheme was to use the data source sync, but this ha the problem of loosing our cell position (it always jumped to cell 0, 0).
I have been shown some clever work arounds (go back to the cell after the case, which by the way is hugely appreciated thanks),
but it after some lengthy testing (by myself and others) seemed to be a little "glitchy" (perhaps I just need to work on this more)
At any rate, I wanted to explore perhaps not using this datasource sync and perhaps just do the server side calls "manually" (which is a bit is a pity, but if that's what we need to do, so be it). If I do this, I would want to reset the cell little red cell "dirty" indicators.
I thought I could use something similar to this scheme (except rather than resetting the flag, I want to unset).
So, as in the above link, I have the following..
var pendingChanges = [];
function gridEdit(e) {
var cellHeader = $("#gridID").find("th[data-field='" + e.field + "']");
if (cellHeader[0] != undefined) {
var pendingChange = new Object();
pendingChange.PropertyName = e.field;
pendingChange.ColumnIndex = cellHeader[0].cellIndex;
pendingChange.uid = e.items[0].uid;
pendingChanges.push(pendingChange);
}
}
where we call gridEdit from the datasource change..
var dataSrc = new kendo.data.DataSource({
change: function (e) {
gridEdit(e);
},
Now assuming we have a callback that detects the row change, I thought I could do the following...
// clear cell property (red indicator)
for (var i = 0; i < pendingChanges.length; i++) {
var row = grid.tbody.find("tr[data-uid='" + pendingChanges[i].uid + "']");
var cell = row.find("td:eq(" + pendingChanges[i].ColumnIndex + ")");
if (cell.hasClass("k-dirty-cell")) {
cell.removeClass("k-dirty-cell");
console.log("removed dirty class");
}
}
pendingChanges.length = 0;
// No good, we loose current cell again! (sigh..)
//grid.refresh();
When this didn't work, I also tried resetting the data source dirty flag..
// clear dirty flag from the database
var dirtyRows = $.grep(vm.gridData.view(),
function (item) {
return item.dirty == true;
})
if (dirtyRows && dirtyRows.length > 0) {
dirtyRows[0].dirty = false;
}
demo here
After none of the above worked, I tried the grid.refresh(), but this has the same problem as the datasource sync (we loose our current cell)
Would anyone have any idea how I can clear this cell indicator, without refreshing the whole grid that seems to totally loose our editing context?
Thanks in advance for any help!
Css :
.k-dirty-clear {
border-width:0;
}
Grid edit event :
edit: function(e) {
$("#grid .k-dirty").addClass("k-dirty-clear"); //Clear indicators
$("#grid .k-dirty").removeClass("k-dirty-clear"); //Show indicators
}
http://jsbin.com/celajewuwe/2/edit
Simple solution for resolve that problem is to override the color of the "flag" to transparent.
just override the ".k-dirty" class (border-color)
just adding the above lines to your css
CSS:
//k-dirty is the class that kendo grid use for mark edited cells that not saved yet.
//we override that class cause we do not want the red flag
.k-dirty {
border-color:transparent transparent transparent transparent;
}
This can also be done by applying the below style,
<style>
.k-dirty{
display: none;
}
</style>
Well, I've been reading the documentation and I believe that I'm calling functions and passing parameters correctly, but for the life of me I can't get this simple UI code to work.
I'm generating a UI for a Spreadsheet using the following code:
function checkOut() {
var app = buildUI();
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
function buildUI() {
var gui = UiApp.createApplication();
gui.setTitle("Check-Out/Check-In");
gui.setStyleAttribute("background", "lavender");
// Absolute panel for setting specific locations for elements
var panel = gui.createAbsolutePanel();
// Equipment ID#s Label
var equipmentIDLabel = gui.createLabel("Equipment ID#s");
equipmentIDLabel.setHorizontalAlignment(UiApp.HorizontalAlignment.CENTER);
equipmentIDLabel.setSize("20px", "125px");
equipmentIDLabel.setStyleAttributes({background: "SteelBlue", color: "white"});
// Add all components to panel
panel.add(equipmentIDLabel, 10, 0);
gui.add(panel);
return gui;
}
function getUIdata(eventInfo) {
// I know how to get the data from each element based on ID
}
It generates the Absolute Panel correctly when checkOut() is called, but the EquipmentIDLabel is never added to the panel. I am basing the code on the simplistic design I created in the GUI builder (that will be deprecated in a few days, which is why I am writing the code so that I can change it later):
So what exactly is going wrong here? If I can figure out how to add one element, I can infer the rest by looking at the docs. I've never been any good at GUI development!
You could maybe use grid as an interesting alternative... here is an example :
// define styles
var labelStyle = {background: "SteelBlue", color: "white",'textAlign':'center','line-height':'20px','vertical-align':'middle','font-family':"Arial, sans-serif",'fontSize':'10pt'};// define a common label style
var fieldStyle = {background: "white", color: "SteelBlue",'font-family':"Courrier, serif",'fontSize':'11pt'};// define a common label style
function checkOut() {
var app = buildUI();
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
function buildUI() {
var gui = UiApp.createApplication();
gui.setTitle("Check-Out/Check-In");
gui.setStyleAttribute("background", "lavender");
var panel = gui.createAbsolutePanel().setStyleAttribute('padding','10px');
var grid = gui.createGrid(4,2).setWidth('300').setCellPadding(10);//define grid size in number of row & cols
var equipmentID = ['equipmentIDLabel','equipmentIDLabel1','equipmentIDLabel2','equipmentIDLabel3'];// define labels in an array of strings
for(var n=0 ;n<equipmentID.length ; n++){;// iterate
var equipmentIDLabel = gui.createLabel(equipmentID[n]).setWidth('125').setStyleAttributes(labelStyle);
var equipmentIDField = gui.createTextBox().setText('Enter value here').setName(equipmentID[n]).setSize("125", "20").setStyleAttributes(fieldStyle);
grid.setWidget(n,0,equipmentIDLabel).setWidget(n,1,equipmentIDField);
}
gui.add(panel.add(grid));
return gui;
}
It looks like the absolute panel offset method is a little capricious and take control of your positioning, in my tests I have been able to position panels that are visible in the following way:
panel.add(equipmentIDLabel);
panel.add(equipmentIDField,150,0);
panel.add(otherLabel);
panel.add(otherField, 150, 20);
Try it out with trial and error, you may get the UI you need, if not I would move to an alternate layout, verticalPanel is a little better behaved and of course you can use forms as well.
Another small bug is that you inverted the length and hight in equipmentIDLabel.setSize("20px", "125px");
Let me know if I can be of more assitance
The specific problem in your code is the following line :
// Add all components to panel
panel.add(equipmentIDLabel, 10, 0);
Simply change it to : panel.add(equipmentIDLabel);
..and you will see the field (at position 0,0).
As patt0 observes, you can then add OTHER components and use positioning. It seems to be a limitation of adding the first field to an absolutePanel.
Of course, the Google Script gui is now deprecated (since December 2014) but I was interested to try your code and see that it still basically executes (as at Feb 2016).