PDA

View Full Version : [OPEN] [#296] Charts - Bar labels show/hide



cwolcott
Jul 05, 2013, 2:35 PM
How can I show/hide bar chart labels based on context menu CheckMenuItem;

I tried the following javascript handler, but the chart will not redraw:



function chartLabels(checked) {

var chart = Ext.getCmp('CrtsChart');
var series = chart.series.items[0];

if (checked === false) {
series.label.display = "none";
}
else {
series.label.display = "insideEnd";
}

chart.redraw();
}

Daniil
Jul 05, 2013, 3:27 PM
Hi Chris,

Please try this.

Hide


App.Chart1.series.items[0].hideLabels();

Show

App.Chart1.series.items[0].renderLabels()

cwolcott
Jul 05, 2013, 4:04 PM
Looked good at first, but a slight issue. If you move the mouse across any of the bars the chart rerenders again and the labels show back up after they were hidden. I could get around it with:



function chartLabels(checked) {
var chart = Ext.getCmp('CrtsChart');
var series = chart.series.items[0];

if (checked === false) {
series.label.display = "none";
series.hideLabels();
}
else {
series.label.display = "insideEnd";
series.renderLabels();
}
}


But that is causing an issue when the chart is initially created with a display of "none". I can't get the labels to show up at all.

Let me work up a full sample.

cwolcott
Jul 05, 2013, 5:27 PM
Hopefully this details everything.

6491649264936494

#1: Issue
I added the following line so that after the chart is rendered it would properly show/hide the data labels based on the context menu option. The problem with this is that the if the ShowLabel is false the labels are still animated and then removed.


byOrgChart.Series[0].Listeners.AfterRender.Handler = "chartLabels(Ext.getCmp('ShowLabels').checked);";


You can duplicate the problem by letting the chart display, turn off the Data Labels via the context menu, select another year to be displayed and watch how the animation occurs and then the labels are hidden.

#2: Issue
So instead of the listener I was just going to set the label.display in the behind code to "none" or "insideEnd" based on the ShowLabels.Checked value. But if I write the code that way the labels will not show up in subsequent displays.


//byOrgChart.Series[0].Listeners.AfterRender.Handler = "chartLabels(Ext.getCmp('ShowLabels').checked);";
cSeries.Label.Display = SeriesLabelDisplay.None;
if (ShowLabels.Checked == true)
cSeries.Label.Display = SeriesLabelDisplay.InsideEnd;

You can duplicate the problem by letting the chart display, turn off the Data Labels via the context menu, select another year to be display, try to turn on the Data Labels via the context menu.

#3: Observation
I made a slight modification to your suggestion for the javascript listeners that handle the hiding/showing of the data labels. Instead of just calling hideLabels/renderLabels I needed to reset the label.display to "none" or "insideEnd" because if I didn't the labels would redisplay when I moved the mouse over a bar series.



function chartLabels(checked) {

var chart = Ext.getCmp('CrtsChart');
var series = chart.series.items[0];

if (checked === false) {
series.label.display = "none";
series.hideLabels();
}
else {
series.label.display = "insideEnd";
series.renderLabels();
}
}


You can duplicate the observation by commenting out the label.display = "..." and let the chart display, turn off the Data Labels via the context menu and move the mouse over any bar series, the labels are redisplayed.


Here is a full example.


<%@ Page Language="C#" %>

<%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="System.Globalization" %>
<script runat="server">

public class ChartByOrg
{
public static string[] Orgs = new string[] { "Accounting", "HR", "Corporate", "Graphics" };

public string Name { get; set; }
public double? Y2013 { get; set; }
public double? Y2012 { get; set; }
public double? Y2011 { get; set; }
public double? Y2010 { get; set; }
public double? Y2009 { get; set; }

public static Dictionary<String, ChartByOrg> GenerateData(int n = 4)
{
var data = new Dictionary<String, ChartByOrg>();
for (int i = 0; i < n; i++)
{
string org = Orgs[i];
data.Add(org, new ChartByOrg { Name = org });
}
return data;
}
}

public class RequestsInfo
{
public string RequestOrg { get; set; }
public int RequestYear { get; set; }
public int? Requests { get; set; }
}

protected void Page_Init(object sender, EventArgs e)
{
BuildChartYearCheckBoxes();
}

protected void Page_Load(Object sender, EventArgs e)
{
if (X.IsAjaxRequest)
return;

((Ext.Net.Checkbox)ChartYearsCbGroup.Items[0]).Checked = true;
BuildChart();

}

private void BuildChartYearCheckBoxes()
{
// Just hardcode years for this example
for (int year = 2013; year >= 2009; year--)
{
ChartYearsCbGroup.Items.Add(new Ext.Net.Checkbox
{
ID = "Y" + year.ToString(),
BoxLabel = year.ToString(),
InputValue = year.ToString(),
StyleSpec = "StyleSpec='font-size: 11px;'",
ClientIDMode = ClientIDMode.Static
});
}


}
protected void ChartYearsChange(object sender, DirectEventArgs e)
{
BuildChart();
}

private void BuildChart()
{
if (ChartPanelSouth.Hidden) return;

CheckboxGroup cbGroup = X.GetCmp<CheckboxGroup>("ChartYearsCbGroup");
List<Checkbox> yrsSelected = cbGroup.CheckedItems;
if (yrsSelected.Count == 0)
{
ChartPanel.RemoveAll();
ChartPanel.Update("<p>Select a Request Year to display a chart.</p>");
ChartPanel.BodyStyle = "text-align: center; display: table-cell; vertical-align: middle;";
ChartPanelSouth.Title = "Charts";
Ext.Net.X.Mask.Hide();
return;
}
else
{
//ChartPanel.ClearContent();
ChartPanel.BodyStyle = "text-align: left; display:inline; vertical-align: top;";
}

ChartRequestsByOrg(yrsSelected);

}

private void ChartRequestsByOrg(List<Checkbox> yrsSelected)
{
int yrsSelectedCount = yrsSelected.Count();

string[] yFields = new string[yrsSelectedCount];
string[] yTitles = new string[yrsSelectedCount];
for (int i = 0; i < yrsSelectedCount; i++)
{
yFields[i] = "Y" + yrsSelected[i].InputValue;
yTitles[i] = yrsSelected[i].InputValue;
}

// Create: Line Chart (Requests by Months)
Chart byOrgChart = new Chart();
byOrgChart.ID = "CrtsChart";
byOrgChart.ClientIDMode = ClientIDMode.Static;
byOrgChart.StyleSpec = "background:#fff";
byOrgChart.StandardTheme = StandardChartTheme.Category1;
byOrgChart.ContextMenuID = "CrtsChart_ContextMenu";

if (!(Ext.Net.RequestManager.IsIE7 || Ext.Net.RequestManager.IsIE8))
{
byOrgChart.Animate = true;
byOrgChart.AnimateConfig = new AnimConfig { Easing = Easing.EaseIn, Duration = 1000 };
byOrgChart.AnimateConfig.Listeners.AfterAnimate.Fn = "chartAnimateComplete";
}
else
byOrgChart.Listeners.AfterRender.Handler = "Ext.net.Mask.hide();";

// Configure: Chart Legend
byOrgChart.LegendConfig = new ChartLegend();
byOrgChart.LegendConfig.Position = LegendPosition.Right;

// Create: Category Axis
CategoryAxis cAxis = new CategoryAxis();
cAxis.Position = Position.Bottom;
cAxis.Title = "Organization";
cAxis.Fields = new String[] { "Name" };
byOrgChart.Axes.Add(cAxis);

// Create: Numeric Axis
NumericAxis nAxis = new NumericAxis();
nAxis.Position = Position.Left;
nAxis.Title = "Requests";
nAxis.Fields = yFields;
nAxis.MinorTickSteps = 1;
nAxis.Minimum = 0;
nAxis.Grid = true;
nAxis.GridConfig = new AxisGrid { Odd = new SpriteAttributes { Opacity = 1, Fill = "#ddd", Stroke = "#bbb", StrokeWidth = 0.5 } };
byOrgChart.Axes.Add(nAxis);

// Create: Column Series
ColumnSeries cSeries = new ColumnSeries();
cSeries.Axis = Position.Left;
cSeries.Highlight = true;
cSeries.XField = new String[] { "Name" };
cSeries.YField = yFields;
cSeries.Titles = yTitles;

// Configure: Column Series Tips
cSeries.Tips = new ChartTip();
cSeries.Tips.TrackMouse = true;
cSeries.Tips.TitleAlign = TitleAlign.Center;
cSeries.Tips.BodyStyle = "text-align: center; font-size: 9px; ";
cSeries.Tips.Renderer.Handler = "this.update('<b>' + String(item.yField) + '<br/>' + String(item.value[0]) + '<br/>Requests</b><hr><font color=blue>' + String(item.value[1]) + '</font>');";

// Configure: Column Series Data Labels
cSeries.Label = new SeriesLabel();
cSeries.Label.Contrast = true;
cSeries.Label.Display = SeriesLabelDisplay.InsideEnd;
cSeries.Label.Color = "#000";
cSeries.Label.TextAnchor = "top";
cSeries.Label.Field = yFields;
cSeries.Label.Orientation = Ext.Net.Orientation.Vertical;

if (ShowLabels.Checked == true)
cSeries.Label.Display = SeriesLabelDisplay.InsideEnd;

byOrgChart.Series.Add(cSeries);

// Configure: Enable/Disable series tooltips based on Context Menu option.
byOrgChart.Series[0].Listeners.AfterRender.Handler = "chartTips(Ext.getCmp('ShowTips').checked);";
byOrgChart.Series[0].Listeners.AfterRender.Handler = "chartLabels(Ext.getCmp('ShowLabels').checked);";

// Create: Store
Store store = new Store();
store.ID = "ChartStore";
store.ClientIDMode = ClientIDMode.Static;

// Create: Model based on the years checked and add it to the store.
store.Model.Add(createModel(yrsSelected));

// Populate: Build the data for the store
store.DataSource = generateData(yrsSelected).Values.ToList();
//store.DataBind();
byOrgChart.Store.Add(store);

ChartPanel.RemoveAll();
byOrgChart.AddTo(ChartPanel);

ChartPanelSouth.Title = "Requests by Organization";


}

// Create a model based on the years checked
private Model createModel(List<Checkbox> yrsSelected)
{
Model model = new Model();
model.ID = "ModelByOrg";
model.Fields.Add("Name");

for (int i = 0; i < yrsSelected.Count(); i++)
{
ModelField field = new ModelField();
field.Name = "Y" + yrsSelected[i].InputValue;
field.Type = ModelFieldType.Float;

// Allow special interpretation of the data only for this line chart.
field.Convert.Handler = "if (value === null) {value = undefined;} return value;";
model.Fields.Add(field);
}

return model;
}

private Dictionary<String, ChartByOrg> generateData(List<Checkbox> yrsSelected)
{
var theData = ChartByOrg.GenerateData();
Random random = new Random();
double p = (random.NextDouble() * 4) + 1;

RequestsInfo[] byOrgRequests = new RequestsInfo[yrsSelected.Count() * 4];
for (int y = 0; y < yrsSelected.Count(); y++)
{
for (int m = 0; m < 4; m++)
{
RequestsInfo ri = new RequestsInfo();
ri.RequestOrg = ChartByOrg.Orgs[m];
ri.RequestYear = Convert.ToInt32(yrsSelected[y].InputValue);
ri.Requests = Convert.ToInt32(Math.Floor(Math.Max(random.NextDou ble() * 100, 0)));

byOrgRequests[y * 4 + m] = ri;
}
}

foreach (var reqCnts in byOrgRequests)
{
if (!theData.ContainsKey(reqCnts.RequestOrg))
theData.Add(reqCnts.RequestOrg, new ChartByOrg { Name = reqCnts.RequestOrg });

string org = reqCnts.RequestOrg;

switch (reqCnts.RequestYear)
{

case 2013:
theData[reqCnts.RequestOrg].Y2013 = reqCnts.Requests;
break;

case 2012:
theData[reqCnts.RequestOrg].Y2012 = reqCnts.Requests;
break;

case 2011:
theData[reqCnts.RequestOrg].Y2011 = reqCnts.Requests;
break;

case 2010:
theData[reqCnts.RequestOrg].Y2010 = reqCnts.Requests;
break;

case 2009:
theData[reqCnts.RequestOrg].Y2009 = reqCnts.Requests;
break;

default:
break;
}

};

return theData;
}

</script>
<!DOCTYPE html >
<html>
<head id="Head1" runat="server">
<title>Window issue IE7</title>
</head>
<body>
<form id="Form1" runat="server">
<ext:ResourceManager ID="ResourceManager1" runat="server" />
<ext:Viewport ID="Viewport1" runat="server" Layout="BorderLayout">
<Items>
<ext:Panel ID="Center" runat="server" Region="Center" Title="Center" />
<ext:Panel ID="ChartPanelSouth" runat="server" ClientIDMode="Static" Region="South"
Title="Charts" TitleAlign="Center" Icon="ChartBar" Height="240" Split="true"
Collapsible="true" Layout="BorderLayout" Collapsed="false">
<HtmlBin>
<script type="text/javascript">

function commonChartInit() {
Ext.net.Mask.show({ el: Ext.get('ChartPanel'), msg: 'Building Chart ...' });
}

var chartAnimateComplete = Ext.Function.createBuffered(function () { Ext.net.Mask.hide(); }, 1200);


</script>
</HtmlBin>
<DockedItems>
<ext:Toolbar ID="Toolbar1" runat="server" Dock="Left" Vertical="true">
<Items>
<ext:Button ID="ChartYears" runat="server" ToolTip="years" Icon="Calendar" ArrowAlign="Right"
MenuAlign="tl-tr" AutoShow="true">
<Menu>
<ext:Menu ID="ChartYearsMenu" runat="server" Title="Request Years" TitleAlign="Center"
ShowSeparator="false" RenderToForm="true">
<Items>
<ext:CheckboxGroup ID="ChartYearsCbGroup" runat="server" ClientIDMode="Static" ColumnsWidths="80,80"
Vertical="true">
<Listeners>
<Change Fn="commonChartInit" />
</Listeners>
<DirectEvents>
<Change OnEvent="ChartYearsChange" />
</DirectEvents>
</ext:CheckboxGroup>
</Items>
</ext:Menu>
</Menu>
</ext:Button>
</Items>
</ext:Toolbar>
</DockedItems>
<Items>
<ext:Panel ID="ChartPanel" runat="server" ClientIDMode="Static" Region="Center" Layout="BorderLayout">
<HtmlBin>
<script type="text/javascript">
function chartLabels(checked) {

var chart = Ext.getCmp('CrtsChart');
var series = chart.series.items[0];

if (checked === false) {
series.label.display = "none";
series.hideLabels();
}
else {
series.label.display = "insideEnd";
series.renderLabels();
}
}

function chartTips(checked) {

var chart = Ext.getCmp('CrtsChart');

for (var i = 0; i < chart.series.length; i++) {
var series = chart.series.items[i];

if (checked === false) {
// No reason to set originalTooltip if tooltip is undefined
// OriginalTooltip might already have been set and do not want to clear it.
if (series.tooltip != undefined)
series.originalTooltip = series.tooltip;
delete series.tooltip;
}
else {
// Just incase we start out as true, there is nothing in the original.
if (series.originalTooltip != undefined)
series.tooltip = series.originalTooltip;
delete series.originalTooltip;
}
}
}
</script>
</HtmlBin>
<Bin>
<ext:Menu ID="CrtsChart_ContextMenu" runat="server" ClientIDMode="Static" RenderToForm="true"
Title="Chart Options" TitleAlign="Center">
<Items>
<ext:CheckMenuItem ID="ShowLabels" runat="server" ClientIDMode="Static" Checked="true"
Text="Data Labels">
<Listeners>
<CheckChange Handler="chartLabels(this.checked)" />
</Listeners>
</ext:CheckMenuItem>
<ext:CheckMenuItem ID="ShowTips" runat="server" ClientIDMode="Static" Checked="true"
Text="Tips">
<Listeners>
<CheckChange Handler="chartTips(this.checked)" />
</Listeners>
</ext:CheckMenuItem>
</Items>
</ext:Menu>
</Bin>
<Items>
</Items>
</ext:Panel>
</Items>
</ext:Panel>
</Items>
</ext:Viewport>
</form>
</body>
</html>

Baidaly
Jul 06, 2013, 1:00 AM
Hello!

Try the following:



function chartLabels(checked) {

var chart = Ext.getCmp('CrtsChart');
var series = chart.series.items[0];

if (checked === false) {
series.label.display = "none";
series.hideLabels();
}
else {
series.label.display = "insideEnd";
series.labelsGroup = series.chart.surface.getGroup(series.seriesId + '-labels');
var animate = series.chart.animate;
series.chart.animate = null;
series.renderLabels();
series.chart.animate = animate;
}
}

cwolcott
Jul 08, 2013, 2:01 PM
Based on your recommendations I now notice in the Ext.chart.Label function constructor that if the original display is "None" the labelsGroup will never be set and thus during the renderLabels function the labels will never be drawn.

It would be nice if the class would allow you to reset the display and the labelsGroup would be rebuilt if needed, instead of having to know this relationship and perform it outside the class.

I have taken your recommendation for the updated javascript code. I was going to remove the code that temporarily disabled the animation, but finally figured out that you added those 3 lines to mimic how labels are really shown after a chart has been displayed. If the chart is displayed without labels and then the user selects the context menu option to display them the labels would not be animated. But if those 3 lines are commented out and this is the first time for the labels to be displayed in the chart they were being animated.



function chartLabels(checked) {
var chart = Ext.getCmp('CrtsChart');
var series = chart.series.items[0];
if (checked === false) {
series.label.display = "none";
series.hideLabels();
} else {
series.label.display = "insideEnd";
series.labelsGroup = series.chart.surface.getGroup(series.seriesId + '-labels');
var animate = series.chart.animate;
series.chart.animate = null;
series.renderLabels();
series.chart.animate = animate;
}
}


You can close this thread. Please consider any overrides that would be convenient to the developer.

cwolcott
Jul 09, 2013, 12:54 PM
Please see post above.

You can close this thread. Please consider any overrides that would be convenient to the developer.

Daniil
Jul 09, 2013, 2:32 PM
Please clarify are you talking about a feature request to have a more convenient way to hide and show labels on the fly?

cwolcott
Jul 09, 2013, 2:55 PM
Yea, I guess my situation would call for a feature request to have a more convenient way to hide and show labels on the fly.

I was looking at this as a learning experience on how to extend/override the ExtJS javascript code to understand setDisplay(mode) and have it perform a couple of internal actions like:


Set the display attribute to function parameter mode;
Assign lablesGroup if not already assign;

Daniil
Jul 10, 2013, 1:22 PM
It is a nice suggestion. I posted a thread on the Sencha forums.
http://www.sencha.com/forum/showthread.php?267656

Hopefully, they will answer something.

By the way, I would wrap the following in an "if" statement.

if (!series.labelsGroup) {
series.labelsGroup = series.chart.surface.getGroup(series.seriesId + '-labels');
}

Daniil
Jul 12, 2013, 4:40 AM
@slemmon answered the following:

Nice idea. I'm going to move the thread over to the request forum and put it on the request line for review.

Probably, he moved the thread into the Feature Requests forum which is a premium one. I have no access, but Geoffrey does have.

So, we will monitor its status. Created an Issue.
https://github.com/extnet/Ext.NET/issues/296