{********************************************}
{ TeeChart exporting to Javascript HTML5     }
{ Copyright (c) 2012-2020 by Steema Software }
{         All Rights Reserved                }
{********************************************}
unit TeeJavaScript;
{$I TeeDefs.inc}

interface

uses
  {$IFNDEF FMX}
  {$IFNDEF LINUX}
  Windows, Messages,
  {$ENDIF}
  {$ENDIF}

  SysUtils, Classes,

  {$IFDEF D16}
  System.UITypes,
  {$ENDIF}

  {$IFDEF FMX}
  FMX.Types, FMX.Forms, FMXTee.Canvas, FMXTee.Procs, FMXTee.Engine, FMXTee.Chart,
  FMXTee.Tools, FMXTee.Editor.Export, FMX.ListBox
  {$ELSE}
  Graphics, Controls, Forms,
  TeCanvas, TeeProcs, TeeExport, Chart, TeEngine, TeeTools
  {$ENDIF}
  ;

type
  TJavascriptGetSeries=function(const ASeries:TChartSeries):String of object;

  TCustomJSExportFormat=class(TTeeExportFormat)
  private
    FProperties: TForm;

    FPanelName : String;
    FCanvasName : String;
    FCustomCode : TStrings;
    FDoFullPage : Boolean;
    FMinify: Boolean;
    FSourceScriptPath : String;

    {$IFDEF FMX}
    IFormat       : String;
    {$ENDIF}

    procedure AddHeader(const S:TStrings);
    procedure CheckProperties;
    procedure EmitCustomCode(const S:TStrings);
    function GetCanvasName:String;
    function GetPanelName:String;
    procedure SetSourceScriptPath(const Value : String);
    procedure SetCanvasName(const Value: String);
    procedure SetPanelName(const Value: String);
    procedure SetCustomCode(const S:TStrings);
    procedure SetDoFullPage(const Value: Boolean);
    procedure SetMinify(const Value: Boolean);
    procedure SaveToFile(const Chart:TCustomChart; const FileName:String);
  protected
    procedure AddStrings(const S:TStrings);
    procedure DoAddStrings(const S:TStrings); virtual; abstract;
    Procedure DoCopyToClipboard; override;

    {$IFDEF FMX}
    class procedure AddSubFormats(const APanel:TCustomTeePanel;
                     const AList:TListBox; var FilterIndex:Integer); override;
    {$ELSE}
    function FileFilterIndex: Integer; override;
    procedure IncFileFilterIndex(var FilterIndex: Integer); override;
    {$ENDIF}
  public
    Constructor Create; override;
    Destructor Destroy; override;

    function Description:String; override;
    class function FileFilter:String; override;
    procedure SaveToStream(Stream:TStream); override;

    {$IFDEF FMX}
    function Editor(AOwner:TComponent):TForm; override;
    {$ELSE}
    Function Options(Check:Boolean=True):TForm; override;
    {$ENDIF}

    function FileExtension:String; override;
    function JScript:TStringList;

    property CanvasName:String read GetCanvasName write SetCanvasName;
    property PanelName:String read GetPanelName write SetPanelName;
    property CustomCode:TStrings read FCustomCode write SetCustomCode;
    property DoFullPage:Boolean read FDoFullPage write SetDoFullPage;
    property Minify:Boolean read FMinify write SetMinify;
    property SourceScriptPath:String read FSourceScriptPath write SetSourceScriptPath;
  end;

  TJavascriptExportFormat=class(TCustomJSExportFormat)
  private
    IEmptiedChart : Boolean;
    IHasDateTime  : Boolean;
    IHasExtras    : Boolean;
    IHasGauges    : Boolean;

    function Chart:TCustomChart;
    procedure AddHeader(const S:TStrings);
    procedure EmitEmptyChart(const S:TStrings);
    function GetChartName:String;
    procedure ResetFlags;
    procedure SetChartName(const Value: String);
  protected
    FOnGetSeries : TJavascriptGetSeries;

    procedure DoAddStrings(const S:TStrings); override;
  public
    procedure SaveToFile(const Chart:TCustomChart; const FileName:String);

    property ChartName:String read GetChartName write SetChartName;
  end;

{$IFDEF D11}
{$DEFINE HASENCODING}
{$ENDIF}

procedure TeeSaveToJavascriptFile( const APanel:TCustomChart; const FileName: String;
                                   AWidth:Integer=0; AHeight: Integer=0;
                                   AMinify:Boolean=False
                                   {$IFDEF HASENCODING}
                                   ; const AEncoding:TEncoding=nil
                                   {$ENDIF}
                                   );

implementation

uses {$IFDEF FMX}
     FMXTee.Constants, FMXTee.Series, FMXTee.Series.Bubble,
     FMXTee.Series.Donut, FMXTee.Series.Candle, FMXTee.Series.OHLC,
     FMXTee.Series.Gantt, FMXTee.Series.Polar,
     {$ELSE}
     TeeConst, Series, BubbleCh, CandleCh, OHLChart, TeeDonut, GanttCh,
     TeeCircularGauge, TeePolar,
     {$ENDIF}
     Math;

{$IFDEF D15}
{$DEFINE TEEFORMAT}
{$ENDIF}

{$IFDEF LCL}
{$DEFINE TEEFORMAT}
{$ENDIF}

{ TCustomJSExportFormat }

Constructor TCustomJSExportFormat.Create;
begin
  inherited;

  FDoFullPage:=True;

  // Latest online public version:

  FSourceScriptPath:='http://www.steema.com/files/public/teechart/html5/latest/src';

  // Local, not-minified version:
  //FSourceScriptPath='file:///C:/Root/TeeChartJScript/src/teechart.js';

  FCustomCode:=TStringList.Create;

  FCanvasName:='Canvas1';
end;

Destructor TCustomJSExportFormat.Destroy;
begin
  FCustomCode.Free;
  inherited;
end;

Procedure TCustomJSExportFormat.CheckProperties;
begin
  if not Assigned(FProperties) then
     FProperties:=CreateProperties('TTeeJScriptOptions');
end;

function TCustomJSExportFormat.Description: String;
begin
  result:=TeeMsg_AsJScript;
end;

procedure TCustomJSExportFormat.DoCopyToClipboard;
begin
  with JScript do
  try
    TeeCopyClipboard(Text);
  finally
    Free;
  end;
end;

procedure TCustomJSExportFormat.AddStrings(const S:TStrings);
var Old : Char;
begin
  // JSON Decimal separator is always a dot "."
  Old:={$IFDEF TEEFORMAT}FormatSettings.{$ENDIF}DecimalSeparator;
  {$IFDEF TEEFORMAT}FormatSettings.{$ENDIF}DecimalSeparator:='.';
  try
    DoAddStrings(S);
  finally
   {$IFDEF TEEFORMAT}FormatSettings.{$ENDIF}DecimalSeparator:=Old;
  end;
end;

{$IFDEF FMX}
class procedure TCustomJSExportFormat.AddSubFormats(const APanel:TCustomTeePanel;
                     const AList:TListBox; var FilterIndex:Integer);

  procedure AddFormat(const S:String);
  var tmpExport : TJavascriptExportFormat;
  begin
    tmpExport:=TJavascriptExportFormat.Create;
    tmpExport.Panel:=APanel;
    tmpExport.IFormat:=S;
    tmpExport.IncFileFilterIndex(FilterIndex);
    tmpExport.AddItem(AList,TeeMsg_AsJScript+' ('+S+')',tmpExport);
  end;

begin
  AddFormat('html');
  AddFormat('js');
end;

{$ELSE}
function TCustomJSExportFormat.FileFilterIndex: Integer;
begin
  if DoFullPage then
     result:=FFilterIndex
  else
     result:=Succ(FFilterIndex);
end;

procedure TCustomJSExportFormat.IncFileFilterIndex(var FilterIndex: Integer);
begin
  inherited;
  Inc(FilterIndex);
end;
{$ENDIF}

function TCustomJSExportFormat.FileExtension: String;
begin
  {$IFDEF FMX}
  result:=IFormat;
  {$ELSE}
  if DoFullPage then
     result:='html' // Do not localize
  else
     result:='js'; // Do not localize
  {$ENDIF}
end;

class function TCustomJSExportFormat.FileFilter: String;
begin
  result:=TeeMsg_JScriptFilter;
end;

function TCustomJSExportFormat.Jscript: TStringList;
begin
  CheckSize;
  CheckProperties;

  if Assigned(FProperties) then
     FProperties.CloseQuery;

  result:=TStringList.Create;
  AddStrings(result);
end;

{$IFDEF FMX}
function TCustomJSExportFormat.Editor(AOwner:TComponent):TForm;
{$ELSE}
Function TCustomJSExportFormat.Options(Check:Boolean=True):TForm;
{$ENDIF}
begin
  {$IFNDEF FMX}if Check then{$ENDIF} CheckProperties;
  result:=FProperties;
end;

procedure TCustomJSExportFormat.SaveToFile(const Chart: TCustomChart;
  const FileName: String);
var tmpStream : TStream;
begin
  CheckSize;

  tmpStream:=TFileStream.Create(Filename, fmCreate);
  try
    SaveToStream(tmpStream);
  finally
    tmpStream.Free;
  end;
end;

procedure TCustomJSExportFormat.SaveToStream(Stream:TStream);
begin
  with JScript do
  try
    SaveToStream(Stream);
  finally
    Free;
  end;
end;

procedure TCustomJSExportFormat.SetCanvasName(const Value: String);
begin
  FCanvasName := Value;
end;

procedure TCustomJSExportFormat.SetPanelName(const Value: String);
begin
  FPanelName := Value;
end;

procedure TCustomJSExportFormat.SetCustomCode(const S: TStrings);
begin
  FCustomCode.Assign(S);
end;

procedure TCustomJSExportFormat.SetDoFullPage(const Value: Boolean);
begin
  FDoFullPage:=Value;
end;

procedure TCustomJSExportFormat.SetMinify(const Value: Boolean);
begin
  FMinify := Value;
end;

function TCustomJSExportFormat.GetCanvasName:String;
begin
  if FCanvasName='' then
     result:='Canvas1'
  else
     result:=FCanvasName;
end;

function TCustomJSExportFormat.GetPanelName:String;
begin
  result:=FPanelName;
end;

procedure TCustomJSExportFormat.SetSourceScriptPath(const Value: String);
begin
  FSourceScriptPath := Value;
end;

procedure TCustomJSExportFormat.EmitCustomCode(const S: TStrings);
begin
  if Assigned(CustomCode) then
     S.AddStrings(CustomCode);
end;

procedure TCustomJSExportFormat.AddHeader(const S: TStrings);
begin
  S.Insert(0,'<!DOCTYPE html>');
  S.Insert(1,'<HTML>');
  S.Insert(2,'<HEAD>');
end;

procedure TJavascriptExportFormat.AddHeader(const S: TStrings);
var tmp : Integer;
begin
  inherited;
  S.Insert(3,'<meta charset="utf-8">');
  S.Insert(4,'<title>'+Chart.Name+'</title>');

  tmp:=5;

  S.Insert(tmp,'<script src="'+SourceScriptPath+'/teechart.js" type="text/javascript"></script>');
  Inc(tmp);

  if IHasDateTime then
  begin
    S.Insert(tmp,'<script src="'+SourceScriptPath+'/date.format.js" type="text/javascript"></script>');
    Inc(tmp);
  end;

  if IHasExtras then
  begin
    S.Insert(tmp,'<script src="'+SourceScriptPath+'/teechart-extras.js" type="text/javascript"></script>');
    Inc(tmp);
  end;

  if IHasGauges then
  begin
    S.Insert(tmp,'<script src="'+SourceScriptPath+'/teechart-gauges.js" type="text/javascript"></script>');
    Inc(tmp);
  end;

  S.Insert(tmp,'<script type="text/javascript">');
  S.Insert(tmp+1,'var '+ChartName+';');
  S.Insert(tmp+2,'function draw() {');
end;

type
  TChartAccess=class(TCustomAxisPanel);
  TSeriesAccess=class(TChartSeries);
  TShapeAccess=class(TTeeCustomShape);

procedure TJavascriptExportFormat.DoAddStrings(const S:TStrings);
var
  TempString : String;

  procedure AddScript(const St:String);
  begin
    if Minify then
    begin
      TempString:=TempString+St;

      if Length(TempString)>200 then
      begin
        s.Add(TempString);
        TempString:='';
      end;
    end
    else
      s.Add('  '+St);
  end;

var
  ILocals : Integer;

  function FindNewLocal:String;
  var tmp : Integer;
  begin
    Inc(ILocals);
    tmp:=ILocals;
    result:='';

    while tmp>25 do
    begin
      result:=result+Chr(97+(tmp mod 25));
      tmp:=tmp div 25;
    end;

    if tmp>0 then
       result:=result+Chr(97+tmp);

    if result='if' then
       result:=FindNewLocal;
  end;

  procedure NewLocalVar(var Tag:String);
  var tmp : String;
  begin
    tmp:=FindNewLocal;
    AddScript('var '+tmp+'='+Tag+';');
    Tag:=tmp;
  end;

  function JSMouseButton(AButton:TMouseButton):String;
  begin
    case AButton of
      {$IFDEF FMX}TMouseButton.{$ENDIF}mbLeft: result:='0';
      {$IFDEF FMX}TMouseButton.{$ENDIF}mbMiddle: result:='1';
    else
      result:='2';
    end;
  end;

  function HtmlColor(const AColor:TColor; const Transparency:TTeeTransparency=0):String;
  begin
    if Transparency>0 then
       result:='rgba('+TeeStr(TRGBA(AColor).Red)+','+
                       TeeStr(TRGBA(AColor).Green)+','+
                       TeeStr(TRGBA(AColor).Blue)+','+
                       FormatFloat('0.##',(100-Transparency)*0.01)+')'
    else
       result:='#'+IntToHex(TRGBA(AColor).Red,2)
                  +IntToHex(TRGBA(AColor).Green,2)
                  +IntToHex(TRGBA(AColor).Blue,2);
  end;

  function FontStyle(const AStyle:TFontStyles):String;
  begin
    result:='';
    if {$IFDEF FMX}TFontStyle.{$ENDIF}fsBold in AStyle then result:=result+' bold';
    if {$IFDEF FMX}TFontStyle.{$ENDIF}fsItalic in AStyle then result:=result+' italic';
    if result<>'' then result:=result+' ';
  end;

  procedure EmitStrings(const Tag:String; AStrings:TStrings);
  var tmp : String;
      t   : Integer;
  begin
    tmp:=Tag+'="';

    for t:=0 to AStrings.Count-1 do
    begin
      if t>0 then tmp:=tmp+'\n';
      tmp:=tmp+AStrings[t];
    end;

    AddScript(tmp+'";');
  end;

  function ToBool(Value:Boolean):String;
  begin
    if Minify then
       if Value then result:='1' else result:='0'
    else
       if Value then result:='true' else result:='false';
  end;

  type TCached=record Name,Value:String; end;
  var Cached:Array of TCached;

  procedure GetCachedVar(var S:String);
  var t,l : Integer;
  begin
    l:=Length(Cached);

    for t := 0 to l-1 do
    if Cached[t].Value=S then
    begin
      S:=Cached[t].Name;
      Exit;
    end;

    SetLength(Cached,l+1);
    Cached[l].Name:=FindNewLocal;
    Cached[l].Value:=S;

    AddScript('var '+Cached[l].Name+ '=' + S + ';');

    S:=Cached[l].Name;
  end;

  procedure EmitGradient(Tag:String; const AGradient:TTeeGradient);

    function GradientColors:String;
    var t : Integer;
    Begin
      result := '[';

      for t:=0 to AGradient.Colors.Count-1 do
      begin
        if t>0 then
           result:=result+',';

        result:=result + '"' + HtmlColor(AGradient.Colors[t].Color)+'"';
      end;

      result:= result + ']';
    end;

    function GradientStops:String;
    var t : Integer;
    Begin
      result := '[';

      for t:=0 to AGradient.Colors.Count-1 do
      begin
        if t>0 then
           result:=result+',';

        result:=result + FloatToStr(AGradient.Colors[t].Offset);
      end;

      result:= result + ']';
    end;

    function GradientDirection(Direction : TGradientDirection) : String;
    begin
      case Direction of
        gdTopBottom    : result := 'bottomtop'; //colours ideally reverse logic
        gdBottomTop    : result := 'topbottom';
        gdLeftRight    : result := 'rightleft';
        gdRightLeft    : result := 'leftright';
        gdRadial       : result := 'radial';
        gdDiagonalUp   : result := 'diagonalup';
        gdDiagonalDown : result := 'diagonaldown';
      else
        result := 'topbottom';

        //gdFromCenter: result := 'topbottom';
        //gdFromTopLeft: result := 'topbottom';
        //gdFromBottomLeft: result := 'topbottom';
      end;
    end;

  var tmp : String;
  begin
    if Minify then NewLocalVar(Tag);

    tmp:=GradientColors;
    if Minify then GetCachedVar(tmp);

    AddScript(Tag + '.colors=' + tmp + ';');

    tmp:=GradientStops;
    if Minify then GetCachedVar(tmp);
    AddScript(Tag + '.stops=' + tmp + ';');

    AddScript(Tag + '.direction="' + GradientDirection(AGradient.Direction) + '";');
    AddScript(Tag + '.visible=' + ToBool(AGradient.Visible)+';');

    if AGradient.RadialX<>0 then
       AddScript(Tag+'.offset.x='+IntToStr(AGradient.RadialX)+';');

    if AGradient.RadialY<>0 then
       AddScript(Tag+'.offset.y='+IntToStr(AGradient.RadialY)+';');
  end;

  procedure EmitStroke(Tag:String; const APen:TTeePen;
              ADefPenColor:TColor={$IFDEF FMX}TAlphaColors.Black{$ELSE}clBlack{$ENDIF};
              ATransparency: Integer=0);
  begin
    if Minify then NewLocalVar(Tag);

    if (not APen.Visible) or (APen.Style=psClear) then
       AddScript(Tag+'.fill="";')
    else
    if (ATransparency>0) or ((APen.Color<>clTeeColor) and (APen.Color<>ADefPenColor)) then
       AddScript(Tag+'.fill="'+HtmlColor(APen.Color, ATransparency)+'";');

    if APen.Width<>1 then
       AddScript(Tag+'.size='+{$IFDEF FMX}FloatToStr{$ELSE}IntToStr{$ENDIF}(APen.Width)+';');

    case APen.JoinStyle of
      jsRound: ; // default
      jsBevel: AddScript(Tag+'.join="bevel";');
      jsMitter: AddScript(Tag+'.join="miter";');
    end;

    case APen.EndStyle of
      esRound: AddScript(Tag+'.cap="round";');
      esSquare: ; // default
      esFlat: AddScript(Tag+'.cap="butt";');
    end;

    if APen.SmallDots then
       AddScript(Tag+'.dash=[2,2];')  // (1,1 is too small)
    else
    case APen.Style of
       psSolid: ;
       psDash: AddScript(Tag+'.dash=[4,4];');
       psDashDot:  AddScript(Tag+'.dash=[4,2];');
       psDashDotDot:  AddScript(Tag+'.dash=[4,2,2];');
    else
       AddScript(Tag+'.dash=[2,2];');
    end;
  end;

  procedure EmitShadow(Tag:String; const AShadow:TTeeShadow);
  const Silver2=$505050;
  begin
    if Minify then NewLocalVar(Tag);

    if (AShadow.Color<>clTeeColor) and (AShadow.Color<>Silver2) then
       AddScript(Tag+'.color="'+HtmlColor(AShadow.Color)+'";');

    if AShadow.HorizSize<>4 then
       AddScript(Tag+'.width='+IntToStr(AShadow.HorizSize)+';');

    if AShadow.VertSize<>4 then
       AddScript(Tag+'.height='+IntToStr(AShadow.HorizSize)+';');

    if not AShadow.Visible then
       AddScript(Tag+'.visible='+ToBool(False)+';');

    if not AShadow.Smooth then
       AddScript(Tag+'.blur=0;')
    else
    if AShadow.SmoothBlur<>0 then
       AddScript(Tag+'.blur='+IntToStr(Round((AShadow.SmoothBlur+75)*0.1))+';');
  end;

  procedure EmitFont(Tag:String; const AFont:TTeeFont);
  var tmpSize : Single;
  begin
    if Minify then NewLocalVar(Tag);

    // http://stackoverflow.com/questions/15430763/fractional-font-sizes-html5-canvas

    if Chart.View3DOptions.FontZoom=100 then
       tmpSize:=AFont.SizeFloat
    else
       tmpSize:=AFont.SizeFloat*Chart.View3DOptions.FontZoom/100.0;

    AddScript(Tag+'.style="'+FormatFloat('0.###',tmpSize*(96/72))+'px '+FontStyle(AFont.Style)+ AFont.{$IFDEF FMX}Family{$ELSE}Name{$ENDIF}+'";');

    if AFont.Color<>{$IFDEF FMX}TAlphaColors.Black{$ELSE}clBlack{$ENDIF} then
       AddScript(Tag+'.fill="'+HtmlColor(AFont.Color)+'";');

    if AFont.Gradient.Visible then
       EmitGradient(Tag+'.gradient',AFont.Gradient);

    if AFont.Shadow.ShouldDraw then
       EmitShadow(Tag+'.shadow',AFont.Shadow);

    if AFont.OutLine.Visible then
       EmitStroke(Tag+'.stroke',AFont.OutLine);
  end;

  procedure EmitCustomShapeFormat(Tag:String; const AShape:TTeeCustomShape; AEmitFont:Boolean=True);
  var tmp : String;
  begin
    if Minify then NewLocalVar(Tag);

    if (not AShape.Transparent) and (AShape.Color<>clTeeColor) then
       AddScript(Tag+'.fill="'+HtmlColor(AShape.Color)+'";');

    if AShape.ShapeStyle=fosRectangle then
       tmp:='0'
    else
       tmp:=IntToStr(AShape.RoundSize);

    AddScript(Tag+'.round.x='+tmp+';');
    AddScript(Tag+'.round.y='+tmp+';');

    if AShape.Transparent then
    begin
      // This is not done in this way: AddScript(Tag+'.transparent=true;');

      AddScript(Tag+'.fill="";');
      AddScript(Tag+'.stroke.fill="";');
    end;

    if TShapeAccess(AShape).Transparency>0 then
       AddScript(Tag+'.transparency='+FloatToStr(TShapeAccess(AShape).Transparency*0.01)+';');

    if AShape.Gradient.Visible then
       EmitGradient(Tag+'.gradient',AShape.Gradient);

    if AEmitFont then
       EmitFont(Tag+'.font',AShape.Font);

    if (not AShape.Transparent) and AShape.Pen.Visible then
       EmitStroke(Tag+'.stroke',AShape.Pen, {$IFDEF FMX}TAlphaColors.Silver{$ELSE}clSilver{$ENDIF});

    if AShape.Shadow.ShouldDraw then
       EmitShadow(Tag+'.shadow',AShape.Shadow)
    else
       AddScript(Tag+'.shadow.visible=false;');
  end;

  procedure EmitTitle(Tag:String; const ATitle:TChartTitle);
  begin
    if Minify then NewLocalVar(Tag);

    EmitStrings(Tag+'.text',ATitle.Text);

    EmitCustomShapeFormat(Tag+'.format',ATitle);

    if not ATitle.Visible then
       AddScript(Tag+'.visible='+ToBool(False)+';');

    if not ATitle.Transparent then
       AddScript(Tag+'.transparent='+ToBool(False)+';');
  end;

  function Floats(const Series:TChartSeries; const AList:TChartValues):String;

    function ValueFloatToStr(Index:Integer):String;
    begin
      if Series.IsNull(Index) then
         result:='null'
      else
         result:=FloatToStr(AList[Index]);
    end;

  var t : Integer;
  begin
    if Series.Count>0 then
    begin
      result:=ValueFloatToStr(0);

      for t:=1 to Series.Count-1 do
          result:=result+','+ValueFloatToStr(t);
    end
    else
      result:='';
  end;

  procedure EmitLegend(Tag:String; const ALegend:TChartLegend);

    procedure EmitSymbol(const Tag:String; const ASymbol:TLegendSymbol);
    begin
      if not ASymbol.Visible then
         AddScript(Tag+'.visible='+ToBool(False)+';');

      EmitGradient(Tag+'.format.gradient',ASymbol.Gradient);
      EmitStroke(Tag+'.format.stroke',ASymbol.Pen);
      EmitShadow(Tag+'.format.shadow',ASymbol.Shadow);
    end;

    procedure EmitLegendTitle(Tag:String; const ATitle:TLegendTitle);
    begin
      if Minify then NewLocalVar(Tag);

      EmitStrings(Tag+'.text',ATitle.Text);
      EmitCustomShapeFormat(Tag+'.format', ATitle);

      if not ATitle.Visible then
         AddScript(Tag+'.visible='+ToBool(False)+';');

      if not ATitle.Transparent then
         AddScript(Tag+'.transparent='+ToBool(False)+';');
    end;

    function Direction:String;
    begin
      case ALegend.Alignment of
         laLeft: result:='left';
        laRight: result:='right';
          laTop: result:='top';
      else
         result:='bottom';
      end;
    end;

  begin
    if Minify then NewLocalVar(Tag);

    AddScript(Tag+'.position="'+Direction+'";');

    EmitCustomShapeFormat(Tag+'.format', ALegend);

    if ALegend.GlobalTransparency>0 then
       AddScript(Tag+'.transparency='+FloatToStr(ALegend.GlobalTransparency*0.01)+';');

    if not ALegend.Visible then
       AddScript(Tag+'.visible='+ToBool(False)+';');

    if ALegend.Inverted then
       AddScript(Tag+'.inverted='+ToBool(True)+';');

    if ALegend.Transparent then
       AddScript(Tag+'.transparent='+ToBool(True)+';');

    EmitSymbol(Tag+'.symbol',ALegend.Symbol);

    if ALegend.Title.Visible and (ALegend.Title.Caption<>'') then
       EmitLegendTitle(Tag+'.title',ALegend.Title);

    if ALegend.FontSeriesColor then
       AddScript(Tag+'.fontColor='+ToBool(True)+';');

  end;

  procedure EmitMarksStyle(Tag:String; AStyle:TSeriesMarksStyle; HasLabels:Boolean);
  begin
    case AStyle of
           smsValue: AddScript(Tag+'="value";');
         smsPercent: AddScript(Tag+'="percent";');
           smsLabel: if HasLabels then AddScript(Tag+'="label";');
    smsLabelPercent: AddScript(Tag+'="labelpercent";');
      smsLabelValue: AddScript(Tag+'="labelvalue";');
          smsLegend: AddScript(Tag+'="legend";');
      smsPointIndex: AddScript(Tag+'="index";');
    smsLabelOrValue: AddScript(Tag+'="";');
    end;
  end;

  procedure EmitSeries(const Tag:String; const ASeries:TChartSeriesList);

    function Colors(const Series:TChartSeries):String;

      function SeriesColor(Index:Integer):String;
      var tmp:TColor;
      begin
        tmp:=TSeriesAccess(Series).ValueColor[Index];
        if tmp=clTeeColor then
           result:='null'
        else
           result:='"'+HtmlColor(tmp)+'"';
      end;

    var t : Integer;
    begin
      result:=SeriesColor(0);

      for t:=1 to Series.Count-1 do
        result:=result+','+SeriesColor(t);
    end;

    function Labels(const Series:TChartSeries):String;
    var t : Integer;
    begin
      result:='"'+Series.Labels[0]+'"';
      for t:=1 to Series.Count-1 do
        result:=result+',"'+Series.Labels[t]+'"';
    end;

    procedure Emit(const Series:TChartSeries);
    var sName:String;

      procedure AddNew(const AClass:String);
      begin
        if Minify then
           sName:=FindNewLocal
        else
        begin
          sName:=Series.Name;
          if sName='' then
             sName:='Series'+IntToStr(1+Chart.SeriesList.IndexOf(Series));
        end;

        AddScript('var '+sName+'='+Tag+'addSeries(new Tee.'+AClass+'());');
      end;

      procedure EmitPointer(Tag:String; const APointer:TSeriesPointer);
      begin
        if Minify then NewLocalVar(Tag);

        EmitStroke(Tag+'.format.stroke',APointer.Pen);
        EmitGradient(Tag+'.format.gradient',APointer.Gradient);

        if APointer.Shadow.ShouldDraw then
           EmitShadow(Tag+'.format.shadow',APointer.Shadow);

        if APointer.Color<>clTeeColor then
           AddScript(Tag+'.fill="'+HtmlColor(APointer.Color)+'";');

        AddScript(Tag+'.visible='+ToBool(APointer.Visible)+';');

        AddScript(Tag+'.width='+IntToStr(APointer.HorizSize*2)+';');
        AddScript(Tag+'.height='+IntToStr(APointer.VertSize*2)+';');

        if APointer.Style<>psRectangle then
        case APointer.Style of
            psCircle: AddScript(Tag+'.style="ellipse";');
          psTriangle: AddScript(Tag+'.style="triangle";');
           psDiamond: AddScript(Tag+'.style="diamond";');
      psDownTriangle: AddScript(Tag+'.style="downtriangle";');
             psCross: AddScript(Tag+'.style="cross";');
        psStar,
        psDiagCross : AddScript(Tag+'.style="x";');
        end;
      end;

      procedure EmitSeriesMarks(Tag:String; const AMarks:TSeriesMarks);
      begin
        if Minify then NewLocalVar(Tag);

        AddScript(Tag+'.visible='+ToBool(AMarks.Visible)+';');

        if AMarks.Transparent then
           AddScript(Tag+'.transparent='+ToBool(True)+';');

        EmitCustomShapeFormat(Tag+'.format',AMarks);

        EmitMarksStyle(Tag+'.style',AMarks.Style,AMarks.ParentSeries.Labels.Count>0);

        if AMarks.DrawEvery>1 then
           AddScript(Tag+'.drawEvery='+IntToStr(AMarks.DrawEvery)+';');
      end;

      function DateTimes(const Series:TChartSeries; const AList:TChartValues):String;

        function ValueDateTimeToStr(Index:Integer):String;
        const
          Jan1st_1970:TDateTime=25569.0;
        begin
          if Series.IsNull(Index) then
             result:='null'
          else
             result:='new Date('+IntToStr(Round((AList[Index]-Jan1st_1970)*86400000))+')';
        end;

      var t : Integer;
      begin
        if Series.Count>0 then
        begin
          result:=ValueDateTimeToStr(0);

          for t:=1 to Series.Count-1 do
              result:=result+','+ValueDateTimeToStr(t);
        end
        else
          result:='';
      end;

      function EmitValueList(const ASeries:TChartSeries; const AList:TChartValueList):String;
      begin
        if AList.DateTime then
        begin
          IHasDateTime:=True;
          result:=DateTimes(ASeries,AList.Value);
        end
        else
           result:=Floats(ASeries,AList.Value);
      end;

    var tmp : String;
        tmpTransp : TTeeTransparency;
        bar : TCustomBarSeries;
        t   : Integer;
    begin
      if (Series is TLineSeries) or (Series is TFastLineSeries) then
          AddNew('Line')
      else
      if Series is TAreaSeries then AddNew('Area') else
      if Series is TBarSeries then AddNew('Bar') else
      if Series is THorizLineSeries then AddNew('HorizLine') else
      if Series is TGanttSeries then AddNew('Gantt') else
      if Series is TPointSeries then AddNew('PointXY') else
      if Series is THorizAreaSeries then AddNew('HorizArea') else
      if Series is THorizBarSeries then AddNew('HorizBar') else
      if Series is TDonutSeries then AddNew('Donut') else // <-- Before Pie
      if Series is TPieSeries then AddNew('Pie') else
      if Series is TBubbleSeries then AddNew('Bubble') else
      if Series is TCandleSeries then AddNew('Candle') else
      if Series is TVolumeSeries then AddNew('Volume') else
      if Series is TCustomPolarSeries then AddNew('Polar') else // <-- Radar, Polar, etc

      {$IFNDEF FMX}
      if Series is TCircularGauge then
      begin
        AddNew('CircularGauge');
        AddScript(sName+'.min='+FloatToStr(TCircularGauge(Series).Minimum)+';');
        AddScript(sName+'.max='+FloatToStr(TCircularGauge(Series).Maximum)+';');
        AddScript(sName+'.rotation='+FloatToStr(TCircularGauge(Series).RotationAngle)+';');
        AddScript(sName+'.value='+FloatToStr(TCircularGauge(Series).Value)+';');

        IHasGauges:=True;
      end
      else
      {$ENDIF}
      begin
        if Assigned(FOnGetSeries) then
           tmp:=FOnGetSeries(Series)
        else
           tmp:='';

        if tmp='' then
        begin
          IEmptiedChart:=True;
          EmitEmptyChart(s);
          Exit;
        end
        else
        begin
          AddNew(tmp);

          if SameText(tmp,'map') then
             AddScript(sName+'.setMap(new Tee.WorldMap());');
        end;
      end;

      tmp:=sName+'.';

      AddScript(tmp+'title="'+SeriesTitleOrName(Series)+'";');

      if not Series.Visible then
         AddScript(tmp+'visible='+ToBool(False)+';');

      if Series.Color<>clTeeColor then
      begin
        if Series is TCustomBarSeries then
           tmpTransp:=TCustomBarSeries(Series).Transparency
        else
           tmpTransp:=0;

        AddScript(tmp+'format.fill="'+HtmlColor(Series.Color,tmpTransp)+'";');
      end;

      EmitStroke(tmp+'format.stroke', Series.Pen,
                 {$IFDEF FMX}TAlphaColors.Black{$ELSE}clBlack{$ENDIF},
                 Series.Transparency);

      if Series.ColorEachPoint then
         AddScript(tmp+'colorEach="yes";')
      else
         AddScript(tmp+'colorEach="no";');

      if Series.Count>0 then
      begin
        AddScript(tmp+'data.values=['+EmitValueList(Series,Series.MandatoryValueList)+'];');

        if HasNoMandatoryValues(Series) or (Series is TGanttSeries) then
           AddScript(tmp+'data.x=['+EmitValueList(Series,Series.NotMandatoryValueList)+'];');

        if Series is TGanttSeries then
        begin
          AddScript(tmp+'data.start='+tmp+'data.values;');
          AddScript(tmp+'data.start=['+EmitValueList(Series,Series.MandatoryValueList)+'];');
          AddScript(tmp+'data.end=['+EmitValueList(Series,TGanttSeries(Series).EndValues)+'];');
        end;

        if HasColors(Series) then
           AddScript(tmp+'palette.colors=['+Colors(Series)+'];');

        if HasLabels(Series) then
           AddScript(tmp+'data.labels=['+Labels(Series)+'];');

        if Series is TBubbleSeries then
           AddScript(tmp+'data.radius=['+Floats(Series,TBubbleSeries(Series).RadiusValues.Value)+'];');

        if Series is TOHLCSeries then
        begin
           AddScript(tmp+'data.open=['+Floats(Series,TOHLCSeries(Series).OpenValues.Value)+'];');
           AddScript(tmp+'data.low=['+Floats(Series,TOHLCSeries(Series).LowValues.Value)+'];');
           AddScript(tmp+'data.high=['+Floats(Series,TOHLCSeries(Series).HighValues.Value)+'];');
           AddScript(tmp+'data.close=['+Floats(Series,TOHLCSeries(Series).CloseValues.Value)+'];');
        end;

        if Series is TCandleSeries then
        begin
           AddScript(tmp+'higher.fill="'+HtmlColor(TCandleSeries(Series).UpCloseColor)+'";');
           AddScript(tmp+'lower.fill="'+HtmlColor(TCandleSeries(Series).DownCloseColor)+'";');
        end;
      end;

      if Series.CustomHorizAxis<>nil then
        for t:=0 to Chart.CustomAxes.Count-1 do
          if Series.CustomHorizAxis=Chart.CustomAxes[t] then
          begin
            AddScript(tmp+'horizAxis='+Tag+'axes.items['+IntToStr(t+4)+'];');
            Break;
          end
      else
      begin
        case Series.HorizAxis of
          aBottomAxis: ;
             aTopAxis: AddScript(tmp+'horizAxis="top";');
       aBothHorizAxis: AddScript(tmp+'horizAxis="both";');
        end;
      end;

      if Series.CustomVertAxis<>nil then
        for t:=0 to Chart.CustomAxes.Count-1 do
          if Series.CustomVertAxis=Chart.CustomAxes[t] then
          begin
            AddScript(tmp+'vertAxis='+Tag+'axes.items['+IntToStr(t+4)+'];');
            Break;
          end
      else
      begin
        case Series.VertAxis of
            aLeftAxis: ;
           aRightAxis: AddScript(tmp+'vertAxis="right";');
        aBothVertAxis: AddScript(tmp+'vertAxis="both";');
        end;
      end;

      EmitSeriesMarks(tmp+'marks',Series.Marks);

      if Series is TFastLineSeries then
      begin
        if TFastLineSeries(Series).Stairs then
           AddScript(tmp+'stairs='+ToBool(True)+';');
      end
      else
      if Series is TCustomSeries then
      begin
        if TCustomSeries(Series).Stairs then
           AddScript(tmp+'stairs='+ToBool(True)+';');

        //.stacked="no"; // "yes", "100"

        // "Pointer" not supported in Tee.Volume and Gantt series
        if not (Series is TVolumeSeries) and not (Series is TGanttSeries) then
           EmitPointer(tmp+'pointer',TCustomSeries(Series).Pointer);
      end
      else
      if Series is TCustomBarSeries then
      begin
        bar:=TCustomBarSeries(Series);

        if bar.Gradient.Visible then // <-- fix ColorEach not working
           EmitGradient(tmp+'format.gradient',bar.Gradient)
        else
           AddScript(tmp+'format.gradient.visible='+ToBool(false)+';');

        if bar.BarWidthPercent<>70 then
           AddScript(tmp+'barSize='+IntToStr(bar.BarWidthPercent)+';');

        if not bar.UseYOrigin then
           AddScript(tmp+'useOrigin='+ToBool(False)+';');

        if bar.OffsetPercent<>0 then
           AddScript(tmp+'offset='+IntToStr(bar.OffsetPercent)+';');

        if bar.YOrigin<>0 then
           AddScript(tmp+'origin='+FloatToStr(bar.YOrigin)+';');

        if not bar.SideMargins then
           AddScript(tmp+'sideMargins=0;');

        case bar.MultiBar of
          mbSide : ;
          mbStacked: AddScript(tmp+'stacked="yes";');
          mbStacked100: AddScript(tmp+'stacked="100";');
          mbSideAll: AddScript(tmp+'stacked="sideAll";');
          mbSelfStack: AddScript(tmp+'stacked="self";');
        end;

        if Bar.Shadow.ShouldDraw then
           EmitShadow(tmp+'format.shadow',Bar.Shadow);

        case Bar.BarStyle of
          bsCylinder : AddScript(tmp+'barStyle="cylinder";');
          bsCone     : AddScript(tmp+'barStyle="cone";');
          bsEllipse  : AddScript(tmp+'barStyle="ellipse";');
          bsArrow    : AddScript(tmp+'barStyle="line";');  // arrow pending
          bsRoundRectangle: ;  // default
        else
          begin
            AddScript(tmp+'format.round.x=0;');
            AddScript(tmp+'format.round.y=0;');
          end;
        end;
      end
      else if Series is TCircledSeries then
      begin
        EmitGradient(tmp+'format.gradient',TPieSeries(Series).Gradient);
        if Series is TPieSeries then
        begin
          if Series.YValues.Order<>loNone then
          begin
             AddScript(tmp+'sort="values";');
             AddScript(tmp+'orderAscending='+ToBool(Series.YValues.Order=loAscending)+';')
          end
          else
             AddScript(tmp+'sort="none";');

          AddScript(tmp+'clockwise=false;');
        end;
      end;
    end;

  var t : Integer;
  begin
    for t:=0 to ASeries.Count-1 do
      Emit(ASeries[t]);
  end;

  procedure EmitWalls(Tag:String; const Walls:TChartWalls);

    procedure EmitWall(Tag:String; const Wall:TChartWall);
    begin
      if Minify then NewLocalVar(Tag);

      if not Wall.Visible then
         AddScript(Tag+'.visible='+ToBool(False)+';');

      EmitCustomShapeFormat(Tag+'.format',Wall,False);
    end;

  begin
    if not Walls.Visible then
       AddScript(Tag+'.visible='+ToBool(False)+';');

    EmitWall(Tag+'.back',Walls.Back);
  end;

  function TextToHTML(const S:String):String;
  begin
    result:=StringReplace(S,TeeLineSeparator,'\n',[]);
  end;

  procedure EmitAxis(Tag:String; const Axis:TChartAxis);

    procedure EmitLabels(Tag:String);
    begin
      if Minify then NewLocalVar(Tag);

      if Axis.LabelsAlternate then
         AddScript(Tag+'.alternate='+ToBool(True)+';');

      case Axis.LabelStyle of
         talAuto: ;
         talNone: AddScript(Tag+'.visible='+ToBool(False)+';');
        talValue: AddScript(Tag+'.labelStyle="value";');
         talMark: AddScript(Tag+'.labelStyle="mark";');
         talText: AddScript(Tag+'.labelStyle="text";');
   talPointValue: ;
      end;

      if not Axis.Labels then
         AddScript(Tag+'.visible='+ToBool(False)+';');

      EmitFont(Tag+'.format.font',Axis.LabelsFont);

      if Axis.LabelsAngle <> 0 then
        AddScript(Tag+'.rotation='+IntToStr(Axis.LabelsAngle)+';');
    end;

    procedure EmitAxisTitle(Tag:String; const ATitle:TChartAxisTitle);
    begin
      if Minify then NewLocalVar(Tag);

      if ATitle.Caption<>'' then
         AddScript(Tag+'.text="'+TextToHTML(ATitle.Caption)+'";');

      EmitCustomShapeFormat(Tag+'.format',ATitle);

      if not ATitle.Visible then
         AddScript(Tag+'.visible='+ToBool(False)+';');

      if not ATitle.Transparent then
         AddScript(Tag+'.transparent='+ToBool(False)+';');
    end;

  var tmpS: string;
  begin
    if Minify then NewLocalVar(Tag);

    if not TChartAccess(Chart).CalcIsAxisVisible(Axis) then
       AddScript(Tag+'.visible='+ToBool(False)+';');

    if Axis.Inverted then
       AddScript(Tag+'.inverted='+ToBool(True)+';');

    if Axis.Logarithmic then
       AddScript(Tag+'.log='+ToBool(True)+';');

    if Axis.Increment<>0 then
       if Axis.IsDateTime then
          AddScript(Tag+'.increment='+FloatToStr(86400000*Axis.Increment)+';')
       else
          AddScript(Tag+'.increment='+FloatToStr(Axis.Increment)+';');

    if Axis.IsDateTime then
    begin
      tmpS:=LowerCase(Axis.DateTimeFormat);
      tmpS:=StringReplace(tmpS, 'n', 'M', [rfReplaceAll]);
      AddScript(Tag+'.labels.dateFormat="'+tmpS+'";');
    end;

    if not Axis.Automatic then
    begin
      AddScript(Tag+'.automatic='+ToBool(False)+';');
        AddScript(Tag+'.maximum='+FloatToStr(Axis.Maximum)+';');
        AddScript(Tag+'.minimum='+FloatToStr(Axis.Minimum)+';');
    end;

    if Axis.StartPosition<>0 then
       AddScript(Tag+'.start='+FloatToStr(Axis.StartPosition)+';');

    if Axis.EndPosition<>100 then
       AddScript(Tag+'.end='+FloatToStr(Axis.EndPosition)+';');

    EmitLabels(Tag+'.labels');

    EmitStroke(Tag+'.format.stroke',Axis.Axis);

    EmitStroke(Tag+'.grid.format.stroke',Axis.Grid);
    if not Axis.Grid.Visible then
       AddScript(Tag+'.grid.visible='+ToBool(False)+';');

    if Axis.Title.Caption<>'' then
       EmitAxisTitle(Tag+'.title',Axis.Title);
  end;

  procedure EmitAxes(Tag:String; const Axes:TChartAxes);
  begin
    if Minify then NewLocalVar(Tag);

    if not Axes.Visible then
       AddScript(Tag+'.visible='+ToBool(False)+';');

    EmitAxis(Tag+'.left',Axes.Left);
    EmitAxis(Tag+'.top',Axes.Top);
    EmitAxis(Tag+'.right',Axes.Right);
    EmitAxis(Tag+'.bottom',Axes.Bottom);
  end;

  procedure EmitCustomAxes(Tag:String; const Axes:TChartCustomAxes);
  var i: Integer;
      AxisTag, horiz, otherSide: string;
  begin
    for i:=0 to Axes.Count-1 do
    begin
      if Axes[i].Horizontal then
        horiz:='true'
      else
        horiz:='false';

      if Axes[i].OtherSide then
        otherSide:='true'
      else
        otherSide:='false';

      AxisTag:='axis'+IntToStr(i);
      if Minify then NewLocalVar(AxisTag);

      AddScript('var '+AxisTag+'='+Tag+'.add('+horiz+','+otherSide+');');
      EmitAxis(AxisTag,Axes[i]);
    end;
  end;

  procedure EmitPanel(Tag:String; const APanel:TCustomTeePanelExtended);
  var tmp : String;
  begin
    if Minify then NewLocalVar(Tag);

    if APanel.Color<>clTeeColor then
    begin
      if APanel.Color=clNone then
         AddScript(Tag+'.transparent='+ToBool(True)+';')
      else
         AddScript(Tag+'.format.fill="'+HtmlColor(TChartAccess(APanel).PanelColor(APanel.Color))+'";');
    end;

    tmp:=IntToStr(APanel.BorderRound);
    AddScript(Tag+'.format.round.x='+tmp+';');
    AddScript(Tag+'.format.round.y='+tmp+';');

    EmitStroke(Tag+'.format.stroke',APanel.Border);
    EmitGradient(Tag+'.format.gradient',APanel.Gradient);

    if APanel.Shadow.ShouldDraw then
       EmitShadow(Tag+'.format.shadow',APanel.Shadow);

    if APanel.MarginLeft<>2 then
      AddScript(Tag+'.margins.left='+IntToStr(APanel.MarginLeft)+';');
    if APanel.MarginRight<>2 then
      AddScript(Tag+'.margins.right='+IntToStr(APanel.MarginRight)+';');
    if APanel.MarginTop<>2 then
      AddScript(Tag+'.margins.top='+IntToStr(APanel.MarginTop)+';');
    if APanel.MarginBottom<>2 then
      AddScript(Tag+'.margins.bottom='+IntToStr(APanel.MarginBottom)+';');
  end;

  procedure EmitTools(Tag:String; const AChart:TCustomChart);

    function EmitTool(const ATool:TTeeCustomTool; const AClass:String):String;
    begin
      if Minify then
         result:=FindNewLocal
      else
      begin
         result:=ATool.Name;
         if result='' then
            result:='Tool'+IntToStr(1+Chart.Tools.IndexOf(ATool));
      end;

      AddScript('var '+result+'='+Tag+'.tools.add(new Tee.'+AClass+'('+ChartName+'));');
    end;

  var t   : Integer;
      tmp : String;
      a   : TAnnotationTool;
      c   : TCursorTool;
      m   : TMarksTipTool;
  begin
    for t := 0 to AChart.Tools.Count-1 do
    if AChart.Tools[t] is TAnnotationTool then
    begin
      a:=TAnnotationTool(AChart.Tools[t]);
      tmp:=EmitTool(a,'Annotation');
      EmitCustomShapeFormat(tmp+'.format',a.Shape);
      AddScript(tmp+'.position.x=' + IntToStr(a.Left) + ';');
      AddScript(tmp+'.position.y=' + IntToStr(a.Top) + ';');
      AddScript(tmp+'.text="'+TextToHTML(a.Text)+'";');

      if not a.Visible then
         AddScript(tmp+'.visible='+ToBool(False)+';');

      if a.Shape.Transparent then
         AddScript(tmp+'.transparent='+ToBool(True)+';');
    end
    else
    if AChart.Tools[t] is TCursorTool then
    begin
      c:=TCursorTool(AChart.Tools[t]);
      tmp:=EmitTool(c,'CursorTool');

      if not c.FollowMouse then
         AddScript(tmp+'.followMouse='+ToBool(False)+';');

      EmitStroke(tmp+'.format.stroke',c.Pen);

      if c.Style=cssHorizontal then
         AddScript(tmp+'.direction="horizontal";')
      else
      if c.Style=cssVertical then
         AddScript(tmp+'.direction="vertical";');

      if c.HorizSize<>0 then
         AddScript(tmp+'.size.x='+IntToStr(c.HorizSize)+';');

      if c.VertSize<>0 then
         AddScript(tmp+'.size.y='+IntToStr(c.VertSize)+';');
    end
    else
    if AChart.Tools[t] is TMarksTipTool then
    begin
      m:=TMarksTipTool(AChart.Tools[t]);
      tmp:=EmitTool(m,'ToolTip');

      //EmitMarksStyle(tmp+'.style',m.Style,True);

      EmitCustomShapeFormat(tmp+'.format',m.Format);
    end;
  end;

  procedure EmitZoom(Tag:String; const AZoom:TTeeZoom);
  begin
    if Minify then NewLocalVar(Tag);

    if not AZoom.Allow then
       AddScript(Tag+'.enabled='+ToBool(False)+';');

    if AZoom.Direction=tzdHorizontal then
       AddScript(Tag+'.direction="horizontal";')
    else
    if AZoom.Direction=tzdVertical then
       AddScript(Tag+'.direction="vertical";');

    AddScript(Tag+'.mouseButton='+JSMouseButton(AZoom.MouseButton)+';');

    EmitStroke(Tag+'.format.stroke',AZoom.Pen);

    if AZoom.Brush.Style<>bsClear then
    begin
      AddScript(Tag+'.format.fill="'+HtmlColor(AZoom.Brush.Color)+'";');
      EmitGradient(Tag+'.format.gradient',AZoom.Brush.Gradient);
    end;
  end;

  procedure EmitScroll(Tag:String; AMode:TPanningMode; AMouseButton:TMouseButton);
  begin
    if Minify then NewLocalVar(Tag);

    case AMode of
      pmNone: AddScript(Tag+'.enabled='+ToBool(False)+';');
      pmHorizontal:  AddScript(Tag+'.direction="horizontal";');
      pmVertical:  AddScript(Tag+'.direction="vertical";');
    end;

    AddScript(Tag+'.mouseButton='+JSMouseButton(AMouseButton)+';');
  end;

  function ColorsOf(const C:TColorArray):String;
  var t : Integer;
  begin
    result:='"'+HtmlColor(C[0])+'"';

    for t:=1 to Length(C)-1 do
      result:=result+',"'+HtmlColor(C[t])+'"';
  end;

begin
  ResetFlags;

  AddScript(ChartName+'=new Tee.Chart("'+CanvasName+'");');

  EmitPanel(ChartName+'.panel',Chart);
  EmitWalls(ChartName+'.walls',Chart.Walls);

  if Chart.Title.Caption<>'' then
     EmitTitle(ChartName+'.title',Chart.Title);

  if Chart.Foot.Caption<>'' then
     EmitTitle(ChartName+'.footer',Chart.Foot);

  EmitAxes(ChartName+'.axes',Chart.Axes);
  EmitCustomAxes(ChartName+'.axes',Chart.CustomAxes);

  EmitSeries(ChartName+'.',Chart.SeriesList);

  if (not IEmptiedChart) then
  Begin
    EmitLegend(ChartName+'.legend',Chart.Legend);
    EmitCustomCode(s);
  end;

  EmitTools(ChartName,Chart);

  EmitZoom(ChartName+'.zoom',Chart.Zoom);
  EmitScroll(ChartName+'.scroll',Chart.AllowPanning, Chart.ScrollMouseButton);

  // Note: This could be replaced with "IHasExtras:=True" (to force using
  // script teechart-extras.js),
  // and code to emit palette usage like: Chart.applyPalette("modern");
  if Chart.ColorPaletteIndex<>13 then
     if Length(Chart.ColorPalette)>0 then
        AddScript(ChartName+'.palette.colors=['+ColorsOf(Chart.ColorPalette)+'];');

  AddScript(ChartName+'.draw();');

  if Minify and (TempString<>'') then
     s.Add(TempString);

  if DoFullPage Then
  Begin
    AddHeader(s);

    s.Add('}');
    s.Add('</script>');
    s.Add('</HEAD>');
    s.Add('<BODY onload="draw()">');
    s.Add('<canvas id="'+CanvasName+'" width="'+IntToStr(Width)+'" height="'+IntToStr(Height)+'">');
    s.Add('This browser does not seem to support HTML5 Canvas.');
    s.Add('</canvas>');
    s.Add('</BODY>');
    s.Add('</HTML>');
  end;

  Cached:=nil;
end;

function TJavascriptExportFormat.Chart: TCustomChart;
begin
  result:=TCustomChart(Panel);
end;

procedure TJavascriptExportFormat.ResetFlags;
begin
  IHasDateTime:=False;
  IHasExtras:=False;
  IHasGauges:=False;
end;

procedure TJavascriptExportFormat.EmitEmptyChart(const S:TStrings);
begin
  S.Clear;

  ResetFlags;

  //AddHeader(Chart,S);

  S.Add('  ' + 'var '+ ChartName + '=new Tee.Chart("' + CanvasName + '");');
  S.Add('  ' + 'var a2=new Tee.Annotation(' + ChartName + ');');
  S.Add('  ' + 'a2.format.fill="orange";');
  S.Add('  ' + 'a2.format.stroke.color="blue";');
  S.Add('  ' + 'a2.format.font.style="18px Arial";');
  S.Add('  ' + 'a2.format.font.fill="brown";');
  S.Add('  ' + 'a2.text="' + StringReplace(TeeMsg_JScriptNonSupportMsg,#13,'\n',[rfReplaceAll]) + '";');
  S.Add('  ' + 'a2.position.x=20;');
  S.Add('  ' + 'a2.position.y=20;');
  S.Add('  ' + ChartName + '.tools.add(a2);');
end;

function TJavascriptExportFormat.GetChartName:String;
begin
  if FPanelName='' then
     result:='Chart1'
  else
     result:=FPanelName;
end;

procedure TJavascriptExportFormat.SetChartName(const Value: String);
begin
  FPanelName := Value;
end;

procedure TJavascriptExportFormat.SaveToFile(const Chart: TCustomChart;
  const FileName: String);
begin
  Panel:=Chart;
  inherited;
end;

procedure TeeSaveToJavascriptFile( const APanel:TCustomChart; const FileName: String;
                                   AWidth:Integer=0; AHeight: Integer=0;
                                   AMinify:Boolean=False
                                   {$IFDEF HASENCODING}
                                   ; const AEncoding:TEncoding=nil
                                   {$ENDIF}
                                   );
var st : TStringList;
begin
  st:=TStringList.Create;
  try
    with TJavascriptExportFormat.Create do
    try
      Width:=AWidth;
      Height:=AHeight;

      Panel:=APanel;

      CheckSize;

      Minify:=AMinify;

      AddStrings(st);
    finally
      Free;
    end;

    {$IFDEF HASENCODING}
    if AEncoding=nil then
       st.SaveToFile(FileName)
    else
       st.SaveToFile(FileName,AEncoding);
    {$ELSE}
    st.SaveToFile(FileName);
    {$ENDIF}
  finally
    st.Free;
  end;
end;

initialization
  {$IFDEF FMX}
  TTeeExportFormats.RegisterFormat(TJavascriptExportFormat);
  {$ELSE}
  RegisterTeeExportFormat(TJavascriptExportFormat);
  {$ENDIF}
finalization
  {$IFDEF FMX}
  TTeeExportFormats.UnRegisterFormat(TJavascriptExportFormat);
  {$ELSE}
  UnRegisterTeeExportFormat(TJavascriptExportFormat);
  {$ENDIF}
end.
