package er.googlechart.components; import java.net.MalformedURLException; import java.util.List; import com.webobjects.appserver.WOAssociation; import com.webobjects.appserver.WOComponent; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WODynamicElement; import com.webobjects.appserver.WOElement; import com.webobjects.appserver.WOMessage; import com.webobjects.appserver.WOResponse; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSMutableArray; import er.ajax.AjaxUtils; import er.extensions.foundation.ERXMutableURL; import er.extensions.foundation.ERXStringUtilities; import er.extensions.foundation.ERXValueUtilities; import er.googlechart.util.GCAbstractEncoding; import er.googlechart.util.GCEncoding; import er.googlechart.util.GCTextEncoding; /** * The base class for all charts. Note that not all bindings are available for all chart types. * * @binding data the array, or array of arrays, of data * @binding size "wxh" format chart size ("300x400") * @binding width the width of the chart * @binding height the height of the chart * @binding colors an array of color values (for lines, bars, pie slices) * @binding title the title of the chart * @binding titleColor the color of the chart title * @binding titleSize the size of the chart title * @binding backgroundStyle "solid", "gradient", or "stripes" * @binding background the solid color of the background * @binding chartBackgroundStyle "solid", "gradient", or "stripes" * @binding chartBackground the solid color of the chart background * @binding transparency the transparency color of the chart background * @binding legend an array of legend values * @binding labeledAxes an array of axes that have labels ("x,y,r") (see http://code.google.com/apis/chart/#multiple_axes_labels) * @binding axisLabels an array of array of axis labels * @binding custom custom query string parameters to append * @binding id the id of the img tag * @binding class the class of the img tag * @binding alt the alt text of the img tag * @binding encoding the explicit chart encoding to use ("simple", "extended", "text") * @binding normalize if true, values will be normalized relative to the max value * @binding maxValue if false, normalization is off or set to a number to override the max value * @binding scaling if true, numbers will be scaled with an automatic min/max, or set to min/max string values (see http://code.google.com/apis/chart/#data_scaling) * @binding fillArea the fill area (see http://code.google.com/apis/chart/#fill_area_marker) * @binding lineStyles the line styles (see http://code.google.com/apis/chart/#line_styles) * @binding rangeMarkers the string that specifies range markers (see http://code.google.com/apis/chart/#hor_line_marker) * @binding shapeMarkers the string that specifies shape markers (see http://code.google.com/apis/chart/#shape_markers2) * @binding gridLines the override for specifying all gridline values (see http://code.google.com/apis/chart/#grid) * @binding gridXStep the number of steps on the x axis between grid lines * @binding gridYStep the number of steps on the y axis between grid lines * @binding gridLineSize the number of pixels in the line of the dash part of the grid line * @binding gridBlankSize the numer of pixels in the spacing between dashes in the grid line * * @author mschrag */ public abstract class GCAbstractChart extends WODynamicElement { protected WOAssociation _data; protected WOAssociation _size; protected WOAssociation _width; protected WOAssociation _height; protected WOAssociation _colors; protected WOAssociation _title; protected WOAssociation _titleColor; protected WOAssociation _titleSize; protected WOAssociation _backgroundStyle; protected WOAssociation _background; protected WOAssociation _chartBackgroundStyle; protected WOAssociation _chartBackground; protected WOAssociation _transparency; protected WOAssociation _legend; protected WOAssociation _labeledAxes; protected WOAssociation _axisLabels; protected WOAssociation _custom; protected WOAssociation _id; protected WOAssociation _class; protected WOAssociation _alt; protected WOAssociation _encoding; protected WOAssociation _normalize; protected WOAssociation _maxValue; protected WOAssociation _scaling; protected WOAssociation _fillArea; protected WOAssociation _lineStyles; protected WOAssociation _rangeMarkers; protected WOAssociation _shapeMarkers; protected WOAssociation _gridLines; protected WOAssociation _gridXStep; protected WOAssociation _gridYStep; protected WOAssociation _gridLineSize; protected WOAssociation _gridBlankSize; public GCAbstractChart(String name, NSDictionary associations, WOElement element) { super(name, associations, element); _data = (WOAssociation) associations.objectForKey("data"); _size = (WOAssociation) associations.objectForKey("size"); _width = (WOAssociation) associations.objectForKey("width"); _height = (WOAssociation) associations.objectForKey("height"); _colors = (WOAssociation) associations.objectForKey("colors"); _title = (WOAssociation) associations.objectForKey("title"); _titleColor = (WOAssociation) associations.objectForKey("titleColor"); _titleSize = (WOAssociation) associations.objectForKey("titleSize"); _backgroundStyle = (WOAssociation) associations.objectForKey("backgroundStyle"); _background = (WOAssociation) associations.objectForKey("background"); _chartBackgroundStyle = (WOAssociation) associations.objectForKey("chartBackgroundStyle"); _chartBackground = (WOAssociation) associations.objectForKey("chartBackground"); _transparency = (WOAssociation) associations.objectForKey("transparency"); _legend = (WOAssociation) associations.objectForKey("legend"); _labeledAxes = (WOAssociation) associations.objectForKey("labeledAxes"); _axisLabels = (WOAssociation) associations.objectForKey("axisLabels"); _custom = (WOAssociation) associations.objectForKey("custom"); _id = (WOAssociation) associations.objectForKey("id"); _class = (WOAssociation) associations.objectForKey("class"); _alt = (WOAssociation) associations.objectForKey("alt"); _encoding = (WOAssociation) associations.objectForKey("encoding"); _normalize = (WOAssociation) associations.objectForKey("normalize"); _maxValue = (WOAssociation) associations.objectForKey("maxValue"); _scaling = (WOAssociation) associations.objectForKey("scaling"); _fillArea = (WOAssociation) associations.objectForKey("fillArea"); _lineStyles = (WOAssociation) associations.objectForKey("lineStyles"); _rangeMarkers = (WOAssociation) associations.objectForKey("rangeMarkers"); _shapeMarkers = (WOAssociation) associations.objectForKey("shapeMarkers"); _gridLines = (WOAssociation) associations.objectForKey("gridLines"); _gridXStep = (WOAssociation) associations.objectForKey("gridXStep"); _gridYStep = (WOAssociation) associations.objectForKey("gridYStep"); _gridLineSize = (WOAssociation) associations.objectForKey("gridLineSize"); _gridBlankSize = (WOAssociation) associations.objectForKey("gridBlankSize"); } protected void addQueryParameters(ERXMutableURL chartUrl, WOResponse response, WOContext context) { // DO NOTHING } protected void checkData(List<List<Number>> data, WOResponse response, WOContext context) { // DO NOTHING } protected Number maxValue(WOResponse response, WOContext context) { return _maxValue == null ? null : GCEncoding.numberFromObject(_maxValue.valueInComponent(context.component())); } protected boolean normalize(WOResponse response, WOContext context) { WOComponent component = context.component(); boolean normalize = true; if (_normalize == null) { if (_maxValue != null) { Object maxValue = _maxValue.valueInComponent(component); if (maxValue instanceof Boolean) { normalize = ((Boolean) maxValue).booleanValue(); } else if (maxValue == null) { normalize = true; } } } else { normalize = _normalize.booleanValueInComponent(component); } return normalize; } protected Object scaling(WOResponse response, WOContext context) { Object scaling = _scaling == null ? null : _scaling.valueInComponent(context.component()); return scaling; } protected GCAbstractEncoding encoding(List<List<Number>> data, WOResponse response, WOContext context) { GCAbstractEncoding encoding; if (_encoding != null) { String encodingName = (String) _encoding.valueInComponent(context.component()); try { encoding = Class.forName("GC" + ERXStringUtilities.capitalize(encodingName) + "Encoding").asSubclass(GCAbstractEncoding.class).newInstance(); } catch (Exception e) { throw new IllegalArgumentException("Unable to create the encoding named '" + encodingName + "'."); } } else { Object scaling = scaling(response, context); if ((scaling instanceof Boolean && ((Boolean) scaling).booleanValue()) || scaling != null) { encoding = new GCTextEncoding(); } else { Number maxValue = maxValue(response, context); if (maxValue != null) { encoding = GCEncoding.recommendedEncoding(maxValue, data); } else { encoding = GCEncoding.recommendedEncoding(normalize(response, context), data); } } } return encoding; } protected String encode(List<List<Number>> data, WOResponse response, WOContext context) { GCAbstractEncoding encoding = encoding(data, response, context); String encodedValue; Number maxValue = maxValue(response, context); if (maxValue != null) { encodedValue = encoding.encode(maxValue, data); } else { encodedValue = encoding.encode(normalize(response, context), data); } return encodedValue; } protected String styleKey(String styleName) { String styleKey; if ("solid".equals(styleName)) { styleKey = "s"; } else if ("stripes".equals(styleName)) { styleKey = "ls"; } else if ("gradient".equals(styleName)) { styleKey = "lg"; } else { throw new IllegalArgumentException("Unknown style name '" + styleName + "'."); } return styleKey; } @Override public void appendToResponse(WOResponse response, WOContext context) { ERXMutableURL chartUrl = new ERXMutableURL(); chartUrl.setProtocol("http"); chartUrl.setHost("chart.apis.google.com"); chartUrl.setPath("/chart"); WOComponent component = context.component(); int width = 300; int height = 200; if (_size != null) { String sizeStr = (String) _size.valueInComponent(component); if (sizeStr != null) { String[] sizeStrs = sizeStr.split("x"); width = Integer.parseInt(sizeStrs[0]); height = Integer.parseInt(sizeStrs[1]); } } else { if (_width != null) { width = ERXValueUtilities.intValueWithDefault(_width.valueInComponent(component), width); } if (_height != null) { height = ERXValueUtilities.intValueWithDefault(_height.valueInComponent(component), height); } } chartUrl.setQueryParameter("chs", width + "x" + height); NSArray<String> colors = AjaxUtils.arrayValueForAssociation(component, _colors); if (colors != null) { chartUrl.setQueryParameter("chco", colors.componentsJoinedByString(",")); } List<List<Number>> data = null; if (_data != null) { Object dataValue = _data.valueInComponent(component); if (dataValue instanceof String && ((String) dataValue).length() >= 2 && ((String) dataValue).charAt(1) == ':') { chartUrl.setQueryParameter("chd", (String) dataValue); } else { data = GCEncoding.convertToNumberLists(AjaxUtils.arrayValueForAssociation(component, _data)); chartUrl.setQueryParameter("chd", encode(data, response, context)); } } Object scaling = scaling(response, context); if (scaling instanceof Boolean && ((Boolean) scaling).booleanValue()) { if (data != null) { NSMutableArray<String> scaleNumbers = new NSMutableArray<>(); for (List<Number> innerList : data) { Float minValue = Float.valueOf(GCEncoding.minValueInList(innerList)); scaleNumbers.addObject(String.format("%1$.1f", minValue)); Number maxValue = maxValue(response, context); if (maxValue == null) { maxValue = Float.valueOf(GCEncoding.minValueInList(innerList)); } scaleNumbers.addObject(String.format("%1$.1f", maxValue)); } chartUrl.setQueryParameter("chds", scaleNumbers.componentsJoinedByString(",")); } } else { chartUrl.setQueryParameter("chds", (String) scaling); } if (_title != null) { String title = (String) _title.valueInComponent(component); if (title != null) { chartUrl.setQueryParameter("chtt", title); } } if (_titleColor != null || _titleSize != null) { String titleColor = "454545"; if (_titleColor != null) { titleColor = (String) _titleColor.valueInComponent(component); } if (_titleSize != null) { Object titleSize = _titleSize.valueInComponent(component); chartUrl.setQueryParameter("chts", titleColor + "," + titleSize); } else { chartUrl.setQueryParameter("chts", titleColor); } } if (_lineStyles != null) { chartUrl.setQueryParameter("chls", (String) _lineStyles.valueInComponent(component)); } if (_fillArea != null) { chartUrl.setQueryParameter("chm", (String) _fillArea.valueInComponent(component)); } if (_rangeMarkers != null) { chartUrl.setQueryParameter("chm", (String) _rangeMarkers.valueInComponent(component)); } if (_shapeMarkers != null) { chartUrl.setQueryParameter("chm", (String) _shapeMarkers.valueInComponent(component)); } if (_gridLines != null) { chartUrl.setQueryParameter("chg", (String) _gridLines.valueInComponent(component)); } else if (_gridXStep != null || _gridYStep != null || _gridLineSize != null || _gridBlankSize != null) { StringBuilder chg = new StringBuilder(); if (_gridXStep != null && _gridYStep != null) { chg.append(_gridXStep.valueInComponent(component)); chg.append(','); chg.append(_gridYStep.valueInComponent(component)); } if (_gridLineSize != null || _gridBlankSize != null) { if (chg.length() == 0) { chg.append("20,50"); } if (_gridLineSize != null) { chg.append(','); chg.append(_gridLineSize.valueInComponent(component)); } if (_gridBlankSize != null) { if (_gridLineSize == null) { chg.append(",5"); } chg.append(','); chg.append(_gridBlankSize.valueInComponent(component)); } } chartUrl.setQueryParameter("chg", chg.toString()); } StringBuilder fill = new StringBuilder(); String backgroundStyle = "solid"; if (_backgroundStyle != null) { backgroundStyle = (String) _backgroundStyle.valueInComponent(component); } if (_background != null) { fill.append("bg,"); fill.append(styleKey(backgroundStyle)); fill.append(','); fill.append(_background.valueInComponent(component)); } String chartBackgroundStyle = "solid"; if (_chartBackgroundStyle != null) { chartBackgroundStyle = (String) _chartBackgroundStyle.valueInComponent(component); } if (_chartBackground != null) { if (fill.length() > 0) { fill.append('|'); } fill.append("c,"); fill.append(styleKey(chartBackgroundStyle)); fill.append(','); fill.append(_chartBackground.valueInComponent(component)); } if (_chartBackground != null || _transparency != null) { if (_transparency != null) { if (fill.length() > 0) { fill.append('|'); } fill.append("a,s,"); fill.append(_transparency.valueInComponent(component)); } } if (fill.length() > 0) { chartUrl.setQueryParameter("chf", fill.toString()); } NSArray<String> legend = AjaxUtils.arrayValueForAssociation(component, _legend); if (legend != null) { chartUrl.setQueryParameter("chdl", legend.componentsJoinedByString("|")); } NSArray<String> labeledAxes = AjaxUtils.arrayValueForAssociation(component, _labeledAxes); if (labeledAxes != null) { chartUrl.setQueryParameter("chxt", labeledAxes.componentsJoinedByString(",")); } NSArray<Object> axisLabels = AjaxUtils.arrayValueForAssociation(component, _axisLabels); if (axisLabels != null) { StringBuilder axisLabelsStr = new StringBuilder(); for (int i = 0; i < axisLabels.count(); i++) { Object singleAxisLabels = axisLabels.objectAtIndex(i); if (i > 0) { axisLabelsStr.append('|'); } axisLabelsStr.append(i + ":|"); if (singleAxisLabels instanceof Object[]) { axisLabelsStr.append(new NSArray<>((Object[]) singleAxisLabels).componentsJoinedByString("|")); } else { axisLabelsStr.append(singleAxisLabels); } } chartUrl.setQueryParameter("chxl", axisLabelsStr.toString()); } if (_custom != null) { String custom = (String) _custom.valueInComponent(component); if (custom != null) { try { chartUrl.addQueryParameters(custom); } catch (MalformedURLException e) { throw new IllegalArgumentException("Failed to add the query parameters '" + custom + "'.", e); } } } addQueryParameters(chartUrl, response, context); response.appendContentString("<img"); if (_id != null) { response._appendTagAttributeAndValue("id", (String) _id.valueInComponent(component), true); } if (_class != null) { response._appendTagAttributeAndValue("class", (String) _class.valueInComponent(component), true); } if (_alt != null) { response._appendTagAttributeAndValue("alt", (String) _alt.valueInComponent(component), true); } String chartSrc = WOMessage.stringByEscapingHTMLAttributeValue(chartUrl.toExternalForm()); response._appendTagAttributeAndValue("src", chartSrc, false); response._appendTagAttributeAndValue("width", String.valueOf(width), false); response._appendTagAttributeAndValue("height", String.valueOf(height), false); response.appendContentString("/>"); } }