Integrating OpenLayers 3 with Cesium Viewer - openlayers-3

I'm developing using Cesium built on top of Cesium.Viewer. Cesium lacks some features so I wish to integrate with OpenLayers. I'd like to add existing OpenLayers layers to the Cesium.Viewer as if they were "imagery layers".
I've found ol3-cesium, however this only allows an entire OpenLayers map instance to be visualized on a Cesium.Scene which it creates for you. Cesium.Viewer also creates an instance of Cesium.Scene targeted at a given DOM element.
How can I add OpenLayers layers to a Cesium.Viewer?
Some code snippets for illustration
var olLayer1= new ol.layer.Tile({
source: new ol.source.MapQuest({layer: 'sat'})
});
var olLayer2= new ol.layer.Vector({
source : ol.source.Vector();
});
var map = new ol.Map({
layers: [olLayer1, olLayer2],
target: 'map',
view: new ol.View({
})
});
Existing Cesium viewer
var viewer = new Cesium.Viewer('cesium-map', {});
// viewer setup code
ol3-cesium initialization - but this doesn't allow usage with existing viewer??
var ol3d = new olcs.OLCesium({map: map}); // map is the ol.Map instance

I just looked through the initialization code for OL3-Cesium and while it is essentially a wrapper on top of Cesium, the means that they decided to implement a Cesium environment is not going to play nicely if you want a hybrid Cesium.Viewer and an OL3 object.
I'm not sure of your comfort level with modifying JS libraries, but
what I personally would do is make my own ol3 cesium viewer class. Something like this gist I just threw together.
Just a warning, I have not tested this code yet. You may have to make some additional modifications if you receive errors. There may be a reason that the OL3-Cesium developers choose not to use the Cesium widget or viewer to initialize Cesium in their library, but I see no reason this wouldn't work.
Here is the constructor, but you would want the whole Gist as a separate file in your ol3-cesium library. Put it in the same directory as the ol3Cesium.js file.
excerpt from https://gist.github.com/maikuru/9e650bf88aed84982667
olcs.OLCesiumViewer = function(options) {
/**
* #type {!ol.Map}
* #private
*/
this.map_ = options.map;
var fillArea = 'position:absolute;top:0;left:0;width:100%;height:100%;';
/**
* #type {!Element}
* #private
*/
this.container_ = goog.dom.createDom(goog.dom.TagName.DIV,
{style: fillArea + 'visibility:hidden;'});
var targetElement = goog.dom.getElement(options.target || null);
if (targetElement) {
goog.dom.appendChild(targetElement, this.container_);
} else {
var vp = this.map_.getViewport();
var oc = goog.dom.getElementByClass('ol-overlaycontainer', vp);
if (oc) {
goog.dom.insertSiblingBefore(this.container_, oc);
}
}
/**
* Whether the Cesium container is placed over the ol map.
* #type {boolean}
* #private
*/
this.isOverMap_ = !goog.isDefAndNotNull(targetElement);
/**
* #type {!HTMLCanvasElement}
* #private
*/
this.canvas_ = /** #type {!HTMLCanvasElement} */
(goog.dom.createDom(goog.dom.TagName.CANVAS, {style: fillArea}));
this.canvas_.oncontextmenu = function() { return false; };
this.canvas_.onselectstart = function() { return false; };
goog.dom.appendChild(this.container_, this.canvas_);
/**
* #type {boolean}
* #private
*/
this.enabled_ = false;
/**
* #type {!Array.<ol.interaction.Interaction>}
* #private
*/
this.pausedInteractions_ = [];
/**
* #type {?ol.layer.Group}
* #private
*/
this.hiddenRootGroup_ = null;
/**
* #type {!Object.<Cesium.Viewer.Options>}
* #private
*/
var cesiumViewerConfig_ = (options.viewer || {}).scene3DOnly = true;
/**
* #type {!Cesium.Viewer}
* #private
*/
this.viewer_ = new Cesium.Viewer(this.container_, cesiumViewerConfig_);
/**
* #type {!Cesium.Scene}
* #private
*/
this.scene_ = this.viewer_.scene;
var sscc = this.scene_.screenSpaceCameraController;
sscc.inertiaSpin = 0;
sscc.ineartiaTranslate = 0;
sscc.inertiaZoom = 0;
sscc.tiltEventTypes.push({
'eventType': Cesium.CameraEventType.LEFT_DRAG,
'modifier': Cesium.KeyboardEventModifier.SHIFT
});
sscc.tiltEventTypes.push({
'eventType': Cesium.CameraEventType.LEFT_DRAG,
'modifier': Cesium.KeyboardEventModifier.ALT
});
sscc.enableLook = false;
this.scene_.camera.constrainedAxis = Cesium.Cartesian3.UNIT_Z;
/**
* #type {!olcs.Camera}
* #private
*/
this.camera_ = new olcs.Camera(this.scene_, this.map_);
/**
* #type {!Cesium.Globe}
* #private
*/
this.globe_ = this.scene_.globe;
this.scene_.skyAtmosphere = new Cesium.SkyAtmosphere();
var synchronizers = goog.isDef(options.createSynchronizers) ?
options.createSynchronizers(this.map_, this.scene_) :
[
new olcs.RasterSynchronizer(this.map_, this.scene_),
new olcs.VectorSynchronizer(this.map_, this.scene_)
];
for (var i = synchronizers.length - 1; i >= 0; --i) {
synchronizers[i].synchronize();
}
if (this.isOverMap_) {
// if in "stacked mode", hide everything except canvas (including credits)
var credits = goog.dom.getNextElementSibling(this.canvas_);
if (goog.isDefAndNotNull(credits)) {
credits.style.display = 'none';
}
}
this.camera_.readFromView();
this.cesiumRenderingDelay_ = new goog.async.AnimationDelay(function(time) {
this.scene_.initializeFrame();
this.handleResize_();
this.scene_.render();
this.enabled_ && this.camera_.checkCameraChange();
this.cesiumRenderingDelay_.start();
}, undefined, this);
};

There are some internal classes which emulate layers using the Cesium API calls. These are in the documentation so I'm assuming "public" API.
The layers must be added to a map and cannot be used in isolation. This is required as the map defines the projection etc.
For example VectorSynchronizer creates listeners on the underlying layers and keeps the Cesium scene in sync...
new olcs.VectorSynchronizer(map, viewer.scene);

Related

TYPO3 FAL upload frontend add title, alternative and description

I have an extension where user can upload in frontend some images to their frontend-profile. Image upload and saving to user-profile works fine, I user following code - but is there a simple way to add title, alternative and description too?
Thanks for help!
Martin
/** #var \TYPO3\CMS\Core\Resource\StorageRepository $storageRepository */
$storageRepository = $this->objectManager->get('TYPO3\CMS\Core\Resource\StorageRepository');
/** #var \TYPO3\CMS\Core\Resource\ResourceStorage $storage */
$storage = $storageRepository->findByUid('1');
#$storageRepository = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\StorageRepository');
#$storage = $storageRepository->findByUid(1); # Fileadmin = 1
#$saveFolder = $storage->getFolder($this->settings['uploadFolder']);
$fileData = array();
$fileData['name'] = $_FILES['tx_key_datenwartung']['name']['logo'][0];
$fileData['type'] = $_FILES['tx_key_datenwartung']['type']['logo'][0];
$fileData['tmp_name'] = $_FILES['tx_key_datenwartung']['tmp_name']['logo'][0];
$fileData['size'] = $_FILES['tx_key_datenwartung']['size']['logo'][0];
// this will already handle the moving of the file to the storage:
$newFileObject = $storage->addFile(
$fileData['tmp_name'], $saveFolder, $userUid.'_'.$fileData['name']
);
$newFileObject = $storage->getFile($newFileObject->getIdentifier());
#$newFile = $this->fileRepository->findByUid($newFileObject->getProperty('uid'));
$newFile = $newFileObject->getUid();
$persistenceManager = $this->objectManager->get('TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager');
$persistenceManager->persistAll();
/** #var \Vendor\Myextension\Domain\Model\FileReference $newFileReference */
$newFileReference = $this->objectManager->get('Ven\Key\Domain\Model\FileReference');
$newFileReference->setFile($newFile);
$user->addLogo($newFileReference);
.... could solve it myself. just add setter to model and add texts:
/**
* Set alternative
*
* #param \xx\yy\Domain\Model\File $file
*/
public function setAlternative($alternative) {
$this->alternative = $alternative;
}
and to controller:
$newFileReference->setAlternative('your alternative text for image');

How to extrude height ol3-Cesium?

I've got a GeoJson that I can load in ol3 without any problems.
var layer = new ol.layer.Vector({
source: new ol.source.Vector({
format: new ol.format.GeoJson(),
url: 'my_file.json',
})
});
My GeoJson have got a properties (let's say foo ) that I want to use to extrudedHeight.
I've done this in Cesium (see the Cesium exemple): http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=GeoJSON%20and%20TopoJSON.html&label=Showcases
But I can't find a way to do this on my ol3 layer.
Any clue ?
Edit:
I've hacked a bit and create my FeatureConverter like this:
test = {};
function extend(base, sub) {
// Avoid instantiating the base class just to setup inheritance
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
// for a polyfill
// Also, do a recursive merge of two prototypes, so we don't overwrite
// the existing prototype, but still maintain the inheritance chain
// Thanks to #ccnokes
var origProto = sub.prototype;
sub.prototype = Object.create(base.prototype);
for (var key in origProto) {
sub.prototype[key] = origProto[key];
}
// Remember the constructor property was set wrong, let's fix it
sub.prototype.constructor = sub;
// In ECMAScript5+ (all modern browsers), you can make the constructor property
// non-enumerable if you define it like this instead
Object.defineProperty(sub.prototype, 'constructor', {
enumerable: false,
value: sub
});
}
test.FeatureConverter = function(scene) {
olcs.FeatureConverter.call(this, scene);
}
test.FeatureConverter.prototype = {
olPolygonGeometryToCesium : function(layer, feature, olGeometry, projection, olStyle) {
olGeometry = olcs.core.olGeometryCloneTo4326(olGeometry, projection);
goog.asserts.assert(olGeometry.getType() == 'Polygon');
var rings = olGeometry.getLinearRings();
// always update Cesium externs before adding a property
var hierarchy = {};
var polygonHierarchy = hierarchy;
goog.asserts.assert(rings.length > 0);
for (var i = 0; i < rings.length; ++i) {
var olPos = rings[i].getCoordinates();
var positions = olcs.core.ol4326CoordinateArrayToCsCartesians(olPos);
goog.asserts.assert(positions && positions.length > 0);
if (i == 0) {
hierarchy.positions = positions;
} else {
hierarchy.holes = {
// always update Cesium externs before adding a property
positions: positions
};
hierarchy = hierarchy.holes;
}
}
var fillGeometry = new Cesium.PolygonGeometry({
// always update Cesium externs before adding a property
polygonHierarchy: polygonHierarchy,
perPositionHeight: true,
extrudedHeight: parseInt(feature.getProperties()['foo'])
});
var outlineGeometry = new Cesium.PolygonOutlineGeometry({
// always update Cesium externs before adding a property
polygonHierarchy: hierarchy,
perPositionHeight: true
});
var primitives = this.wrapFillAndOutlineGeometries(
layer, feature, olGeometry, fillGeometry, outlineGeometry, olStyle);
return this.addTextStyle(layer, feature, olGeometry, olStyle, primitives);
}
}
extend(olcs.FeatureConverter, test.FeatureConverter);
But it does not work... (and I do not know why...).

Absolute path in Magento2

How can I get the absolute path of a media file in Magento2?
I write a function to get the absolute path, but it is not working.
public function getAbsolutePath()
{
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
/** #var \Magento\Framework\Filesystem $filesystem */
$filesystem = $objectManager->get('Magento\Framework\Filesystem');
/** #var \Magento\Framework\Filesystem\Directory\WriteInterface $mediaDirectory */
$mediaDirectory = $filesystem->getDirectoryWrite(Magento\Framework\App\Filesystem\DirectoryList::MEDIA);
$mediaPath = $mediaDirectory->getAbsolutePath();
return $mediaPath;
}
app/autoloader.php
contains:
/**
* Shortcut constant for the root directory
*/
define('BP', dirname(__DIR__));
One could do something like:
$mediaPath = BP.'/pub/media/';
That said, Magento\Framework\Filesystem via dependency injection is my preferred method
You can use to get media and base Absolute path in magento 2 like:
$storeManager = $_objectMedia->get('Magento\Store\Model\StoreManagerInterface');
$currentStore = $storeManager->getStore();
$mediaUrl = $currentStore->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA);
$_baseurl = $storeManager->getStore()->getBaseUrl();
Try it's working fine for me....
/* #var \Magento\Framework\ObjectManagerInterface $om /
$om = \Magento\Framework\App\ObjectManager::getInstance();
$dir = $om->get('Magento\Framework\Filesystem');
$mediadir = $dir->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::MEDIA);
$path = $mediadir->create('foldername');
try it its working for me..
Relying on the BP isn't a good way to go this, BP should only be used in the low layers in Magento.
Magneto has init config values where the media directory could possibly be outside of the base path. You should rely on the \Magento\Framework\App\Filesystem\DirectoryList object instead.
In the bootstrap you can see:
\Magento\Framework\App\Bootstrap::createFilesystemDirectoryList
class Bootstrap
{
/**
* Creates instance of filesystem directory list
*
* #param string $rootDir
* #param array $initParams
* #return DirectoryList
*/
public static function createFilesystemDirectoryList($rootDir, array $initParams)
{
$customDirs = [];
if (isset($initParams[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS])) {
$customDirs = $initParams[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS];
}
return new DirectoryList($rootDir, $customDirs);
}
}
This means if you set $_SERVER variables during bootstrap:
$tmpDir = '/var/tmpfs_mount';
$applicationDir = '/var/www/html/magento';
$loggingDir = '/var/log/restricted';
$params = $_SERVER;
$filesystemsDirs = $params[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS];
$filesystemsDirs[DirectoryList::CONFIG] = [DirectoryList::PATH => $applicationDir . "/var/cache"];
$filesystemsDirs[DirectoryList::MEDIA] = [DirectoryList::PATH => $tmpDir . "/var/cache"];
$filesystemsDirs[DirectoryList::GENERATED] = [DirectoryList::PATH => $tmpDir . "/generated"];
$filesystemsDirs[DirectoryList::CACHE] = [DirectoryList::PATH => $tmpDir . "/var/cache"];
$filesystemsDirs[DirectoryList::LOG] = [DirectoryList::PATH => $loggingDir . "/var/log"];
$params[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS] = $filesystemsDirs;
$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $params);
To do via the object manager, add the DirectoryList object to the constructor:
use \Magento\Framework\App\Filesystem\DirectoryList;
class MyModuleClass
{
/**
* #var DirectoryList
*/
private $directoryList;
/**
* #param DirectoryList $directoryList
*/
public function __construct(DirectoryList $directoryList)
{
$this->directoryList = $directoryList;
}
/**
* #var DirectoryList
*/
private $mediaPath;
public function getMediaAbsolutePath(){
if (!$this->mediaPath){
$this->mediaPath = $this->directoryList->getPath(DirectoryList::MEDIA);
}
return $this->mediaPath;
}
}
Please note this will only get you the path, but it won't validate if via the PathValidator introduced in Magento 2.3. This PathValidator ensures that the path is allowed within the installation, you'll need to validate it yourself
vendor/magento/framework/Filesystem/Directory/PathValidator.php
P.S I haven't tested the code, I've just eye'd it from memory, so if it breaks let me know.
With Object manager:
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$directoryList = $objectManager->get('Magento\Framework\App\Filesystem\DirectoryList');
$media = $directoryList->getPath('media');
With dependency injection:
Use class `\Magento\Framework\App\Filesystem\DirectoryList
protected $_directoryList;
public function __construct(
...
\Magento\Framework\App\Filesystem\DirectoryList $directoryList
...
) {
$this->_directoryList = $directoryList;
}
For Root Folder
$this->_directoryList->getRoot();
For media Folder
$this->_directoryList->getPath('media');
Other Folders
// Get app folder
$this->_directoryList->getPath('app');
// Get etc Folder
$this->_directoryList->getPath('etc');
// Get public folder
$this->_directoryList->getPath('pub');
// Get var folder
$this->_directoryList->getPath('var');
you can use this:
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$fileSystem = $om->get('Magento\Framework\Filesystem');
$mediaDirectory = $fileSystem->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::MEDIA);
Use blow code for geting absolute url of an image you have uploaded in product page.
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$b_url = $objectManager->get('Magento\Store\Model\StoreManagerInterface')
->getStore()
->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA);
// here product_badge is custom image attribute code, replace it with yours attribute.
$_product = $block->getProduct();
$product_badge = $_product->getResource()->getAttribute('product_badge')->getFrontend()->getValue($_product);
?>
<img src="<?php echo $b_url.'/catalog/product'.$product_badge; ?>" />
$rootDirectory = Magento\Framework\App\ObjectManager::getInstance()->get('Magento\Framework\Filesystem')
->getDirectoryRead(DirectoryList::MEDIA);

List all indexeddb for one host in firefox addon

I figured if the devtool can list all created IndexedDB, then there should be an API to retrieve them...?
Dose anyone know how I get get a list of names with the help of a firefox SDK?
I did dig into the code and looked at the source. unfortunately there wasn't any convenient API that would pull out all the databases from one host.
The way they did it was to lurk around in the user profiles folder and look at all folder and files for .sqlite and make a sql query (multiple times in case there is an ongoing transaction) to each .sqlite and ask for the database name
it came down this peace of code
// striped down version of: https://dxr.mozilla.org/mozilla-central/source/devtools/server/actors/storage.js
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {async} = require("resource://gre/modules/devtools/async-utils");
const { setTimeout } = require("sdk/timers");
const promise = require("sdk/core/promise");
// A RegExp for characters that cannot appear in a file/directory name. This is
// used to sanitize the host name for indexed db to lookup whether the file is
// present in <profileDir>/storage/default/ location
const illegalFileNameCharacters = [
"[",
// Control characters \001 to \036
"\\x00-\\x24",
// Special characters
"/:*?\\\"<>|\\\\",
"]"
].join("");
const ILLEGAL_CHAR_REGEX = new RegExp(illegalFileNameCharacters, "g");
var OS = require("resource://gre/modules/osfile.jsm").OS;
var Sqlite = require("resource://gre/modules/Sqlite.jsm");
/**
* An async method equivalent to setTimeout but using Promises
*
* #param {number} time
* The wait time in milliseconds.
*/
function sleep(time) {
let deferred = promise.defer();
setTimeout(() => {
deferred.resolve(null);
}, time);
return deferred.promise;
}
var indexedDBHelpers = {
/**
* Fetches all the databases and their metadata for the given `host`.
*/
getDBNamesForHost: async(function*(host) {
let sanitizedHost = indexedDBHelpers.getSanitizedHost(host);
let directory = OS.Path.join(OS.Constants.Path.profileDir, "storage",
"default", sanitizedHost, "idb");
let exists = yield OS.File.exists(directory);
if (!exists && host.startsWith("about:")) {
// try for moz-safe-about directory
sanitizedHost = indexedDBHelpers.getSanitizedHost("moz-safe-" + host);
directory = OS.Path.join(OS.Constants.Path.profileDir, "storage",
"permanent", sanitizedHost, "idb");
exists = yield OS.File.exists(directory);
}
if (!exists) {
return [];
}
let names = [];
let dirIterator = new OS.File.DirectoryIterator(directory);
try {
yield dirIterator.forEach(file => {
// Skip directories.
if (file.isDir) {
return null;
}
// Skip any non-sqlite files.
if (!file.name.endsWith(".sqlite")) {
return null;
}
return indexedDBHelpers.getNameFromDatabaseFile(file.path).then(name => {
if (name) {
names.push(name);
}
return null;
});
});
} finally {
dirIterator.close();
}
return names;
}),
/**
* Removes any illegal characters from the host name to make it a valid file
* name.
*/
getSanitizedHost: function(host) {
return host.replace(ILLEGAL_CHAR_REGEX, "+");
},
/**
* Retrieves the proper indexed db database name from the provided .sqlite
* file location.
*/
getNameFromDatabaseFile: async(function*(path) {
let connection = null;
let retryCount = 0;
// Content pages might be having an open transaction for the same indexed db
// which this sqlite file belongs to. In that case, sqlite.openConnection
// will throw. Thus we retey for some time to see if lock is removed.
while (!connection && retryCount++ < 25) {
try {
connection = yield Sqlite.openConnection({ path: path });
} catch (ex) {
// Continuously retrying is overkill. Waiting for 100ms before next try
yield sleep(100);
}
}
if (!connection) {
return null;
}
let rows = yield connection.execute("SELECT name FROM database");
if (rows.length != 1) {
return null;
}
let name = rows[0].getResultByName("name");
yield connection.close();
return name;
})
};
module.exports = indexedDBHelpers.getDBNamesForHost;
If anyone want to use this then here is how you would use it
var getDBNamesForHost = require("./getDBNamesForHost");
getDBNamesForHost("http://example.com").then(names => {
console.log(names);
});
Think it would be cool if someone were to build a addon that adds indexedDB.mozGetDatabaseNames to work the same way as indexedDB.webkitGetDatabaseNames. I'm not doing that... will leave it up to you if you want. would be a grate dev tool to have ;)

Scaling multiple axes in PlotChart

When I programmatically create a PlotChart starting w/ no series and adding series w/ own scaling and verticalAxes (and Renderers but not necessary for behavior error), the axes and data show, but the default axis controls all scaling and the other axes are erroneous. Thus, the yValue magnitudes do not correspond to their associated axes and mixing series w/ grossly different orders of magnitude squishes all but the largest into indistinguishable floor values. I cannot null out the default vertical axis as it gets an null pointer error. [Click anywhere on the chart to add the next series.]
package
{
/*
* Attempt to dynamically create graph w/ varying series. Having difficulty getting axis to render correctly based
* on sets of series. Having trouble getting more than one series. I can't munge the yValue property because in my
* actual code all of the data elements are instances of the same class with properties akin to series, xValue, and yValue.
*
*/
import flash.events.MouseEvent;
import mx.charts.AxisRenderer;
import mx.charts.DateTimeAxis;
import mx.charts.LinearAxis;
import mx.charts.PlotChart;
import mx.charts.chartClasses.Series;
import mx.charts.events.ChartEvent;
import mx.charts.series.PlotSeries;
import mx.collections.ArrayCollection;
public class GraphSample extends PlotChart
{
public function GraphSample()
{
super();
this.selectionMode = "single";
this.percentWidth = 80;
this.series = new Array();
this.verticalAxisRenderers = new Array();
this.showDataTips = true;
}
protected override function createChildren():void {
super.createChildren();
// Create the horizontal axis
var hAxis:DateTimeAxis = new DateTimeAxis();
hAxis.dataUnits = "days";
this.horizontalAxis = hAxis;
// NOTE: I do not want an automatically created verticalAxis, but can't seem to avoid it
// Add vertical axis renderer
var axis:AxisRenderer = new AxisRenderer();
axis.axis = this.verticalAxis;
axis.horizontal = false;
axis.setStyle("showLabels", true);
axis.setStyle("showLine", true);
this.verticalAxisRenderers.push(axis);
this.verticalAxisRenderers = this.verticalAxisRenderers;
this.addEventListener(ChartEvent.CHART_CLICK, addSeries);
}
private var seriesColors:Array = [0x888888, 0xFFC833, 0xFF6433, 0xE73399,
0x8133CC, 0x346B9B, 0x33C399, 0x98F133];
private var seriesColorCursor:int = 0;
private const ONE_DAY:Number = (1000 * 60 * 60 * 24);
private const TODAY:Date = new Date();
private function addSeries(event:MouseEvent):void {
var dimension:Object = newDimension();
var series:Series = new PlotSeries();
series.displayName = dimension.name;
series.dataFunction = genericFieldRetriever;
series.setStyle("fill", seriesColors[seriesColorCursor++]);
if (seriesColorCursor > seriesColors.length) seriesColorCursor %= seriesColors.length;
series.dataProvider = dimension.elements;
this.series.push(series);
var seriesAxis:LinearAxis = new LinearAxis();
series.setAxis("verticalAxis", seriesAxis);
var min:Number = Number.MAX_VALUE, max:Number = 0;
for each (var ele:Object in series.dataProvider) {
if (ele.value > max) max = ele.value;
if (ele.value < min) min = ele.value;
}
seriesAxis.minimum = min * 0.85;
seriesAxis.maximum = max * 1.2;
seriesAxis.displayName = series.displayName;
seriesAxis.title = series.displayName;
// Add vertical axis renderer
var axis:AxisRenderer = new AxisRenderer();
axis.axis = seriesAxis;
axis.horizontal = false;
axis.setStyle("color", series.getStyle("fill"));
axis.setStyle("showLabels", true);
axis.setStyle("showLine", true);
this.verticalAxisRenderers.push(axis);
this.verticalAxisRenderers = this.verticalAxisRenderers;
// also strokes?
// http://www.flexdeveloper.eu/forums/flex-charting/creating-linecolumn-series-dynamically/
this.series = this.series;
this.invalidateSeriesStyles();
if (names.length == 0) this.removeEventListener(ChartEvent.CHART_CLICK, addSeries);
}
/**
* A fake of the server data retrieval mechanism.
* #return wrapper w/ name and elements set where each element has a data and value.
*/
private function newDimension():Object {
var result:Object = new Object();
result['name'] = names.shift();
var elements:ArrayCollection = new ArrayCollection();
var thisBase:int = base.shift();
var thisMax:int = maxAdd.shift();
for (var date:Date = new Date(TODAY.getTime() - 10 * ONE_DAY); date.getTime() <= TODAY.getTime(); date = new Date(date.getTime() + ONE_DAY)) {
var element:Object = new Object();
element['date'] = date;
element['value'] = thisBase + Math.random() * thisMax;
elements.addItem(element);
}
result['elements'] = elements;
return result;
}
private var names:Array = ['fat', 'carbs', 'steps', 'walking miles', 'pounds', 'cycling miles', 'running miles', 'skiing hours', 'lifting mins'];
private var base:Array = [0, 20, 1000, 0, 100, 0, 0, 0, 0];
private var maxAdd:Array = [100, 200, 9000, 4, 200, 40, 8, 3, 75];
private function genericFieldRetriever(series:Series, item:Object, fieldName:String):Object {
if(fieldName == 'yValue')
return(item.value);
else if(fieldName == "xValue")
return(item.date);
else
return null;
}
}
}
The problem was the line
series.setAxis("verticalAxis", seriesAxis);
That is not equivalent to
series.verticalAxis = seriesAxis;
Once I changed that, everything works perfectly, but it cannot handle the abstract Series class b/c that class does not have the setter for verticalAxis.

Resources