/// <inheritdoc/>
        public override object GetNormalisedValuePrecise(object originalValue, int identifier)
        {
            DimensionData.Metadata meta = this[identifier].MetaData;

            // Determine which textualDimensionsList to use
            Dictionary <string, Dictionary <string, int> > textualDimensionsListReverse;

            if (nodeDimensionData.Select(x => x.Identifier).FirstOrDefault() != null)
            {
                textualDimensionsListReverse = nodeTextualDimensionsListReverse;
            }
            else
            {
                textualDimensionsListReverse = edgeTextualDimensionsListReverse;
            }

            if (meta.Type == IATKDataType.String)
            {
                int stringIdx = textualDimensionsListReverse[this[identifier].Identifier][originalValue.ToString()];
                return(UtilMath.NormaliseValue(stringIdx, meta.Min, meta.Max, 0f, 1f));
            }
            else
            {
                return(UtilMath.NormaliseValue((float)originalValue, meta.Min, meta.Max, 0f, 1f));
            }
        }
        /// <summary>
        /// Initialises the metadata for the data, including its string identifier and type. This function assumes that the header is the first element.
        /// </summary>
        /// <param name="lines">A string array representing lines from a data file.</param>
        /// <returns><c>true</c> if the headers and types were successfully loaded, otherwise <c>false</c>.</returns>
        private bool LoadMetaData(string[] lines, ref List <DimensionData> dimensionData)
        {
            if (lines.Length > 0)
            {
                string[] identifiers = lines[0].Split(split);
                // Create metadata
                DimensionData.Metadata[] metadata = new DimensionData.Metadata[identifiers.Length];

                // Clean string identifiers for each dimension
                for (int i = 0; i < identifiers.Length; i++)
                {
                    string id = CleanDataString(identifiers[i]);
                    identifiers[i] = id;
                }

                // Check the types for each dimension
                if (lines.Length > 1)
                {
                    string[] typesToRead = lines[1].Split(split);
                    for (int i = 0; i < typesToRead.Length; i++)
                    {
                        metadata[i].Type = DataTypeExtension.inferFromString(CleanDataString(typesToRead[i]));
                    }
                }

                // Populate the dimension data list
                for (int i = 0; i < identifiers.Length; ++i)
                {
                    dimensionData.Add(new DimensionData(identifiers[i], i, metadata[i]));
                }
                return(true);
            }
            return(false);
        }
        /// <inheritdoc/>
        public override object GetValuePrecise(float normalisedValue, string identifier)
        {
            DimensionData.Metadata meta = this[identifier].MetaData;

            // Determine which textualDimensionsList to use
            Dictionary <string, Dictionary <int, string> > textualDimensionsList;

            if (nodeDimensionData.FirstOrDefault(x => x.Identifier == identifier) != null)
            {
                textualDimensionsList = nodeTextualDimensionsList;
            }
            else
            {
                textualDimensionsList = edgeTextualDimensionsList;
            }

            float normValue = UtilMath.NormaliseValue(normalisedValue, 0f, 1f, meta.Min, meta.Max);

            // Dimensions of type String should return a string from the textual dimensions list
            if (meta.Type == IATKDataType.String)
            {
                return(textualDimensionsList[identifier][(int)normValue]);
            }
            // Otherwise re can return the de-normalised value
            else
            {
                return(normValue);
            }
        }
        /// <inheritdoc/>
        public override object GetValueApproximate(float normalisedValue, string identifier)
        {
            DimensionData.Metadata meta = this[identifier].MetaData;
            // Determine which textualDimensionsList to use

            Dictionary <string, Dictionary <int, string> > textualDimensionsList;

            if (nodeDimensionData.FirstOrDefault(x => x.Identifier == identifier) != null)
            {
                textualDimensionsList = nodeTextualDimensionsList;
            }
            else
            {
                textualDimensionsList = edgeTextualDimensionsList;
            }

            // Dimensions of type String should return a string from the textual dimensions list
            if (meta.Type == IATKDataType.String)
            {
                // Since this function allows for approximate input values, we need to find the value closest to the given one
                float normValue = UtilMath.NormaliseValue(ValueClosestTo(this[identifier].Data, normalisedValue), 0f, 1f, meta.Min, meta.Max);
                return(textualDimensionsList[identifier][(int)normValue]);
            }
            // Otherwise we can return a de-normalised value
            else
            {
                return(UtilMath.NormaliseValue(normalisedValue, 0f, 1f, meta.Min, meta.Max));
            }
        }
 /// <inheritdoc/>
 public override object GetNormalisedValuePrecise(object originalValue, int identifier)
 {
     DimensionData.Metadata meta = this[identifier].MetaData;
     if (meta.Type == IATKDataType.String)
     {
         int stringIdx = textualDimensionsListReverse[this[identifier].Identifier][originalValue.ToString()];
         return(UtilMath.NormaliseValue(stringIdx, meta.Min, meta.Max, 0f, 1f));
     }
     else
     {
         return(UtilMath.NormaliseValue((float)originalValue, meta.Min, meta.Max, 0f, 1f));
     }
 }
        /// <inheritdoc/>
        public override object GetValuePrecise(float normalisedValue, string identifier)
        {
            DimensionData.Metadata meta = this[identifier].MetaData;
            float normValue             = UtilMath.NormaliseValue(normalisedValue, 0f, 1f, meta.Min, meta.Max);

            // Dimensions of type String should return a string from the textual dimensions list
            if (meta.Type == IATKDataType.String)
            {
                return(textualDimensionsList[this[identifier].Identifier][(int)normValue]);
            }
            // Otherwise re can return the de-normalised value
            else
            {
                return(normValue);
            }
        }
 /// <inheritdoc/>
 public override object GetValueApproximate(float normalisedValue, string identifier)
 {
     DimensionData.Metadata meta = this[identifier].MetaData;
     // Dimensions of type String should return a string from the textual dimensions list
     if (meta.Type == IATKDataType.String)
     {
         // Since this function allows for approximate input values, we need to find the value closest to the given one
         float normValue = UtilMath.NormaliseValue(ValueClosestTo(this[identifier].Data, normalisedValue), 0f, 1f, meta.Min, meta.Max);
         return(textualDimensionsList[identifier][(int)normValue]);
     }
     // Otherwise we can return a de-normalised value
     else
     {
         return(UtilMath.NormaliseValue(normalisedValue, 0f, 1f, meta.Min, meta.Max));
     }
 }
        /// <summary>
        /// Normalises a given column from a 2D array of float values within the range 0..1. This function also sets some metadata values.
        /// </summary>
        /// <param name="dataArray">A 2D float array of data.</param>
        /// <param name="col">An integer index of the column to normalise.</param>
        /// <returns>A normalised float array in the range 0..1.</returns>
        private float[] NormaliseColumn(float[,] dataArray, int col, ref List <DimensionData> dimensionData)
        {
            float[] result   = GetColumn(dataArray, col);
            float   minValue = result.Min();
            float   maxValue = result.Max();

            if (minValue == maxValue)
            {
                // where there are no distinct values, need the dimension to be distinct
                // otherwise lots of maths breaks with division by zero, etc.
                // this is the most elegant hack I could think of, but should be fixed properly in future
                minValue -= 1.0f;
                maxValue += 1.0f;
            }

            // Populate metadata values
            DimensionData.Metadata metadata = dimensionData[col].MetaData;
            metadata.Min           = minValue;
            metadata.Max           = maxValue;
            metadata.Categories    = result.Distinct().Select(x => UtilMath.NormaliseValue(x, minValue, maxValue, 0.0f, 1.0f)).ToArray();
            metadata.CategoryCount = metadata.Categories.Count();
            metadata.BinCount      = (int)(maxValue - minValue + 1);
            dimensionData[col].SetMetadata(metadata);

            for (int j = 0; j < result.Length; j++)
            {
                if (minValue < maxValue)
                {
                    result[j] = UtilMath.NormaliseValue(result[j], minValue, maxValue, 0f, 1f);
                }
                else
                {
                    // Avoid NaNs or nonsensical normalization
                    result[j] = 0;
                }
            }

            return(result);
        }