Embedding famo.us in existing DOM - famo.us

Is there a way to create a famo.us container in an existing DOM structure?
What I mean is to create a famo.us context, and the resulting .famous-container (?), and append it to an existing element.

Absolutely. This gets asked a lot. As shown in this video by Mike O'Brien at Famo.us it is pretty easy to do.
Example code on jsBin.
var el = document.getElementById('famous-app');
var mainContext = Engine.createContext(el);
var surface = new Surface({
size: [200, 200],
content: "Hello World, I'm Famo.us",
classes: ["red-bg"],
properties: {
lineHeight: "200px",
textAlign: "center",
backgroundColor: 'rgba(0,0,0,0.5)'
}
});
mainContext.add(surface);
The DOM has an element with id='famous-app' in this case. It can be any element in your DOM.
<body>
<div>Not Famous here</div>
<div id="famous-app"></div>
</body>
To append it to the DOM.
Example 2 code on jsBin.
var el = document.createElement('div');
el.id = 'test';
document.body.appendChild(el);
var mainContext = Engine.createContext(el);
var surface = new Surface({
size: [200, 200],
content: "Hello World,<br/> Famo.us-ly added",
classes: ["red-bg"],
properties: {
lineHeight: "40px",
textAlign: "center",
backgroundColor: 'rgba(255,0,0,0.5)'
}
});
mainContext.add(surface);
<body>
<div>Not Famous here</div>
</body>

Related

How to add vertical lines and annotations Google timeline chart

I am using a google timeline similar to the code snippet below. I want my chart to look like the one below. I have managed to get everything to work expect how to add the dashed lines and text notation. Unfortunately, when I am searching for annotations I keep getting the AnnotatedTimeline, which is a different google chart.
Is there a simple way to do this?
<html>
<head>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load('current', {'packages':['timeline']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var container = document.getElementById('timeline');
var chart = new google.visualization.Timeline(container);
var dataTable = new google.visualization.DataTable();
dataTable.addColumn({ type: 'string', id: 'President' });
dataTable.addColumn({ type: 'date', id: 'Start' });
dataTable.addColumn({ type: 'date', id: 'End' });
dataTable.addRows([
[ 'Washington', new Date(1789, 3, 30), new Date(1797, 2, 4) ],
[ 'Adams', new Date(1797, 2, 4), new Date(1801, 2, 4) ],
[ 'Jefferson', new Date(1801, 2, 4), new Date(1809, 2, 4) ]]);
chart.draw(dataTable);
}
</script>
</head>
<body>
<div id="timeline" style="height: 180px;"></div>
</body>
</html>
I was able to get this to work by finding the position of the rects. I started by drawing divs for each line I would want to show. Then after the timeline is draw I repositions those divs based on the location of the rectangle. I was not able to get a good minimal working snippet here because of the window positions used in the snippet code, but I got pretty close. In my own code I have it working perfectly.
.hline {
border-left: 5px solid black;
height: 100px;
position:absolute;
visibility:hidden;
top:144px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<html>
<head>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
</head>
<body>
<div id="timeline" style="height: 180px;"></div>
<div id = "Hline1" class= "hline" > <div style = "position: relative; top:-18px">HLine1</div>
<div id = "Hline2" class= "hline" > <div style = "position: relative; top:-18px">HLine2</div>
<div id = "Hline3" class= "hline" > <div style = "position: relative; top:-18px">HLine3</div>
</div>
</body>
<script>
var options = {
timeline: { showRowLabels: false }
};
const lime="#00ff00" //color for average time
google.charts.load('current', {'packages':['timeline']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var container = document.getElementById('timeline');
var chart = new google.visualization.Timeline(container);
var dataTable = new google.visualization.DataTable();
dataTable.addColumn({ type: 'string', id: 'Project Stage', });
dataTable.addColumn({ type: 'string', id: 'Bar'});
dataTable.addColumn({ type: 'string', role: 'style'});
dataTable.addColumn({ type: 'date', id: 'Start' });
dataTable.addColumn({ type: 'date', id: 'End' });
dataTable.addRows([
[ 'Washington','Washington',lime, new Date(1789, 3, 30), new Date(1797, 2, 4) ],
[ 'Adams', 'Adams',lime, new Date(1797, 2, 4), new Date(1801, 2, 4) ],
[ 'Jefferson','Jefferson',lime, new Date(1801, 2, 4), new Date(1809, 2, 4) ]]);
chart.draw(dataTable,options);
function redraw (){
var rects = $('rect') //get all rectangles on plot.
function checkColor(arr){
var results = [];
for (let i of arr){
var colorCheck=$(i).attr('fill')
var x =$(i).attr('x')
var width = $(i).attr('width')
var x2 =parseFloat(x)+parseFloat(width)
if(colorCheck == lime){results.push(x2)}
};
return results
};
var linPositions = checkColor(rects) //get x coordinates for vertical lines
var yStart = $('rect')
//console.log(linPositions)
yStart = $(yStart[0]).offset().top;
xMargin=$("#timeline").offset().left;
var yHeight = $('rect')
yHeight = $(yHeight[0]).attr('height');
var lineNames=['Hline1','Hline2','Hline3']
for (let i = 0; i < linPositions.length; i++) {
var position = linPositions[i]+xMargin+"px"
var newTop = i*yHeight + yStart
/* set line information based on current chart positions */
document.getElementById(lineNames[i]).style.left = position;
document.getElementById(lineNames[i]).style.visibility = "visible";
document.getElementById(lineNames[i]).style.top = newTop;
document.getElementById(lineNames[i]).style.height = yHeight;
};
};
redraw()
function resizeChart () {
chart.draw(dataTable, options);
}
if (document.addEventListener) {
window.addEventListener('resize', resizeChart);
window.addEventListener('resize', redraw)
}
else if (document.attachEvent) {
window.attachEvent('onresize', resizeChart);
window.attachEvent('onresize', redraw);
}
else {
window.resize = resizeChart;
window.resize = redraw;
}
}
</script>
</html>

Do famo.us layouts like SequentialLayout participate in the render tree?

When using a SequentialLayout in trying to apply StateModifiers to Surface objects that had been added to a layout, it looks like some unexpected behavior happens:
When applying transformations via setTransform on a StateModifier, I expect to see the transformation applied from the origin of the Surface in question.
Instead, the transform is applied from an origin of 0,0 in relation to the parent SequentialLayout
Given the code below, the above behavior seems to make no logical sense (for context, I am working on a sorting algorithms demo, using famo.us):
/* globals define */
define(function(require, exports, module) {
'use strict';
// import dependencies
var Engine = require('famous/core/Engine');
var Modifier = require('famous/core/Modifier');
var Transform = require('famous/core/Transform');
var Surface = require('famous/core/Surface');
var StateModifier = require('famous/modifiers/StateModifier');
var SequentialLayout = require('famous/views/SequentialLayout');
// create the main context
var mainContext = Engine.createContext();
// your app here
var surfaces = [];
// Sorter
var Sort = require('sort');
var arr = [100,25,20,15,30,-20,-10,10,0];
var min = Math.min.apply(null, arr);
var base_dims = [ 50, 50 ];
arr.forEach(function(el) {
surfaces.push(new Surface({
content: el,
size: base_dims.map(function(d) { return d + (el - min); }),
properties: {
backgroundColor: 'rgb(240, 238, 233)',
textAlign: 'center',
padding: '5px',
border: '2px solid rgb(210, 208, 203)',
marginTop: '50px',
marginLeft: '50px'
}
}));
});
var sequentialLayout = new SequentialLayout({
direction: 0,
itemSpacing:20
});
sequentialLayout.sequenceFrom(surfaces);
mainContext.add(sequentialLayout);
var swap_modifiers = [
new StateModifier({}), new StateModifier({})
];
Sort.bubble_sort_iterative(arr, function(first_swap_index, second_swap_index) {
swap_modifiers[0].setTransform(
Transform.translate(300, 0, 0),
{ duration : 750, curve: 'linear' }
);
swap_modifiers[1].setTransform(
Transform.translate(300, 0, 0),
{ duration : 750, curve: 'linear' }
);
mainContext.add(swap_modifiers[0]).add(surfaces[first_swap_index]);
mainContext.add(swap_modifiers[1]).add(surfaces[second_swap_index]);
});
});
A surface has no origin, a (state-)modifier has an origin. Since you don't provide any origin vaue, the default value is set up, which is [0, 0]. See more:
http://famo.us/university/lessons/#/famous-101/positioning/8
Think of your SequentialLayout as a Render Node in your tree. Adding surfaces to SequentialLayout is in essence adding individual nodes to that tree branch. SequentialLayout happens to be adding each item at the same level in the tree.
Sort.bubble_sort_iterative(... changes the location of the surfaces by adding them to the mainContext of your application. This is the same level as the sequentialLayout and makes their origin the same origin as the sequentialLayout. Not what you wanted!
Remember: Adding a modifier to a context will make that context the parent node.
Without knowing the specifics of the above code, we know that we can add a View rather than surfaces to the sequentialLayout and could transition the View's modifiers within each of those items without changing their location in the render tree.
A simple code example of views in the sequential layout:
arr.forEach(function(el) {
var surfSize = base_dims.map(function(d) { return d + (el - min); });
console.log(size);
var view = new View();
view.mod = new StateModifier({ size: surfSize });
view.surface = new Surface({
content: el,
size: [undefined, undefined],
properties: {
backgroundColor: 'rgb(240, 238, 233)',
textAlign: 'center',
padding: '5px',
border: '2px solid rgb(210, 208, 203)',
marginTop: '50px',
marginLeft: '50px'
}
});
view.add(view.mod).add(view.surface);
surfaces.push(view);
});
Trying to swap out the views from one to the other will give you some unexpected results. It would be better to just swap out the options and content values.

in Famo.us, how do you pipe events to parent view from inside parent view

I have a custom view that contains a surface. I am needing to pipe the surface events to the parent view. I can do this easily from outside the view, but how do I do this from inside the view? Here is my custom view with the code that does NOT work:
define([
"famous/core/view",
"famous/core/Surface",
"famous/modifiers/StateModifier"
], function(View, Surface, StateModifier){
function _createContainer() {
var self = this;
var container = new Surface({
classes: ['blue-bg'],
content: 'HERE IS A LOVELY BIT OF CONTENT FOR MY SURFACE'
});
// THIS DOESN'T WORK, BUT ILLUSTRATES WHAT I'M NEEDING TO DO:
container.pipe(self);
self.containerNode.add(container);
self.form = container;
}
function FormView(){
var self = this;
View.apply(self, arguments);
var containerMod = new StateModifier({
size: self.options.size
});
self.containerNode = self.add(containerMod);
_createContainer.call(self);
}
FormView.prototype = Object.create(View.prototype);
FormView.prototype.constructor = FormView;
FormView.DEFAULT_OPTIONS = {
size: [300, 800]
};
return FormView;
});
Here is example code that does work, but that I want to do from inside the view:
var myView = new View();
mainContext.add(myView);
var surface = new Surface({
size: [100, 100],
content: 'click me',
properties: {
color: 'white',
textAlign: 'center',
backgroundColor: '#FA5C4F'
}
});
myView.add(surface);
surface.pipe(myView);
Inside your custom view FormView you need to pipe to the view's event handler. This will allow the view's events to be seen by a Scrollview when they are added to surfaces.
Change
container.pipe(self);
to
container.pipe(self._eventOutput);

famo.us: can I animate the header/footer heights of a header footer layout?

I want to have my header and footer almost take up the entire screen (there will just be a thin line left in the middle which will contain a textbox. If the user enters the right password, I want the textbox to disappear and the header and footer to gradually get shorter (making more room for content to appear in the center of the screen).
Is it possible to apply a transition to the height of the header and footer on a HeaderFooterLayout?
How do I show a typical password box where the characters all show as *'s?
Like many animations that are not supported by default, you can add a transition by using the Transitionable class.. Here is an example that expands the header when you click it..
var Engine = require("famous/core/Engine");
var Surface = require("famous/core/Surface");
var HeaderFooterLayout = require("famous/views/HeaderFooterLayout");
var Transitionable = require("famous/transitions/Transitionable");
var Easing = require("famous/transitions/Easing");
var mainContext = Engine.createContext();
var layout = new HeaderFooterLayout({
headerSize: 100,
footerSize: 50
});
var header = new Surface({
size: [undefined, undefined],
content: "Header",
classes: ["red-bg"],
properties: {
lineHeight: "100px",
textAlign: "center"
}
})
var open = false;
header.on("click",function(){
var transition = {duration: 400, curve: Easing.inOutQuad };
var start = open ? 200 : 100 ;
var end = open ? 100 : 200 ;
open = !open;
var transitionable = new Transitionable(start);
var prerender = function(){ layout.setOptions({ headerSize: transitionable.get()} ) };
var complete = function(){ Engine.removeListener('prerender', prerender) };
Engine.on('prerender', prerender);
transitionable.set(end, transition, complete);
});
layout.header.add(header);
layout.content.add(new Surface({
size: [undefined, undefined],
content: "Content",
classes: ["grey-bg"],
properties: {
lineHeight: window.innerHeight - 150 + 'px',
textAlign: "center"
}
}));
layout.footer.add(new Surface({
size: [undefined, 50],
content: "Footer",
classes: ["red-bg"],
properties: {
lineHeight: "50px",
textAlign: "center"
}
}));
mainContext.add(layout);
As for the password field, you simply create an InputSurface and set it's type to password..
inputSurface = new InputSurface({
size:[200,60],
type: 'password'
});
^ Watch out for performance issues when using Transitionable on headerSize. Especially iPhones with iOS 7 seem to be acting glitchy.
You can also animate header / footer size via CSS transitions, although it's a bit of a bubblegum fix and has it's pitfalls:
var headerContainer = new ContainerSurface({
size: [undefined, 50],
classes: ['my-header']
});
layout.header.add(headerContainer);
headerContainer.setSize([undefined,300]);
Then in CSS:
.my-header { transition: 200ms all; }

Combining two types of Category Filter in Google Charts API

I wanted to enable users to filter the results being displayed on the chart. Google API provides CategoryFilter which enforces filtering by rows. Here is my code which works perfectly fine
<html>
<head>
<!--Load the Ajax API-->
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script type="text/javascript">
// Load the Visualization API and the piechart package.
google.load('visualization', '1', {'packages':['controls']});
// Set a callback to run when the Google Visualization API is loaded.
google.setOnLoadCallback(drawChart);
function drawChart() {
// Create our data table out of JSON data loaded from server.
var data = new google.visualization.DataTable(<?=$jsonTable?>);
var countryPicker = new google.visualization.ControlWrapper({
controlType: 'CategoryFilter',
containerId: 'negeri',
dataTable: data,
options: {
filterColumnLabel: 'Negeri',
ui: {
labelStacking: 'vertical',
allowTyping: false,
allowMultiple: true
}
},
// Define an initial state, i.e. a set of metrics to be initially selected.
state: {'selectedValues': ['Kedah', 'Johor']}
});
var chart = new google.visualization.ChartWrapper({
chartType: 'ColumnChart',
containerId: 'chart_div',
options: {
title: 'Statistik Negeri vs. Kategori Sukan',
width: 1000,
height: 1000,
hAxis: {title: 'Negeri', titleTextStyle: {color: 'blue'}},
vAxis: {title: 'Jumlah Kategori', titleTextStyle: {color: 'blue'}}
}
});
// Create the dashboard.
new google.visualization.Dashboard(document.getElementById('dashboard')).
// Configure the controls so that:
// - the 'Country' selection drives the 'Region' one,
// - the 'Region' selection drives the 'City' one,
// - and finally the 'City' output drives the chart
bind(countryPicker, chart).
// Draw the dashboard
draw(data);
}
</script>
</head>
<body>
<div id="dashboard">
<div id="negeri"></div>
<div id="chart_div"></div>
</div>
</body>
</html>
However in my datatable, I would also want to filter by columns. These two types of filtering should work together. (dependent; by bind() function). I have referred to #asgallant http://jsfiddle.net/asgallant/WaUu2/ and that is the feature that I wanted to combine with.
How can I possibly combine them? I have tried combining setChartView() by #asgallant with google's dashboard() but it's not working.
<html>
<head>
<!--Load the Ajax API-->
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script type="text/javascript">
// Load the Visualization API and the piechart package.
google.load('visualization', '1', {'packages':['controls']});
// Set a callback to run when the Google Visualization API is loaded.
google.setOnLoadCallback(drawChart);
function drawChart() {
// Create our data table out of JSON data loaded from server.
var data = new google.visualization.DataTable(<?=$jsonTable?>);
var columnsTable = new google.visualization.DataTable();
columnsTable.addColumn('number', 'colIndex');
columnsTable.addColumn('string', 'colLabel');
var initState= {selectedValues: []};
// put the columns into this data table (skip column 0)
for (var i = 1; i < data.getNumberOfColumns(); i++) {
columnsTable.addRow([i, data.getColumnLabel(i)]);
// you can comment out this next line if you want to have a default selection other than the whole list
initState.selectedValues.push(data.getColumnLabel(i));
}
var countryPicker = new google.visualization.ControlWrapper({
controlType: 'CategoryFilter',
containerId: 'negeri',
dataTable: data,
options: {
filterColumnLabel: 'Negeri',
ui: {
labelStacking: 'vertical',
allowTyping: false,
allowMultiple: true
}
},
// Define an initial state, i.e. a set of metrics to be initially selected.
state: {'selectedValues': ['Kedah', 'Johor']}
});
var columnFilter = new google.visualization.ControlWrapper({
controlType: 'CategoryFilter',
containerId: 'colFilter_div',
dataTable: columnsTable,
options: {
filterColumnLabel: 'colLabel',
ui: {
label: 'Kategori Sukan',
allowTyping: false,
allowMultiple: true,
allowNone: false,
selectedValuesLayout: 'belowStacked'
}
},
state: initState
});
var chart = new google.visualization.ChartWrapper({
chartType: 'ColumnChart',
containerId: 'chart_div',
options: {
title: 'Statistik Negeri vs. Kategori Sukan',
width: 1000,
height: 1000,
hAxis: {title: 'Negeri', titleTextStyle: {color: 'blue'}},
vAxis: {title: 'Jumlah Kategori', titleTextStyle: {color: 'blue'}}
}
});
// Create the dashboard.
new google.visualization.Dashboard(document.getElementById('dashboard')).
// Configure the controls so that:
// - the 'Country' selection drives the 'Region' one,
// - the 'Region' selection drives the 'City' one,
// - and finally the 'City' output drives the chart
bind(countryPicker, columnFilter).
bind(columnFilter, chart).
// Draw the dashboard
draw(data);
}
</script>
</head>
<body>
<div id="dashboard">
<div id="negeri"></div>
<div id="chart_div"></div>
</div>
</body>
</html>
You want to bind your countryPicker filter to the chart as normal, but do not bind the columnFilter control to anything - the setChartView function handles everything for the columnFilter. You need to tweak a couple other lines to make it work with a dashboard, but nothing major. This is what it should look like:
function drawChart() {
// Create our data table out of JSON data loaded from server.
var data = new google.visualization.DataTable(<?=$jsonTable?>);
var columnsTable = new google.visualization.DataTable();
columnsTable.addColumn('number', 'colIndex');
columnsTable.addColumn('string', 'colLabel');
var initState= {selectedValues: []};
// put the columns into this data table (skip column 0)
for (var i = 1; i < data.getNumberOfColumns(); i++) {
columnsTable.addRow([i, data.getColumnLabel(i)]);
// you can comment out this next line if you want to have a default selection other than the whole list
initState.selectedValues.push(data.getColumnLabel(i));
}
var countryPicker = new google.visualization.ControlWrapper({
controlType: 'CategoryFilter',
containerId: 'negeri',
dataTable: data,
options: {
filterColumnLabel: 'Negeri',
ui: {
labelStacking: 'vertical',
allowTyping: false,
allowMultiple: true
}
},
// Define an initial state, i.e. a set of metrics to be initially selected.
state: {'selectedValues': ['Kedah', 'Johor']}
});
var columnFilter = new google.visualization.ControlWrapper({
controlType: 'CategoryFilter',
containerId: 'colFilter_div',
dataTable: columnsTable,
options: {
filterColumnLabel: 'colLabel',
ui: {
label: 'Kategori Sukan',
allowTyping: false,
allowMultiple: true,
allowNone: false,
selectedValuesLayout: 'belowStacked'
}
},
state: initState
});
var chart = new google.visualization.ChartWrapper({
chartType: 'ColumnChart',
containerId: 'chart_div',
options: {
title: 'Statistik Negeri vs. Kategori Sukan',
width: 1000,
height: 1000,
hAxis: {title: 'Negeri', titleTextStyle: {color: 'blue'}},
vAxis: {title: 'Jumlah Kategori', titleTextStyle: {color: 'blue'}}
}
});
// Create the dashboard.
var dashboard = new google.visualization.Dashboard(document.getElementById('dashboard')).
bind(countryPicker, chart);
function setChartView () {
var state = columnFilter.getState();
var row;
var view = {
columns: [0]
};
for (var i = 0; i < state.selectedValues.length; i++) {
row = columnsTable.getFilteredRows([{column: 1, value: state.selectedValues[i]}])[0];
view.columns.push(columnsTable.getValue(row, 0));
}
// sort the indices into their original order
view.columns.sort(function (a, b) {
return (a - b);
});
chart.setView(view);
chart.draw();
}
google.visualization.events.addListener(columnFilter, 'statechange', setChartView);
var runOnce = google.visualization.events.addListener(dashboard, 'ready', function () {
google.visualization.events.removeListener(runOnce);
setChartView();
});
columnFilter.draw();
dashboard.draw(data);
}
google.load('visualization', '1', {packages:['corechart'], callback: drawChart});