Highcharts - Hide child labels in a multiple levels and multiple layouts treemap - layout

On highcharts, I have a treemap with 2 levels, each with a different layout algorithm. Now I want to limit what we can see to the current level. It means that on level 1, I don't want to see the labels of level 2, which would only appear when drilling down (and the labels of level 1 would disappear).
I know it is easy with levelIsConstant: false, but this only works with 1 level, and I use 2 because I need different layouts.
Here is the link to what I currently have:
series: [{
type: "treemap",
allowDrillToNode: true,
alternateStartingDirection: true,
levels: [{
level: 1,
layoutAlgorithm: 'squarified',
dataLabels: {
enabled: true,
align: 'left',
verticalAlign: 'top',
style: {
fontSize: '15px',
fontWeight: 'bold'
}
}
}, {
level: 2,
layoutAlgorithm: 'stripes',
color: 'blue'
}],
//...
http://jsfiddle.net/dhfera2y/2/
I want all the names to be hidden, as well as the lines separating them.
EDIT: Using a rgba color on each level, I can hide the nodes below it, but I still cannot hide their label!

Thank you it is a smart idea for the labels issue, but as I say I cannot use levelIsConstant: false because I need a different layout for each level at every moment. With this solution both levels can have a different layout when I am on the top level, but I soon as I drill down I loose the correct layout for the new view.
Almost :-)
EDIT : Okay so I finally managed to do it. I think it is impossible to achieve this in the way I was trying to, which was using the parent option for each child point of the serie in order to determine the hierarchy of the tree. So instead of using one serie with a hierarchy, I use one serie for the top level which I link to several series for the level below.
I was able the do this thanks to the drilldown option.
I found the solution in the official documentation :
http://www.highcharts.com/docs/chart-concepts/drilldown
I adjusted the code and it was all right. Here is the solution I came up with (it is a different work from my first link) :
http://jsfiddle.net/ff964fog/47/
series: [{
type: 'treemap',
layoutAlgorithm: 'squarified',
borderWidth: 3,
data: modulesData
}],
drilldown: {
series: servicesSerie
},
I still have to adjust a few things (like the disappearance of the animations for the bottom level), but in the end I have exactly what I wanted !

My take on some settings you can use to achieve this (JSFiddle):
series: [{
type: "treemap",
allowDrillToNode: true,
levelIsConstant: false,
// ...
levels: [{
level: 1,
dataLabels: {
enabled: true
}
// ...
}, {
level: 2,
borderWidth: 0,
dataLabels: {
enabled: false
}
}],
data[{
//...
}]
}]
The level 2 settings apply only when viewing from level 1. When drilling down the new view is considered to be level 1 because of levelIsConstant: false.
Setting borderWidth: 0 in level 2 is only needed if you want to hide the grid of level 2 when viewing from level 1.

You can use a custom formatter for plotOptions.treemap.datalabels. The code bellow is an example that uses this.series.rootNode and this.point.parent and compare them to examine if the label should be shown or not:
plotOptions: {
treemap: {
dataLabels: {
formatter: function(data) {
if (this.point.parent == (this.series.rootNode || null)) {
return this.key;
}
}
}
}
}
You can use any other property that is available in formatter function. Just log (console.log) this and data in the formatter function to see all the properties available:
plotOptions: {
treemap: {
dataLabels: {
formatter: function(data) {
console.log(this, data);
}
}
}
}

Related

JointJS: How can you create a oneSide link with 2 different sides

I am trying to create a link that links from one shape with a fixed side to another with a different fixed side. Is this possible?
link.router('oneSide', {
side: 'top',
padding: 30
});
https://resources.jointjs.com/docs/jointjs/v3.1/joint.html#routers.oneSide
I'm not sure 'oneSide' should even be in the routers because in the documentation the router only defines the middle points of the path not the start and end.
In either case, the router function must return an array of route points that the link should go through (not including the start/end connection points). The function is expected to have the form function(vertices, args, linkView)
I found out that in defining the links you can specify the anchor which is basically the start and end point. For the shapes there some built-in anchors. We have to use 'left' and 'right' for this case.
Here is my code. It's in TypeScript but I'm sure you can convert it to JS easily if you need to.
makeLink(parentElementLabel, childElementLabel) {
return new joint.shapes.standard.Link({
source: {
id: parentElementLabel,
anchor: {
name: 'right'
}
},
target: {
id: childElementLabel,
anchor: {
name: 'left'
}
},
router: {
name: 'normal'
},
attrs: {
line: {
stroke: 'black',
cursor: 'default',
},
wrapper: {
cursor: 'default'
}
},
// smooth: true,
});
}

flot.js - points don't match up with x-axis, null data set, legend overlapping

Just started using flot.js for the first time and have a few problems/questions. The chart displays fine minus a few things that are bothering me.
1 - The ticks and dates don't seem to line up properly. For example, in the current chart I am working on there are only two entries for two different days. The actual data value (point) is not above the date it corresponds with for some reason. See the image here
2 - Is there any way to make the y axis larger than the dataset to prevent the lines from overlapping the legend? In this example it is fine, but I have seen others where the data overlaps the legend.
3 - How to handle no data? I plan to expand on this to allow the user to select start/end dates... so if there are no results is there a nice way to return this and/or possibly show a message in the chart? The main thing here is the script doesn't break when there is no data.
JS :
//get the data
$.ajax({
url: "/process/chart_main.php",
type: "POST",
dataType: "json",
success: onDataReceived
});
function onDataReceived(series) {
console.log(series);
// Load all the data in one pass; if we only got partial
// data we could merge it with what we already have.
data = series;
var options = {
series: {
lines: {
show: true,
lineWidth: 2,
fill: true,
fillColor: {
colors: [{
opacity: 0.05
}, {
opacity: 0.01
}
]
}
},
points: {
show: true
},
shadowSize: 2
},
grid: {
hoverable: true,
clickable: true,
tickColor: "#eee",
borderWidth: 0
},
//colors: ["#d12610", "#37b7f3", "#52e136"],
xaxis: {
tickSize: [1, "day"],
mode: "time",
timeformat: "%m-%d-%Y"
},
yaxes: [{
position: "left"
}],
legend: {
position: "ne"
}
};
$.plot("#flot_chart", data, options);
}
PHP:
//actual select removed for this display
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
if($results)
{
foreach($results as $row)
{
$data1[] = array( strtotime($row['date']) * 1000, $row['day_count'] );
}
//send our data values to $mergedData, add in your custom label and color
$mergedData[] = array('label' => "Events" , 'data' => $data1, 'color' => '#37b7f3');
}
// return result array to ajax
echo json_encode($mergedData);
Probably the ticks are at midnight (07-09-2014 00:00:00) while the data points are not. If you want to line them up remove the time from your datapoints or give flot the ticks with the same time as your datapoints as an array instead of the tickSize option (see here, you have to give the ticks as timestamps like in the datapoints):
xaxis: {
ticks: [1405942268798, 1405942368798, ...]
}
Use the min and max properties for the axis options (see same link as above):
xaxis: {
min: 1405942268798,
max: 1405942368798
}
flot doesn't "break" when there is no data, it just shows an empty chart. If you want to show an additional message, you can check for yourself before calling $.plot():
if (data.length == 0) {
alert('No data');
}

Ext JS: TabPanel rendering Infinite Grid too quickly

Let's say I have a TabPanel that gets two components added to it. Each component contains an Infinite Grid. Each Infinite Grid loads its data from a service call, and each set of data contains 2,000 records. After the components are added to the TabPanel, we set each one to be the active tab, using setActiveTab. We first set the 2nd tab as the active tab and then set the first tab. When the page loads, the first tab is selected, as we expected.
When looking at the first tab, everything looks fine... we can infinitely scroll, sort, hide columns, etc. However, if we switch to the second tab, we see that it has partially loaded, and we can't scroll past the x records that have loaded, hide columns, sort, etc. It's almost as if using setActiveTab was a bit premature with rendering the grid... as if the store wasn't completely loaded, but the tab was rendered anyway. (this is what I'm assuming the issue is)
I do have code, but it takes a little work on your end to reproduce (because you need a service call). I'm using CompoundJS within a Node.js application, so it was very easy for me to create the test case. If you have access to a database, and can make a quick service call, just modify my Ext JS code, but if you want to use Node.js, you can try this:
Ext JS 4.2.1
Ext.onReady(function() {
var tabPanel = Ext.create('Ext.tab.Panel', {
width: 400,
height: 400,
renderTo: Ext.getBody()
});
Ext.define('myGrid', {
extend: 'Ext.grid.Panel',
constructor: function(config) {
this.columns = config.columns;
this.store = Ext.create('Ext.data.Store', {
fields: config.fields,
buffered: true,
leadingBufferZone: 20,
pageSize: 50,
proxy: {
type: 'ajax',
url: '/getData?id=' + config.id,
reader: {
totalProperty: 'totalCount',
type: 'json',
root: 'root'
}
},
autoLoad: true
});
this.id = config.id;
this.callParent();
}
});
var grid1 = Ext.create('myGrid', {
id: 'blah',
columns: [{
text: 'one',
dataIndex: 'one'
}, {
text: 'two',
dataIndex: 'two'
}, {
text: 'three',
dataIndex: 'three'
}],
fields: ['one', 'two', 'three'],
title: 'grid1'
});
var grid2 = Ext.create('myGrid', {
id: 'bleh',
columns: [{
text: 'one',
dataIndex: 'one'
}, {
text: 'two',
dataIndex: 'two'
}, {
text: 'three',
dataIndex: 'three'
}],
fields: ['one', 'two', 'three'],
title: 'grid2'
});
var c1 = [];
c1.items = [grid1];
c1.title = "BLAH";
c1.layout = 'fit';
var c2 = [];
c2.items = [grid2];
c2.title = "BLEH";
c2.layout = "fit";
tabPanel.add([c1, c2]);
tabPanel.setActiveTab(1);
tabPanel.setActiveTab(0);
});
Node.js code
compound init test && cd test
npm install
compound g s testcontroller
Replace app/controllers/testcontrollers_controller.js with:
load('application');
action('getData', function(data) {
var query = data.req.query;
var id = query.id;
var page = query.page;
var pushObj;
if (id === 'blah') {
pushObj = {
one: 'bye',
two: 'goodbye',
three: 'auf wiedersehen'
};
}
else {
pushObj = {
one: 'hi',
two: 'hello',
three: 'guten tag'
};
}
var obj = [];
for (var i = 0; i < 50; i++) {
obj.push(pushObj);
}
send({
totalCount: 2000,
root: obj,
page: page
});
});
In config/routes.js, remove testcontroller's map.resources line, and add this:
map.get('getData', 'testcontrollers#getData');
In public/index.html, make it a generic HTML file, and add in your links to Ext JS and my above Ext JS code.
Now, once you've done all of that, you should be able to reproduce my issue. The first tab will open and be just fine, but the second tab only loads the first x amount of records and doesn't function properly. I believe this is due to the store not loading completely when setActiveTab is fired, which makes the rendering load an impartial store.
What I want to know is, how do I get this to work properly? I've tried waiting for the store to load, and then adding it to the tab, but that still gives me inconsistent results, as well as tried waiting for the grid to stop rendering, but still, I get inconsistent results... inconsistent meaning, sometimes the grid loads all the way, and the tab is fine, but other times, I get a cannot read property 'length' of undefined in ext-all-dev.js:135,786... which makes it seem like the store hasn't completely loaded, as that line contains a reference to records.length.
If anyone has any ideas, I'd love to hear them! Cross-posted from the Sencha forums.
EDIT: Thanks to #rixo, I was able to reproduce the problem in this example. If you enable Firebug, you'll see the error about property length of undefined, as I stated above.
I tracked the issue down to the plugin caching incorrect size values when it is hidden.
Here's an override that would fix this behavior:
/**
* Prevents BufferedRenderer plugin to break when buffered views are
* rendered or refreshed while hidden, like in a card layout.
*
* Tested with Ext 4.2.1
*/
Ext.define('Ext.ux.Ext.grid.plugin.BufferedRenderer.HiddenRenderingSupport', {
override: 'Ext.grid.plugin.BufferedRenderer'
/**
* Refreshes the view and row size caches if they have a value of 0
* (meaning they have probably been cached when the view was not visible).
*/
,onViewResize: function() {
if (this.rowHeight === 0) {
if (this.view.body.getHeight() > 0) {
this.view.refresh();
}
} else {
this.callParent(arguments);
}
}
});
That being said, I advice against using this and unnecessarily bloating your code base.
You should rather organise your code in a way that avoid this situation. The problem is created by the fact that you're calling setActiveTab multiple time in the same execution frame. If you avoid that, Ext will handle the situation correctly all by itself.
As a general principle, you should try to make your Ext code more declarative... Ext architecture rather expects you to build big configuration objects to create your component at once (allowing for atomic DOM updates, etc.), and use methods that will update the state only later, to drive the components behaviour.
In your precise case, you should use the activeTab option to set the initial tab, and only call setActiveTab later, when you actually need to change the tab. That will save some unnecessary processing at initialization, and will also resolve your rendering issue...
Here's how your code should look like:
Ext.create('Ext.tab.Panel', {
renderTo: Ext.getBody(),
width: 400,
height: 400,
// Use this option to set the initial tab.
activeTab: 2,
// You don't need to overnest your grids into another Panel, you can add
// them directly as children of the tab panel.
items: [
// Ideally, you should give an xtype to your class, and add this to
// avoid instantiating the object yourself... Thus giving Ext more
// control on the rendering process, etc.
Ext.create('myGrid', {
id: 'blah',
title: 'Bleh',
columns: [{
text: 'one',
dataIndex: 'one'
}, {
text: 'two',
dataIndex: 'two'
}, {
text: 'three',
dataIndex: 'three'
}],
fields: ['one', 'two', 'three'],
title: 'grid1'
}),
Ext.create('myGrid', {
id: 'bleh',
title: 'Bleh',
columns: [{
text: 'one',
dataIndex: 'one'
}, {
text: 'two',
dataIndex: 'two'
}, {
text: 'three',
dataIndex: 'three'
}],
fields: ['one', 'two', 'three'],
title: 'grid2'
})
]
});
As you can see, the tab panel child items are set at creation time, avoiding useless processing with the add method. Likewise, the activeTab option avoid to initialize the panel in a given state and change it right away... Later (in another execution frame, for example in a button handler), you can use setActiveTab without triggering the bug demonstrated in your original example.

extjs search box

I'm, trying to add a search box exactly as one on sencha docs home page http://docs.sencha.com/ext-js/4-0/
I used the code from the example
http://docs.sencha.com/ext-js/4-0/#!/example/form/forum-search.html
and everything works as expected except for one thing ..
when I select an option from a list in my search box the combobox value is set to the selected value .. and when I press arrow down button it performs a new search with modified query ..
but I just want to see the results of the previous search - exactly the behaviour of the search box on sencha page
any ideas how to achieve that ?
After trying various things the code below does what i need, but maybe there is a better way ..
I had to set triggerAction to 'query' and also had to manually reset the text of the combobox in the the select event handler
var searchBox = {
xtype: 'combo',
store: dataStore,
displayField: 'title',
valueField: 'id',
autoSelect: false,
typeAhead: false,
fieldLabel: 'Search for',
hideTrigger:true,
anchor: '100%',
mode:'remote',
triggerAction: 'query',
listeners: {
'select' : function(combo) {
var selected = this.value;
combo.setValue(combo.lastQuery);
showResult(selected);
}
},
listConfig: {
loadingText: 'Searching ...',
emptyText: 'No matching posts found.',
getInnerTpl: function() {
return '<a class="search-item" href="?term={id}" onclick="return javascript:showResult(\'{id}\')">' +
'<h3><span>{title}<br /></span>{id}</h3></a>';
}
},
pageSize: 10
}
You need first sample from this page. Type "A" first
http://docs.sencha.com/ext-js/4-0/#!/example/form/combos.html

extjs4 MVC Scope Issue

I am trying to call resetCombo method once i click on link from tooltip which is rendered on combo
But i am not able to access it because of scope issue not sure what i am missing. Please help me on this.
Ext.define('test.BasicForm', {
extend: 'Ext.form.Panel',
renderTo:Ext.getBody(),
initComponent :function(){
this.items=[
{
fieldLabel: 'Test',
xtype: 'combo',
displayField: 'name',
width: 320,
labelWidth: 130,
store: [
[1, 'Value 1'],
[2, 'Value 2'],
[3, 'Value 3'],
[4, 'Value 4']
],
listeners:{
afterrender: function(combo) {
Ext.create('Ext.tip.ToolTip', {
target: combo.getEl(),
autoHide: false,
name:'tool-tip',
scope:this,
html: 'Old value was '+ combo.getValue()+ ' test',
listeners: {
beforeshow: function() {
return combo.isDirty();
}
}
});
}
},
value:'1'
}];
this.callParent(arguments);
},
resetCombo:function(){
alert('called');
}
});
First this has nothing to do with ExtJS4's MVC features which are generally associated with a controller's control method:
http://docs.sencha.com/ext-js/4-1/#!/api/Ext.app.Controller-method-control
Second, you may be able to instead get the effect you want by switching to the following, fully qualified path to reset combo:
onclick="javascript:test.BasicForm.resetCombo();" //etcetera
Lastly, though you may get the above to work, it is far from best practice. I don't have time to give the complete answer, but essentially what you want to do consists of:
Adding a click event handler to the tooltip's underlying Element
Then inside the element use the arguments to Ext.dom.Element.click (see http://docs.sencha.com/ext-js/4-1/#!/api/Ext.dom.Element-event-click) to ensure it was an <A> tag that got clicked
Then invoke the desired function without having to use Javascript pseudo-URL's and a fully qualified path to the function
Here is my working re-write of the afterrender listener following the above guidelines, with some tweaks to scope, in particular storing a reference to the form in a variable of the same name.
listeners:{
afterrender: function(combo) {
var form = this;
var tooltip = Ext.create('Ext.tip.ToolTip', {
target: combo.getEl(),
autoHide: false,
name:'tool-tip',
html: 'Old value was '+ combo.getValue()+ ' <a class="tooltipHref" href="#">test</a>',
listeners: {
beforeshow: function() {
return combo.isDirty();
},
afterrender: function() {
tooltip.el.on('click', function(event, element) {
if (element.className == 'tooltipHref') {
form.resetCombo();
}
});
}
}
});
},
scope: this
}
test
this code is attempting to call a function named resetCombo which is stored inside the top-level object (the window object).

Resources