| Current File : /home/jvzmxxx/wiki1/extensions/Graph/modules/ve-graph/ve.dm.MWGraphModel.js |
/*!
* VisualEditor DataModel MWGraphModel class.
*
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* MediaWiki graph model.
*
* @class
* @mixins OO.EventEmitter
*
* @constructor
* @param {Object} [spec] The Vega specification as a JSON object
*/
ve.dm.MWGraphModel = function VeDmMWGraphModel( spec ) {
// Mixin constructors
OO.EventEmitter.call( this );
// Properties
this.spec = spec || {};
this.originalSpec = ve.copy( this.spec );
this.cachedPadding = ve.copy( this.spec.padding ) || this.getDefaultPaddingObject();
};
/* Inheritance */
OO.mixinClass( ve.dm.MWGraphModel, OO.EventEmitter );
/* Static Members */
ve.dm.MWGraphModel.static.defaultPadding = 30;
ve.dm.MWGraphModel.static.minDimensions = {
width: 60,
height: 60
};
ve.dm.MWGraphModel.static.graphConfigs = {
area: {
mark: {
type: 'area',
properties: {
enter: {
fill: { value: 'steelblue' },
interpolate: { value: 'monotone' },
stroke: undefined,
strokeWidth: undefined,
width: undefined
}
}
},
scale: {
name: 'x',
type: 'linear'
},
fields: [
'x',
'y'
]
},
bar: {
mark: {
type: 'rect',
properties: {
enter: {
fill: { value: 'steelblue' },
interpolate: undefined,
stroke: undefined,
strokeWidth: undefined,
// HACK: Boolean values set to true need to be wrapped
// in strings until T118883 is resolved
width: { scale: 'x', band: 'true', offset: -1 }
}
}
},
scale: {
name: 'x',
type: 'ordinal'
},
fields: [
'x',
'y'
]
},
line: {
mark: {
type: 'line',
properties: {
enter: {
fill: undefined,
interpolate: { value: 'monotone' },
stroke: { value: 'steelblue' },
strokeWidth: { value: 3 },
width: undefined
}
}
},
scale: {
name: 'x',
type: 'linear'
},
fields: [
'x',
'y'
]
}
};
/* Events */
/**
* @event specChange
*
* Change when the JSON specification is updated
*
* @param {Object} The new specification
*/
/* Static Methods */
/**
* Updates a spec with new parameters.
*
* @param {Object} spec The spec to update
* @param {Object} params The new params to update. Properties set to undefined will be removed from the spec.
* @return {Object} The new spec
*/
ve.dm.MWGraphModel.static.updateSpec = function ( spec, params ) {
var undefinedProperty,
undefinedProperties = ve.dm.MWGraphModel.static.getUndefinedProperties( params ),
i;
// Remove undefined properties from spec
for ( i = 0; i < undefinedProperties.length; i++ ) {
undefinedProperty = undefinedProperties[ i ].split( '.' );
ve.dm.MWGraphModel.static.removeProperty( spec, $.extend( [], undefinedProperty ) );
ve.dm.MWGraphModel.static.removeProperty( params, $.extend( [], undefinedProperty ) );
}
// Extend remaining properties
spec = $.extend( true, {}, spec, params );
return spec;
};
/**
* Recursively gets all the keys to properties set to undefined in a JSON object
*
* @author Based on the work on Artyom Neustroev at http://stackoverflow.com/a/15690816/2055594
* @private
* @param {Object} obj The object to iterate
* @param {string} [stack] The parent property of the root property of obj. Used internally for recursion.
* @param {string[]} [list] The list of properties to return. Used internally for recursion.
* @return {string[]} The list of properties to return.
*/
ve.dm.MWGraphModel.static.getUndefinedProperties = function ( obj, stack, list ) {
var property;
list = list || [];
// Append . to the stack if it's defined
stack = ( stack === undefined ) ? '' : stack + '.';
for ( property in obj ) {
if ( obj.hasOwnProperty( property ) ) {
if ( $.type( obj[ property ] ) === 'object' || $.type( obj[ property ] ) === 'array' ) {
ve.dm.MWGraphModel.static.getUndefinedProperties( obj[ property ], stack + property, list );
} else if ( obj[ property ] === undefined ) {
list.push( stack + property );
}
}
}
return list;
};
/**
* Removes a nested property from an object
*
* @param {Object} obj The object
* @param {Array} prop The path of the property to remove
*/
ve.dm.MWGraphModel.static.removeProperty = function ( obj, prop ) {
var firstProp = prop.shift();
try {
if ( prop.length > 0 ) {
ve.dm.MWGraphModel.static.removeProperty( obj[ firstProp ], prop );
} else {
if ( $.type( obj ) === 'array' ) {
obj.splice( parseInt( firstProp ), 1 );
} else {
delete obj[ firstProp ];
}
}
} catch ( err ) {
// We don't need to bubble errors here since hitting a missing property
// will not exist anyway in the object anyway
}
};
/**
* Check if a spec currently has something in its dataset
*
* @param {Object} spec The spec
* @return {boolean} The spec has some data in its dataset
*/
ve.dm.MWGraphModel.static.specHasData = function ( spec ) {
// FIXME: Support multiple pipelines
return !!spec.data[ 0 ].values.length;
};
/* Methods */
/**
* Switch the graph to a different type
*
* @param {string} type Desired graph type. Can be either area, line or bar.
* @fires specChange
*/
ve.dm.MWGraphModel.prototype.switchGraphType = function ( type ) {
var params = {
scales: [ ve.copy( this.constructor.static.graphConfigs[ type ].scale ) ],
marks: [ ve.copy( this.constructor.static.graphConfigs[ type ].mark ) ]
};
this.updateSpec( params );
this.emit( 'specChange', this.spec );
};
/**
* Apply changes to the node
*
* @param {ve.dm.MWGraphNode} node The node to be modified
* @param {ve.dm.Surface} surfaceModel The surface model for the document
*/
ve.dm.MWGraphModel.prototype.applyChanges = function ( node, surfaceModel ) {
var mwData = ve.copy( node.getAttribute( 'mw' ) );
// Send transaction
mwData.body.extsrc = this.getSpecString();
surfaceModel.change(
ve.dm.Transaction.newFromAttributeChanges(
surfaceModel.getDocument(),
node.getOffset(),
{ mw: mwData }
)
);
surfaceModel.applyStaging();
};
/**
* Update the spec with new parameters
*
* @param {Object} params The new parameters to be updated in the spec
* @fires specChange
*/
ve.dm.MWGraphModel.prototype.updateSpec = function ( params ) {
var updatedSpec = ve.dm.MWGraphModel.static.updateSpec( $.extend( true, {}, this.spec ), params );
// Only emit a change event if the spec really changed
if ( !OO.compare( this.spec, updatedSpec ) ) {
this.spec = updatedSpec;
this.emit( 'specChange', this.spec );
}
};
/**
* Sets and validates the specification from a stringified version
*
* @param {string} str The new specification string
* @fires specChange
*/
ve.dm.MWGraphModel.prototype.setSpecFromString = function ( str ) {
var newSpec = ve.dm.MWGraphNode.static.parseSpecString( str );
// Only apply changes if the new spec is valid JSON and if the
// spec truly was modified
if ( !OO.compare( this.spec, newSpec ) ) {
this.spec = newSpec;
this.emit( 'specChange', this.spec );
}
};
/**
* Get the specification
*
* @return {Object} The specification
*/
ve.dm.MWGraphModel.prototype.getSpec = function () {
return this.spec;
};
/**
* Get the stringified specification
*
* @return {string} The specification string
*/
ve.dm.MWGraphModel.prototype.getSpecString = function () {
return ve.dm.MWGraphNode.static.stringifySpec( this.spec );
};
/**
* Get the original stringified specificiation
*
* @return {string} The original JSON string specification
*/
ve.dm.MWGraphModel.prototype.getOriginalSpecString = function () {
return ve.dm.MWGraphNode.static.stringifySpec( this.originalSpec );
};
/**
* Get the graph type
*
* @return {string} The graph type
*/
ve.dm.MWGraphModel.prototype.getGraphType = function () {
var markType = this.spec.marks[ 0 ].type;
switch ( markType ) {
case 'area':
return 'area';
case 'rect':
return 'bar';
case 'line':
return 'line';
default:
return 'unknown';
}
};
/**
* Get graph size
*
* @return {Object} The graph width and height
*/
ve.dm.MWGraphModel.prototype.getSize = function () {
return {
width: this.spec.width,
height: this.spec.height
};
};
/**
* Set the graph width
*
* @param {number} value The new width
* @fires specChange
*/
ve.dm.MWGraphModel.prototype.setWidth = function ( value ) {
this.spec.width = value;
this.emit( 'specChange', this.spec );
};
/**
* Set the graph height
*
* @param {number} value The new height
* @fires specChange
*/
ve.dm.MWGraphModel.prototype.setHeight = function ( value ) {
this.spec.height = value;
this.emit( 'specChange', this.spec );
};
/**
* Get the padding values of the graph
*
* @return {Object} The paddings
*/
ve.dm.MWGraphModel.prototype.getPaddingObject = function () {
return this.spec.padding;
};
/**
* Return the default padding
*
* @return {Object} The default padding values
*/
ve.dm.MWGraphModel.prototype.getDefaultPaddingObject = function () {
var i,
indexes = [ 'top', 'bottom', 'left', 'right' ],
paddingObj = {};
for ( i = 0; i < indexes.length; i++ ) {
paddingObj[ indexes[ i ] ] = ve.dm.MWGraphModel.static.defaultPadding;
}
return paddingObj;
};
/**
* Set a padding value
*
* @param {string} index The index to change. Can be either top, right, bottom or right
* @param {number} value The new value
* @fires specChange
*/
ve.dm.MWGraphModel.prototype.setPadding = function ( index, value ) {
if ( this.isPaddingAutomatic() ) {
this.spec.padding = this.getDefaultPaddingObject();
}
this.spec.padding[ index ] = value;
this.emit( 'specChange', this.spec );
};
/**
* Toggles automatic and manual padding modes
*
* @param {boolean} auto Padding is now automatic
* @fires specChange
*/
ve.dm.MWGraphModel.prototype.setPaddingAuto = function ( auto ) {
if ( auto ) {
this.cachedPadding = ve.copy( this.spec.padding ) || this.getDefaultPaddingObject();
ve.dm.MWGraphModel.static.removeProperty( this.spec, [ 'padding' ] );
} else {
this.spec.padding = ve.copy( this.cachedPadding );
}
this.emit( 'specChange', this.spec );
};
/**
* Get the fields for a data pipeline
*
* @param {number} [id] The pipeline's id
* @return {string[]} The fields for the pipeline
*/
ve.dm.MWGraphModel.prototype.getPipelineFields = function ( id ) {
var firstEntry = ve.getProp( this.spec, 'data', id, 'values', 0 );
// Get the fields directly from the pipeline data if the pipeline exists and
// has data, otherwise default back on the fields intended for this graph type
if ( firstEntry ) {
return Object.keys( firstEntry );
} else {
return ve.dm.MWGraphModel.static.graphConfigs[ this.getGraphType() ].fields;
}
};
/**
* Get a data pipeline
*
* @param {number} [id] The pipeline's id
* @return {Object} The data pipeline within the spec
*/
ve.dm.MWGraphModel.prototype.getPipeline = function ( id ) {
return this.spec.data[ id ];
};
/**
* Set the field value of an entry in a pipeline
*
* @param {number} [entry] ID of the entry
* @param {string} [field] The field to change
* @param {number} [value] The new value
* @fires specChange
*/
ve.dm.MWGraphModel.prototype.setEntryField = function ( entry, field, value ) {
if ( this.spec.data[ 0 ].values[ entry ] === undefined ) {
this.spec.data[ 0 ].values[ entry ] = this.buildNewEntry( 0 );
}
this.spec.data[ 0 ].values[ entry ][ field ] = value;
this.emit( 'specChange', this.spec );
};
/**
* Builds and returns a new entry for a pipeline
*
* @private
* @param {number} [pipelineId] The ID of the pipeline the entry is intended for
* @return {Object} The new entry
*/
ve.dm.MWGraphModel.prototype.buildNewEntry = function ( pipelineId ) {
var fields = this.getPipelineFields( pipelineId ),
newEntry = {},
i;
for ( i = 0; i < fields.length; i++ ) {
newEntry[ fields[ i ] ] = '';
}
return newEntry;
};
/**
* Removes an entry from a pipeline
*
* @param {number} [index] The index of the entry to delete
* @fires specChange
*/
ve.dm.MWGraphModel.prototype.removeEntry = function ( index ) {
// FIXME: Support multiple pipelines
this.spec.data[ 0 ].values.splice( index, 1 );
this.emit( 'specChange', this.spec );
};
/**
* Returns whether the current spec has been modified since the dialog was opened
*
* @return {boolean} The spec was changed
*/
ve.dm.MWGraphModel.prototype.hasBeenChanged = function () {
return !OO.compare( this.spec, this.originalSpec );
};
/**
* Returns whether the padding is set to be automatic or not
*
* @return {boolean} The padding is automatic
*/
ve.dm.MWGraphModel.prototype.isPaddingAutomatic = function () {
return OO.compare( this.spec.padding, undefined );
};