Java tutorial
/* * Copyright (C) 2014 (JenniferSoft Inc.) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jennifer.ui.chart; import com.jennifer.ui.chart.brush.EqualizerBrush; import com.jennifer.ui.chart.brush.*; import com.jennifer.ui.chart.grid.*; import com.jennifer.ui.chart.widget.*; import com.jennifer.ui.util.*; import com.jennifer.ui.util.dom.Svg; import com.jennifer.ui.util.dom.Transform; import org.json.JSONArray; import org.json.JSONObject; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import java.util.*; import static com.jennifer.ui.util.DomUtil.el; /** * ChartBuilder options list * * width : px * height : px * padding : 0 * paddingLeft : 0 * paddingRight : 0 * paddingTop : 0 * paddingBottom : 0 * theme * series * brush * widget * data * * * Created by yuni on 2014-10-23. */ public class ChartBuilder extends AbstractDraw { private JSONObject options; private JSONObject builderoptions = new JSONObject(); private HashMap<String, Class> grids = new HashMap<String, Class>(); private HashMap<String, Class> brushes = new HashMap<String, Class>(); private HashMap<String, Class> widgets = new HashMap<String, Class>(); private Svg svg; private JSONObject themeList = new JSONObject(); private Transform defs; private String clipId; private Transform root; public ChartBuilder() { this(new JSONObject()); } public ChartBuilder(String json) { this(new JSONObject(json)); } public ChartBuilder(JSONObject o) { this.setOptions(o); init(); } public ChartBuilder(int width, int height) { this(new JSONObject().put("width", width).put("height", height)); } public ChartBuilder(int width, int height, String theme) { this(new JSONObject().put("width", width).put("height", height).put("theme", theme)); } public void setOptions(JSONObject options) { this.options = options; if (!this.options.has("brush")) { this.options.put("brush", new JSONArray()); } if (!this.options.has("widget")) { this.options.put("widget", new JSONArray()); } if (!this.options.has("data")) { this.options.put("data", new JSONArray()); } if (!this.options.has("grid")) { this.options.put("grid", new JSONObject()); } } public JSONObject getOptions() { return this.options; } public boolean set(String jsonPath, Object value) { return JSONUtil.set(this.options, jsonPath, value); } public Object get(String jsonPath) { return JSONUtil.get(this.options, jsonPath); } private void init() { initPadding(); initTheme(); // default grid initGrid(); // default brush initBrush(); // default widget initWidget(); // svg main initSvg(); setTheme(options.optString("theme", "jennifer")); //TODO: support style JSONObject if (options.has("style")) { } } public ChartBuilder gridDateFormat(String axis, int index, String key, ChartDateFormat value) { JSONObject grid = options.getJSONObject("grid"); if (grid.has(axis)) { JSONObject o = grid.getJSONArray(axis).optJSONObject(index); o.put(key, value); } return this; } public ChartBuilder grid(String axis, JSONObject o) { JSONObject grid = options.getJSONObject("grid"); if (!grid.has(axis)) { grid.put(axis, new JSONArray()); } JSONArray array = grid.getJSONArray(axis); array.put(o); return this; } public ChartBuilder brush(JSONObject o) { JSONArray brush = (JSONArray) options.get("brush"); brush.put(o); return this; } public ChartBuilder widget(JSONObject o) { JSONArray widget = (JSONArray) options.get("widget"); widget.put(o); return this; } private void initSvg() { JSONObject o = new JSONObject(); o.put("width", bi("width")); o.put("height", bi("height")); this.svg = new Svg(o); this.root = this.svg.g().translate(0.5, 0.5); } private void initWidget() { plugin("widget", "title", TitleWidget.class); plugin("widget", "legend", LegendWidget.class); } private void initBrush() { plugin("brush", "area", AreaBrush.class); plugin("brush", "bar", BarBrush.class); plugin("brush", "bargauge", BarGaugeBrush.class); plugin("brush", "bubble", BubbleBrush.class); plugin("brush", "candlestick", CandleStickBrush.class); plugin("brush", "circlegauge", CircleGaugeBrush.class); plugin("brush", "column", ColumnBrush.class); plugin("brush", "donut", DonutBrush.class); plugin("brush", "equalizer", EqualizerBrush.class); plugin("brush", "fillgauge", FillGaugeBrush.class); plugin("brush", "fullgauge", FullGaugeBrush.class); plugin("brush", "fullstack", FullStackBrush.class); plugin("brush", "gauge", GagueBrush.class); plugin("brush", "line", LineBrush.class); plugin("brush", "ohlc", OhlcBrush.class); plugin("brush", "path", PathBrush.class); plugin("brush", "pie", PieBrush.class); plugin("brush", "scatter", ScatterBrush.class); plugin("brush", "scatterpath", ScatterPathBrush.class); plugin("brush", "stackarea", StackAreaBrush.class); plugin("brush", "stackbar", StackBarBrush.class); plugin("brush", "stackcolumn", StackColumnBrush.class); plugin("brush", "stackgauge", StackGagueBrush.class); plugin("brush", "stackline", StackLineBrush.class); plugin("brush", "stackscatter", StackScatterBrush.class); } private void initGrid() { plugin("grid", "block", BlockGrid.class); plugin("grid", "range", RangeGrid.class); plugin("grid", "date", DateGrid.class); plugin("grid", "rule", RuleGrid.class); plugin("grid", "radar", RadarGrid.class); } private void initTheme() { addTheme("jennifer", JSONUtil.loadJSONFile("chart/theme/jennifer.json")); addTheme("dark", JSONUtil.loadJSONFile("chart/theme/dark.json")); addTheme("gradient", JSONUtil.loadJSONFile("chart/theme/gradient.json")); addTheme("pastel", JSONUtil.loadJSONFile("chart/theme/pastel.json")); } private void addTheme(String name, JSONObject themeObj) { themeList.put(name, themeObj); } private void initPadding() { builderoptions.put("width", options.optInt("width", 400)); builderoptions.put("height", options.optInt("height", 400)); if (this.options.has("padding")) { Object padding = this.options.get("padding"); if (padding instanceof String && "empty".equals((String) padding)) { JSONObject o = new JSONObject().put("left", 0).put("right", 0).put("top", 0).put("bottom", 0); this.builderoptions.put("padding", o); } else { JSONObject source = (JSONObject) padding; JSONObject o = new JSONObject(); o.put("left", source.optInt("left", 50)); o.put("right", source.optInt("right", 50)); o.put("bottom", source.optInt("bottom", 50)); o.put("top", source.optInt("top", 50)); this.builderoptions.put("padding", o); } } else { JSONObject o = new JSONObject().put("left", 50).put("right", 50).put("top", 50).put("bottom", 50); this.builderoptions.put("padding", o); } } public void plugin(String type, String name, Class cls) { if ("brush".equals(type)) { brushes.put(name, cls); } else if ("widget".equals(type)) { widgets.put(name, cls); } else if ("grid".equals(type)) { grids.put(name, cls); } } public int i(String key) { return this.options.getInt(key); } public int bi(String key) { return this.builderoptions.getInt(key); } private void caculate() { int width = bi("width") - (padding("left") + padding("right")); int height = bi("height") - (padding("top") + padding("bottom")); int x = padding("left"); int y = padding("top"); int x2 = x + width; int y2 = y + height; JSONObject area = new JSONObject(); area.put("width", width).put("height", height).put("x", x).put("y", y).put("x2", x2).put("y2", y2); this.builderoptions.put("area", area); } public JSONObject area() { return (JSONObject) builderoptions.getJSONObject("area"); } public int area(String key) { return area().getInt(key); } public int padding(String key) { return builderoptions.getJSONObject("padding").getInt(key); } private JSONObject cloneObject(String key) { if (options.has(key)) { return JSONUtil.clone(options.getJSONObject(key)); } else { return new JSONObject(); } } private JSONArray cloneArray(String key) { JSONArray list = new JSONArray(); JSONArray source = options.getJSONArray(key); if (source != null) { for (int i = 0, len = source.length(); i < len; i++) { list.put(JSONUtil.clone(source.getJSONObject(i))); } } return list; } private Object clone(String key) { return options.opt(key); } public String color(int index, JSONArray colors) { String color = null; // color rotate index = index % colors.length(); if (colors != null) { color = colors.getString(index); } if (color == null) { color = bobject("theme").getJSONArray("colors").getString(index); } if (bobject("hash").has(color)) { return url(bobject("hash").getString(color)); } return getColor(color); } private String getColor(String color) { Object parsedColor = ColorUtil.parse(color); if (parsedColor instanceof String) { return (String) parsedColor; } return createGradient((JSONObject) parsedColor, color); } private String createGradient(JSONObject parsedColor, String hashKey) { JSONObject hash = bobject("hash"); if (hash.has(hashKey)) { return url(hash.getString(hashKey)); } String id = StringUtil.createId("gradient"); parsedColor.put("id", id); String type = parsedColor.getString("type"); Transform g = el(type + "Gradient", parsedColor); JSONArray stops = (JSONArray) parsedColor.getJSONArray("stops"); for (int i = 0, len = stops.length(); i < len; i++) { g.append(el("stop", (JSONObject) stops.getJSONObject(i))); } parsedColor.remove("stops"); this.defs.append(g); if (hashKey != null) { hash.put(hashKey, id); } return url(id); } public String url(String id) { return "url(#" + id + ")"; } @Override public void drawBefore() { JSONObject series = cloneObject("series"); JSONObject grid = cloneObject("grid"); Object brush = clone("brush"); Object widget = clone("widget"); JSONArray data = cloneArray("data"); // series ?? for (int i = 0, len = data.length(); i < len; i++) { JSONObject row = (JSONObject) data.getJSONObject(i); Iterator it = row.keys(); while (it.hasNext()) { String key = (String) it.next(); if (!series.has(key)) { series.put(key, new JSONObject()); } JSONObject obj = JSONUtil.clone(series.getJSONObject(key)); if (obj == null) continue; Object valueObject = row.get(key); if (valueObject instanceof String) continue; if (valueObject instanceof Double || valueObject instanceof Integer) { double value = row.getDouble(key); if (!obj.has("min")) obj.put("min", value); if (!obj.has("max")) obj.put("max", value); if (value < obj.getDouble("min")) obj.put("min", value); if (value > obj.getDouble("max")) obj.put("max", value); } else if (row.get(key) instanceof Long) { long value = row.getLong(key); if (!obj.has("min")) obj.put("min", value); if (!obj.has("max")) obj.put("max", value); if (value < obj.getLong("min")) obj.put("min", value); if (value > obj.getLong("max")) obj.put("max", value); } series.put(key, obj); } } // series_list barray("brush", createBrushData(brush, series.names())); barray("widget", createBrushData(widget, series.names())); bobject("grid", grid); bobject("hash", null); barray("data", data); bobject("series", series); } private JSONArray createBrushData(Object brush, JSONArray series_list) { JSONArray list = new JSONArray(); if (brush != null) { if (brush instanceof String) { JSONObject o = new JSONObject(); o.put("type", brush); list.put(o); } else if (brush instanceof JSONObject) { list.put(brush); } else if (brush instanceof JSONObject) { list.put(JSONUtil.clone((JSONObject) brush)); } else if (brush instanceof JSONArray) { list = (JSONArray) brush; } else if (brush instanceof JSONArray) { list = JSONUtil.clone((JSONArray) brush); } for (int i = 0, len = list.length(); i < len; i++) { JSONObject b = list.getJSONObject(i); if (b.isNull("target")) { b.put("target", series_list); } else if (b.get("target") instanceof String) { b.put("target", new JSONArray().put(b.getString("target"))); } } } return list; } private JSONArray barray(String key) { return builderoptions.has(key) ? (JSONArray) builderoptions.getJSONArray(key) : new JSONArray(); } private void barray(String key, JSONArray value) { if (value == null) { value = new JSONArray(); } this.builderoptions.put(key, value); } private JSONObject bobject(String key) { return (JSONObject) this.builderoptions.getJSONObject(key); } private void bobject(String key, JSONObject value) { if (value == null) { value = new JSONObject(); } this.builderoptions.put(key, value); } @Override public Object draw() { return null; } private void drawObject(String type) { if (builderoptions.has(type) && !builderoptions.isNull(type)) { JSONArray list = (JSONArray) builderoptions.getJSONArray(type); for (int i = 0, len = list.length(); i < len; i++) { JSONObject obj = list.getJSONObject(i); String objType = obj.getString("type"); Class cls = "brush".equals(type) ? brushes.get(objType) : widgets.get(objType); JSONObject drawObject; if ("widget".equals(type)) { drawObject = JSONUtil.clone(builderoptions.getJSONArray(type).getJSONObject(i)); } else { drawObject = JSONUtil.clone(obj); } setGridAxis(obj, drawObject); obj.put("index", i); // create object and rendering try { Drawable drawable = (Drawable) cls.getDeclaredConstructor(ChartBuilder.class, JSONObject.class) .newInstance(this, obj); JSONObject result = (JSONObject) drawable.render(); Transform root = (Transform) result.get("root"); root.addClass(type + " " + objType); this.root.append(root); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } } private void setGridAxis(JSONObject obj, JSONObject drawObject) { obj.remove("x"); obj.remove("y"); obj.remove("c"); if (!builderoptions.has("scales")) { return; } JSONObject scales = builderoptions.getJSONObject("scales"); if (scales.has("x") || scales.has("x1")) { if (drawObject.has("x1") && drawObject.getInt("x1") > -1) { obj.put("x", scales.getJSONArray("x1").get(drawObject.getInt("x1"))); } else { obj.put("x", scales.getJSONArray("x").get(drawObject.optInt("x", 0))); } } if (scales.has("y") || scales.has("y1")) { if (drawObject.has("y1") && drawObject.getInt("y1") > -1) { obj.put("y", scales.getJSONArray("y1").get(drawObject.getInt("y1"))); } else { obj.put("y", scales.getJSONArray("y").get(drawObject.optInt("y", 0))); } } if (scales.has("c")) { obj.put("c", scales.getJSONArray("c").get(drawObject.optInt("c", 0))); } } private void drawWidget() { drawObject("widget"); } private void drawBrush() { drawObject("brush"); } private void drawGrid() { JSONObject grid = builderoptions.getJSONObject("grid"); String[] names = JSONObject.getNames(grid); if (names == null) return; if (grid != null && grid.names().length() > 0) { // create default cusotm grid if (grid.has("type")) { grid = new JSONObject().put("c", new JSONArray().put(JSONUtil.clone(grid))); } if (!builderoptions.has("scales")) { builderoptions.put("scales", new JSONObject()); } JSONObject scales = (JSONObject) builderoptions.getJSONObject("scales"); JSONArray keys = grid.names(); for (int i = 0, len = keys.length(); i < len; i++) { String key = keys.getString(i); Orient orient = Orient.CUSTOM; if ("x".equals(key)) { orient = Orient.BOTTOM; } else if ("y".equals(key)) { orient = Orient.LEFT; } else if ("x1".equals(key)) { orient = Orient.TOP; } else if ("y1".equals(key)) { orient = Orient.RIGHT; } if (!scales.has(key)) { scales.put(key, new JSONArray()); } JSONArray scale = (JSONArray) scales.getJSONArray(key); Object objGrid = grid.get(key); if (!(objGrid instanceof JSONArray) && !(objGrid instanceof JSONArray)) { JSONArray o = new JSONArray(); o.put(JSONUtil.clone(grid.getJSONObject(key))); grid.put(key, o); } else if (objGrid instanceof JSONArray) { grid.put(key, JSONUtil.clone((JSONArray) objGrid)); } JSONArray gridObject = (JSONArray) grid.getJSONArray(key); for (int keyIndex = 0, gridLen = gridObject.length(); keyIndex < gridLen; keyIndex++) { JSONObject g = JSONUtil.clone(gridObject.getJSONObject(keyIndex)); Class cls = grids.get(g.getString("type")); Grid newGrid = null; try { newGrid = (Grid) cls .getDeclaredConstructor(Orient.class, ChartBuilder.class, JSONObject.class) .newInstance(orient, this, g); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } JSONObject ret = (JSONObject) newGrid.render(); int dist = g.optInt("dist", 0); Transform root = (Transform) ret.get("root"); if ("y".equals(key)) { root.translate(area("x") - dist, area("y")); } else if ("y1".equals(key)) { root.translate(area("x2") + dist, area("y")); } else if ("x".equals(key)) { root.translate(area("x"), area("y2") + dist); } else if ("x1".equals(key)) { root.translate(area("x"), area("y") - dist); } this.root.append(root); scales.getJSONArray(key).put(keyIndex, newGrid); } } } } public String render() { caculate(); drawBefore(); drawDefs(); drawGrid(); drawBrush(); drawWidget(); this.svg.css("background", theme("backgroundColor")); return this.svg.render(); } public String toDataURL() { this.render(); return this.svg.toDataURL(); } public String export(String type) { if ("datauri".equals(type)) { return this.toDataURL(); } else { return this.render(); } } private void drawDefs() { this.defs = (Transform) this.root.defs(); this.clipId = StringUtil.createId("clip-id"); JSONObject o = new JSONObject().put("id", this.clipId); JSONObject rect = new JSONObject().put("x", 0).put("y", 0).put("width", area("width")).put("height", area("height")); this.defs.clipPath(o).rect(rect); } public ChartBuilder add(JSONObject o) { options.getJSONArray("data").put(o); return this; } public ChartBuilder data(JSONArray data) { options.put("data", data); return this; } public JSONArray data() { return barray("data"); } public JSONObject data(int i) { return (JSONObject) barray("data").getJSONObject(i); } public Object data(int i, String key) { return barray("data").getJSONObject(i).get(key); } public double dataDouble(int i, String key) { return barray("data").getJSONObject(i).getDouble(key); } public JSONObject series() { return bobject("series"); } public JSONArray brush() { return barray("brush"); } public JSONObject brush(int index) { return JSONUtil.clone(barray("brush").getJSONObject(index)); } public JSONObject series(String key) { return (JSONObject) series().getJSONObject(key); } public JSONObject theme() { return (JSONObject) builderoptions.getJSONObject("theme"); } public double themeDouble(String key) { return Double.parseDouble(theme(key).replaceAll("px", "")); } public String theme(String key) { JSONObject theme = theme(); if (theme.has(key)) { if (key.indexOf("Color") > -1) { return getColor(theme.get(key).toString()); } else { return theme.get(key).toString(); } } return ""; } public String theme(boolean checked, String key1, String key2) { String value = (checked) ? key1 : key2; return theme(value); } public void setTheme(String theme) { setTheme(themeList.getJSONObject(theme)); } public void setTheme(JSONObject o) { builderoptions.put("theme", o); } public void setTheme(String key, String value) { theme().put(key, value); } public Transform text(JSONObject textOpt, String text) { JSONObject o = new JSONObject(); o.put("font-family", theme("fontFamily")); o.put("font-size", theme("fontSize")); o.put("fill", theme("fontColor")); return (Transform) el("text", JSONUtil.extend(o, textOpt)).textNode(text); } public String clipId() { return url(clipId); } public Transform defs() { return defs; } public void writeFile(String saveFilename) { try { BufferedWriter out = new BufferedWriter(new FileWriter(saveFilename)); out.write(this.render()); out.close(); } catch (IOException e) { e.printStackTrace(); System.exit(1); } } }