From e96da1f860a9f3d8f98170e661b0c5e61f5f626c Mon Sep 17 00:00:00 2001 From: skoo87 Date: Thu, 5 Sep 2013 00:55:21 +0800 Subject: [PATCH] add goplot --- chart.go | 17 ++++ column.go | 1 + data.go | 201 ++++++++++++++++++++++++++++++++++++++++++++ examples/line.chart | 7 ++ js.go | 41 +++++++++ line.go | 71 ++++++++++++++++ main.go | 5 ++ pie.go | 1 + server.go | 96 +++++++++++++++++++++ 9 files changed, 440 insertions(+) create mode 100644 chart.go create mode 100644 column.go create mode 100644 data.go create mode 100644 examples/line.chart create mode 100644 js.go create mode 100644 line.go create mode 100644 main.go create mode 100644 pie.go create mode 100644 server.go diff --git a/chart.go b/chart.go new file mode 100644 index 0000000..06dd70c --- /dev/null +++ b/chart.go @@ -0,0 +1,17 @@ +package main + +var colorSets = [...]string{ + "#2980B9", // blue + "#C0392B", // red + "#F39C12", // yellow + "#8E44AD", // WISTERIA + "#16A085", // green + "#2C3E50", // black +} + +func GetColorValue(i int) string { + if i >= len(colorSets) { + return colorSets[0] + } + return colorSets[i] +} diff --git a/column.go b/column.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/column.go @@ -0,0 +1 @@ +package main diff --git a/data.go b/data.go new file mode 100644 index 0000000..735b700 --- /dev/null +++ b/data.go @@ -0,0 +1,201 @@ +package main + +import ( + "bufio" + "bytes" + "encoding/json" + "errors" + "io" + "log" + "os" + "path/filepath" + "strconv" + "strings" +) + +const ( + START = iota + PROP + DATA +) + +var ( + prop_header_str = []byte("===") + data_header_str = []byte("---") +) + +type ChartPropType struct { + Name string + Width int + Height int +} + +type ChartItemType struct { + key string + values []int +} + +func newChartItem(d string) *ChartItemType { + item := &ChartItemType{} + item.values = make([]int, 0, 2) + + fields := strings.Split(d, " ") + toValue := false + + for _, f := range fields { + if len(f) == 0 { + continue + } + + if toValue { + if i, err := strconv.Atoi(f); err != nil { + log.Println(err) + } else { + item.values = append(item.values, i) + } + } else { + item.key = f + toValue = true + } + } + + return item +} + +type ChartDataType struct { + prop *bytes.Buffer + items []*ChartItemType +} + +func newChartData() *ChartDataType { + c := new(ChartDataType) + c.prop = bytes.NewBuffer(make([]byte, 0, 128)) + c.items = make([]*ChartItemType, 0, 10) + return c +} + +func (c *ChartDataType) appendProp(p []byte) { + c.prop.Write(p) +} + +func (c *ChartDataType) appendValue(item *ChartItemType) { + c.items = append(c.items, item) +} + +func (c *ChartDataType) Prop() (p ChartPropType, err error) { + b := c.prop.Bytes() + b = bytes.Trim(b, " ") + err = json.Unmarshal(b, &p) + return +} + +func (c *ChartDataType) ItemNum() int { + return len(c.items) +} + +func (c *ChartDataType) ItemName() []string { + names := make([]string, 0, 5) + for _, it := range c.items { + names = append(names, it.key) + } + return names +} + +func (c *ChartDataType) ValueNum() int { + if len(c.items) == 0 { + return 0 + } + return len(c.items[0].values) +} + +func (c *ChartDataType) ItemValue(i int) []int { + values := make([]int, 0, 5) + for _, it := range c.items { + if i >= len(it.values) { + return nil + } + values = append(values, it.values[i]) + } + return values +} + +func ParseDataFile(file string) ([]*ChartDataType, error) { + f, err := os.Open(file) + if err != nil { + return nil, err + } + defer f.Close() + + var c *ChartDataType + charts := make([]*ChartDataType, 0, 2) + reader := bufio.NewReader(f) + status := START + + for { + line, _, err := reader.ReadLine() + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + + if line = bytes.Trim(line, " "); len(line) == 0 { + continue + } + + switch status { + case START: + if bytes.Compare(line, prop_header_str) == 0 { + status = PROP + c = newChartData() + } else { + return nil, errors.New("invalid chart file") + } + case PROP: + if bytes.Compare(line, data_header_str) == 0 { + status = DATA + } else { + c.appendProp(line) + } + case DATA: + if bytes.Compare(line, prop_header_str) == 0 { + status = PROP + charts = append(charts, c) + c = newChartData() + } else { + item := newChartItem(string(line)) + c.appendValue(item) + } + } + } + + charts = append(charts, c) + return charts, nil +} + +func LookupCurrentDir(dir string) ([]*ChartDataType, error) { + var data []*ChartDataType + + err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { + if f == nil { + return err + } + + if f.IsDir() { + return nil + } + + if ok, err := filepath.Match("*.chart", f.Name()); err != nil { + return err + } else if ok { + if path == f.Name() { + data, err = ParseDataFile(path) + return err + } + } + return nil + }) + + return data, err +} diff --git a/examples/line.chart b/examples/line.chart new file mode 100644 index 0000000..77f7672 --- /dev/null +++ b/examples/line.chart @@ -0,0 +1,7 @@ +=== +{ "Name" : "line", "Height" : 300, "Width" : 550 } +--- +aaa 10 13 10 +bbb 15 12 11 +ccc 23 14 15 +ddd 20 24 25 diff --git a/js.go b/js.go new file mode 100644 index 0000000..8762e17 --- /dev/null +++ b/js.go @@ -0,0 +1,41 @@ +package main + +const Chartjs = `var Chart=function(s){function v(a,c,b){a=A((a-c.graphMin)/(c.steps*c.stepValue),1,0);return b*c.steps*a}function x(a,c,b,e){function h(){g+=f;var k=a.animation?A(d(g),null,0):1;e.clearRect(0,0,q,u);a.scaleOverlay?(b(k),c()):(c(),b(k));if(1>=g)D(h);else if("function"==typeof a.onAnimationComplete)a.onAnimationComplete()}var f=a.animation?1/A(a.animationSteps,Number.MAX_VALUE,1):1,d=B[a.animationEasing],g=a.animation?0:1;"function"!==typeof c&&(c=function(){});D(h)}function C(a,c,b,e,h,f){var d;a= +Math.floor(Math.log(e-h)/Math.LN10);h=Math.floor(h/(1*Math.pow(10,a)))*Math.pow(10,a);e=Math.ceil(e/(1*Math.pow(10,a)))*Math.pow(10,a)-h;a=Math.pow(10,a);for(d=Math.round(e/a);dc;)a=dc?c:!isNaN(parseFloat(b))&& +isFinite(b)&&a)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return c? +b(c):b}var r=this,B={linear:function(a){return a},easeInQuad:function(a){return a*a},easeOutQuad:function(a){return-1*a*(a-2)},easeInOutQuad:function(a){return 1>(a/=0.5)?0.5*a*a:-0.5*(--a*(a-2)-1)},easeInCubic:function(a){return a*a*a},easeOutCubic:function(a){return 1*((a=a/1-1)*a*a+1)},easeInOutCubic:function(a){return 1>(a/=0.5)?0.5*a*a*a:0.5*((a-=2)*a*a+2)},easeInQuart:function(a){return a*a*a*a},easeOutQuart:function(a){return-1*((a=a/1-1)*a*a*a-1)},easeInOutQuart:function(a){return 1>(a/=0.5)? +0.5*a*a*a*a:-0.5*((a-=2)*a*a*a-2)},easeInQuint:function(a){return 1*(a/=1)*a*a*a*a},easeOutQuint:function(a){return 1*((a=a/1-1)*a*a*a*a+1)},easeInOutQuint:function(a){return 1>(a/=0.5)?0.5*a*a*a*a*a:0.5*((a-=2)*a*a*a*a+2)},easeInSine:function(a){return-1*Math.cos(a/1*(Math.PI/2))+1},easeOutSine:function(a){return 1*Math.sin(a/1*(Math.PI/2))},easeInOutSine:function(a){return-0.5*(Math.cos(Math.PI*a/1)-1)},easeInExpo:function(a){return 0==a?1:1*Math.pow(2,10*(a/1-1))},easeOutExpo:function(a){return 1== +a?1:1*(-Math.pow(2,-10*a/1)+1)},easeInOutExpo:function(a){return 0==a?0:1==a?1:1>(a/=0.5)?0.5*Math.pow(2,10*(a-1)):0.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return 1<=a?a:-1*(Math.sqrt(1-(a/=1)*a)-1)},easeOutCirc:function(a){return 1*Math.sqrt(1-(a=a/1-1)*a)},easeInOutCirc:function(a){return 1>(a/=0.5)?-0.5*(Math.sqrt(1-a*a)-1):0.5*(Math.sqrt(1-(a-=2)*a)+1)},easeInElastic:function(a){var c=1.70158,b=0,e=1;if(0==a)return 0;if(1==(a/=1))return 1;b||(b=0.3);ea?-0.5*e*Math.pow(2,10* +(a-=1))*Math.sin((1*a-c)*2*Math.PI/b):0.5*e*Math.pow(2,-10*(a-=1))*Math.sin((1*a-c)*2*Math.PI/b)+1},easeInBack:function(a){return 1*(a/=1)*a*(2.70158*a-1.70158)},easeOutBack:function(a){return 1*((a=a/1-1)*a*(2.70158*a+1.70158)+1)},easeInOutBack:function(a){var c=1.70158;return 1>(a/=0.5)?0.5*a*a*(((c*=1.525)+1)*a-c):0.5*((a-=2)*a*(((c*=1.525)+1)*a+c)+2)},easeInBounce:function(a){return 1-B.easeOutBounce(1-a)},easeOutBounce:function(a){return(a/=1)<1/2.75?1*7.5625*a*a:a<2/2.75?1*(7.5625*(a-=1.5/2.75)* +a+0.75):a<2.5/2.75?1*(7.5625*(a-=2.25/2.75)*a+0.9375):1*(7.5625*(a-=2.625/2.75)*a+0.984375)},easeInOutBounce:function(a){return 0.5>a?0.5*B.easeInBounce(2*a):0.5*B.easeOutBounce(2*a-1)+0.5}},q=s.canvas.width,u=s.canvas.height;window.devicePixelRatio&&(s.canvas.style.width=q+"px",s.canvas.style.height=u+"px",s.canvas.height=u*window.devicePixelRatio,s.canvas.width=q*window.devicePixelRatio,s.scale(window.devicePixelRatio,window.devicePixelRatio));this.PolarArea=function(a,c){r.PolarArea.defaults={scaleOverlay:!0, +scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce", +animateRotate:!0,animateScale:!1,onAnimationComplete:null};var b=c?y(r.PolarArea.defaults,c):r.PolarArea.defaults;return new G(a,b,s)};this.Radar=function(a,c){r.Radar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!1,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)", +scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,angleShowLineOut:!0,angleLineColor:"rgba(0,0,0,.1)",angleLineWidth:1,pointLabelFontFamily:"'Arial'",pointLabelFontStyle:"normal",pointLabelFontSize:12,pointLabelFontColor:"#666",pointDot:!0,pointDotRadius:3,pointDotStrokeWidth:1,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?y(r.Radar.defaults,c):r.Radar.defaults;return new H(a,b,s)};this.Pie=function(a, +c){r.Pie.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,onAnimationComplete:null};var b=c?y(r.Pie.defaults,c):r.Pie.defaults;return new I(a,b,s)};this.Doughnut=function(a,c){r.Doughnut.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,percentageInnerCutout:50,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1, +onAnimationComplete:null};var b=c?y(r.Doughnut.defaults,c):r.Doughnut.defaults;return new J(a,b,s)};this.Line=function(a,c){r.Line.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,bezierCurve:!0, +pointDot:!0,pointDotRadius:4,pointDotStrokeWidth:2,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?y(r.Line.defaults,c):r.Line.defaults;return new K(a,b,s)};this.Bar=function(a,c){r.Bar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'", +scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?y(r.Bar.defaults,c):r.Bar.defaults;return new L(a,b,s)};var G=function(a,c,b){var e,h,f,d,g,k,j,l,m;g=Math.min.apply(Math,[q,u])/2;g-=Math.max.apply(Math,[0.5*c.scaleFontSize,0.5*c.scaleLineWidth]); +d=2*c.scaleFontSize;c.scaleShowLabelBackdrop&&(d+=2*c.scaleBackdropPaddingY,g-=1.5*c.scaleBackdropPaddingY);l=g;d=d?d:5;e=Number.MIN_VALUE;h=Number.MAX_VALUE;for(f=0;fe&&(e=a[f].value),a[f].valuel&&(l=h);g-=Math.max.apply(Math,[l,1.5*(c.pointLabelFontSize/2)]);g-=c.pointLabelFontSize;l=g=A(g,null,0);d=d?d:5;e=Number.MIN_VALUE; +h=Number.MAX_VALUE;for(f=0;fe&&(e=a.datasets[f].data[m]),a.datasets[f].data[m]Math.PI?"right":"left";b.textBaseline="middle";b.fillText(a.labels[d],f,-h)}b.restore()},function(d){var e=2*Math.PI/a.datasets[0].data.length;b.save();b.translate(q/2,u/2);for(var g=0;gt?e:t;q/a.labels.lengthe&&(e=a.datasets[f].data[l]),a.datasets[f].data[l]d?h:d;d+=10}r=q-d-t;m=Math.floor(r/(a.labels.length-1));n=q-t/2-r;p=g+c.scaleFontSize/2;x(c,function(){b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(q-t/2+5,p);b.lineTo(q-t/2-r-5,p);b.stroke();0t?e:t;q/a.labels.lengthe&&(e=a.datasets[f].data[l]),a.datasets[f].data[l]< +h&&(h=a.datasets[f].data[l]);f=Math.floor(g/(0.66*d));d=Math.floor(0.5*(g/d));l=c.scaleShowLabels?c.scaleLabel:"";c.scaleOverride?(j={steps:c.scaleSteps,stepValue:c.scaleStepWidth,graphMin:c.scaleStartValue,labels:[]},z(l,j.labels,j.steps,c.scaleStartValue,c.scaleStepWidth)):j=C(g,f,d,e,h,l);k=Math.floor(g/j.steps);d=1;if(c.scaleShowLabels){b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;for(e=0;ed?h:d;d+=10}r=q-d-t;m= +Math.floor(r/a.labels.length);s=(m-2*c.scaleGridLineWidth-2*c.barValueSpacing-(c.barDatasetSpacing*a.datasets.length-1)-(c.barStrokeWidth/2*a.datasets.length-1))/a.datasets.length;n=q-t/2-r;p=g+c.scaleFontSize/2;x(c,function(){b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(q-t/2+5,p);b.lineTo(q-t/2-r-5,p);b.stroke();0", name, height, width) +} + +func (l *LineChart) JsonCode(c *ChartDataType) (string, error) { + lines := new(lineDataType) + + lineNum := c.ValueNum() + + lines.Labels = c.ItemName() + lines.Datasets = make([]*lineDataSetsType, 0, lineNum) + + for i := 0; i < lineNum; i++ { + line := &lineDataSetsType{} + line.FillColor = "rgba(220,220,220,0)" + line.StrokeColor = GetColorValue(i) + line.PointColor = GetColorValue(i) + line.PointStrokeColor = "#fff" + + line.Data = c.ItemValue(i) + lines.Datasets = append(lines.Datasets, line) + } + + b, err := json.Marshal(lines) + if err != nil { + return "", err + } + + return fmt.Sprintf("var lineJsonStr = '%s';", string(b)), nil +} + +func (l *LineChart) NewChart(name string) string { + return fmt.Sprintf("new Chart(document.getElementById(\"%s\").getContext(\"2d\")).Line(eval('('+lineJsonStr+')'));", name) +} + +func init() { + line := new(LineChart) + line.name = "line" + + ChartHandlers["line"] = line +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..c61a87d --- /dev/null +++ b/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + ListenAndServe(":8000") +} diff --git a/pie.go b/pie.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/pie.go @@ -0,0 +1 @@ +package main diff --git a/server.go b/server.go new file mode 100644 index 0000000..36caca0 --- /dev/null +++ b/server.go @@ -0,0 +1,96 @@ +package main + +import ( + "net/http" + "text/template" +) + +const html = `{{define "T"}} + + + + Line Chart + + + + + +
+ {{.Canvas}} + + + +{{end}} +` + +type ChartIf interface { + Canvas(string, int, int) string + JsonCode(*ChartDataType) (string, error) + NewChart(string) string +} + +var ChartHandlers = make(map[string]ChartIf) + +func handler(w http.ResponseWriter, r *http.Request) { + datas, err := LookupCurrentDir(".") + if err != nil { + w.Write([]byte(err.Error())) + return + } + + if len(datas) == 0 { + return + } + c := datas[0] + + var chart ChartIf + var Args = map[string]string{ + "Chartjs": Chartjs, + } + + if prop, err := c.Prop(); err != nil { + w.Write([]byte(err.Error())) + return + } else { + chart = ChartHandlers[prop.Name] + + canvas := chart.Canvas("test", prop.Height, prop.Width) + Args["Canvas"] = canvas + + newChart := chart.NewChart("test") + Args["NewChart"] = newChart + + if json, err := chart.JsonCode(c); err != nil { + w.Write([]byte(err.Error())) + return + } else { + Args["JsonCode"] = json + } + } + + t, err1 := template.New("foo").Parse(html) + if err1 != nil { + w.Write([]byte(err1.Error())) + return + } + + err = t.ExecuteTemplate(w, "T", Args) + if err != nil { + w.Write([]byte(err.Error())) + return + } +} + +func ListenAndServe(addr string) error { + http.HandleFunc("/", handler) + http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {}) + return http.ListenAndServe(addr, nil) +}